diff --git a/include/common/GDrawListSurface.h b/include/common/GDrawListSurface.h --- a/include/common/GDrawListSurface.h +++ b/include/common/GDrawListSurface.h @@ -1,73 +1,73 @@ #ifndef _GDRAW_LIST_SURFACE_H_ #define _GDRAW_LIST_SURFACE_H_ class GDrawListSurface : public GSurface { struct GDrawListSurfacePriv *d; public: GDrawListSurface(int Width, int Height, GColourSpace Cs = CsRgba32); GDrawListSurface(GSurface *FromSurface); ~GDrawListSurface(); // Calls specific to this class: ssize_t Length(); bool OnPaint(GSurface *Dest); GFont *GetFont(); void SetFont(GFont *Font); GColour Background(); GColour Background(GColour c); GDisplayString *Text(int x, int y, const char *Str, int Len = -1); // Calls that are stored and played back: GRect ClipRgn(); GRect ClipRgn(GRect *Rgn); COLOUR Colour(); COLOUR Colour(COLOUR c, int Bits = 0); GColour Colour(GColour c); int Op() { return GDC_SET; } int Op(int Op, NativeInt Param = -1) { return GDC_SET; } int X(); int Y(); ssize_t GetRowStep(); int DpiX(); int DpiY(); int GetBits(); uchar *operator[](int y) { return NULL; } void GetOrigin(int &x, int &y) { x = OriginX; y = OriginY; } void SetOrigin(int x, int y); void Set(int x, int y); COLOUR Get(int x, int y) { return 0; } // Primitives 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); - uint LineStyle(uint32 Bits, uint32 Reset = 0x80000000); + uint LineStyle(uint32_t Bits, uint32_t Reset = 0x80000000); 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(GRect *a = NULL); void Rectangle(int x1, int y1, int x2, int y2); void Rectangle(GRect *a = NULL); void Blt(int x, int y, GSurface *Src, GRect *a = NULL); void StretchBlt(GRect *d, GSurface *Src, GRect *s); void Polygon(int Points, GdcPt2 *Data); void Bezier(int Threshold, GdcPt2 *Pt); void FloodFill(int x, int y, int Mode, COLOUR Border = 0, GRect *Bounds = NULL); // Stubs that don't work here.. bool HasAlpha() { return false; } bool HasAlpha(bool b) { return false; } bool Applicator(GApplicator *pApp) { return false; } GApplicator *Applicator() { return NULL; } GPalette *Palette() { return NULL; } void Palette(GPalette *pPal, bool bOwnIt = true) { } }; #endif diff --git a/include/common/GMime.h b/include/common/GMime.h --- a/include/common/GMime.h +++ b/include/common/GMime.h @@ -1,156 +1,156 @@ #ifndef _GMIME_H_ #define _GMIME_H_ #include "LgiNetInc.h" #include "GStream.h" #include "INetTools.h" extern void CreateMimeBoundary(char *Buf, int BufLen); class GMime; class GMimeAction { friend class GMime; protected: // Parent ptr GMime *Mime; public: GMimeAction() { Mime = 0; } virtual void Empty() {} // reset to initial state }; class GMimeBuf : public GStringPipe { int Total; GStreamI *Src; GStreamEnd *End; public: GMimeBuf(GStreamI *src, GStreamEnd *end); ssize_t Pop(GArray &Buf) override; ssize_t Pop(char *Str, ssize_t BufSize) override; }; class GMime { // Header info char *Headers; // Data info ssize_t DataPos; ssize_t DataSize; LMutex *DataLock; GStreamI *DataStore; bool OwnDataStore; // Other info char *TmpPath; GMime *Parent; GArray Children; // Private methods bool Lock(); void Unlock(); bool CreateTempData(); char *NewValue(char *&s, bool Alloc = true); char *StartOfField(char *s, const char *Feild); char *NextField(char *s); char *GetTmpPath(); public: static const char *DefaultCharset; GMime(char *TmpFileRoot = 0); virtual ~GMime(); // Methods bool Insert(GMime *m, int pos = -1); void Remove(); ssize_t Length() { return Children.Length(); } - GMime *operator[](uint32 i); + GMime *operator[](uint32_t i); GMime *NewChild(); void DeleteChildren() { Children.DeleteObjects(); } void Empty(); bool SetHeaders(const char *h); char *GetHeaders() { return Headers; } ssize_t GetLength() { return DataSize; } GStreamI *GetData(bool Detach = false); bool SetData(bool OwnStream, GStreamI *Input, int RdPos = 0, int RdSize = -1, LMutex *Lock = 0); bool SetData(char *Str, int Len); // Simple Header Management char *Get(const char *Field, bool Short = true, const char *Default = 0); // 'Short'=true returns the value with out subfields bool Set(const char *Field, const char *Value); // 'Value' has to include any subfields. char *GetSub(const char *Field, const char *Sub); bool SetSub(const char *Field, const char *Sub, const char *Value, const char *DefaultValue = 0); // Header Shortcuts (uses Get[Sub]/Set[Sub]) char *GetMimeType() { return Get("Content-Type", true, "text/plain"); } bool SetMimeType(const char *s) { return Set("Content-Type", s); } char *GetEncoding() { return Get("Content-Transfer-Encoding"); } bool SetEncoding(const char *s) { return Set("Content-Transfer-Encoding", s); } char *GetCharset() { return GetSub("Content-Type", "Charset"); } bool SetCharset(const char *s) { return SetSub("Content-Type", "Charset", s, DefaultCharset); } char *GetBoundary() { return GetSub("Content-Type", "Boundary"); } bool SetBoundary(const char *s) { return SetSub("Content-Type", "Boundary", s, DefaultCharset); } char *GetFileName(); bool SetFileName(const char *s) { return SetSub("Content-Type", "Name", s, DefaultCharset); } // Streaming class GMimeText { public: class GMimeDecode : public GPullStreamer, public GMimeAction { public: ssize_t Pull(GStreamI *Source, GStreamEnd *End = 0); int Parse(GStringPipe *Source, class ParentState *State = 0); void Empty(); } Decode; class GMimeEncode : public GPushStreamer, public GMimeAction { public: ssize_t Push(GStreamI *Dest, GStreamEnd *End = 0); void Empty(); } Encode; } Text; friend class GMime::GMimeText::GMimeDecode; friend class GMime::GMimeText::GMimeEncode; class GMimeBinary { public: class GMimeRead : public GPullStreamer, public GMimeAction { public: ssize_t Pull(GStreamI *Source, GStreamEnd *End = 0); void Empty(); } Read; class GMimeWrite : public GPushStreamer, public GMimeAction { public: int64 GetSize(); ssize_t Push(GStreamI *Dest, GStreamEnd *End = 0); void Empty(); } Write; } Binary; friend class GMime::GMimeBinary::GMimeRead; friend class GMime::GMimeBinary::GMimeWrite; }; #endif diff --git a/include/common/GRichTextEdit.h b/include/common/GRichTextEdit.h --- a/include/common/GRichTextEdit.h +++ b/include/common/GRichTextEdit.h @@ -1,236 +1,236 @@ /// \file /// \author Matthew Allen /// \brief A unicode text editor #ifndef _RICH_TEXT_EDIT_H_ #define _RICH_TEXT_EDIT_H_ #include "GDocView.h" #include "GUndo.h" #include "GDragAndDrop.h" #include "GCapabilities.h" #if _DEBUG #include "GTree.h" #endif enum RichEditMsgs { M_BLOCK_MSG = M_USER + 0x1000, M_IMAGE_LOAD_FILE, M_IMAGE_SET_SURFACE, M_IMAGE_ERROR, M_IMAGE_COMPONENT_MISSING, M_IMAGE_PROGRESS, M_IMAGE_RESAMPLE, M_IMAGE_FINISHED, M_IMAGE_COMPRESS, M_IMAGE_ROTATE, M_IMAGE_FLIP, M_IMAGE_LOAD_STREAM, M_COMPONENT_INSTALLED, // A = GString *ComponentName }; extern char Delimiters[]; /// Styled unicode text editor control. class #if defined(MAC) LgiClass #endif GRichTextEdit : public GDocView, public ResObject, public GDragDropTarget, public GCapabilityClient { friend bool RichText_FindCallback(GFindReplaceCommon *Dlg, bool Replace, void *User); public: enum GTextViewSeek { PrevLine, NextLine, StartLine, EndLine }; protected: class GRichTextPriv *d; friend class GRichTextPriv; bool IndexAt(int x, int y, ssize_t &Off, int &LineHint); // Overridables virtual void PourText(ssize_t Start, ssize_t Length); virtual void PourStyle(ssize_t Start, ssize_t Length); virtual void OnFontChange(); virtual void OnPaintLeftMargin(GSurface *pDC, GRect &r, GColour &colour); public: // Construction GRichTextEdit( int Id, int x = 0, int y = 0, int cx = 100, int cy = 100, GFontType *FontInfo = 0); ~GRichTextEdit(); const char *GetClass() { return "GRichTextEdit"; } // Data char *Name(); bool Name(const char *s); char16 *NameW(); bool NameW(const char16 *s); int64 Value(); void Value(int64 i); const char *GetMimeType() { return "text/html"; } int GetSize(); const char *GetCharset(); void SetCharset(const char *s); ssize_t HitTest(int x, int y); bool DeleteSelection(char16 **Cut = 0); bool SetSpellCheck(class GSpellCheck *sp); bool GetFormattedContent(const char *MimeType, GString &Out, GArray *Media = NULL); // Dom bool GetVariant(const char *Name, GVariant &Value, char *Array = NULL); bool SetVariant(const char *Name, GVariant &Value, char *Array = NULL); // Font GFont *GetFont(); void SetFont(GFont *f, bool OwnIt = false); void SetFixedWidthFont(bool i); // Options - void SetTabSize(uint8 i); + void SetTabSize(uint8_t i); void SetReadOnly(bool i); bool ShowStyleTools(); void ShowStyleTools(bool b); enum RectType { ContentArea, ToolsArea, // CapabilityArea, // CapabilityBtn, FontFamilyBtn, FontSizeBtn, BoldBtn, ItalicBtn, UnderlineBtn, ForegroundColourBtn, BackgroundColourBtn, MakeLinkBtn, RemoveLinkBtn, RemoveStyleBtn, EmojiBtn, HorzRuleBtn, MaxArea }; GRect GetArea(RectType Type); /// Sets the wrapping on the control, use #TEXTED_WRAP_NONE or #TEXTED_WRAP_REFLOW void SetWrapType(LDocWrapType i); // State / Selection void SetCursor(int i, bool Select, bool ForceFullUpdate = false); ssize_t IndexAt(int x, int y); bool IsDirty(); void IsDirty(bool d); bool HasSelection(); void UnSelectAll(); void SelectWord(size_t From); void SelectAll(); ssize_t GetCaret(bool Cursor = true); bool GetLineColumnAtIndex(GdcPt2 &Pt, ssize_t Index = -1); size_t GetLines(); void GetTextExtent(int &x, int &y); char *GetSelection(); void SetStylePrefix(GString s); // File IO bool Open(const char *Name, const char *Cs = 0); bool Save(const char *Name, const char *Cs = 0); // Clipboard IO bool Cut(); bool Copy(); bool Paste(); // Undo/Redo void Undo(); void Redo(); bool GetUndoOn(); void SetUndoOn(bool b); // Action UI virtual bool DoGoto(); virtual bool DoCase(bool Upper); virtual bool DoFind(); virtual bool DoFindNext(); virtual bool DoReplace(); // Action Processing bool ClearDirty(bool Ask, char *FileName = 0); void UpdateScrollBars(bool Reset = false); int GetLine(); void SetLine(int Line); GDocFindReplaceParams *CreateFindReplaceParams(); void SetFindReplaceParams(GDocFindReplaceParams *Params); void OnAddStyle(const char *MimeType, const char *Styles); // Object Events bool OnFind(GFindReplaceCommon *Params); bool OnReplace(GFindReplaceCommon *Params); bool OnMultiLineTab(bool In); void OnSetHidden(int Hidden); void OnPosChange(); void OnCreate(); void OnEscape(GKey &K); bool OnMouseWheel(double Lines); // Capability target stuff // bool NeedsCapability(const char *Name, const char *Param = NULL); // void OnInstall(CapsHash *Caps, bool Status); // void OnCloseInstaller(); // Window Events void OnFocus(bool f); void OnMouseClick(GMouse &m); void OnMouseMove(GMouse &m); bool OnKey(GKey &k); void OnPaint(GSurface *pDC); GMessage::Result OnEvent(GMessage *Msg); int OnNotify(GViewI *Ctrl, int Flags); void OnPulse(); int OnHitTest(int x, int y); bool OnLayout(GViewLayoutInfo &Inf); // D'n'd target int WillAccept(List &Formats, GdcPt2 Pt, int KeyState); int OnDrop(GArray &Data, GdcPt2 Pt, int KeyState); // Virtuals virtual bool Insert(int At, char16 *Data, int Len); virtual bool Delete(int At, int Len); virtual void OnEnter(GKey &k); virtual void OnUrl(char *Url); virtual void DoContextMenu(GMouse &m); #if _DEBUG void DumpNodes(GTree *Root); void SelectNode(GString Param); #endif }; #endif diff --git a/include/common/LDbTable.h b/include/common/LDbTable.h --- a/include/common/LDbTable.h +++ b/include/common/LDbTable.h @@ -1,160 +1,160 @@ #ifndef _DB_TABLE_H_ #define _DB_TABLE_H_ #include "GVariant.h" #include "Store3.h" struct DbTablePriv; class LDbTable; struct LDbDate { size_t Sizeof(); bool Serialize(GPointer &p, LDateTime &dt, bool Write); }; struct LDbField { int Id; GVariantType Type; int Offset; int DataSize(); size_t Sizeof(); bool Serialize(GPointer &p, bool Write); }; class LDbRow : public GDataPropI { friend class LDbTable; friend struct DbTablePriv; // Global table specific data DbTablePriv *d; // The doubly linked list of rows. LDbRow *Next, *Prev; // This is the position in the tables read-only data // for this row. ssize_t Pos; // When editing a record, it can grow in size, so we copy the // Read-only data in the table into an edit buffer own by this // record. GArray Edit; // This pointers to the record data. // Format // uint32 Magic; // char FixedSizeData[d->FixedSz] // uint32 VariableOffsets[d->Variable] // char VariableData[??] GPointer Base; // This points to the offset data: // [0] -> Variable offset table (part of this record) // [1] -> Fixed offset table (owned by 'd') int32 *Offsets[2]; // Date cache LDateTime Cache; LDbRow(struct DbTablePriv *priv); bool StartEdit(); void PostEdit(); bool Compact(); - uint32 GetInitialSize(); + uint32_t GetInitialSize(); public: static int HeaderSz; ~LDbRow(); // Fields size_t GetFields(); LDbField &GetField(size_t Idx); // Row level op bool Delete(); - uint32 Size(uint32 Set = 0); + uint32_t Size(uint32_t Set = 0); GString ToString(); // Data access bool CopyProps(GDataPropI &p); char *GetStr(int id); Store3Status SetStr(int id, const char *str); int64 GetInt(int id); Store3Status SetInt(int id, int64 i); LDateTime *GetDate(int id); Store3Status SetDate(int id, LDateTime *i); GVariant *GetVar(int id); Store3Status SetVar(int id, GVariant *i); GDataPropI *GetObj(int id); Store3Status SetObj(int id, GDataPropI *i); GDataIt GetList(int id); Store3Status SetRfc822(GStreamI *Rfc822Msg); }; class DbIndex { DbTablePriv *d; public: DbIndex(DbTablePriv *priv); virtual ~DbIndex(); virtual bool OnNew(LDbRow *r) = 0; virtual bool OnDelete(LDbRow *r) = 0; }; class DbArrayIndex : public DbIndex, public GArray { friend class LDbTable; LDbField Fld; bool Ascend; DbArrayIndex(DbTablePriv *priv); bool Sort(LDbField *fld, bool ascend); public: bool OnNew(LDbRow *r); bool OnDelete(LDbRow *r); bool Resort(); }; class LDbTable { struct DbTablePriv *d; public: LDbTable(const char *File = NULL); ~LDbTable(); // Fields bool AddField(int Id, GVariantType Type); bool DeleteField(int Id); int GetFields(); LDbField &GetField(int Idx); // Rows bool Empty(); bool Iterate(LDbRow *&Ptr); int GetRows(); LDbRow *NewRow(); bool DeleteRow(LDbRow *r); /// This returns a sorted array of rows according to the specified /// id and sort direction. The array remains the property of the /// table. When done just free the index object. DbArrayIndex *Sort(int Id, bool Ascending = true); // IO bool Serialize(const char *Path, bool Write); // Testing GString ToString(); static bool UnitTests(); }; #endif diff --git a/src/common/Db/LDbTable.cpp b/src/common/Db/LDbTable.cpp --- a/src/common/Db/LDbTable.cpp +++ b/src/common/Db/LDbTable.cpp @@ -1,1156 +1,1156 @@ #include "Lgi.h" #include "LDbTable.h" /////////////////////////////////////////////////////////////////// #define MAGIC(v) LgiSwap32(v) #define OBJ_HEAD(magic) \ char *Start = p.c; \ uint32 *Sz = NULL; \ if (Write) \ { \ *p.u32++ = magic; \ Sz = p.u32++; \ } \ else if (*p.u32 != FieldMagic) \ return false; \ else \ { \ p.u32++; \ Sz = p.u32++; \ } #define SERIALIZE(type, var) \ if (Write) *p.type++ = var; \ else var = *p.type++; #define SERIALIZE_FN(fn, type) \ if (Write) *p.type++ = fn(); \ else fn(*p.type++); #define SERIALIZE_CAST(cast, type, var) \ if (Write) *p.type++ = var; \ else var = (cast) *p.type++; #define OBJ_TAIL() \ if (Write) \ *Sz = (uint32) (p.c - Start); \ else \ p.c = Start + *Sz; \ LgiAssert(Sizeof() == *Sz); \ return true; #define DB_DATE_SZ \ ( \ 2 + /* Year */ \ 1 + /* Month */ \ 1 + /* Day */ \ 1 + /* Hour */ \ 1 + /* Min */ \ 1 + /* Sec */ \ 2 /* TimeZone */ \ ) /////////////////////////////////////////////////////////////////// enum OffType { VariableOff, FixedOff }; enum DbMagic { TableMagic = MAGIC('tbl\0'), FieldMagic = MAGIC('fld\0'), RowMagic = MAGIC('row\0'), }; /////////////////////////////////////////////////////////////////// inline bool IsFixed(GVariantType t) { return t != GV_STRING && t != GV_BINARY; } struct Info { GVariantType Type; int Index; Info(GVariantType t = GV_NULL, int index = -1) { Type = t; Index = index; } bool operator !=(const Info &i) { return Type != i.Type && Index != i.Index; } bool operator ==(const Info &i) { return !(*this != i); } }; ////////////////////////////////////////////////////////////////////////////////////// struct DbTablePriv { // Fields unsigned Fixed; // Count of fixed size fields. unsigned FixedSz; // Byte size of fixed size fields. unsigned Variable; // Count of variable sized fields. GArray Fields; GArray FixedOffsets; LHashTbl, Info> Map; // Rows int Rows; LDbRow *First, *Last; GArray Data; bool Dirty; // Indexes GArray Indexes; // Methods DbTablePriv() : Map(0, Info()) { First = Last = NULL; Rows = 0; Fixed = 0; Variable = 0; FixedSz = 0; Dirty = false; } ~DbTablePriv() { Indexes.DeleteObjects(); } void SetDirty(bool b = true) { Dirty = b; } LDbField *FindField(int Id) { for (unsigned i=0; i 0) { LgiAssert(!"Adding fields with records not supported yet."); return false; } LDbField f; f.Id = Id; f.Type = Type; f.Offset = -1; if (IsFixed(Type)) Fields.AddAt(Fixed, f); else // Is variable size Fields.New() = f; return OffsetFields(); } bool DeleteField(int Id) { for (unsigned i=0; i 0); Fixed--; } else { LgiAssert(i >= Fixed && Variable > 0); Variable--; } Fields.DeleteAt(i, true); } } return OffsetFields(); } bool DeleteRow(LDbRow *r) { if (r->Prev) { r->Prev->Next = r->Next; } else { LgiAssert(r == First); First = r->Next; } if (r->Next) { r->Next->Prev = r->Prev; } else { LgiAssert(r == Last); Last = r->Prev; } r->Prev = NULL; r->Next = NULL; Rows--; DeleteObj(r); return true; } }; ////////////////////////////////////////////////////////////////////////////////////// DbIndex::DbIndex(DbTablePriv *priv) { d = priv; } DbIndex::~DbIndex() { LgiAssert(d->Indexes.HasItem(this)); d->Indexes.Delete(this); } DbArrayIndex::DbArrayIndex(DbTablePriv *priv) : DbIndex(priv) { Fld.Id = -1; Fld.Type = GV_NULL; Fld.Offset = -1; } bool DbArrayIndex::OnNew(LDbRow *r) { return Delete(r); } bool DbArrayIndex::OnDelete(LDbRow *r) { Add(r); return true; } struct CompareParams { int Id; bool Ascend; CompareParams(int i, bool a) { Id = i; Ascend = a; } }; DeclGArrayCompare(RowIntCompare, LDbRow*, CompareParams) { int64 A = (*a)->GetInt(param->Id); int64 B = (*b)->GetInt(param->Id); return (int) (param->Ascend ? A - B : B - A); } DeclGArrayCompare(RowStrCompare, LDbRow*, CompareParams) { const char *A = (*a)->GetStr(param->Id); if (!A) A = ""; const char *B = (*b)->GetStr(param->Id); if (!B) B = ""; return param->Ascend ? stricmp(A, B) : stricmp(B, A); } DeclGArrayCompare(RowDateCompare, LDbRow*, CompareParams) { LDateTime *A = (*a)->GetDate(param->Id); LDateTime *B = (*b)->GetDate(param->Id); if (!A || !B) { LgiAssert(0); return 0; } uint64 UtcA, UtcB; if (!A->Get(UtcA) || !B->Get(UtcB)) { LgiAssert(0); return 0; } int64 r = param->Ascend ? UtcA - UtcB : UtcB - UtcA; if (r < 0) return -1; if (r > 0) return 1; return 0; } bool DbArrayIndex::Sort(LDbField *fld, bool ascend) { if (!fld) return false; Fld = *fld; Ascend = ascend; CompareParams p(Fld.Id, Ascend); switch (Fld.Type) { case GV_INT32: case GV_INT64: GArray::Sort(RowIntCompare, &p); break; case GV_STRING: GArray::Sort(RowStrCompare, &p); break; case GV_DATETIME: GArray::Sort(RowDateCompare, &p); break; default: LgiAssert(0); return false; } return true; } bool DbArrayIndex::Resort() { return Sort(&Fld, Ascend); } /////////////////////////////////////////////////////////////////////////////////// size_t LDbDate::Sizeof() { return DB_DATE_SZ; } bool LDbDate::Serialize(GPointer &p, LDateTime &dt, bool Write) { #ifdef _DEBUG char *Start = p.c; #endif SERIALIZE_FN(dt.Year, u16); SERIALIZE_FN(dt.Month, u8); SERIALIZE_FN(dt.Day, u8); SERIALIZE_FN(dt.Hours, u8); SERIALIZE_FN(dt.Minutes, u8); SERIALIZE_FN(dt.Seconds, u8); uint16 Tz = dt.GetTimeZone(); SERIALIZE(u16, Tz); if (!Write) dt.SetTimeZone(Tz, false); LgiAssert(p.c - Start == DB_DATE_SZ); return true; } /////////////////////////////////////////////////////////////////////////////////// int LDbField::DataSize() { switch (Type) { case GV_BOOL: return 1; case GV_INT32: return 4; case GV_INT64: return 8; case GV_DATETIME: return DB_DATE_SZ; default: LgiAssert(!"Impl me."); break; } return 0; } size_t LDbField::Sizeof() { return 8 + sizeof(Id) + 2 + // Type sizeof(Offset); } bool LDbField::Serialize(GPointer &p, bool Write) { OBJ_HEAD(FieldMagic); SERIALIZE(s32, Id); SERIALIZE(s32, Offset); SERIALIZE_CAST(GVariantType, u16, Type); OBJ_TAIL(); } /////////////////////////////////////////////////////////////////////////////////// -int LDbRow::HeaderSz = sizeof(uint32) << 1; // Magic + Size +int LDbRow::HeaderSz = sizeof(uint32_t) << 1; // Magic + Size LDbRow::LDbRow(DbTablePriv *priv) { d = priv; Next = NULL; Prev = NULL; Pos = -1; Base.c = NULL; Offsets[FixedOff] = d->FixedOffsets.AddressOf(); Offsets[VariableOff] = NULL; } LDbRow::~LDbRow() { } size_t LDbRow::GetFields() { return d->Fields.Length(); } LDbField &LDbRow::GetField(size_t Idx) { return d->Fields[Idx]; } struct VarBlock : public GRange { int Index; }; int VarCmp(VarBlock *a, VarBlock *b) { return (int) (a->Start - b->Start); } -uint32 LDbRow::GetInitialSize() +uint32_t LDbRow::GetInitialSize() { - return HeaderSz + d->FixedSz + (d->Variable * sizeof(uint32)); + return HeaderSz + d->FixedSz + (d->Variable * sizeof(uint32_t)); } bool LDbRow::Compact() { if (Edit.Length()) { // The variable sized fields can get fragmented, this function removes unused space. GArray v; for (unsigned i=0; iVariable; i++) { if (Offsets[VariableOff][i] >= 0) { VarBlock &b = v.New(); b.Index = i; b.Start = Offsets[VariableOff][i]; b.Len = strlen(Base.c + b.Start) + 1; } } v.Sort(VarCmp); - uint32 Pos = GetInitialSize(); + uint32_t Pos = GetInitialSize(); for (unsigned i=0; i (ssize_t)Pos) { // Move block down memcpy(Base.c + Pos, Base.c + b.Start, b.Len); Offsets[VariableOff][b.Index] = Pos; b.Start = Pos; } Pos = (int32) (b.Start + b.Len); } if (Base.u32[1] > Pos) Base.u32[1] = Pos; } return true; } GString LDbRow::ToString() { GString::Array a; a.SetFixedLength(false); for (unsigned i=0; iFields.Length(); i++) { LDbField &f = d->Fields[i]; switch (f.Type) { case GV_INT32: case GV_INT64: a.New().Printf(LPrintfInt64, GetInt(f.Id)); break; case GV_STRING: { GString s = GetStr(f.Id); if (s.Length() > 0) a.New() = s; else a.New() = "NULL"; break; } case GV_DATETIME: { LDateTime *dt = GetDate(f.Id); if (dt) a.New() = dt->Get(); else a.New() = "NULL"; break; } default: LgiAssert(0); break; } } GString Sep(", "); return Sep.Join(a); } -uint32 LDbRow::Size(uint32 Set) +uint32_t LDbRow::Size(uint32_t Set) { if (Base.c) { if (Set) Base.u32[1] = Set; return Base.u32[1]; } LgiAssert(0); return 0; } bool LDbRow::Delete() { return d->DeleteRow(this); } bool LDbRow::CopyProps(GDataPropI &p) { for (size_t i=0; iMap.Find(id); if (i.Index < 0 || i.Type != GV_STRING || !Base.c) return NULL; LgiAssert((unsigned)i.Index < d->Variable); return Base.c + Offsets[VariableOff][i.Index]; } bool LDbRow::StartEdit() { if (Edit.Length() == 0) { if (Base.c) { if (!Edit.Length(Base.u32[1])) return false; memcpy(Edit.AddressOf(), Base.c, Base.u32[1]); } else { int InitialSize = GetInitialSize(); if (!Edit.Length(InitialSize)) return false; Base.c = Edit.AddressOf(); Base.u32[0] = RowMagic; Base.u32[1] = InitialSize; // Initialize fixed fields to zero memset(Base.c + 8, 0, d->FixedSz); if (d->Variable > 0) { // And the variable offset table to -1 memset(Base.c + 8 + d->FixedSz, 0xff, InitialSize - d->FixedSz); } } PostEdit(); } return true; } void LDbRow::PostEdit() { Base.c = Edit.AddressOf(); if (Base.c) { - Size((uint32)Edit.Length()); + Size((uint32_t)Edit.Length()); Offsets[VariableOff] = (int32*) Edit.AddressOf(8 + d->FixedSz); } } Store3Status LDbRow::SetStr(int id, const char *str) { Info i = d->Map.Find(id); if (i.Index < 0) return Store3Error; if (!StartEdit()) return Store3Error; size_t len = str ? strlen(str) + 1 : 1; Offsets[VariableOff][i.Index] = (int32)Edit.Length(); if (str) Edit.Add((char*)str, len); else Edit.Add((char*)"", 1); PostEdit(); d->SetDirty(); return Store3Success; } int64 LDbRow::GetInt(int id) { Info i = d->Map.Find(id); if (i.Index < 0 || !Base.c) return -1; GPointer p = { Base.s8 + Offsets[FixedOff][i.Index] }; if (i.Type == GV_INT32) return *p.s32; if (i.Type == GV_INT64) return *p.s64; return -1; } Store3Status LDbRow::SetInt(int id, int64 val) { Info i = d->Map.Find(id); if (i.Index < 0) return Store3Error; if (!Base.c) StartEdit(); GPointer p = { Base.s8 + Offsets[FixedOff][i.Index] }; if (i.Type == GV_INT32) { *p.s32 = (int32)val; d->SetDirty(); return Store3Success; } if (i.Type == GV_INT64) { *p.s64 = val; d->SetDirty(); return Store3Success; } return Store3Error; } LDateTime *LDbRow::GetDate(int id) { Info i = d->Map.Find(id); if (i.Index < 0 || !Base.c) return NULL; GPointer p = { Base.s8 + Offsets[FixedOff][i.Index] }; if (i.Type != GV_DATETIME) return NULL; LDbDate dd; dd.Serialize(p, Cache, false); return &Cache; } Store3Status LDbRow::SetDate(int id, LDateTime *dt) { Info i = d->Map.Find(id); if (i.Index < 0 || !dt) return Store3Error; if (!Base.c) StartEdit(); GPointer p = { Base.s8 + Offsets[FixedOff][i.Index] }; if (i.Type != GV_DATETIME) return Store3Error; LDbDate dd; dd.Serialize(p, *dt, true); d->SetDirty(); return Store3Success; } GVariant *LDbRow::GetVar(int id) { LgiAssert(0); return NULL; } Store3Status LDbRow::SetVar(int id, GVariant *i) { LgiAssert(0); return Store3Error; } GDataPropI *LDbRow::GetObj(int id) { LgiAssert(0); return NULL; } Store3Status LDbRow::SetObj(int id, GDataPropI *i) { LgiAssert(0); return Store3Error; } GDataIt LDbRow::GetList(int id) { LgiAssert(0); return NULL; } Store3Status LDbRow::SetRfc822(GStreamI *Rfc822Msg) { LgiAssert(0); return Store3Error; } //////////////////////////////////////////////////////////////////////////////////// LDbTable::LDbTable(const char *File) { d = new DbTablePriv; if (File) Serialize(File, false); } LDbTable::~LDbTable() { Empty(); DeleteObj(d); } bool LDbTable::AddField(int Id, GVariantType Type) { return d->AddField(Id, Type); } bool LDbTable::DeleteField(int Id) { return d->DeleteField(Id); } int LDbTable::GetFields() { return (int)d->Fields.Length(); } LDbField &LDbTable::GetField(int Idx) { return d->Fields[Idx]; } bool LDbTable::Iterate(LDbRow *&Ptr) { if (Ptr && Ptr->d != d) { LgiAssert(!"Wrong table."); return false; } if (Ptr) Ptr = Ptr->Next; else Ptr = d->First; return Ptr != NULL; } int LDbTable::GetRows() { return d->Rows; } LDbRow *LDbTable::NewRow() { LDbRow *r = new LDbRow(d); if (!r) return NULL; if (d->Last) { LgiAssert(d->Last->Next == NULL); d->Last->Next = r; r->Prev = d->Last; d->Last = r; d->Rows++; } else { LgiAssert(d->Rows == 0); d->First = d->Last = r; d->Rows = 1; } for (unsigned i=0; iIndexes.Length(); i++) d->Indexes[i]->OnNew(r); return r; } bool LDbTable::Empty() { d->Indexes.DeleteObjects(); LDbRow *n; for (LDbRow *r = d->First; r; r = n) { n = r->Next; delete r; } d->First = d->Last = NULL; d->Rows = 0; d->Fixed = 0; d->FixedSz = 0; d->Variable = 0; d->Fields.Empty(); d->FixedOffsets.Empty(); d->Map.Empty(); d->Data.Empty(); return true; } bool LDbTable::DeleteRow(LDbRow *r) { if (!r || r->d != d) return false; for (unsigned i=0; iIndexes.Length(); i++) d->Indexes[i]->OnDelete(r); return d->DeleteRow(r); } DbArrayIndex *LDbTable::Sort(int Id, bool Ascending) { LDbField *f = d->FindField(Id); if (!f) return NULL; // Collect all the records DbArrayIndex *i = new DbArrayIndex(d); if (!i) return NULL; if (!i->Length(d->Rows)) { delete i; return NULL; } int Idx = 0; for (LDbRow *r = d->First; r; r = r->Next) (*i)[Idx++] = r; LgiAssert(Idx == d->Rows); // Sort them i->Sort(f, Ascending); // Save the index d->Indexes.Add(i); return i; } bool LDbTable::Serialize(const char *Path, bool Write) { GFile f; if (!f.Open(Path, Write ? O_WRITE : O_READ)) return false; if (Write) { if (d->Fields.Length() > 0) { - size_t Sz = sizeof(uint32) + + size_t Sz = sizeof(uint32_t) + d->Fields.Length() * d->Fields[0].Sizeof(); if (d->Data.Length() < Sz) d->Data.Length(Sz); GPointer p = {(int8*)d->Data.AddressOf()}; *p.u32++ = TableMagic; for (unsigned i=0; iFields.Length(); i++) { if (!d->Fields[i].Serialize(p, true)) return false; } f.SetSize(0); size_t Bytes = p.c - d->Data.AddressOf(); LgiAssert(Bytes == Sz); if (f.Write(d->Data.AddressOf(), Bytes) != Bytes) return false; } for (LDbRow *r = d->First; r; r = r->Next) { // Fix the size before we write LgiAssert(r->Base.u32[0] == RowMagic); if (r->Edit.Length()) r->Compact(); if (f.Write(r->Base.c, r->Size()) != r->Size()) return false; } } else { Empty(); // Read all the data into memory if (!d->Data.Length((size_t)f.GetSize())) return false; auto Rd = f.Read(d->Data.AddressOf(0), (ssize_t)f.GetSize()); if (Rd != f.GetSize()) return false; GPointer p = {(int8*)d->Data.AddressOf()}; if (*p.u32++ != TableMagic) return false; // Create all the fields char *End = p.c + d->Data.Length(); d->Fixed = 0; d->FixedSz = 0; d->Variable = 0; d->Map.Empty(); d->FixedOffsets.Empty(); while (p.c < End - 8 && *p.u32 == FieldMagic) { LDbField &f = d->Fields.New(); if (!f.Serialize(p, false)) return false; if (IsFixed(f.Type)) { d->FixedSz += f.DataSize(); d->FixedOffsets[d->Fixed] = f.Offset; d->Map.Add(f.Id, Info(f.Type, d->Fixed++)); } else { d->Map.Add(f.Id, Info(f.Type, d->Variable++)); } } // Create the rows d->Rows = 0; while (p.c < End - 8 && *p.u32 == RowMagic) { LDbRow *r = new LDbRow(d); if (!r) return false; r->Base = p; r->Offsets[FixedOff] = d->FixedOffsets.AddressOf(); r->Offsets[VariableOff] = (int32*) (r->Base.c + 8 + d->FixedSz); if (d->Last) { r->Prev = d->Last; d->Last->Next = r; d->Last = r; d->Rows++; } else { d->First = d->Last = r; d->Rows = 1; } p.c += p.u32[1]; } } return true; } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// enum TestFields { TestUid = 100, TestInt32, TestString, TestDate, TestString2, }; GString LDbTable::ToString() { GString::Array a; a.SetFixedLength(false); for (LDbRow *r = NULL; Iterate(r); ) { GString s = r->ToString(); a.Add(s); } GString Sep("\n"); return Sep.Join(a); } bool LDbTable::UnitTests() { LDbTable t; t.AddField(TestUid, GV_INT64); t.AddField(TestInt32, GV_INT32); t.AddField(TestString, GV_STRING); t.AddField(TestDate, GV_DATETIME); t.AddField(TestString2, GV_STRING); const char *Days[] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }; for (unsigned i=0; iSetInt(TestUid, 1000+i); r->SetInt(TestInt32, i * 3); if (i % 2) { r->SetStr(TestString, Days[i]); r->SetStr(TestString2, "asdasdasdasdasd"); } else { r->SetStr(TestString2, "asdasdasdasdasd"); r->SetStr(TestString, Days[i]); } } LgiTrace("Export table:\n"); for (LDbRow *r = NULL; t.Iterate(r); ) { int64 Id = r->GetInt(TestUid); int64 Int = r->GetInt(TestInt32); char *s = r->GetStr(TestString); char *s2 = r->GetStr(TestString2); LgiTrace("\t%i: %i, %s, %s\n", (int)Id, (int)Int, s, s2); } const char *File = "test.db"; t.Serialize(File, true); LDbTable in; if (!in.Serialize(File, false)) return false; LgiTrace("Import table:\n"); LDbRow *Nine = NULL; for (LDbRow *r = NULL; in.Iterate(r); ) { int64 Start = LgiMicroTime(); int64 Id = r->GetInt(TestUid); int64 Int = r->GetInt(TestInt32); char *s = r->GetStr(TestString); char *s2 = r->GetStr(TestString2); int64 Time = LgiMicroTime()-Start; LgiTrace("\t%i: %i, %s, %s (%.4fms)\n", (int)Id, (int)Int, s, s2, (double)Time/1000.0); if (Int == 9) Nine = r; else if (Int == 12) r->SetStr(TestString2, "This is a new string."); else if (Int == 15) r->SetStr(TestString, NULL); else if (Int == 6) r->SetInt(TestInt32, 66); } if (!Nine) return false; in.DeleteRow(Nine); LgiTrace("Post delete:\n"); for (LDbRow *r = NULL; in.Iterate(r); ) { int64 Id = r->GetInt(TestUid); int64 Int = r->GetInt(TestInt32); char *s = r->GetStr(TestString); char *s2 = r->GetStr(TestString2); LgiTrace("\t%i: %i, %s, %s\n", (int)Id, (int)Int, s, s2); } if (!in.Serialize(File, true)) return false; if (!t.Serialize(File, false)) return false; LgiTrace("Reread:\n"); for (LDbRow *r = NULL; t.Iterate(r); ) { int64 Id = r->GetInt(TestUid); int64 Int = r->GetInt(TestInt32); char *s = r->GetStr(TestString); char *s2 = r->GetStr(TestString2); LgiTrace("\t%i: %i, %s, %s\n", (int)Id, (int)Int, s, s2); } return true; } diff --git a/src/common/Gdc2/Font/LEmojiFont.cpp b/src/common/Gdc2/Font/LEmojiFont.cpp --- a/src/common/Gdc2/Font/LEmojiFont.cpp +++ b/src/common/Gdc2/Font/LEmojiFont.cpp @@ -1,195 +1,195 @@ #include "Lgi.h" #include "LEmojiFont.h" #include "Emoji.h" #include "GFontPriv.h" #include "GdcTools.h" #define FILE_NAME "EmojiMap.png" // 200,960-219,979 struct LEmojiFontPriv { GAutoString Fn; GAutoPtr Img, Scaled; GArray Resampled; int Cell; LEmojiFontPriv() { Cell = EMOJI_CELL_SIZE; } }; LEmojiFont::LEmojiFont() { priv = new LEmojiFontPriv; } LEmojiFont::~LEmojiFont() { delete priv; } void LEmojiFont::_Measure(int &x, int &y, OsChar *Str, int len) { ssize_t Len = len * sizeof(*Str); x = 0; y = priv->Cell; for (auto s = (const uint16*)Str; *s && Len > 0; ) { - uint32 u32 = LgiUtf16To32(s, Len); + uint32_t u32 = LgiUtf16To32(s, Len); if (u32) x += priv->Cell; } } int LEmojiFont::_CharAt(int xPos, OsChar *Str, int len, LgiPxToIndexType Type) { ssize_t Len = len * sizeof(*Str); int x = 0; int Char = 0; for (auto s = (const uint16*)Str; *s && Len > 0; ) { - uint32 u32 = LgiUtf16To32(s, Len); + uint32_t u32 = LgiUtf16To32(s, Len); if (u32) { if (xPos >= x && xPos < x + priv->Cell) return Char; x += priv->Cell; Char++; } else break; } return -1; } void LEmojiFont::_Draw(GSurface *pDC, int x, int y, OsChar *Str, int len, GRect *r, GColour &fore) { if (!priv->Img) return; ssize_t Bytes = len * sizeof(*Str); pDC->Op(GDC_ALPHA); pDC->Colour(Back()); for (auto s = (const uint16*)Str; *s && Bytes > 0; ) { - uint32 u32 = LgiUtf16To32(s, Bytes); + uint32_t u32 = LgiUtf16To32(s, Bytes); if (!u32) break; GSurface *Src = priv->Scaled ? priv->Scaled : priv->Img; auto Idx = EmojiToIconIndex(&u32, 1); if (Idx >= 0) { int Cx = Idx % EMOJI_GROUP_X; int Cy = Idx / EMOJI_GROUP_X; GRect Icon(0, 0, priv->Cell-1, priv->Cell-1); Icon.Offset(Cx * priv->Cell, Cy * priv->Cell); if (!Transparent()) { if (r) pDC->Rectangle(r); else pDC->Rectangle(x, y, x + priv->Cell - 1, y + priv->Cell - 1); } if (priv->Scaled && !priv->Resampled[Idx]) { priv->Resampled[Idx] = true; GAutoPtr s(priv->Scaled->SubImage(Icon)); GRect Src(0, 0, EMOJI_CELL_SIZE-1, EMOJI_CELL_SIZE-1); Src.Offset(Cx * EMOJI_CELL_SIZE, Cy * EMOJI_CELL_SIZE); ResampleDC(s, priv->Img.Get(), &Src); } pDC->Blt(x, y, Src, &Icon); x += priv->Cell; } else LgiAssert(!"Invalid char"); } } int LEmojiFont::GetHeight() { return priv->Cell; } bool LEmojiFont::Create(const char *Face, GCss::Len Sz, GSurface *pSurface) { if (Sz.IsValid()) Size(Sz); if (!priv->Fn) { priv->Fn.Reset(LgiFindFile(FILE_NAME)); if (!priv->Fn) { GFile::Path p(LSP_APP_INSTALL); p += "..\\Lgi\\trunk\\src\\common\\Text\\Emoji\\EmojiMap.png"; if (p.Exists()) priv->Fn.Reset(NewStr(p)); } } if (priv->Fn && !priv->Img) { if (priv->Img.Reset(LoadDC(priv->Fn))) { LgiAssert(priv->Img->GetBits() == 32); } else return false; } if (!d->GlyphMap) { uint Bytes = (MAX_UNICODE + 1) >> 3; d->GlyphMap = new uchar[Bytes]; if (d->GlyphMap) { memset(d->GlyphMap, 0, Bytes); - for (uint32 u=0x203c; u<=0x3299; u++) + for (uint32_t u=0x203c; u<=0x3299; u++) { if (EmojiToIconIndex(&u, 1) >= 0) d->GlyphMap[u>>3] |= 1 << (u & 7); } - for (uint32 u=0x1f004; u<=0x1f6c5; u++) + for (uint32_t u=0x1f004; u<=0x1f6c5; u++) { if (EmojiToIconIndex(&u, 1) >= 0) d->GlyphMap[u>>3] |= 1 << (u & 7); } } } auto Dpi = LgiScreenDpi(); Sz = Size(); if (Sz.IsValid() && priv->Img) { int NewCell = (int) (Sz.Value * Dpi / 50); int Cx = priv->Img->X() / EMOJI_CELL_SIZE; int Cy = priv->Img->Y() / EMOJI_CELL_SIZE; int Nx = Cx * NewCell; int Ny = Cy * NewCell; if ( NewCell != EMOJI_CELL_SIZE && !priv->Scaled && priv->Scaled.Reset(new GMemDC(Nx, Ny, priv->Img->GetColourSpace())) ) { priv->Cell = NewCell; } } return priv->Img != NULL; } diff --git a/src/common/Gdc2/GDrawListSurface.cpp b/src/common/Gdc2/GDrawListSurface.cpp --- a/src/common/Gdc2/GDrawListSurface.cpp +++ b/src/common/Gdc2/GDrawListSurface.cpp @@ -1,504 +1,504 @@ #include "Lgi.h" #include "GDisplayString.h" #include "GDrawListSurface.h" struct LCmd { virtual ~LCmd() {} virtual bool OnPaint(GDrawListSurfacePriv *d, GSurface *pDC) = 0; }; struct GDrawListSurfacePriv : public GArray { int x, y, Bits; int DpiX, DpiY; GColour Fore, Back; GSurface *CreationSurface; GFont *Font; GDrawListSurfacePriv() { x = y = 0; CreationSurface = NULL; Font = NULL; } ~GDrawListSurfacePriv() { DeleteObjects(); } bool OnPaint(GSurface *pDC) { for (unsigned i=0; iOnPaint(this, pDC)) { return false; } } return true; } }; struct LCmdTxt : public LCmd { GdcPt2 p; GDisplayString *Ds; LCmdTxt(int x, int y, GDisplayString *ds) : p(x, y) { Ds = ds; } bool OnPaint(GDrawListSurfacePriv *d, GSurface *pDC) { GFont *f = Ds->GetFont(); f->Transparent(!d->Back.IsValid()); f->Colour(d->Fore, d->Back); Ds->Draw(pDC, p.x, p.y); return true; } }; struct LCmdBlt : public LCmd { GdcPt2 p; GSurface *Img; GRect Dst; GRect Src; bool Stretch; LCmdBlt(int X, int Y, GSurface *img, GRect *dst, GRect *src, bool stretch = false) : p(X, Y) { Img = img; if (dst) Dst = *dst; else Dst.ZOff(-1,-1); if (src) Src = *src; else Src.ZOff(-1, -1); Stretch = stretch; if (Stretch) LgiAssert(Src.Valid() && Dst.Valid()); } bool OnPaint(GDrawListSurfacePriv *d, GSurface *pDC) { if (Stretch) pDC->StretchBlt(&Dst, Img, &Src); else pDC->Blt(p.x, p.y, Img, Src.Valid() ? &Src : NULL); return true; } }; struct LCmdRect : public LCmd { GRect r; bool Filled; LCmdRect(GRect &rc, bool filled) { r = rc; Filled = filled; } bool OnPaint(GDrawListSurfacePriv *d, GSurface *pDC) { if (Filled) pDC->Rectangle(&r); else pDC->Box(&r); return true; } }; struct LCmdPixel : public LCmd { GdcPt2 p; LCmdPixel(int x, int y) : p(x, y) { } bool OnPaint(GDrawListSurfacePriv *d, GSurface *pDC) { pDC->Set(p.x, p.y); return true; } }; struct LCmdColour : public LCmd { GColour c; LCmdColour(GColour &col) { c = col; } bool OnPaint(GDrawListSurfacePriv *d, GSurface *pDC) { pDC->Colour(c); return true; } }; struct LCmdLine : public LCmd { GdcPt2 s, e; LCmdLine(int x1, int y1, int x2, int y2) : s(x1, y1), e(x2, y2) { } bool OnPaint(GDrawListSurfacePriv *d, GSurface *pDC) { pDC->Line(s.x, s.y, e.x, e.y); return true; } }; GDrawListSurface::GDrawListSurface(int Width, int Height, GColourSpace Cs) { d = new GDrawListSurfacePriv; d->x = Width; d->y = Height; d->DpiX = d->DpiY = LgiScreenDpi(); d->Bits = GColourSpaceToBits(Cs); d->Fore = GColour::Black; ColourSpace = Cs; } GDrawListSurface::GDrawListSurface(GSurface *FromSurface) { d = new GDrawListSurfacePriv; d->x = FromSurface->X(); d->y = FromSurface->Y(); d->DpiX = FromSurface->DpiX(); d->DpiY = FromSurface->DpiX(); ColourSpace = FromSurface->GetColourSpace(); d->Bits = GColourSpaceToBits(ColourSpace); d->Fore = GColour::Black; d->CreationSurface = FromSurface; } GDrawListSurface::~GDrawListSurface() { delete d; } ssize_t GDrawListSurface::Length() { return d->Length(); } bool GDrawListSurface::OnPaint(GSurface *Dest) { return d->OnPaint(Dest); } GFont *GDrawListSurface::GetFont() { return d->Font; } void GDrawListSurface::SetFont(GFont *Font) { d->Font = Font; } GColour GDrawListSurface::Background() { return d->Back; } GColour GDrawListSurface::Background(GColour c) { GColour Prev = d->Back; d->Back = c; return Prev; } GDisplayString *GDrawListSurface::Text(int x, int y, const char *Str, int Len) { if (!d->Font) { LgiAssert(!"No font."); return NULL; } GDisplayString *Ds = new GDisplayString(d->Font, Str, Len, d->CreationSurface); if (!Ds) { LgiAssert(0); return NULL; } LCmdTxt *Txt = new LCmdTxt(x, y, Ds); if (!Txt) { delete Ds; return NULL; } d->Add(Txt); return Ds; } GRect GDrawListSurface::ClipRgn() { return Clip; } GRect GDrawListSurface::ClipRgn(GRect *Rgn) { Clip = *Rgn; return Clip; } COLOUR GDrawListSurface::Colour() { switch (d->Bits) { case 8: return d->Fore.c8(); case 15: return CBit(d->Bits, d->Fore.c24()); case 24: return d->Fore.c24(); case 32: return d->Fore.c32(); } LgiAssert(0); return 0; } COLOUR GDrawListSurface::Colour(COLOUR c, int Bits) { COLOUR Prev = Colour(); d->Fore.Set(c, Bits); LCmdColour *cmd = new LCmdColour(d->Fore); if (cmd) d->Add(cmd); else LgiAssert(0); return Prev; } GColour GDrawListSurface::Colour(GColour c) { GColour Prev = d->Fore; d->Fore = c; LCmdColour *cmd = new LCmdColour(d->Fore); if (cmd) d->Add(cmd); else LgiAssert(0); return Prev; } int GDrawListSurface::X() { return d->x; } int GDrawListSurface::Y() { return d->y; } ssize_t GDrawListSurface::GetRowStep() { ssize_t Row = (d->Bits * d->x + 7) >> 3; return Row; } int GDrawListSurface::DpiX() { return d->DpiX; } int GDrawListSurface::DpiY() { return d->DpiY; } int GDrawListSurface::GetBits() { return d->Bits; } void GDrawListSurface::SetOrigin(int x, int y) { GSurface::SetOrigin(x, y); } void GDrawListSurface::Set(int x, int y) { LCmdPixel *c = new LCmdPixel(x, y); if (c) d->Add(c); else LgiAssert(0); } void GDrawListSurface::HLine(int x1, int x2, int y) { LCmdLine *c = new LCmdLine(x1, y, x2, y); if (c) d->Add(c); else LgiAssert(0); } void GDrawListSurface::VLine(int x, int y1, int y2) { LCmdLine *c = new LCmdLine(x, y1, x, y2); if (c) d->Add(c); else LgiAssert(0); } void GDrawListSurface::Line(int x1, int y1, int x2, int y2) { LCmdLine *c = new LCmdLine(x1, y1, x2, y2); if (c) d->Add(c); else LgiAssert(0); } -uint GDrawListSurface::LineStyle(uint32 Bits, uint32 Reset) +uint GDrawListSurface::LineStyle(uint32_t Bits, uint32_t Reset) { LgiAssert(!"Impl me."); return 0; } void GDrawListSurface::Circle(double cx, double cy, double radius) { LgiAssert(!"Impl me."); } void GDrawListSurface::FilledCircle(double cx, double cy, double radius) { LgiAssert(!"Impl me."); } void GDrawListSurface::Arc(double cx, double cy, double radius, double start, double end) { LgiAssert(!"Impl me."); } void GDrawListSurface::FilledArc(double cx, double cy, double radius, double start, double end) { LgiAssert(!"Impl me."); } void GDrawListSurface::Ellipse(double cx, double cy, double x, double y) { LgiAssert(!"Impl me."); } void GDrawListSurface::FilledEllipse(double cx, double cy, double x, double y) { LgiAssert(!"Impl me."); } void GDrawListSurface::Box(int x1, int y1, int x2, int y2) { GRect r(x1, y1, x2, y2); LCmdRect *c = new LCmdRect(r, false); if (c) d->Add(c); else LgiAssert(0); } void GDrawListSurface::Box(GRect *r) { GRect rc = r ? r : Bounds(); LCmdRect *c = new LCmdRect(rc, false); if (c) d->Add(c); else LgiAssert(0); } void GDrawListSurface::Rectangle(int x1, int y1, int x2, int y2) { GRect r(x1, y1, x2, y2); LCmdRect *c = new LCmdRect(r, true); if (c) d->Add(c); else LgiAssert(0); } void GDrawListSurface::Rectangle(GRect *r) { GRect rc = r ? r : Bounds(); LCmdRect *c = new LCmdRect(rc, true); if (c) d->Add(c); else LgiAssert(0); } void GDrawListSurface::Blt(int x, int y, GSurface *Src, GRect *SrcRc) { if (!Src) { LgiAssert(0); return; } LCmdBlt *c = new LCmdBlt(x, y, Src, NULL, SrcRc, false); if (c) d->Add(c); else LgiAssert(0); } void GDrawListSurface::StretchBlt(GRect *drc, GSurface *Src, GRect *s) { if (!d || !Src || !s) { LgiAssert(0); return; } LCmdBlt *c = new LCmdBlt(drc->x1, drc->y1, Src, drc, s, true); if (c) d->Add(c); else LgiAssert(0); } void GDrawListSurface::Polygon(int Points, GdcPt2 *Data) { LgiAssert(!"Impl me."); } void GDrawListSurface::Bezier(int Threshold, GdcPt2 *Pt) { LgiAssert(!"Impl me."); } void GDrawListSurface::FloodFill(int x, int y, int Mode, COLOUR Border, GRect *Bounds) { LgiAssert(!"Impl me."); } diff --git a/src/common/General/GTnef.cpp b/src/common/General/GTnef.cpp --- a/src/common/General/GTnef.cpp +++ b/src/common/General/GTnef.cpp @@ -1,361 +1,361 @@ #include "Lgi.h" #include "GTnef.h" #include "GVariant.h" #define TNEF_SIGNATURE ((uint32) 0x223E9F78) #define atpTriples ((uint16) 0x0000) #define atpString ((uint16) 0x0001) #define atpText ((uint16) 0x0002) #define atpDate ((uint16) 0x0003) #define atpShort ((uint16) 0x0004) #define atpLong ((uint16) 0x0005) #define atpByte ((uint16) 0x0006) #define atpWord ((uint16) 0x0007) #define atpDword ((uint16) 0x0008) #define atpMax ((uint16) 0x0009) #define LVL_MESSAGE ((uint8) 0x01) #define LVL_ATTACHMENT ((uint8) 0x02) #define ATT_ID(_att) ((uint16) ((_att) & 0x0000FFFF)) #define ATT_TYPE(_att) ((uint16) (((_att) >> 16) & 0x0000FFFF)) #define ATT(_atp, _id) ((((uint32) (_atp)) << 16) | ((uint16) (_id))) #define attNull ATT( 0, 0x0000) #define attFrom ATT( atpTriples, 0x8000) /* PR_ORIGINATOR_RETURN_ADDRESS */ #define attSubject ATT( atpString, 0x8004) /* PR_SUBJECT */ #define attDateSent ATT( atpDate, 0x8005) /* PR_CLIENT_SUBMIT_TIME */ #define attDateRecd ATT( atpDate, 0x8006) /* PR_MESSAGE_DELIVERY_TIME */ #define attMessageStatus ATT( atpByte, 0x8007) /* PR_MESSAGE_FLAGS */ #define attMessageClass ATT( atpWord, 0x8008) /* PR_MESSAGE_CLASS */ #define attMessageID ATT( atpString, 0x8009) /* PR_MESSAGE_ID */ #define attParentID ATT( atpString, 0x800A) /* PR_PARENT_ID */ #define attConversationID ATT( atpString, 0x800B) /* PR_CONVERSATION_ID */ #define attBody ATT( atpText, 0x800C) /* PR_BODY */ #define attPriority ATT( atpShort, 0x800D) /* PR_IMPORTANCE */ #define attAttachData ATT( atpByte, 0x800F) /* PR_ATTACH_DATA_xxx */ #define attAttachTitle ATT( atpString, 0x8010) /* PR_ATTACH_FILENAME */ #define attAttachMetaFile ATT( atpByte, 0x8011) /* PR_ATTACH_RENDERING */ #define attAttachCreateDate ATT( atpDate, 0x8012) /* PR_CREATION_TIME */ #define attAttachModifyDate ATT( atpDate, 0x8013) /* PR_LAST_MODIFICATION_TIME */ #define attDateModified ATT( atpDate, 0x8020) /* PR_LAST_MODIFICATION_TIME */ #define attAttachTransportFilename ATT( atpByte, 0x9001) /* PR_ATTACH_TRANSPORT_NAME */ #define attAttachRenddata ATT( atpByte, 0x9002) #define attMAPIProps ATT( atpByte, 0x9003) #define attRecipTable ATT( atpByte, 0x9004) /* PR_MESSAGE_RECIPIENTS */ #define attAttachment ATT( atpByte, 0x9005) #define attTnefVersion ATT( atpDword, 0x9006) #define attOemCodepage ATT( atpByte, 0x9007) #define attOriginalMessageClass ATT( atpWord, 0x0006) /* PR_ORIG_MESSAGE_CLASS */ #define attOwner ATT( atpByte, 0x0000) /* PR_RCVD_REPRESENTING_xxx or PR_SENT_REPRESENTING_xxx */ #define attSentFor ATT( atpByte, 0x0001) /* PR_SENT_REPRESENTING_xxx */ #define attDelegate ATT( atpByte, 0x0002) /* PR_RCVD_REPRESENTING_xxx */ #define attDateStart ATT( atpDate, 0x0006) /* PR_DATE_START */ #define attDateEnd ATT( atpDate, 0x0007) /* PR_DATE_END */ #define attAidOwner ATT( atpLong, 0x0008) /* PR_OWNER_APPT_ID */ #define attRequestRes ATT( atpShort, 0x0009) /* PR_RESPONSE_REQUESTED */ struct DTR { uint16 wYear; uint16 wMonth; uint16 wDay; uint16 wHour; uint16 wMinute; uint16 wSecond; uint16 wDayOfWeek; }; class TnefAttr { public: int64 StreamPos; uint16 Tag; uint16 Type; - uint32 Size; + uint32_t Size; GVariant Value; - uint32 Prop() + uint32_t Prop() { return ATT(Type, Tag); } int64 DataStart() { return StreamPos + 8; } bool Read(GStreamI *s) { StreamPos = s->GetPos(); if (s->Read(&Tag, sizeof(Tag)) == sizeof(Tag) && s->Read(&Type, sizeof(Type)) == sizeof(Tag) && s->Read(&Size, sizeof(Size)) == sizeof(Size)) { uint16 EffectiveType = Type; if (Prop() == attMessageClass) { EffectiveType = atpString; } switch (EffectiveType) { case atpDword: { - uint32 d; + uint32_t d; if (Size == sizeof(d) && s->Read(&d, sizeof(d)) == sizeof(d)) { Value = (int)d; } else { LgiTrace("Tnef: dword size error %i\n", Size); return false; } break; } case atpByte: { char *b = new char[Size]; if (b) { s->Read(b, Size); Value.SetBinary(Size, b); DeleteArray(b); } break; } case atpString: { char *b = new char[Size+1]; if (b) { s->Read(b, Size); b[Size] = 0; Value = b; DeleteArray(b); } break; } case atpDate: { DTR d; if (sizeof(d) == Size && s->Read(&d, sizeof(d))) { LDateTime dt; dt.Year(d.wYear); dt.Month(d.wMonth); dt.Day(d.wDay); dt.Hours(d.wHour); dt.Minutes(d.wMinute); dt.Seconds(d.wSecond); Value = &dt; } break; } default: case atpText: case atpShort: case atpLong: case atpWord: { LgiTrace("Tnef: unsupported type %i\n", EffectiveType); break; } } uint16 Checksum; s->Read(&Checksum, sizeof(Checksum)); } else return false; return true; } }; bool TnefReadIndex(GStreamI *Tnef, GArray &Index) { bool Status = false; if (Tnef) { - uint32 Sig; + uint32_t Sig; uint16 Key; TnefFileInfo *Cur = 0; if (Tnef->Read(&Sig, sizeof(Sig)) && Tnef->Read(&Key, sizeof(Key)) && Sig == TNEF_SIGNATURE && Key > 0) { - uint8 b; + uint8_t b; bool Done = false; - GArray Tags; + GArray Tags; while (!Done && Tnef->Read(&b, sizeof(b)) == sizeof(b)) { switch (b) { case LVL_MESSAGE: { TnefAttr a; if (a.Read(Tnef)) { switch (a.Prop()) { case attTnefVersion: { break; } case attOemCodepage: { break; } case attMessageClass: { break; } case attMAPIProps: { break; } default: { break; } } } else { LgiTrace("Tnef: failed to read msg attribute\n"); } break; } case LVL_ATTACHMENT: { TnefAttr a; if (a.Read(Tnef)) { if (!Cur || Tags.HasItem(a.Prop())) { Index.Add(Cur = new TnefFileInfo); Tags.Length(0); } Tags.Add(a.Prop()); switch (a.Prop()) { case attAttachRenddata: { break; } case attAttachModifyDate: { break; } case attAttachData: { if (Cur) { Cur->Start = a.DataStart(); Cur->Size = a.Size; } break; } case attAttachTitle: { if (Cur) { Cur->Name = NewStr(a.Value.Str()); } break; } case attAttachMetaFile: { break; } case attAttachment: { if (Cur) { Cur->Start = a.DataStart(); Cur->Size = a.Size; } break; } default: { break; } } } else { LgiTrace("Tnef: failed to read attachment attribute\n"); } break; } default: { Done = true; LgiTrace("Tnef: got strange header byte %i\n", b); break; } } } for (unsigned i=0; iName || !fi->Size || !fi->Start) { DeleteObj(Index[i]); Index.DeleteAt(i--); } else { Status = true; } } } } return Status; } bool TnefExtract(GStreamI *Tnef, GStream *Out, TnefFileInfo *File) { bool Status = false; if (Tnef && Out && File) { if (Tnef->SetPos(File->Start) == File->Start) { char Buf[512]; int64 i; for (i=0; iSize; ) { int Block = MIN(sizeof(Buf), (int)(File->Size - i)); ssize_t r = Tnef->Read(Buf, Block); if (r > 0) { i += r; Out->Write(Buf, r); } else break; } Status = i == File->Size; } } return Status; } diff --git a/src/common/INet/GMime.cpp b/src/common/INet/GMime.cpp --- a/src/common/INet/GMime.cpp +++ b/src/common/INet/GMime.cpp @@ -1,1619 +1,1619 @@ #include #include "LgiNetInc.h" #include "Lgi.h" #include "GMime.h" #include "GToken.h" #include "Base64.h" static const char *MimeEol = "\r\n"; static const char *MimeWs = " \t\r\n"; static const char *MimeStr = "\'\""; static const char *MimeQuotedPrintable = "quoted-printable"; static const char *MimeBase64 = "base64"; #define MimeMagic ( ('M'<<24) | ('I'<<24) | ('M'<<24) | ('E'<<24) ) #define SkipWs(s) while (*s && strchr(MimeWs, *s)) s++ #define SkipNonWs(s) while (*s && !strchr(MimeWs, *s)) s++ const char *GMime::DefaultCharset = "text/plain"; template int CastInt(T in) { int out = (int)in; LgiAssert(out == in); return out; } int AltScore(char *Mt) { int Score = 0; if (Mt) { if (stristr(Mt, "/html")) Score = 1; else if (stristr(Mt, "/related")) Score = 2; } // printf("Score '%s' = %i\n", Mt, Score); DeleteArray(Mt); return Score; } int AltSortCmp(GMime **a, GMime **b) { int a_score = AltScore((*a)->GetMimeType()); int b_score = AltScore((*b)->GetMimeType()); return a_score - b_score; } /////////////////////////////////////////////////////////////////////// enum MimeBoundary { MimeData = 0, MimeNextSeg = 1, MimeEndSeg = 2 }; MimeBoundary IsMimeBoundary(char *Boundary, char *Line) { if (Boundary) { size_t BoundaryLen = strlen(Boundary); if (Line && *Line++ == '-' && *Line++ == '-' && strncmp(Line, Boundary, strlen(Boundary)) == 0) { // MIME segment boundary Line += BoundaryLen; if (Line[0] == '-' && Line[1] == '-') { return MimeEndSeg; } else { return MimeNextSeg; } } } return MimeData; } void CreateMimeBoundary(char *Buf, int BufLen) { if (Buf) { static int Count = 1; sprintf_s(Buf, BufLen, "--%x-%x-%x--", (int)LgiCurrentTime(), (int)(uint64)LgiGetCurrentThread(), Count++); } } /////////////////////////////////////////////////////////////////////// class GCoderStream : public GStream { protected: GStreamI *Out; public: GCoderStream(GStreamI *o) { Out = o; } }; class GMimeTextEncode : public GCoderStream { // This code needs to make sure it writes an end-of-line at // the end, otherwise a following MIME boundary could be missed. bool LastEol; public: GMimeTextEncode(GStreamI *o) : GCoderStream(o) { LastEol = false; } ~GMimeTextEncode() { if (!LastEol) { ssize_t w = Out->Write(MimeEol, 2); LgiAssert(w == 2); } } ssize_t Write(const void *p, ssize_t size, int f = 0) { // Make sure any new lines are \r\n char *s = (char*)p, *e = s + size; int wr = 0; while (s < e) { char *c = s; while (c < e && *c != '\r' && *c != '\n') c++; if (c > s) { ptrdiff_t bytes = c - s; ssize_t w = Out->Write(s, (int)bytes); if (w != bytes) return wr; wr += w; LastEol = false; } while (c < e && (*c == '\r' || *c == '\n')) { if (*c == '\n') { ssize_t w = Out->Write(MimeEol, 2); if (w != 2) return wr; LastEol = true; } wr++; c++; } s = c; } return wr; } }; class GMimeQuotedPrintableEncode : public GCoderStream { // 'd' is our current position in 'Buf' char *d; // A buffer for a line of quoted printable text char Buf[128]; public: GMimeQuotedPrintableEncode(GStreamI *o) : GCoderStream(o) { Buf[0] = 0; d = Buf; } ~GMimeQuotedPrintableEncode() { if (d > Buf) { // Write partial line *d++ = '\r'; *d++ = '\n'; ptrdiff_t Len = d - Buf; Out->Write(Buf, CastInt(Len)); } } ssize_t Write(const void *p, ssize_t size, int f = 0) { char *s = (char*)p; char *e = s + size; while (s < e) { if (*s == '\n' || !*s) { *d++ = '\r'; *d++ = '\n'; ptrdiff_t Len = d - Buf; if (Out->Write(Buf, CastInt(Len)) < Len) { LgiAssert(!"write error"); break; } if (!*s) { break; } d = Buf; s++; } else if (*s & 0x80 || *s == '.' || *s == '=') { int Ch = sprintf_s(d, sizeof(Buf)-(d-Buf), "=%2.2X", (uchar)*s); if (Ch < 0) { LgiAssert(!"printf error"); break; } d += Ch; s++; } else if (*s != '\r') { *d++ = *s++; } else { // Consume any '\r' without outputting them s++; } if (d-Buf > 73) { // time for a new line. *d++ = '='; *d++ = '\r'; *d++ = '\n'; ptrdiff_t Len = d-Buf; if (Out->Write(Buf, CastInt(Len)) < Len) { LgiAssert(!"write error"); break; } d = Buf; } } return CastInt(s - (const char*)p); } }; class GMimeQuotedPrintableDecode : public GCoderStream { - char ConvHexToBin(char c) + uint8_t ConvHexToBin(char c) { if (c >= '0' && c <= '9') return c - '0'; if (c >= 'a' && c <= 'f') return c + 10 - 'a'; if (c >= 'A' && c <= 'F') return c + 10 - 'A'; return 0; } public: GMimeQuotedPrintableDecode(GStreamI *o) : GCoderStream(o) {} ssize_t Write(const void *p, ssize_t size, int f = 0) { ssize_t Written = 0; char Line[1024]; char *o = Line; const char *s = (const char*) p; const char *e = s + size; #define NEXT(ptr) if (++ptr >= e) break for (const char *s = (const char*)p; s < e; ) { if (*s == '=') { NEXT(s); // skip '=' if (*s == '\r' || *s == '\n') { if (*s == '\r') NEXT(s); if (*s == '\n') NEXT(s); } else if (*s) { - uint8 hi = ConvHexToBin(*s++); + uint8_t hi = ConvHexToBin(*s++); if (s >= e) break; - uint8 low = ConvHexToBin(*s++); + uint8_t low = ConvHexToBin(*s++); if (s >= e) break; *o++ = (hi << 4) | (low & 0xF); } else break; } else { *o++ = *s++; } if (o - Line > 1000) { auto w = Out->Write(Line, o - Line); if (w > 0) { Written += w; o = Line; } else break; // Error } } if (o > Line) { auto w = Out->Write(Line, o - Line); if (w > 0) Written += w; } return Written; } }; #define BASE64_LINE_SZ 76 #define BASE64_READ_SZ (BASE64_LINE_SZ*3/4) class GMimeBase64Encode : public GCoderStream { GMemQueue Buf; public: GMimeBase64Encode(GStreamI *o) : GCoderStream(o) {} ~GMimeBase64Encode() { uchar b[100]; int64 Len = Buf.GetSize(); LgiAssert(Len < sizeof(b)); ssize_t r = Buf.Read(b, CastInt(Len)); if (r > 0) { char t[256]; ssize_t w = ConvertBinaryToBase64(t, sizeof(t), b, r); Out->Write(t, w); Out->Write(MimeEol, 2); } } ssize_t Write(const void *p, ssize_t size, int f = 0) { Buf.Write((uchar*)p, size); while (Buf.GetSize() >= BASE64_READ_SZ) { uchar b[100]; ssize_t r = Buf.Read(b, BASE64_READ_SZ); if (r) { char t[256]; ssize_t w = ConvertBinaryToBase64(t, sizeof(t), b, r); if (w > 0) { Out->Write(t, w); Out->Write(MimeEol, 2); } else LgiAssert(0); } else return 0; } return size; } }; class GMimeBase64Decode : public GCoderStream { GStringPipe Buf; - uint8 Lut[256]; + uint8_t Lut[256]; public: GMimeBase64Decode(GStreamI *o) : GCoderStream(o) { ZeroObj(Lut); memset(Lut+(int)'a', 1, 'z'-'a'+1); memset(Lut+(int)'A', 1, 'Z'-'A'+1); memset(Lut+(int)'0', 1, '9'-'0'+1); Lut['+'] = 1; Lut['/'] = 1; Lut['='] = 1; } ssize_t Write(const void *p, ssize_t size, int f = 0) { int Status = 0; // Push non whitespace into the memory buf char *s = (char*)p; char *e = s + size; while (s < e) { while (*s && s < e && !Lut[*s]) s++; char *Start = s; while (*s && s < e && Lut[*s]) s++; if (s-Start > 0) Buf.Push(Start, CastInt(s-Start)); else break; } // While there is at least one run of base64 (4 bytes) convert it to text // and write it to the output stream int Size; while ((Size = CastInt(Buf.GetSize())) > 3) { Size &= ~3; char t[256]; ssize_t r = MIN(sizeof(t), Size); if ((r = Buf.GMemQueue::Read((uchar*)t, r)) > 0) { uchar b[256]; ssize_t w = ConvertBase64ToBinary(b, sizeof(b), t, r); Out->Write(b, w); Status += w; } } return Status; } }; /////////////////////////////////////////////////////////////////////// GMimeBuf::GMimeBuf(GStreamI *src, GStreamEnd *end) { Total = 0; Src = src; End = end; Src->SetPos(0); } ssize_t GMimeBuf::Pop(GArray &Out) { ssize_t Ret = 0; while (!(Ret = GStringPipe::Pop(Out))) { if (Src) { char Buf[1024]; ssize_t r = Src ? Src->Read(Buf, sizeof(Buf)) : 0; if (r) { if (End) { ssize_t e = End->IsEnd(Buf, r); if (e >= 0) { // End of stream ssize_t s = e - Total; Push(Buf, s); Total += s; Src = 0; // no more data anyway } else { // Not the end Push(Buf, r); Total += r; } } else { Push(Buf, r); Total += r; } } else { Src = NULL; // Source data is finished } } else { // Is there any unterminated space in the string pipe? int64 Sz = GStringPipe::GetSize(); if (Sz > 0) { if (Out.Length() < Sz) Out.Length(Sz); Ret = GStringPipe::Read(Out.AddressOf(), Sz); } break; } } return Ret; } ssize_t GMimeBuf::Pop(char *Str, ssize_t BufSize) { ssize_t Ret = 0; while (!(Ret = GStringPipe::Pop(Str, BufSize))) { if (Src) { char Buf[1024]; ssize_t r = Src ? Src->Read(Buf, sizeof(Buf)) : 0; if (r) { if (End) { ssize_t e = End->IsEnd(Buf, r); if (e >= 0) { // End of stream ssize_t s = e - Total; Push(Buf, s); Total += s; Src = 0; // no more data anyway } else { // Not the end Push(Buf, r); Total += r; } } else { Push(Buf, r); Total += r; } } else { Src = NULL; // Source data is finished } } else { // Is there any unterminated space in the string pipe? int64 Sz = GStringPipe::GetSize(); if (Sz > 0) { Ret = GStringPipe::Read(Str, BufSize); } break; } } return Ret; } /////////////////////////////////////////////////////////////////////// // Mime Object GMime::GMime(char *tmp) { Parent = 0; TmpPath = NewStr(tmp); Headers = 0; DataPos = 0; DataSize = 0; DataLock = 0; DataStore = 0; OwnDataStore = 0; Text.Decode.Mime = this; Text.Encode.Mime = this; Binary.Read.Mime = this; Binary.Write.Mime = this; } GMime::~GMime() { Remove(); Empty(); DeleteArray(TmpPath); } char *GMime::GetFileName() { char *n = GetSub("Content-Type", "Name"); if (!n) n = GetSub("Content-Disposition", "Filename"); if (!n) { n = Get("Content-Location"); if (n) { char *trim = TrimStr(n, "\'\""); if (trim) { DeleteArray(n); n = trim; } } } return n; } GMime *GMime::NewChild() { GMime *n = new GMime(GetTmpPath()); if (n) Insert(n); return n; } bool GMime::Insert(GMime *m, int Pos) { LgiAssert(m != NULL); if (!m) return false; if (m->Parent) { LgiAssert(m->Parent->Children.HasItem(m)); m->Parent->Children.Delete(m, true); } m->Parent = this; LgiAssert(!Children.HasItem(m)); if (Pos >= 0) Children.AddAt(Pos, m); else Children.Add(m); return true; } void GMime::Remove() { if (Parent) { LgiAssert(Parent->Children.HasItem(this)); Parent->Children.Delete(this, true); Parent = 0; } } -GMime *GMime::operator[](uint32 i) +GMime *GMime::operator[](uint32_t i) { if (i >= Children.Length()) return 0; return Children[i]; } char *GMime::GetTmpPath() { for (GMime *m = this; m; m = m->Parent) { if (m->TmpPath) return m->TmpPath; } return 0; } bool GMime::SetHeaders(const char *h) { DeleteArray(Headers); Headers = NewStr(h); return Headers != 0; } bool GMime::Lock() { bool Lock = true; if (DataLock) { Lock = DataLock->Lock(_FL); } return Lock; } void GMime::Unlock() { if (DataLock) { DataLock->Unlock(); } } bool GMime::CreateTempData() { bool Status = false; DataPos = 0; DataSize = 0; OwnDataStore = true; if ((DataStore = new GTempStream(GetTmpPath(), 4 << 20))) { Status = true; } return Status; } GStreamI *GMime::GetData(bool Detach) { GStreamI *Ds = DataStore; if (Ds) { Ds->SetPos(DataPos); if (Detach) DataStore = 0; } return Ds; } bool GMime::SetData(bool OwnStream, GStreamI *d, int Pos, int Size, LMutex *l) { if (DataStore && Lock()) { DeleteObj(DataStore); Unlock(); } if (d) { OwnDataStore = OwnStream; DataPos = Pos; DataSize = Size >= 0 ? Size : (int)d->GetSize(); DataLock = l; DataStore = d; } return true; } bool GMime::SetData(char *Str, int Len) { if (DataStore && Lock()) { DeleteObj(DataStore); Unlock(); } if (Str) { if (Len < 0) { Len = (int)strlen(Str); } DataLock = 0; DataPos = 0; DataSize = Len; DataStore = new GTempStream(GetTmpPath(), 4 << 20); if (DataStore) { DataStore->Write(Str, Len); } } return true; } void GMime::Empty() { if (OwnDataStore) { if (Lock()) { DeleteObj(DataStore); Unlock(); } } while (Children.Length()) delete Children[0]; DeleteArray(Headers); DataPos = 0; DataSize = 0; DataLock = 0; DataStore = 0; OwnDataStore = 0; } char *GMime::NewValue(char *&s, bool Alloc) { char *Status = 0; int Inc = 0; char *End; if (strchr(MimeStr, *s)) { // Delimited string char Delim = *s++; End = strchr(s, Delim); Inc = 1; } else { // Raw string End = s; while (*End && *End != ';' && *End != '\n' && *End != '\r') End++; while (strchr(MimeWs, End[-1])) End--; } if (End) { if (Alloc) { Status = NewStr(s, End-s); } s = End + Inc; } SkipWs(s); return Status; } char *GMime::StartOfField(char *s, const char *Field) { if (s && Field) { size_t FieldLen = strlen(Field); while (s && *s) { if (strchr(MimeWs, *s)) { s = strchr(s, '\n'); if (s) s++; } else { char *f = s; while (*s && *s != ':' && !strchr(MimeWs, *s)) s++; int fLen = CastInt(s - f); if (*s++ == ':' && fLen == FieldLen && _strnicmp(f, Field, FieldLen) == 0) { return f; break; } else { s = strchr(s, '\n'); if (s) s++; } } } } return 0; } char *GMime::NextField(char *s) { while (s) { while (*s && *s != '\n') s++; if (*s == '\n') { s++; if (!strchr(MimeWs, *s)) { break; } } else { break; } } return s; } char *GMime::Get(const char *Name, bool Short, const char *Default) { char *Status = 0; if (Name && Headers) { char *s = StartOfField(Headers, Name); if (s) { s = strchr(s, ':'); if (s) { s++; SkipWs(s); if (Short) { Status = NewValue(s); } else { char *e = NextField(s); while (strchr(MimeWs, e[-1])) e--; Status = NewStr(s, e-s); } } } if (!Status && Default) { Status = NewStr(Default); } } return Status; } bool GMime::Set(const char *Name, const char *Value) { if (!Name) return false; GStringPipe p; char *h = Headers; if (h) { char *f = StartOfField(h, Name); if (f) { // 'Name' exists, push out pre 'Name' header text p.Push(h, CastInt(f - Headers)); h = NextField(f); } else { if (!Value) { // Nothing to do here... return true; } // 'Name' doesn't exist, push out all the headers p.Push(Headers); h = 0; } } if (Value) { // Push new field int Vlen = CastInt(strlen(Value)); while (Vlen > 0 && strchr(MimeWs, Value[Vlen-1])) Vlen--; p.Push(Name); p.Push(": "); p.Push(Value, Vlen); p.Push(MimeEol); } // else we're deleting the feild if (h) { // Push out any header text post the 'Name' field. p.Push(h); } DeleteArray(Headers); Headers = (char*)p.New(sizeof(char)); return Headers != NULL; } char *GMime::GetSub(const char *Field, const char *Sub) { char *Status = 0; if (Field && Sub) { int SubLen = CastInt(strlen(Sub)); char *v = Get(Field, false); if (v) { // Move past the field value into the sub fields char *s = v; SkipWs(s); while (*s && *s != ';' && !strchr(MimeWs, *s)) s++; SkipWs(s); while (s && *s++ == ';') { // Parse each name=value pair SkipWs(s); char *Name = s; while (*s && *s != '=' && !strchr(MimeWs, *s)) s++; int NameLen = CastInt(s - Name); SkipWs(s); if (*s++ == '=') { bool Found = SubLen == NameLen && _strnicmp(Name, Sub, NameLen) == 0; SkipWs(s); Status = NewValue(s, Found); if (Found) break; } else break; } DeleteArray(v); } } return Status; } bool GMime::SetSub(const char *Field, const char *Sub, const char *Value, const char *DefaultValue) { if (Field && Sub) { char Buf[256]; char *s = StartOfField(Headers, Field); if (s) { // Header already exists s = strchr(s, ':'); if (s++) { SkipWs(s); GStringPipe p; // Push the field data char *e = s; while (*e && !strchr("; \t\r\n", *e)) e++; p.Push(s, CastInt(e-s)); SkipWs(e); // Loop through the subfields and push all those that are not 'Sub' s = e; while (*s++ == ';') { SkipWs(s); char *e = s; while (*e && *e != '=' && !strchr(MimeWs, *e)) e++; char *Name = NewStr(s, e-s); if (Name) { s = e; SkipWs(s); if (*s++ == '=') { char *v = NewValue(s); if (_stricmp(Name, Sub) != 0) { sprintf_s(Buf, sizeof(Buf), ";\r\n\t%s=\"%s\"", Name, v); p.Push(Buf); } DeleteArray(v); } else break; } else break; } if (Value) { // Push the new sub field sprintf_s(Buf, sizeof(Buf), ";\r\n\t%s=\"%s\"", Sub, Value); p.Push(Buf); } char *Data = p.NewStr(); if (Data) { Set(Field, Data); DeleteArray(Data); } } } else if (DefaultValue) { // Header doesn't exist at all if (Value) { // Set sprintf_s(Buf, sizeof(Buf), "%s;\r\n\t%s=\"%s\"", DefaultValue, Sub, Value); return Set(Field, Buf); } else { // Remove return Set(Field, DefaultValue); } } } return false; } ///////////////////////////////////////////////////////////////////////// // Mime Text Conversion // Rfc822 Text -> Object ssize_t GMime::GMimeText::GMimeDecode::Pull(GStreamI *Source, GStreamEnd *End) { GMimeBuf Buf(Source, End); // Stream -> Lines return Parse(&Buf); } class ParentState { public: char *Boundary; MimeBoundary Type; ParentState() { Boundary = 0; Type = MimeData; } }; int GMime::GMimeText::GMimeDecode::Parse(GStringPipe *Source, ParentState *State) { int Status = 0; if (Mime && Source) { Mime->Empty(); if (!Mime->CreateTempData()) LgiAssert(!"CreateTempData failed."); else { // Read the headers.. GStringPipe HeaderBuf; ssize_t r; if (Buffer.Length() == 0) Buffer.Length(1 << 10); while ((r = Source->Pop(Buffer)) > 0) { if (!strchr(MimeEol, Buffer[0])) { // Store part of the headers HeaderBuf.Push(Buffer.AddressOf(), r); } else break; } if (r < 0) return 0; // Not an error Mime->Headers = HeaderBuf.NewStr(); // Get various bits out of the header char *Encoding = Mime->GetEncoding(); char *Boundary = Mime->GetBoundary(); // int BoundaryLen = Boundary ? strlen(Boundary) : 0; GStream *Decoder = 0; if (Encoding) { if (_stricmp(Encoding, MimeQuotedPrintable) == 0) { Decoder = new GMimeQuotedPrintableDecode(Mime->DataStore); } else if (_stricmp(Encoding, MimeBase64) == 0) { Decoder = new GMimeBase64Decode(Mime->DataStore); } } DeleteArray(Encoding); // Read in the rest of the MIME segment bool Done = false; // int64 StartPos = Mime->DataStore->GetPos(); while (!Done) { // Process existing lines ssize_t Len; ssize_t Written = 0; Status = true; while ((Len = Source->Pop(Buffer))) { // Check for boundary MimeBoundary Type = MimeData; if (Boundary) { Type = IsMimeBoundary(Boundary, Buffer.AddressOf()); } if (State) { State->Type = IsMimeBoundary(State->Boundary, Buffer.AddressOf()); if (State->Type) { Status = Done = true; break; } } DoSegment: if (Type == MimeNextSeg) { ParentState MyState; MyState.Boundary = Boundary; GMime *Seg = new GMime(Mime->GetTmpPath()); if (Seg && Seg->Text.Decode.Parse(Source, &MyState)) { Mime->Insert(Seg); if (MyState.Type) { Type = MyState.Type; goto DoSegment; } } else break; } else if (Type == MimeEndSeg) { Done = true; break; } else { // Process data if (Decoder) { Written += Decoder->Write(Buffer.AddressOf(), Len); } else { ssize_t w = Mime->DataStore->Write(Buffer.AddressOf(), Len); if (w > 0) { Written += w; } else { Done = true; Status = false; break; } } } } Mime->DataSize = Written; if (Len == 0) { Done = true; } } DeleteObj(Decoder); DeleteArray(Boundary); } } return Status; } void GMime::GMimeText::GMimeDecode::Empty() { } // Object -> Rfc822 Text ssize_t GMime::GMimeText::GMimeEncode::Push(GStreamI *Dest, GStreamEnd *End) { int Status = 0; if (Mime) { char Buf[1024]; int Ch; // Check boundary char *Boundary = Mime->GetBoundary(); if (Mime->Children.Length()) { // Boundary required if (!Boundary) { // Create one char b[256]; CreateMimeBoundary(b, sizeof(b)); Mime->SetBoundary(b); Boundary = Mime->GetBoundary(); } } else if (Boundary) { // Remove boundary Mime->SetBoundary(0); DeleteArray(Boundary); } // Check encoding char *Encoding = Mime->GetEncoding(); if (!Encoding) { // Detect an appropriate encoding int MaxLine = 0; bool Has8Bit = false; bool HasBin = false; if (Mime->DataStore && Mime->Lock()) { Mime->DataStore->SetPos(Mime->DataPos); int x = 0; for (int i=0; iDataSize; ) { ssize_t m = MIN(Mime->DataSize - i, sizeof(Buf)); ssize_t r = Mime->DataStore->Read(Buf, m); if (r > 0) { for (int n=0; nUnlock(); } if (HasBin) { Encoding = NewStr(MimeBase64); } else if (Has8Bit || MaxLine > 70) { Encoding = NewStr(MimeQuotedPrintable); } if (Encoding) { Mime->SetEncoding(Encoding); } } // Write the headers GToken h(Mime->Headers, MimeEol); for (unsigned i=0; iWrite(h[i], CastInt(strlen(h[i]))); Dest->Write(MimeEol, 2); } Dest->Write(MimeEol, 2); // Write data GStream *Encoder = 0; if (Encoding) { if (_stricmp(Encoding, MimeQuotedPrintable) == 0) { Encoder = new GMimeQuotedPrintableEncode(Dest); } else if (_stricmp(Encoding, MimeBase64) == 0) { Encoder = new GMimeBase64Encode(Dest); } } if (!Encoder) { Encoder = new GMimeTextEncode(Dest); } if (Mime->DataStore) { if (Mime->Lock()) { Mime->DataStore->SetPos(Mime->DataPos); Status = Mime->DataSize == 0; // Nothing is a valid segment?? for (int i=0; iDataSize; ) { ssize_t m = MIN(Mime->DataSize-i, sizeof(Buf)); ssize_t r = Mime->DataStore->Read(Buf, m); if (r > 0) { Encoder->Write(Buf, r); Status = true; } else break; } Mime->Unlock(); } } else { Status = true; } DeleteObj(Encoder); // Write children if (Mime->Children.Length() && Boundary) { GAutoString Mt(Mime->GetMimeType()); if (Mt && !_stricmp(Mt, "multipart/alternative")) { // Sort the children to order richer content at the bottom... Mime->Children.Sort(AltSortCmp); } for (unsigned i=0; iChildren.Length(); i++) { Ch = sprintf_s(Buf, sizeof(Buf), "--%s\r\n", Boundary); Dest->Write(Buf, Ch); if (!Mime->Children[i]->Text.Encode.Push(Dest, End)) { break; } Status = 1; } Ch = sprintf_s(Buf, sizeof(Buf), "--%s--\r\n", Boundary); Dest->Write(Buf, Ch); } // Clean up DeleteArray(Encoding); DeleteArray(Boundary); } return Status; } void GMime::GMimeText::GMimeEncode::Empty() { } ///////////////////////////////////////////////////////////////////////// // Mime Binary Serialization // Source -> Object ssize_t GMime::GMimeBinary::GMimeRead::Pull(GStreamI *Source, GStreamEnd *End) { if (Source) { int32 Header[4]; Mime->Empty(); // Read header block (Magic, HeaderSize, DataSize, # of Children) // and check magic if (Source->Read(Header, sizeof(Header)) == sizeof(Header) && Header[0] == MimeMagic) { // Read header data Mime->Headers = new char[Header[1]+1]; if (Mime->Headers && Source->Read(Mime->Headers, Header[1]) == Header[1]) { // NUL terminate Mime->Headers[Header[1]] = 0; // Skip body data if (Source->SetPos(Source->GetPos() + Header[2]) > 0) { // Read the children in for (int i=0; iGetTmpPath()); if (c && c->Binary.Read.Pull(Source, End)) { Mime->Insert(c); } else break; } } } return 1; // success } } return 0; // failure } void GMime::GMimeBinary::GMimeRead::Empty() { } // Object -> Dest int64 GMime::GMimeBinary::GMimeWrite::GetSize() { int64 Size = 0; if (Mime) { Size = (sizeof(int32) * 4) + // Header magic + block sizes (Mime->Headers ? strlen(Mime->Headers) : 0) + // Headers (Mime->DataStore ? Mime->DataSize : 0); // Data // Children for (unsigned i=0; iChildren.Length(); i++) { Size += Mime->Children[i]->Binary.Write.GetSize(); } } return Size; } ssize_t GMime::GMimeBinary::GMimeWrite::Push(GStreamI *Dest, GStreamEnd *End) { if (Dest && Mime) { int32 Header[4] = { MimeMagic, Mime->Headers ? (int32)strlen(Mime->Headers) : 0, Mime->DataStore ? (int32)Mime->DataSize : 0, (int32) Mime->Children.Length() }; if (Dest->Write(Header, sizeof(Header)) == sizeof(Header)) { if (Mime->Headers) { Dest->Write(Mime->Headers, Header[1]); } if (Mime->DataStore) { char Buf[1024]; ssize_t Written = 0; ssize_t Read = 0; ssize_t r; while ((r = Mime->DataStore->Read(Buf, MIN(sizeof(Buf), Header[2]-Read) )) > 0) { ssize_t w; if ((w = Dest->Write(Buf, r)) <= 0) { // Write error break; } Written += w; Read += r; } // Check we've written out all the data LgiAssert(Written < Header[2]); if (Written < Header[2]) { return 0; } } for (unsigned i=0; iChildren.Length(); i++) { Mime->Children[i]->Binary.Write.Push(Dest, End); } return 1; } } return 0; } void GMime::GMimeBinary::GMimeWrite::Empty() { } diff --git a/src/common/INet/HttpTools.cpp b/src/common/INet/HttpTools.cpp --- a/src/common/INet/HttpTools.cpp +++ b/src/common/INet/HttpTools.cpp @@ -1,1150 +1,1150 @@ #include #include #include "Lgi.h" #include "HttpTools.h" #include "INet.h" #include "INetTools.h" #include "GToken.h" #include "IHttp.h" #ifdef _DEBUG // #define PROXY_OVERRIDE "10.10.0.50:8080" #endif GXmlTag *GetFormField(GXmlTag *Form, char *Field) { if (Form && Field) { char *Ast = strchr(Field, '*'); GArray Matches; for (GXmlTag *x = Form->Children.First(); x; x = Form->Children.Next()) { char *Name = x->GetAttr("Name"); if (Name) { if (Ast) { if (MatchStr(Field, Name)) { Matches.Add(x); } } else if (_stricmp(Name, Field) == 0) { Matches.Add(x); } } } if (Matches.Length() >= 1) { return Matches[0]; } /* else if (Matches.Length() > 1) { for (int i=0; iGetAttr("Name")); } LgiAssert(0); } */ } return 0; } void StrFormEncode(GStream &p, char *s, bool InValue) { for (char *c = s; *c; c++) { if (isalpha(*c) || isdigit(*c) || *c == '_' || *c == '.' || (!InValue && *c == '+') || *c == '-' || *c == '%') { p.Write(c, 1); } else if (*c == ' ') { p.Write((char*)"+", 1); } else { p.Print("%%%02.2X", *c); } } } // This just extracts the forms in the HTML and makes an XML tree of what it finds. GXmlTag *ExtractForms(char *Html, GStream *Log) { GXmlTag *f = 0; if (Html) { WebPage Page(Html, Log); if (Page.Html) { GXmlTag *x = Page.GetRoot(Log); if (x) { for (GXmlTag *c = x->Children.First(); c; c = x->Children.Next()) { if (c->IsTag("form")) { if (!f) { f = new GXmlTag("Forms"); } if (f) { GXmlTag *Form = new GXmlTag("Form"); if (Form) { f->InsertTag(Form); char *s = c->GetAttr("action"); if (s) { GStringPipe p; for (char *c = s; *c; c++) { if (*c == ' ') { char h[6]; - sprintf_s(h, sizeof(h), "%%%2.2X", (uint8)*c); + sprintf_s(h, sizeof(h), "%%%2.2X", (uint8_t)*c); p.Push(h); } else p.Push(c, 1); } char *e = p.NewStr(); Form->SetAttr("action", e); DeleteArray(e); } s = c->GetAttr("method"); if (s) Form->SetAttr("method", s); while (c) { #define CopyAttr(name) \ { char *s = c->GetAttr(name); \ if (s) i->SetAttr(name, s); } if (c->IsTag("input")) { GXmlTag *i = new GXmlTag("Input"); if (i) { Form->InsertTag(i); CopyAttr("type"); CopyAttr("name"); CopyAttr("value"); } } if (c->IsTag("select")) { GXmlTag *i = new GXmlTag("Select"); if (i) { Form->InsertTag(i); CopyAttr("name"); while ((c && !c->GetTag()) || _stricmp(c->GetTag(), "/select") != 0) { if (c->IsTag("option")) { GXmlTag *o = new GXmlTag("Option"); if (o) { char *s = c->GetAttr("Value"); if (s) o->SetAttr("Value", s); o->SetContent(c->GetContent()); i->InsertTag(o); } } c = x->Children.Next(); } } } if (c->IsTag("/form")) { break; } c = x->Children.Next(); } } } } } } } else if (Log) Log->Print("%s:%i - No html after parsing out scripts\n", __FILE__, __LINE__); } else if (Log) Log->Print("%s:%i - No html.\n", __FILE__, __LINE__); return f; } void XmlToStream(GStream *s, GXmlTag *x, char *Style) { if (s && x) { GStringPipe p(1024); GXmlTree t; if (Style) t.SetStyleFile(Style, "text/xsl"); t.Write(x, &p); int Len = (int)p.GetSize(); char *Xml = p.NewStr(); if (Xml) { s->Write(Xml, Len); DeleteArray(Xml); } } } //////////////////////////////////////////////////////////////////////////////////////////////////// WebPage::WebPage(char *Page, GStream *Log) { Html = 0; Script = 0; Parsed = 0; Charset = 0; if (Page) { // Parse out the scripts... GStringPipe h, scr; for (char *s = Page; s && *s; ) { char *e = stristr(s, "'); if (e) { e++; h.Push(s, (int) (e - s)); s = e; e = stristr(s, ""); if (e) { scr.Push(s, (int) (e - s)); s = e; } else { if (Log) Log->Print("%s:%i - No end of script tag????\n", __FILE__, __LINE__); scr.Push(s); break; } } else break; } else { h.Push(s); break; } } Html = h.NewStr(); Script = scr.NewStr(); } } WebPage::~WebPage() { DeleteArray(Html); DeleteArray(Script); DeleteArray(Charset); DeleteObj(Parsed); } char *WebPage::GetCharSet() { if (!Charset) { GXmlTag *t = GetRoot(); if (t) { List::I it = t->Children.begin(); for (GXmlTag *c = *it; !Charset && c; c = *++it) { if (c->IsTag("meta")) { char *http_equiv = c->GetAttr("http-equiv"); if (http_equiv && _stricmp(http_equiv, "Content-Type") == 0) { char *Value = c->GetAttr("Content"); if (Value) { char *s = stristr(Value, "charset="); if (s) { s += 8; char *e = s; while (*e && (isalpha(*e) || isdigit(*e) || *e == '-' || *e == '_')) e++; Charset = NewStr(s, e - s); } } } } } } } return Charset; } GXmlTag *WebPage::GetRoot(GStream *Log) { if (!Parsed && Html) { GXmlTree t(GXT_NO_DOM); if ((Parsed = new GXmlTag)) { GStringPipe p; p.Push(Html); t.GetEntityTable()->Add("nbsp", ' '); if (!t.Read(Parsed, &p, 0) && Log) { Log->Print("%s:%i - Html parse failed: %s\n", _FL, t.GetErrorMsg()); DeleteObj(Parsed); } } } return Parsed; } char *WebPage::GetFormValue(char *field) { GXmlTag *x = GetRoot(); if (x) { for (GXmlTag *t = x->Children.First(); t; t = x->Children.Next()) { if (t->IsTag("input")) { char *Name = t->GetAttr("name"); if (Name && _stricmp(Name, field) == 0) { return t->GetAttr("Value"); } } } } return 0; } ////////////////////////////////////////////////////////////////////////////////////////// FormPost::FormPost(GXmlTag *f) { Form = f; } GXmlTag *FormPost::GetField(char *n) { if (n && Form) { char *a = strchr(n, '*'); for (GXmlTag *t = Form->Children.First(); t; t = Form->Children.Next()) { char *Name = t->GetAttr("name"); if (Name) { if (a) { if (MatchStr(n, Name)) return t; } else { if (_stricmp(Name, n) == 0) return t; } } } } return 0; } char *FormPost::GetActionUri() { return Form->GetAttr("action"); } char *FormPost::EncodeFields(GStream *Debug, char *RealFields, bool EncodePlus) { GStringPipe p; LHashTbl,bool> Done; LHashTbl,char*> Real; if (RealFields) { GToken t(RealFields, "\n"); for (unsigned i=0; iValue && v->Field) { #if 0 if (Debug && stricmp(v->Field, "__VIEWSTATE") == 0) { Debug->Print("OurField '%s' is %i long\n", v->Field, strlen(v->Value)); } #endif if (!Done.Find(v->Field)) { Done.Add(v->Field, true); if (p.GetSize()) p.Push("&"); char *a; while ((a = strchr(v->Field, ' '))) *a = '+'; while ((a = strchr(v->Value, ' '))) *a = '+'; StrFormEncode(p, v->Field, false); p.Write((char*)"=", 1); StrFormEncode(p, v->Value, EncodePlus); if (Debug && RealFields) { char *Value = Real.Find(v->Field); if (Value) { if (_stricmp(Value, v->Value) != 0) { Debug->Print("\tValues for '%s' different: '%s' = '%s'\n", v->Field.Get(), Value, v->Value.Get()); } } else { Debug->Print("\tExtra field in our list: %s = %s\n", v->Field.Get(), v->Value.Get()); } Real.Delete(v->Field); } } } } if (Debug && RealFields) { // char *k; // for (char *p = Real.First(&k); p; p = Real.Next(&k)) for (auto k : Real) { Debug->Print("\tMissing field: %s = %s\n", k.key, k.value); } } #if 0 // if (Form) { for (GXmlTag *t = Form->Children.First(); t; t = Form->Children.Next()) { char *Type = t->GetAttr("type"); if (Type && _stricmp(Type, "image") == 0) continue; char *Name = t->GetAttr("name"); if (Name) { if (!Done.Find(Name)) { Done.Add(Name, true); if (p.GetSize()) p.Push("&"); StrFormEncode(p, Name, false); p.Write((char*)"=", 1); } } } } #endif return p.NewStr(); } bool Match(char *a, char *b) { if (a && b) { bool Fuzzy = strchr(b, '*') != 0; if (Fuzzy) { return MatchStr(b, a); } else { return _stricmp(a, b) == 0; } } return false; } bool FormPost::Set(char *field, char *value, GStream *Log, bool AllowCreate) { bool Status = false; if (field && value && Form) { GXmlTag *f = GetFormField(Form, field); if (f) { if (f->GetTag()) { if (f->IsTag("Input")) { char *Nm = f->GetAttr("Name"); FormValue *v = Nm ? Get(Nm) : 0; if (v) { v->Value.Reset(NewStr(value)); Status = true; } } else if (f->IsTag("Select")) { char *Nm = f->GetAttr("Name"); if (Nm && Match(Nm, field)) { for (GXmlTag *o = f->Children.First(); o; o = f->Children.Next()) { char *Value = o->GetAttr("Value"); char *Content = o->GetContent(); if (Value || Content) { bool Mat = !ValidStr(value); if (!Mat) { Mat = Match(Value, value); } if (!Mat) { Mat = Match(Content, value); } if (Mat) { FormValue *v = Nm ? Get(Nm) : 0; if (v) { if (ValidStr(value)) { char *n = o->GetAttr("Value"); if (n) { v->Value.Reset(NewStr(n)); Status = true; // if (Log) Log->Print("'%s'='%s'\n", v->Field, v->Value); break; } } else { v->Value.Reset(NewStr(value)); Status = true; break; } } } } } if (!Status && Log) { Log->Print("Error: No option for field '%s' had the value '%s'\n", field, value); } } } else if (Log) Log->Print("%s:%i - Unrecognised field '%s'\n", _FL, f->GetTag()); } else if (Log) Log->Print("%s:%i - Field has no tag\n", _FL); } else { FormValue *v = Get(field, AllowCreate); if (v) { v->Value.Reset(NewStr(value)); // if (Log) Log->Print("'%s'='%s'\n", v->Field, v->Value); Status = true; } else if (Log) { Log->Print("%s:%i - Invalid field name '%s'\n", _FL, field); } } } else { if (Log) Log->Print("%s:%i - Invalid arguments trying to set '%s' = '%s'\n", _FL, field, value); } return Status; } FormValue *FormPost::Get(char *Field, bool Create) { char *Ast = strchr(Field, '*'); GArray Matches; for (unsigned i=0; iField.Reset(NewStr(Field)); return v; } return 0; } ////////////////////////////////////////////////////////////////////////////////////////////////////// HttpTools::HttpTools() { Wnd = 0; } HttpTools::~HttpTools() { } void HttpTools::DumpView(GViewI *v, char *p) { if (v && p) { - uint8 *c = (uint8*) p; + uint8_t *c = (uint8_t*) p; while (*c) { if (*c & 0x80) { *c = ' '; } c++; } v->Name(p); } } char *HttpTools::Fetch(char *uri, GStream *Log, GViewI *Dump, CookieJar *Cookies) { char *Page = 0; const char *DefHeaders = "User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.0.9) Gecko/20061206 Firefox/1.5.0.9\r\n" "Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5\r\n" "Accept-Language: en-us,en;q=0.5\r\n" "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n"; if (ValidStr(uri)) { IHttp h; GProxyUri Proxy; if (Proxy.Host) h.SetProxy(Proxy.Host, Proxy.Port); GUri u(uri); if (!u.Port) u.Port = HTTP_PORT; GAutoPtr Sock(new GSocket); if (h.Open(Sock, u.Host)) { int ProtocolStatus = 0; GStringPipe p, hdr; GAutoString Enc = u.GetUri(); IHttp::ContentEncoding type; if (h.Get(Enc, DefHeaders, &ProtocolStatus, &p, &type, &hdr)) { if (Cookies) { char *Headers = hdr.NewStr(); if (Headers) { Cookies->Set(Headers); DeleteArray(Headers); } } if (ProtocolStatus == 200) { Page = p.NewStr(); if (Page) { char *Err = stristr(Page, ""); if (Err) { Err[5] = ' '; } DumpView(Dump, Page); } else { Log->Print("Error: No page data for '%s'\n", uri); } } /* else if (ProtocolStatus == 302) { char *Headers = hdr.NewStr(); if (Headers) { char *Loc = InetGetHeaderField(Headers, "Location", -1); if (Loc) { Log->Print("Redirects to '%s'\n", Loc); DeleteArray(Loc); } DeleteArray(Headers); } } */ else if (Log) { if (Dump) { char *pg = p.NewStr(); if (pg) Dump->Name(pg); DeleteArray(pg); } Log->Print("Error: Error getting '%s' with HTTP error %i\n", Enc.Get(), ProtocolStatus); } } else if (Log) { Log->Print("Error: Failed to GET '%s' (errorcode: %i)\n", uri, ProtocolStatus); } } else if (Log) { Log->Print("Error: Couldn't open connection to '%s' [:%s]\n", u.Host, u.Port); } } return Page; } char *HttpTools::Post(char *uri, char *headers, char *body, GStream *Log, GViewI *Dump) { if (uri && headers && body) { IHttp h; GUri u(uri); GSocket s; bool Open; GProxyUri Proxy; if (Proxy.Host) { Open = s.Open(Proxy.Host, Proxy.Port) != 0; } else { Open = s.Open(u.Host, u.Port ? u.Port : 80) != 0; } if (Open) { char *e = headers + strlen(headers) - 1; while (e > headers && strchr("\r\n", *e)) { *e-- = 0; } size_t ContentLen = strlen(body); GStringPipe p; char *EncPath = u.Encode(u.Path); if (Proxy.Host) { p.Print("POST http://%s%s HTTP/1.1\r\n" "Host: %s\r\n", u.Host, EncPath, u.Host); } else { p.Print("POST %s HTTP/1.1\r\n" "Host: %s\r\n", EncPath, u.Host); } DeleteArray(EncPath); p.Print("Content-Length: %i\r\n" "%s\r\n" "\r\n", ContentLen, headers); int64 Len = p.GetSize(); char *h = p.NewStr(); if (h) { ssize_t w = s.Write(h, (int)Len, 0); if (w == Len) { w = s.Write(body, ContentLen, 0); if (w == ContentLen) { char Buf[1024]; ssize_t r; while ((r = s.Read(Buf, sizeof(Buf), 0)) > 0) { p.Push(Buf, r); } if (Log && p.GetSize() == 0) { Log->Print("HTTP Response: Failed with %i\n", r); } if (p.GetSize()) { char *Page = p.NewStr(); if (Page) { DumpView(Dump, Page); return Page; } } } else { Log->Print("HTTP Request: Error, wrote %i of %i bytes\n", w, ContentLen); } } else { Log->Print("HTTP Request: Error, wrote %i of %i bytes\n", w, Len); } DeleteArray(h); } } else if (Log) { Log->Print("Error: Failed to open socket.\n"); } } return 0; } ////////////////////////////////////////////////////////////////////// void CookieJar::Empty() { // for (char *p = (char*)First(); p; p = (char*)Next()) for (auto p : *this) { DeleteArray(p.value); } } char *EndCookie(char *s) { while (*s) { if (*s == '\n' || *s == '\r' || *s == ';') return s; s++; } return 0; } void CookieJar::Set(char *Cookie, char *Value) { char *s; if ((s = (char*)Find(Cookie))) { DeleteArray(s); Delete(Cookie); } Add(Cookie, NewStr(Value)); } void CookieJar::Set(char *Headers) { Empty(); if (Headers) { const char *Match = "Set-Cookie"; size_t MatchLen = strlen(Match); char Ws[] = " \t\r\n"; for (char *s = stristr(Headers, "\r\n"); s && *s; ) { while (*s && strchr(Ws, *s)) s++; char *Hdr = s; while (*s && *s != ':' && !strchr(Ws, *s)) s++; if (*s == ':') { ssize_t Len = s - Hdr; bool IsCookie = Len == MatchLen && _strnicmp(Hdr, Match, Len) == 0; s++; while (*s && strchr(Ws, *s)) s++; if (IsCookie) { char *e; while ((e = EndCookie(s))) { char *Cookie = NewStr(s, e - s); if (Cookie) { char *Eq = strchr(Cookie, '='); if (Eq) { *Eq++ = 0; Add(Cookie, NewStr(Eq)); } } s = e + 1; while (*s && strchr(" \t", *s)) s++; if (*s == '\r' || *s == '\n' || *s == 0) break; } } while (s && *s) { while (*s && *s != '\n') s++; if (*s) { if (*s == '\n' && s[1] && strchr(Ws, s[1])) s += 2; else break; } } } else break; } } } char *CookieJar::Get() { GStringPipe p; // char *k; // for (char *s = (char*)First(&k); s; s = (char*)Next(&k)) for (auto k : *this) { if (p.GetSize()) p.Print("; "); p.Print("%s=%s", k.key, k.value); } return p.NewStr(); } /////////////////////////////////////////////////////////////////////////////////// char *HtmlTidy(char *Html) { GStringPipe p(256); int Depth = 0; bool LastWasEnd = false; for (char *s = Html; *s; ) { char *c = s; while (*c) { if (*c == '<') { // start tag if (c > s) { Depth++; for (int i=0; i') c++; bool IsEnd = s[1] == '/'; if (IsEnd) { // end tag Depth--; } else { // start tag if (!LastWasEnd) Depth++; } for (int i=0; i Sock(new GSocket); if (Http.Open(Sock, u.Host)) { GStringPipe Data; int Code = 0; IHttp::ContentEncoding enc; if (Http.Get(Uri, 0, &Code, &Data, &enc)) { if (Code == 200) { char n[MAX_PATH], r[32]; do { LgiGetTempPath(n, sizeof(n)); sprintf_s(r, sizeof(r), "_%x", LgiRand()); LgiMakePath(n, sizeof(n), n, r); } while (FileExists(n)); GFile f; if (f.Open(n, O_WRITE)) { GCopyStreamer c; c.Copy(&Data, &f); f.Close(); if ((Img = LoadDC(n))) { // LgiTrace("Read uri '%s'\n", __FILE__, __LINE__, Uri); } else LgiTrace("%s:%i - Failed to read image '%s'\n", _FL, n); FileDev->Delete(n, false); } else LgiTrace("%s:%i - Failed to open '%s'\n", _FL, n); } else LgiTrace("%s:%i - HTTP code %i\n", _FL, Code); } else LgiTrace("%s:%i - failed to download to '%s'\n", _FL, Uri); } else LgiTrace("%s:%i - failed to connect to '%s:%i'\n", _FL, u.Host, u.Port); } return Img; } diff --git a/src/common/INet/Mail.cpp b/src/common/INet/Mail.cpp --- a/src/common/INet/Mail.cpp +++ b/src/common/INet/Mail.cpp @@ -1,4000 +1,4000 @@ /*hdr ** FILE: Mail.cpp ** AUTHOR: Matthew Allen ** DATE: 28/5/98 ** DESCRIPTION: Mail app ** ** Copyright (C) 1998, Matthew Allen ** fret@memecode.com */ #include #include #include #include #include "Lgi.h" #include "Mail.h" #include "GToken.h" #include "Base64.h" #include "INetTools.h" #include "LDateTime.h" #include "GDocView.h" #include "Store3Defs.h" #include "LgiRes.h" #include "../Hash/md5/md5.h" const char *sTextPlain = "text/plain"; const char *sTextHtml = "text/html"; const char *sTextXml = "text/xml"; const char *sApplicationInternetExplorer = "application/internet-explorer"; const char sMultipartMixed[] = "multipart/mixed"; const char sMultipartEncrypted[] = "multipart/encrypted"; const char sMultipartSigned[] = "multipart/signed"; const char sMultipartAlternative[] = "multipart/alternative"; const char sMultipartRelated[] = "multipart/related"; const char sAppOctetStream[] = "application/octet-stream"; ////////////////////////////////////////////////////////////////////////////////////////////////// LogEntry::LogEntry(GColour col) { c = col; } bool LogEntry::Add(const char *t, ssize_t len) { if (!t) return false; if (len < 0) len = strlen(t); /* // Strip off any whitespace on the end of the line. while (len > 0 && strchr(" \t\r\n", t[len-1])) len--; */ GAutoWString w(Utf8ToWide(t, len)); if (!w) return false; size_t ch = StrlenW(w); return Txt.Add(w, ch); } ////////////////////////////////////////////////////////////////////////////////////////////////// // return true if there are any characters with the 0x80 bit set bool Is8Bit(char *Text) { if (!Text) return false; while (*Text) { if (*Text & 0x80) return true; Text++; } return false; } // returns the maximum length of the lines contained in the string int MaxLineLen(char *Text) { if (!Text) return false; int Max = 0; int i = 0; for (char *c = Text; *c; c++) { if (*c == '\r') { // return } else if (*c == '\n') { // eol Max = MAX(i, Max); i = 0; } else { // normal char i++; } } return Max; } bool IsDotLined(char *Text) { if (Text) { for (char *l = Text; l && *l; ) { if (l[0] == '.') { if (l[1] == '\n' || l[1] == 0) { return true; } } l = strchr(l, '\n'); if (l) l++; } } return false; } char ConvHexToBin(char c) { if (c >= '0' && c <= '9') return c - '0'; if (c >= 'a' && c <= 'f') return c + 10 - 'a'; if (c >= 'A' && c <= 'F') return c + 10 - 'A'; return 0; } // Is s a valid non-whitespace string? bool ValidNonWSStr(const char *s) { if (s && *s) { while (*s && strchr(" \r\t\n", *s)) { s++; } if (*s) { return true; } } return false; } void TokeniseStrList(char *Str, List &Output, const char *Delim) { if (Str && Delim) { char *s = Str; while (*s) { while (*s && strchr(WhiteSpace, *s)) s++; char *e = s; for (; *e; e++) { if (strchr("\'\"", *e)) { // handle string constant char delim = *e++; e = strchr(e, delim); } else if (*e == '<') { e = strchr(e, '>'); } else { while (*e && *e != '<' && !IsWhiteSpace(*e) && !strchr(Delim, *e)) e++; } if (!e || !*e || strchr(Delim, *e)) { break; } } ssize_t Len = e ? e - s : strlen(s); if (Len > 0) { char *Temp = new char[Len+1]; if (Temp) { memcpy(Temp, s, Len); Temp[Len] = 0; Output.Insert(Temp); } } if (e) { s = e; for (; *s && strchr(Delim, *s); s++); } else break; } } } //////////////////////////////////////////////////////////////////////////////// char *DecodeBase64Str(char *Str, int Len) { if (Str) { ssize_t B64Len = (Len < 0) ? strlen(Str) : Len; ssize_t BinLen = BufferLen_64ToBin(B64Len); char *s = new char[BinLen+1]; if (s) { ssize_t Converted = ConvertBase64ToBinary((uchar*)s, BinLen, Str, B64Len); s[Converted] = 0; DeleteArray(Str); Str = s; } } return Str; } char *DecodeQuotedPrintableStr(char *Str, ssize_t Len) { if (Str) { if (Len < 0) Len = strlen(Str); uchar *s = new uchar[Len+1]; if (s) { char *Out = (char*) s; char *Text = Str; for (int i=0; i='a'&&(c)<='z')?(c)-'a'+'A':(c) ) #endif char *DecodeRfc2047(char *Str) { if (!Str) return NULL; GStringPipe p(256); for (char *s = Str; *s; ) { char *e = s; bool Decode = 0, Descape = 0; while (*e) { if ( (Decode = (e[0] == '=' && e[1] == '?')) || (Descape = (e[0] == '\\')) ) { // Emit characters between 's' and 'e' if (e > s) p.Write(s, e - s); break; } e++; } if (Decode) { // is there a word remaining bool Encoded = false; char *Start = e + 2; char *First = strchr(Start, '?'); char *Second = First ? strchr(First + 1, '?') : NULL; char *End = Second ? strstr(Second + 1, "?=") : NULL; if (End) { GString Cp(Start, First - Start); int Type = CONTENT_NONE; bool StripUnderscores = false; if (ToUpper(First[1]) == 'B') { // Base64 encoding Type = CONTENT_BASE64; } else if (ToUpper(First[1]) == 'Q') { // Quoted printable Type = CONTENT_QUOTED_PRINTABLE; StripUnderscores = true; } if (Type != CONTENT_NONE) { Second++; char *Block = NewStr(Second, End-Second); if (Block) { switch (Type) { case CONTENT_BASE64: Block = DecodeBase64Str(Block); break; case CONTENT_QUOTED_PRINTABLE: Block = DecodeQuotedPrintableStr(Block); break; } size_t Len = strlen(Block); if (StripUnderscores) { for (char *i=Block; *i; i++) { if (*i == '_') *i = ' '; } } if (Cp && !_stricmp(Cp, "utf-8")) { p.Write((uchar*)Block, Len); } else { GAutoString Utf8((char*)LgiNewConvertCp("utf-8", Block, Cp, Len)); if (Utf8) { if (LgiIsUtf8(Utf8)) p.Write((uchar*)Utf8.Get(), strlen(Utf8)); } else { p.Write((uchar*)Block, Len); } } DeleteArray(Block); } s = End + 2; if (*s == '\n') { s++; while (*s && strchr(WhiteSpace, *s)) s++; } Encoded = true; } } if (!Encoded) { // Encoding error, just emit the raw string and exit. size_t Len = strlen(s); p.Write((uchar*) s, Len); break; } } else if (Descape) { // Un-escape the string... e++; if (*e) p.Write(e, 1); else break; s = e + 1; } else { // Last segment of string... LgiAssert(*e == 0); if (e > s) p.Write(s, e - s); break; } } DeleteArray(Str); return p.NewStr(); } #define MIME_MAX_LINE 76 char *EncodeRfc2047(char *Str, const char *CodePage, List *CharsetPrefs, ssize_t LineLength) { if (!CodePage) { CodePage = "utf-8"; } GStringPipe p(256); if (!Str) return NULL; if (Is8Bit(Str)) { // pick an encoding bool Base64 = false; const char *DestCp = "utf-8"; size_t Len = strlen(Str);; if (_stricmp(CodePage, "utf-8") == 0) { DestCp = LgiDetectCharset(Str, Len, CharsetPrefs); } int Chars = 0; for (unsigned i=0; i 0 && ((double)Chars/Len) > 0.4 ) ) { Base64 = true; } char *Buf = (char*)LgiNewConvertCp(DestCp, Str, CodePage, Len); if (Buf) { // encode the word char Prefix[64]; int Ch = sprintf_s(Prefix, sizeof(Prefix), "=?%s?%c?", DestCp, Base64 ? 'B' : 'Q'); p.Write(Prefix, Ch); LineLength += Ch; if (Base64) { // Base64 size_t InLen = strlen(Buf); // int EstBytes = BufferLen_BinTo64(InLen); char Temp[512]; ssize_t Bytes = ConvertBinaryToBase64(Temp, sizeof(Temp), (uchar*)Buf, InLen); p.Push(Temp, Bytes); } else { // Quoted printable for (char *w = Buf; *w; w++) { if (*w == ' ') { if (LineLength > MIME_MAX_LINE - 3) { p.Print("?=\r\n\t%s", Prefix); LineLength = 1 + strlen(Prefix); } p.Write((char*)"_", 1); LineLength++; } else if (*w & 0x80 || *w == '_' || *w == '?' || *w == '=') { if (LineLength > MIME_MAX_LINE - 5) { p.Print("?=\r\n\t%s", Prefix); LineLength = 1 + strlen(Prefix); } char Temp[16]; Ch = sprintf_s(Temp, sizeof(Temp), "=%2.2X", (uchar)*w); p.Write(Temp, Ch); LineLength += Ch; } else { if (LineLength > MIME_MAX_LINE - 3) { p.Print("?=\r\n\t%s", Prefix); LineLength = 1 + strlen(Prefix); } p.Write(w, 1); LineLength++; } } } p.Push("?="); DeleteArray(Buf); } DeleteArray(Str); Str = p.NewStr(); } else { bool RecodeNewLines = false; for (char *s = Str; *s; s++) { if (*s == '\n' && (s == Str || s[-1] != '\r')) { RecodeNewLines = true; break; } } if (RecodeNewLines) { for (char *s = Str; *s; s++) { if (*s == '\r') ; else if (*s == '\n') p.Write("\r\n", 2); else p.Write(s, 1); } DeleteArray(Str); Str = p.NewStr(); } } return Str; } ////////////////////////////////////////////////////////////////////////////// void DeNullText(char *in, ssize_t &len) { char *out = in; char *end = in + len; while (in < end) { if (*in) { *out++ = *in; } else { len--; } in++; } } ////////////////////////////////////////////////////////////////////////////// typedef char CharPair[2]; static CharPair Pairs[] = { {'<', '>'}, {'(', ')'}, {'\'', '\''}, {'\"', '\"'}, {0, 0}, }; struct MailAddrPart { GAutoString Part; bool Brackets; bool ValidEmail; GAutoString RemovePairs(char *Str, ssize_t Len, CharPair *Pairs) { char *s = Str; if (Len < 0) Len = strlen(s); while (*s && strchr(WhiteSpace, *s)) { s++; Len--; } if (!*s) return GAutoString(); // Get the end of the string... char *e = s; if (Len < 0) e += strlen(s); else e += Len; // Seek back over any trailing whitespace while (e > s && strchr(WhiteSpace, e[-1])) e--; for (CharPair *p = Pairs; (*p)[0]; p++) { if ((*p)[0] == *s && (*p)[1] == e[-1]) { s++; e--; if (s < e) { // reset search p = Pairs - 1; } else break; } } Len = e - s; if (Len < 0) return GAutoString(); return GAutoString(NewStr(s, Len)); } MailAddrPart(char *s, ssize_t len) { ValidEmail = false; Brackets = false; if (s) { if (len < 0) len = strlen(s); while (strchr(WhiteSpace, *s) && len > 0) { s++; len--; } Brackets = *s == '<'; Part = RemovePairs(s, len, Pairs); // ValidEmail = IsValidEmail(Part); } } int Score() { if (!Part) return 0; return (ValidEmail ? 1 : 0) + (Brackets ? 1 : 0); } }; int PartCmp(GAutoPtr *a, GAutoPtr *b) { return (*b)->Score() - (*a)->Score(); } void DecodeAddrName(const char *Str, GAutoString &Name, GAutoString &Addr, const char *DefaultDomain) { /* Testing code char *Input[] = { "\"Sound&Secure@speedytechnical.com\" ", "\"@MM-Social Mailman List\" ", "'Matthew Allen (fret)' ", "Matthew Allen (fret) ", "\"'Matthew Allen'\" ", "Matthew Allen", "fret@memecode.com", "\"\" ", " (fret@memecode.com)", "Matthew Allen ", "\"Matthew, Allen\" (fret@memecode.com)", "Matt'hew Allen ", "john.omalley ", "Bankers' Association (ABA)", "'Amy's Mum' ", "\"Philip Doggett (JIRA)\" ", 0 }; GAutoString Name, Addr; for (char **i = Input; *i; i++) { Name.Reset(); Addr.Reset(); DecodeAddrName(*i, Name, Addr, "name.com"); LgiTrace("N=%-#32s A=%-32s\n", Name, Addr); } */ if (!Str) return; GArray< GAutoPtr > Parts; GString s = Str; GString non; GString email; GString::Array a = s.SplitDelimit("<>"); for (unsigned i=0; i 0) { const char *ChSet = " \t\r\n\'\"<>"; do { non = non.Strip(ChSet); } while (non.Length() > 0 && strchr(ChSet, non(0))); } Name.Reset(NewStr(non)); Addr.Reset(NewStr(email.Strip())); } void StrCopyToEOL(char *d, char *s) { if (d && s) { while (*s && *s != '\r' && *s != '\n') { *d++ = *s++; } *d = 0; } } ////////////////////////////////////////////////////////////////////////////////////////////////// MailTransaction::MailTransaction() { Index = -1; Flags = 0; Status = false; Oversize = false; Stream = 0; UserData = 0; } MailTransaction::~MailTransaction() { } ////////////////////////////////////////////////////////////////////////////////////////////////// FileDescriptor::FileDescriptor() { Embeded = 0; Offset = 0; Size = 0; Data = 0; MimeType = 0; ContentId = 0; Lock = 0; OwnEmbeded = false; } FileDescriptor::FileDescriptor(GStreamI *embed, int64 offset, int64 size, char *name) { Embeded = embed; Offset = offset; Size = size; Data = 0; MimeType = 0; Lock = 0; ContentId = 0; OwnEmbeded = false; if (name) { Name(name); } } FileDescriptor::FileDescriptor(char *name) { Embeded = 0; Offset = 0; Size = 0; Data = 0; MimeType = 0; Lock = 0; ContentId = 0; OwnEmbeded = false; if (name) { Name(name); if (File.Open(name, O_READ)) { Size = File.GetSize(); File.Close(); } } } FileDescriptor::FileDescriptor(char *data, int64 len) { Embeded = 0; Offset = 0; MimeType = 0; Lock = 0; ContentId = 0; Size = len; OwnEmbeded = false; Data = data ? new uchar[(size_t)Size] : 0; if (Data) { memcpy(Data, data, (size_t)Size); } } FileDescriptor::~FileDescriptor() { if (OwnEmbeded) { DeleteObj(Embeded); } DeleteArray(MimeType); DeleteArray(ContentId); DeleteArray(Data); } void FileDescriptor::SetOwnEmbeded(bool i) { OwnEmbeded = i; } void FileDescriptor::SetLock(LMutex *l) { Lock = l; } LMutex *FileDescriptor::GetLock() { return Lock; } GStreamI *FileDescriptor::GotoObject() { if (Embeded) { Embeded->SetPos(Offset); return Embeded; } else if (Name() && File.Open(Name(), O_READ)) { return &File; } else if (Data && Size > 0) { DataStream.Reset(new GMemStream(Data, Size, false)); return DataStream; } return 0; } int FileDescriptor::Sizeof() { return (int)Size; } uchar *FileDescriptor::GetData() { return Data; } bool FileDescriptor::Decode(char *ContentType, char *ContentTransferEncoding, char *MimeData, int MimeDataLen) { bool Status = false; int Content = CONTENT_NONE; if (ContentType && ContentTransferEncoding) { // Content-Type: application/octet-stream; name="Scribe.opt" Content = CONTENT_OCTET_STREAM; if (strnistr(ContentTransferEncoding, "base64", 1000)) { Content = CONTENT_BASE64; } if (strnistr(ContentTransferEncoding, "quoted-printable", 1000)) { Content = CONTENT_QUOTED_PRINTABLE; } if (Content != CONTENT_NONE) { const char *NameKey = "name"; char *n = strnistr(ContentType, NameKey, 1000); if (n) { char *Equal = strchr(n, '='); if (Equal) { Equal++; while (*Equal && *Equal == '\"') { Equal++; } char *End = strchr(Equal, '\"'); if (End) { *End = 0; } Name(Equal); Status = true; } } } } if (Status && MimeData && MimeDataLen > 0 && Content != CONTENT_NONE) { Status = false; char *Base64 = new char[MimeDataLen]; switch (Content) { case CONTENT_OCTET_STREAM: { Size = 0; DeleteObj(Data); Data = new uchar[MimeDataLen]; if (Data) { Size = MimeDataLen; memcpy(Data, MimeData, (size_t)Size); Status = true; } break; } case CONTENT_QUOTED_PRINTABLE: { Size = 0; DeleteObj(Data); Data = new uchar[MimeDataLen+1]; if (Data) { char *Out = (char*) Data; for (int i=0; i= Size - 3; if (Status) { Size = Converted; } else { DeleteArray(Data); Size = 0; } } break; } } } return Status; } ////////////////////////////////////////////////////////////////////////////////////////////////// AddressDescriptor::AddressDescriptor(AddressDescriptor *Copy) { Data = 0; Status = Copy ? Copy->Status : false; CC = Copy ? Copy->CC : false; Addr = Copy ? NewStr(Copy->Addr) : 0; Name = Copy ? NewStr(Copy->Name) : 0; } AddressDescriptor::~AddressDescriptor() { _Delete(); } void AddressDescriptor::_Delete() { Data = 0; Status = false; CC = 0; DeleteArray(Name); DeleteArray(Addr); } void AddressDescriptor::Print(char *Str, int Len) { if (!Str) { LgiAssert(0); return; } if (Addr && Name) { sprintf_s(Str, Len, "%s (%s)", Addr, Name); } else if (Addr) { strcpy_s(Str, Len, Addr); } else if (Name) { sprintf_s(Str, Len, "(%s)", Name); } } ////////////////////////////////////////////////////////////////////////////////////////////////// MailProtocol::MailProtocol() { Buffer[0] = 0; Logger = 0; ErrMsgId = 0; Items = 0; Transfer = 0; } MailProtocol::~MailProtocol() { CharsetPrefs.DeleteArrays(); } void MailProtocol::Log(const char *Str, GSocketI::SocketMsgType type) { if (Logger && Str) { char s[1024]; char *e = s + sizeof(s) - 2; const char *i = Str; char *o = s; while (*i && o < e) { *o++ = *i++; } while (o > s && (o[-1] == '\r' || o[-1] == '\n')) o--; *o++ = '\n'; *o = 0; Logger->Write(s, o - s, type); } } bool MailProtocol::Error(const char *file, int line, const char *msg, ...) { char s[1024]; va_list a; va_start(a, msg); vsprintf_s(s, sizeof(s), msg, a); va_end(a); Log(s, GSocketI::SocketMsgError); LgiTrace("%s:%i - Error: %s", file, line, s); return false; } bool MailProtocol::Read() { bool Status = false; if (Socket) { Status = Socket->Read(Buffer, sizeof(Buffer), 0) > 0; } return Status; } bool MailProtocol::Write(const char *Buf, bool LogWrite) { bool Status = false; if (Socket) { const char *p = Buf ? Buf : Buffer; Status = Socket->Write(p, strlen(p), 0) > 0; if (LogWrite) { Log(p, GSocketI::SocketMsgSend); } } return Status; } ////////////////////////////////////////////////////////////////////////////////////////////////// #define VERIFY_RET_VAL(Arg) \ { \ if (!Arg) \ { \ LMutex::Auto Lck(&SocketLock, _FL); \ Socket.Reset(0); \ return NULL; \ } \ } #define VERIFY_ONERR(Arg) \ { \ if (!Arg) \ { \ LMutex::Auto Lck(&SocketLock, _FL); \ Socket.Reset(0); \ goto CleanUp; \ } \ } void Reorder(GArray &a, const char *s) { for (unsigned i=0; i 0) { a.DeleteAt(i, true); a.AddAt(0, s); break; } } } MailSmtp::MailSmtp() { } MailSmtp::~MailSmtp() { } bool MailSmtp::Open(GSocketI *S, const char *RemoteHost, const char *LocalDomain, const char *UserName, const char *Password, int Port, int Flags) { char Str[256] = ""; bool Status = false; if (!RemoteHost) Error(_FL, "No remote SMTP host.\n"); else { strcpy_s(Str, sizeof(Str), RemoteHost); char *Colon = strchr(Str, ':'); if (Colon) { *Colon = 0; Colon++; Port = atoi(Colon); } if (Port == 0) { if (Flags & MAIL_SSL) Port = SMTP_SSL_PORT; else Port = SMTP_PORT; } GAutoString Server(TrimStr(Str)); if (Server) { if (SocketLock.Lock(_FL)) { Socket.Reset(S); SocketLock.Unlock(); } Socket->SetTimeout(30 * 1000); char Msg[256]; sprintf_s(Msg, sizeof(Msg), "Connecting to %s:%i...", Server.Get(), Port); Log(Msg, GSocketI::SocketMsgInfo); if (!Socket->Open(Server, Port)) Error(_FL, "Failed to connect socket to %s:%i\n", Server.Get(), Port); else { GStringPipe Str; // receive signon message VERIFY_RET_VAL(ReadReply("220")); // Rfc 2554 ESMTP authentication SmtpHello: sprintf_s(Buffer, sizeof(Buffer), "EHLO %s\r\n", (ValidNonWSStr(LocalDomain)) ? LocalDomain : "default"); VERIFY_RET_VAL(Write(0, true)); /*bool HasSmtpExtensions =*/ ReadReply("250", &Str); bool Authed = false; bool NoAuthTypes = false; bool SupportsStartTLS = false; GArray AuthTypes; // Look through the response for the auth line char *Response = Str.NewStr(); if (Response) { GToken Lines(Response, "\n"); - for (uint32 i=0; iSetValue(GSocket_Protocol, v="SSL")) { Flags &= ~MAIL_USE_STARTTLS; goto SmtpHello; } else { // SSL init failed... what to do here? return false; } } if (ValidStr(UserName) && ValidStr(Password)) { if (AuthTypes.Length() == 0) { // No auth types? huh? if (TestFlag(Flags, MAIL_USE_AUTH)) { if (TestFlag(Flags, MAIL_USE_PLAIN)) // Force plain type AuthTypes.Add("PLAIN"); else if (TestFlag(Flags, MAIL_USE_LOGIN)) // Force login type AuthTypes.Add("LOGIN"); else if (TestFlag(Flags, MAIL_USE_CRAM_MD5)) // Force CRAM MD5 type AuthTypes.Add("CRAM-MD5"); else { // Try all AuthTypes.Add("PLAIN"); AuthTypes.Add("LOGIN"); AuthTypes.Add("CRAM-MD5"); } } else { NoAuthTypes = true; } } else { if (TestFlag(Flags, MAIL_USE_AUTH)) { // Force user preference if (TestFlag(Flags, MAIL_USE_PLAIN)) Reorder(AuthTypes, "PLAIN"); else if (TestFlag(Flags, MAIL_USE_LOGIN)) Reorder(AuthTypes, "LOGIN"); else if (TestFlag(Flags, MAIL_USE_CRAM_MD5)) Reorder(AuthTypes, "CRAM-MD5"); } } for (auto Auth : AuthTypes) { // Try all their auth types against our internally support types if (Auth.Equals("LOGIN")) { VERIFY_RET_VAL(Write("AUTH LOGIN\r\n", true)); VERIFY_RET_VAL(ReadReply("334")); ZeroObj(Buffer); ConvertBinaryToBase64(Buffer, sizeof(Buffer), (uchar*)UserName, strlen(UserName)); strcat(Buffer, "\r\n"); VERIFY_RET_VAL(Write(0, true)); if (ReadReply("334")) { ZeroObj(Buffer); ConvertBinaryToBase64(Buffer, sizeof(Buffer), (uchar*)Password, strlen(Password)); strcat(Buffer, "\r\n"); VERIFY_RET_VAL(Write(0, true)); if (ReadReply("235")) { Authed = true; } } } else if (Auth.Equals("PLAIN")) { char Tmp[256]; ZeroObj(Tmp); int ch = 1; ch += sprintf_s(Tmp+ch, sizeof(Tmp)-ch, "%s", UserName) + 1; ch += sprintf_s(Tmp+ch, sizeof(Tmp)-ch, "%s", Password) + 1; char B64[256]; ZeroObj(B64); - ConvertBinaryToBase64(B64, sizeof(B64), (uint8*)Tmp, ch); + ConvertBinaryToBase64(B64, sizeof(B64), (uint8_t*)Tmp, ch); sprintf_s(Buffer, sizeof(Buffer), "AUTH PLAIN %s\r\n", B64); VERIFY_RET_VAL(Write(0, true)); if (ReadReply("235")) { Authed = true; } } else if (Auth.Equals("CRAM-MD5")) { sprintf_s(Buffer, sizeof(Buffer), "AUTH CRAM-MD5\r\n"); VERIFY_RET_VAL(Write(0, true)); if (ReadReply("334")) { auto Sp = strchr(Buffer, ' '); if (Sp) { Sp++; // Decode the server response: - uint8 Txt[128]; + uint8_t Txt[128]; int InLen = strlen(Sp); ssize_t TxtLen = ConvertBase64ToBinary(Txt, sizeof(Txt), Sp, InLen); // Calc the hash: // https://tools.ietf.org/html/rfc2104 char Key[64] = {0}; memcpy(Key, Password, MIN(strlen(Password), sizeof(Key))); - uint8 iKey[256]; + uint8_t iKey[256]; char oKey[256]; for (unsigned i=0; i<64; i++) { iKey[i] = Key[i] ^ 0x36; oKey[i] = Key[i] ^ 0x5c; } memcpy(iKey+64, Txt, TxtLen); md5_state_t md5; md5_init(&md5); md5_append(&md5, iKey, 64 + TxtLen); md5_finish(&md5, oKey + 64); md5_init(&md5); - md5_append(&md5, (uint8*)oKey, 64 + 16); + md5_append(&md5, (uint8_t*)oKey, 64 + 16); char digest[16]; md5_finish(&md5, digest); char r[256]; int ch = sprintf_s(r, sizeof(r), "%s ", UserName); for (unsigned i=0; i<16; i++) - ch += sprintf_s(r+ch, sizeof(r)-ch, "%02x", (uint8)digest[i]); + ch += sprintf_s(r+ch, sizeof(r)-ch, "%02x", (uint8_t)digest[i]); // Base64 encode - ssize_t Len = ConvertBinaryToBase64(Buffer, sizeof(Buffer), (uint8*)r, ch); + ssize_t Len = ConvertBinaryToBase64(Buffer, sizeof(Buffer), (uint8_t*)r, ch); Buffer[Len++] = '\r'; Buffer[Len++] = '\n'; Buffer[Len++] = 0; VERIFY_RET_VAL(Write(0, true)); if (ReadReply("235")) Authed = true; } } } else { LgiTrace("%s:%i - Unsupported auth type '%s'\n", _FL, Auth.Get()); } if (Authed) break; } if (!Authed) { if (NoAuthTypes) SetError(L_ERROR_ESMTP_NO_AUTHS, "The server didn't return the authentication methods it supports."); else { GString p; for (auto i : AuthTypes) { if (p.Get()) p += ", "; p += i; } SetError(L_ERROR_UNSUPPORTED_AUTH, "Authentication failed, types available:\n\t%s", p); } } Status = Authed; } else { Status = true; } } } } return Status; } bool MailSmtp::WriteText(const char *Str) { // we have to convert all strings to CRLF in here bool Status = false; if (Str) { GMemQueue Temp; const char *Start = Str; while (*Str) { if (*Str == '\n') { // send a string ssize_t Size = Str-Start; if (Str[-1] == '\r') Size--; Temp.Write((uchar*) Start, Size); Temp.Write((uchar*) "\r\n", 2); Start = Str + 1; } Str++; } // send the final string ssize_t Size = Str-Start; if (Str[-1] == '\r') Size--; Temp.Write((uchar*) Start, (int)Size); Size = (int)Temp.GetSize(); char *Data = new char[(size_t)Size]; if (Data) { Temp.Read((uchar*) Data, Size); Status = Socket->Write(Data, (int)Size, 0) == Size; DeleteArray(Data); } } return Status; } char *StripChars(char *Str, const char *Chars = "\r\n") { if (Str) { char *i = Str; char *o = Str; while (*i) { if (strchr(Chars, *i)) i++; else *o++ = *i++; } *o++ = 0; } return Str; } char *CreateAddressTag(List &l, int Type, List *CharsetPrefs) { char *Result = 0; List Addr; AddressDescriptor *a; for (a = l.First(); a; a = l.Next()) { if (a->CC == Type) { Addr.Insert(a); } } if (Addr.Length() > 0) { GStringPipe StrBuf; StrBuf.Push((Type == 0) ? (char*)"To: " : (char*)"Cc: "); for (a = Addr.First(); a; ) { AddressDescriptor *NextA = Addr.Next(); char Buffer[256] = ""; StripChars(a->Name); StripChars(a->Addr); if (a->Addr && strchr(a->Addr, ',')) { // Multiple address format GToken t(a->Addr, ","); - for (uint32 i=0; i", t[i]); if (i < t.Length()-1) strcat(Buffer, ",\r\n\t"); StrBuf.Push(Buffer); Buffer[0] = 0; } } else if (a->Name) { // Name and addr char *Mem = 0; char *Name = a->Name; if (Is8Bit(Name)) { Name = Mem = EncodeRfc2047(NewStr(Name), 0, CharsetPrefs); } if (strchr(Name, '\"')) sprintf_s(Buffer, sizeof(Buffer), "'%s' <%s>", Name, a->Addr); else sprintf_s(Buffer, sizeof(Buffer), "\"%s\" <%s>", Name, a->Addr); DeleteArray(Mem); } else if (a->Addr) { // Just addr sprintf_s(Buffer, sizeof(Buffer), "<%s>", a->Addr); } if (NextA) strcat(Buffer, ",\r\n\t"); StrBuf.Push(Buffer); a = NextA; } StrBuf.Push("\r\n"); Result = StrBuf.NewStr(); } return Result; } // This class implements a pipe that writes to a socket class SocketPipe : public GStringPipe { GSocketI *s; MailProtocolProgress *p; public: bool Status; SocketPipe(GSocketI *socket, MailProtocolProgress *progress) { s = socket; p = progress; Status = true; } ssize_t Read(void *Ptr, ssize_t Size, int Flags) { return false; } int64 SetSize(int64 Size) { if (p) { p->Start = LgiCurrentTime(); p->Range = (int)Size; return Size; } return -1; } ssize_t Write(const void *InPtr, ssize_t Size, int Flags) { char *Ptr = (char*)InPtr; char *e = Ptr + Size; while (Ptr < e) { ssize_t w = s->Write(Ptr, e - Ptr, 0); if (w > 0) { Ptr += w; if (p && p->Range && w > 0) p->Value += w; } else break; } return Ptr - (char*)InPtr; } }; bool MailSmtp::SendToFrom(List &To, AddressDescriptor *From, MailProtocolError *Err) { bool AddrOk = false; if (To.Length() == 0) { ErrMsgId = L_ERROR_ESMTP_NO_RECIPIENT; ErrMsgFmt = "No recipients to send to."; ErrMsgParam.Empty(); return false; } // send MAIL message if (From && ValidStr(From->Addr)) { sprintf_s(Buffer, sizeof(Buffer), "MAIL FROM: <%s>\r\n", From->Addr); } else { ErrMsgId = L_ERROR_ESMTP_NO_FROM; ErrMsgFmt = "No 'from' address in email."; ErrMsgParam.Empty(); return false; } VERIFY_RET_VAL(Write(0, true)); VERIFY_RET_VAL(ReadReply("250", 0, Err)); // send RCPT message AddrOk = true; List::I Recip = To.begin(); for (AddressDescriptor *a = *Recip; a; a = *++Recip) { char *Addr = ValidStr(a->Addr) ? a->Addr : a->Name; if (ValidStr(Addr)) { GToken Parts(Addr, ","); for (unsigned p=0; p\r\n", Parts[p]); VERIFY_RET_VAL(Write(0, true)); a->Status = ReadReply("25", 0, Err); AddrOk |= a->Status != 0; // at least one address is ok } } else if (Err) { ErrMsgId = L_ERROR_ESMTP_BAD_RECIPIENT; ErrMsgFmt = "Invalid recipient '%s'."; ErrMsgParam = Addr; } } return AddrOk; } GStringPipe *MailSmtp::SendData(MailProtocolError *Err) { // send DATA message sprintf_s(Buffer, sizeof(Buffer), "DATA\r\n"); VERIFY_RET_VAL(Write(0, true)); VERIFY_RET_VAL(ReadReply("354", 0, Err)); return new SocketPipe(Socket, Transfer); } GStringPipe *MailSmtp::SendStart(List &To, AddressDescriptor *From, MailProtocolError *Err) { return SendToFrom(To, From, Err) ? SendData(Err) : NULL; } bool MailSmtp::SendEnd(GStringPipe *m) { bool Status = false; SocketPipe *Msg = dynamic_cast(m); if (Msg) { // send message terminator and receive reply if (Msg->Status && Msg->Write((void*)"\r\n.\r\n", 5, 0)) { Status = ReadReply("250"); } // else // just close the connection on them // so nothing gets sent } DeleteObj(m); return Status; } /* bool MailSmtp::Send(MailMessage *Msg, bool Mime) { bool Status = false; if (Socket && Msg) { GStringPipe *Sink = SendStart(Msg->To, Msg->From); if (Sink) { // setup a gui progress meter to send the email, // the length is just a guesstimate as we won't know the exact // size until we encode it all, and I don't want it hanging around // in memory at once, so we encode and send on the fly. int Length = 1024 + (Msg->GetBody() ? strlen(Msg->GetBody()) : 0); for (FileDescriptor *f=Msg->FileDesc.First(); f; f=Msg->FileDesc.Next()) { Length += f->Sizeof() * 4 / 3; } // encode and send message for transport Msg->Encode(*Sink, 0, this); Status = SendEnd(Sink); } } return Status; } */ bool MailSmtp::Close() { if (Socket) { // send QUIT message sprintf_s(Buffer, sizeof(Buffer), "QUIT\r\n"); VERIFY_RET_VAL(Write(0, true)); VERIFY_RET_VAL(ReadReply("221")); LMutex::Auto Lock(&SocketLock, _FL); Socket.Reset(0); return true; } return false; } bool MailSmtp::ReadReply(const char *Str, GStringPipe *Pipe, MailProtocolError *Err) { bool Status = false; if (Socket && Str) { int Pos = 0; char *Start = Buffer; ZeroObj(Buffer); while (Pos < sizeof(Buffer)) { ssize_t Len = Socket->Read(Buffer+Pos, sizeof(Buffer)-Pos, 0); if (Len > 0) { char *Eol = strstr(Start, "\r\n"); while (Eol) { // wipe EOL chars *Eol++ = 0; *Eol++ = 0; // process if (Pipe) { if (Pipe->GetSize()) Pipe->Push("\n"); Pipe->Push(Start); } if (Start[3] == ' ') { // end of response if (!strncmp(Start, Str, strlen(Str))) { Status = true; } if (Err) { Err->Code = atoi(Start); char *Sp = strchr(Start, ' '); Err->ErrMsg = Sp ? Sp + 1 : Start; } // Log Log(Start, atoi(Start) >= 400 ? GSocketI::SocketMsgError : GSocketI::SocketMsgReceive); // exit loop Pos = sizeof(Buffer); break; } else { Log(Start, GSocketI::SocketMsgReceive); // more lines follow Start = Eol; Eol = strstr(Start, "\r\n"); } } Pos += Len; } else break; } if (!Status) { SetError(L_ERROR_GENERIC, "Error: %s", Buffer); } } return Status; } ////////////////////////////////////////////////////////////////////////////////////////////////// class Mail2Folder : public GStringPipe { char File[256]; GFile F; public: Mail2Folder(char *Path, List &To) { do { char n[32]; sprintf_s(n, sizeof(n), "%u.mail", LgiRand()); LgiMakePath(File, sizeof(File), Path, n); } while (FileExists(File)); if (F.Open(File, O_WRITE)) { F.Print("Forward-Path: "); int i = 0; for (AddressDescriptor *a=To.First(); a; a=To.Next()) { a->Status = true; GToken Addrs(a->Addr, ","); for (unsigned n=0; n", Addrs[n]); } } F.Print("\r\n"); } } ~Mail2Folder() { F.Close(); } ssize_t Read(void *Buffer, ssize_t Size, int Flags = 0) { return F.Read(Buffer, Size, Flags); } ssize_t Write(const void *Buffer, ssize_t Size, int Flags = 0) { return F.Write(Buffer, Size, Flags); } }; class MailPostFolderPrivate { public: char *Path; MailPostFolderPrivate() { Path = 0; } ~MailPostFolderPrivate() { DeleteArray(Path); } }; MailSendFolder::MailSendFolder(char *Path) { d = new MailPostFolderPrivate; d->Path = NewStr(Path); } MailSendFolder::~MailSendFolder() { DeleteObj(d); } bool MailSendFolder::Open(GSocketI *S, const char *RemoteHost, const char *LocalDomain, const char *UserName, const char *Password, int Port, int Flags) { return DirExists(d->Path); } bool MailSendFolder::Close() { return true; } GStringPipe *MailSendFolder::SendStart(List &To, AddressDescriptor *From, MailProtocolError *Err) { return new Mail2Folder(d->Path, To); } bool MailSendFolder::SendEnd(GStringPipe *Sink) { DeleteObj(Sink); return true; } ////////////////////////////////////////////////////////////////////////////////////////////////// class MailItem { public: char *File; bool Delete; MailItem(char *f) { File = NewStr(f); Delete = false; } ~MailItem() { DeleteArray(File); } }; class MailReceiveFolderPrivate { public: char *Path; List Mail; MailReceiveFolderPrivate() { Path = 0; } ~MailReceiveFolderPrivate() { DeleteArray(Path); Mail.DeleteObjects(); } void Empty() { for (MailItem *m = Mail.First(); m; m = Mail.Next()) { if (m->Delete) { FileDev->Delete(m->File, false); } } Mail.DeleteObjects(); } }; MailReceiveFolder::MailReceiveFolder(char *Path) { d = new MailReceiveFolderPrivate; d->Path = NewStr(Path); } MailReceiveFolder::~MailReceiveFolder() { DeleteObj(d); } bool MailReceiveFolder::Open(GSocketI *S, const char *RemoteHost, int Port, const char *User, const char *Password, GDom *SettingStore, int Flags) { // We don't use the socket so just free it here... DeleteObj(S); // Argument check if (!DirExists(d->Path)) return false; GDirectory Dir; // Loop through files, looking for email for (int b = Dir.First(d->Path, LGI_ALL_FILES); b; b = Dir.Next()) { if (!Dir.IsDir()) { if (MatchStr("*.eml", Dir.GetName()) || MatchStr("*.mail", Dir.GetName())) { char p[300]; Dir.Path(p, sizeof(p)); d->Mail.Insert(new MailItem(p)); } } } return true; } bool MailReceiveFolder::Close() { d->Empty(); return true; } int MailReceiveFolder::GetMessages() { return (int)d->Mail.Length(); } bool MailReceiveFolder::Receive(GArray &Trans, MailCallbacks *Callbacks) { bool Status = false; for (unsigned i=0; iStream) { t->Status = false; MailItem *m = d->Mail[t->Index]; if (m) { GFile i; if (i.Open(m->File, O_READ)) { GCopyStreamer c; if (c.Copy(&i, t->Stream)) { Status = t->Status = true; if (Callbacks && Callbacks->OnReceive) { Callbacks->OnReceive(t, Callbacks->CallbackData); } } } } } } return Status; } bool MailReceiveFolder::Delete(int Message) { MailItem *m = d->Mail[Message]; if (m) { m->Delete = true; return false; } return false; } int MailReceiveFolder::Sizeof(int Message) { MailItem *m = d->Mail[Message]; if (m) { return (int)LgiFileSize(m->File); } return 0; } bool MailReceiveFolder::GetUid(int Message, char *Id, int IdLen) { if (Id) { MailItem *m = d->Mail[Message]; if (m) { char *s = strrchr(m->File, DIR_CHAR); if (s++) { char *e = strchr(s, '.'); if (!e) e = s + strlen(s); ssize_t Len = e - s; memcpy(Id, s, Len); Id[Len] = 0; return true; } } } return false; } bool MailReceiveFolder::GetUidList(List &Id) { bool Status = false; for (int i=0; iMail.Length(); i++) { char Uid[256]; if (GetUid(i, Uid, sizeof(Uid))) { Status = true; Id.Insert(NewStr(Uid)); } else { Id.DeleteArrays(); Status = false; break; } } return Status; } char *MailReceiveFolder::GetHeaders(int Message) { MailItem *m = d->Mail[Message]; if (m) { GFile i; if (i.Open(m->File, O_READ)) { GStringPipe o; GCopyStreamer c; GLinePrefix e("", false); if (c.Copy(&i, &o, &e)) { return o.NewStr(); } } } return 0; } ////////////////////////////////////////////////////////////////////////////////////////////////// MailPop3::MailPop3() { End = "\r\n.\r\n"; Marker = End; Messages = -1; } MailPop3::~MailPop3() { } int MailPop3::GetMessages() { if (Messages < 0) { if (Socket && Socket->IsOpen()) { // see how many messages there are VERIFY_ONERR(Write("STAT\r\n", true)); VERIFY_ONERR(ReadReply()); Messages = GetInt(); } else LgiAssert(!"No socket to get message count."); } CleanUp: return Messages; } int MailPop3::GetInt() { char Buf[32]; char *Start = strchr(Buffer, ' '); if (Start) { Start++; char *End = strchr(Start, ' '); if (End) { int Len = (int) (End - Start); memcpy(Buf, Start, Len); Buf[Len] = 0; return atoi(Buf); } } return 0; } bool MailPop3::ReadReply() { bool Status = false; if (Socket) { int Pos = 0; ZeroObj(Buffer); do { ssize_t Result = Socket->Read(Buffer+Pos, sizeof(Buffer)-Pos, 0); if (Result <= 0) // an error? { // Leave the loop... break; } Pos += Result; } while ( !strstr(Buffer, "\r\n") && sizeof(Buffer)-Pos > 0); Status = (Buffer[0] == '+') && strstr(Buffer, "\r\n"); char *Cr = strchr(Buffer, '\r'); if (Cr) *Cr = 0; if (ValidStr(Buffer)) Log(Buffer, (Status) ? GSocketI::SocketMsgReceive : GSocketI::SocketMsgError); if (Cr) *Cr = '\r'; if (!Status) { SetError(L_ERROR_GENERIC, "Error: %s", Buffer); } } return Status; } bool MailPop3::ListCmd(const char *Cmd, LHashTbl, bool> &Results) { sprintf_s(Buffer, sizeof(Buffer), "%s\r\n", Cmd); if (!Write(0, true)) return false; char *b = Buffer; ssize_t r; while ((r = Socket->Read(b, sizeof(Buffer)-(b-Buffer))) > 0) { b += r; if (strnstr(Buffer, "\r\n.\r\n", b-Buffer)) break; } if (r <= 0) return false; GToken t(Buffer, "\r\n"); for (unsigned i=1; iGetValue("IsSSL", IsSsl) && IsSsl.CastInt32()) Port = POP3_SSL_PORT; else Port = POP3_PORT; } strcpy_s(Str, sizeof(Str), RemoteHost); char *Colon = strchr(Str, ':'); if (Colon) { *Colon = 0; Colon++; Port = atoi(Colon); } if (S && User && Password && (Server = TrimStr(Str))) { S->SetTimeout(30 * 1000); ReStartConnection: if (SocketLock.Lock(_FL)) { Socket.Reset(S); SocketLock.Unlock(); } if (Socket && Socket->Open(Server, Port) && ReadReply()) { GVariant NoAPOP = false; if (SettingStore) SettingStore->GetValue(OPT_Pop3NoApop, NoAPOP); if (!NoAPOP.CastInt32()) { char *s = strchr(Buffer + 3, '<'); if (s) { char *e = strchr(s + 1, '>'); if (e) { Apop = NewStr(s, e - s + 1); } } } // login bool Authed = false; char *user = (char*) LgiNewConvertCp("iso-8859-1", User, "utf-8"); char *pass = (char*) LgiNewConvertCp("iso-8859-1", Password, "utf-8"); if (user && (pass || SecureAuth)) { bool SecurityError = false; if (TestFlag(Flags, MAIL_USE_STARTTLS)) { strcpy_s(Buffer, sizeof(Buffer), "STARTTLS\r\n"); VERIFY_RET_VAL(Write(0, true)); VERIFY_RET_VAL(ReadReply()); GVariant v; if (Socket->SetValue(GSocket_Protocol, v="SSL")) { Flags &= ~MAIL_USE_STARTTLS; } else { SecurityError = true; } } if (!SecurityError && Apop) // GotKey, not implemented { // using encrypted password unsigned char Digest[16]; char HexDigest[33]; // append password char Key[256]; sprintf_s(Key, sizeof(Key), "%s%s", Apop, pass); ZeroObj(Digest); MDStringToDigest(Digest, Key); for (int i = 0; i < 16; i++) sprintf_s(HexDigest + (i*2), 3, "%2.2x", Digest[i]); HexDigest[32] = 0; sprintf_s(Buffer, sizeof(Buffer), "APOP %s %s\r\n", user, HexDigest); VERIFY_ONERR(Write(0, true)); Authed = ReadReply(); if (!Authed) { DeleteArray(Apop); GVariant NoAPOP = true; if (SettingStore) SettingStore->SetValue(OPT_Pop3NoApop, NoAPOP); S->Close(); goto ReStartConnection; } } if (!SecurityError && SecureAuth) { LHashTbl, bool> AuthTypes, Capabilities; if (ListCmd("AUTH", AuthTypes) && ListCmd("CAPA", Capabilities)) { if (AuthTypes.Find("GSSAPI")) { sprintf_s(Buffer, sizeof(Buffer), "AUTH GSSAPI\r\n"); VERIFY_ONERR(Write(0, true)); VERIFY_ONERR(ReadReply()); // http://www.faqs.org/rfcs/rfc2743.html } } } else if (!SecurityError && !Authed) { // have to use non-key method sprintf_s(Buffer, sizeof(Buffer), "USER %s\r\n", user); VERIFY_ONERR(Write(0, true)); VERIFY_ONERR(ReadReply()); sprintf_s(Buffer, sizeof(Buffer), "PASS %s\r\n", pass); VERIFY_ONERR(Write(0, false)); Log("PASS *******", GSocketI::SocketMsgSend); Authed = ReadReply(); } DeleteArray(user); DeleteArray(pass); } if (Authed) { Status = true; } else { if (SocketLock.Lock(_FL)) { Socket.Reset(0); SocketLock.Unlock(); } LgiTrace("%s:%i - Failed auth.\n", _FL); } } else Error(_FL, "Failed to open socket to %s:%i and read reply.\n", Server, Port); } else Error(_FL, "No user/pass.\n"); } CleanUp: DeleteArray(Apop); DeleteArray(Server); return Status; } bool MailPop3::MailIsEnd(char *Ptr, ssize_t Len) { for (char *c = Ptr; Len-- > 0; c++) { if (*c != *Marker) { Marker = End; } if (*c == *Marker) { Marker++; if (!*Marker) { return true; } } } return false; } bool MailPop3::Receive(GArray &Trans, MailCallbacks *Callbacks) { bool Status = false; if (Trans.Length() > 0 && Socket) { for (unsigned n = 0; nIndex; GStreamI *Msg = Trans[n]->Stream; if (Msg) { int Size = 0; // Transfer is not null when the caller wants info on the bytes comming in if (Transfer || Callbacks) { // get message size sprintf_s(Buffer, sizeof(Buffer), "LIST %i\r\n", Message + 1); VERIFY_RET_VAL(Write(0, true)); VERIFY_RET_VAL(ReadReply()); char *s = strchr(Buffer, ' '); if (s) { s = strchr(s+1, ' '); if (s) { Size = atoi(s); } } } MailSrcStatus Action = DownloadAll; int TopLines = 100; if (Callbacks && Callbacks->OnSrc) { Action = Callbacks->OnSrc(Trans[n], Size, &TopLines, Callbacks->CallbackData); } if (Action == DownloadAbort) { break; } if (Action == DownloadAll || Action == DownloadTop) { if (Action == DownloadAll) { sprintf_s(Buffer, sizeof(Buffer), "RETR %i\r\n", Message + 1); } else { sprintf_s(Buffer, sizeof(Buffer), "TOP %i %i\r\n", Message + 1, TopLines); } VERIFY_RET_VAL(Write(0, true)); GLinePrefix End(".\r\n"); if (Transfer) { Transfer->Value = 0; Transfer->Range = Size; Transfer->Start = LgiCurrentTime(); } // Read status line ZeroObj(Buffer); int Used = 0; bool Ok = false; bool Finished = false; int64 DataPos = 0; while (Socket->IsOpen()) { ssize_t r = Socket->Read(Buffer+Used, sizeof(Buffer)-Used-1, 0); if (r > 0) { DeNullText(Buffer + Used, r); if (Transfer) { Transfer->Value += r; } char *Eol = strchr(Buffer, '\n'); if (Eol) { Eol++; Ok = Buffer[0] == '+'; if (Ok) { // Log(Buffer, GSocketI::SocketMsgReceive); // The Buffer was zero'd at the beginning garrenteeing // NULL termination size_t Len = strlen(Eol); ssize_t EndPos = End.IsEnd(Eol, Len); if (EndPos >= 0) { Msg->Write(Eol, EndPos - 3); Status = Trans[n]->Status = true; Finished = true; } else { Msg->Write(Eol, Len); DataPos += Len; } } else { Log(Buffer, GSocketI::SocketMsgError); Finished = true; } break; } Used += r; } else break; } if (!Finished) { if (Ok) { // Read rest of message while (Socket->IsOpen()) { ssize_t r = Socket->Read(Buffer, sizeof(Buffer), 0); if (r > 0) { DeNullText(Buffer, r); if (Transfer) { Transfer->Value += r; } ssize_t EndPos = End.IsEnd(Buffer, r); if (EndPos >= 0) { ssize_t Actual = EndPos - DataPos - 3; if (Actual > 0) { ssize_t w = Msg->Write(Buffer, Actual); LgiAssert(w == Actual); } // else the end point was in the last buffer Status = Trans[n]->Status = true; break; } else { ssize_t w = Msg->Write(Buffer, r); LgiAssert(w == r); DataPos += r; } } else { break; } } if (!Status) { LgiTrace("%s:%i - Didn't get end-of-mail marker.\n", _FL); } } else { LgiTrace("%s:%i - Didn't get Ok.\n", _FL); break; } } if (Callbacks && Callbacks->OnReceive) { Callbacks->OnReceive(Trans[n], Callbacks->CallbackData); } if (Transfer) { Transfer->Empty(); } } else { Trans[n]->Oversize = Status = true; } if (Items) { Items->Value++; } } else { LgiTrace("%s:%i - No stream.\n", _FL); } } } else { LgiTrace("%s:%i - Arg check failed, len=%p, sock=%p.\n", _FL, Trans.Length(), Socket.Get()); } return Status; } bool MailPop3::GetSizes(GArray &Sizes) { if (Socket) { strcpy_s(Buffer, sizeof(Buffer), (char*)"LIST\r\n"); VERIFY_RET_VAL(Write(0, true)); char *s = 0; if (ReadMultiLineReply(s)) { GToken l(s, "\r\n"); DeleteArray(s); for (unsigned i=0; i 0; } int MailPop3::Sizeof(int Message) { int Size = 0; if (Socket) { sprintf_s(Buffer, sizeof(Buffer), "LIST %i\r\n", Message + 1); VERIFY_RET_VAL(Write(0, true)); VERIFY_RET_VAL(ReadReply()); char *s = strchr(Buffer, ' '); if (s) { s = strchr(s+1, ' '); if (s) { Size = atoi(s); } } } return Size; } bool MailPop3::Delete(int Message) { if (Socket) { sprintf_s(Buffer, sizeof(Buffer), "DELE %i\r\n", Message + 1); VERIFY_RET_VAL(Write(0, true)); VERIFY_RET_VAL(ReadReply()); return true; } return false; } bool MailPop3::GetUid(int Index, char *Id, int IdLen) { if (Socket && Id) { sprintf_s(Buffer, sizeof(Buffer), "UIDL %i\r\n", Index + 1); VERIFY_RET_VAL(Write(0, true)); VERIFY_RET_VAL(ReadReply()); char *Space = strchr(Buffer, ' '); if (Space) { Space = strchr(Space+1, ' '); if (Space) { for (char *s = Space+1; *s; s++) { if (*s == '\r' || *s == '\n') { *s = 0; break; } } strcpy_s(Id, IdLen, Space+1); return true; } } } return false; } bool MailPop3::GetUidList(List &Id) { bool Status = false; if (Socket) { char *Str = 0; sprintf_s(Buffer, sizeof(Buffer), "UIDL\r\n"); VERIFY_RET_VAL(Write(0, true)); VERIFY_RET_VAL(ReadMultiLineReply(Str)); if (Str) { Status = true; GToken T(Str, "\r\n"); for (unsigned i=0; iRead(Buffer, sizeof(Buffer), 0); if (ReadLen > 0 && Buffer[0] == '+') { // positive response char *Eol = strchr(Buffer, '\n'); if (Eol) { char *Ptr = Eol + 1; ReadLen -= Ptr-Buffer; memmove(Buffer, Ptr, ReadLen); Temp.Write((uchar*) Buffer, ReadLen); while (!MailIsEnd(Buffer, ReadLen)) { ReadLen = Socket->Read(Buffer, sizeof(Buffer), 0); if (ReadLen > 0) { Temp.Write((uchar*) Buffer, ReadLen); } else break; } int Len = (int)Temp.GetSize(); Str = new char[Len+1]; if (Str) { Temp.Read((uchar*)Str, Len); Str[Len] = 0; Status = true; } } } } return Status; } bool MailPop3::Close() { if (Socket) { // logout VERIFY_RET_VAL(Write("QUIT\r\n", true)); // 2 sec timeout, we don't really care about the server's response Socket->SetTimeout(2000); ReadReply(); if (SocketLock.Lock(_FL)) { Socket.Reset(0); SocketLock.Unlock(); } Messages = 0; return true; } return false; } ////////////////////////////////////////////////////////////////////////////////////////////////// /* MailMessage::MailMessage() { Log = this; From = 0; Reply = 0; InternetHeader = 0; Priority = MAIL_PRIORITY_NORMAL; Text = 0; TextCharset = 0; Html = 0; HtmlCharset = 0; MarkColour = -1; DispositionNotificationTo = false; Raw = 0; } MailMessage::~MailMessage() { Empty(); DeleteObj(From); DeleteObj(Reply); DeleteObj(Raw); } int MailMessage::Write(const void *Ptr, int Size, int Flags) { LgiTrace("%.*s", Size, Ptr); return Size; } void MailMessage::Empty() { Subject.Reset(); MessageID.Reset(); References.Reset(); FwdMsgId.Reset(); BounceMsgId.Reset(); DeleteArray(InternetHeader); DeleteArray(Text); DeleteArray(TextCharset); DeleteArray(Html); DeleteArray(HtmlCharset); To.DeleteObjects(); FileDesc.DeleteObjects(); } char *MailMessage::GetBody() { return Text; } char *MailMessage::GetBodyCharset() { return TextCharset; } bool MailMessage::SetBodyCharset(const char *Cs) { DeleteArray(TextCharset); TextCharset = NewStr(Cs); return true; } bool MailMessage::SetBody(const char *Txt, int Bytes, bool Copy, const char *Cs) { if (Txt != Text) { DeleteArray(Text); Text = Copy ? NewStr(Txt, Bytes) : (char*)Txt; if (Txt && !Text) return false; } if (Cs != TextCharset) { DeleteArray(TextCharset); if (!(TextCharset = NewStr(Cs))) return false; } return true; } char *MailMessage::GetHtml() { return Html; } char *MailMessage::GetHtmlCharset() { return HtmlCharset; } bool MailMessage::SetHtmlCharset(const char *Cs) { DeleteArray(HtmlCharset); HtmlCharset = NewStr(Cs); return true; } bool MailMessage::SetHtml(const char *Txt, int Bytes, bool Copy, const char *Cs) { if (Txt != Html) { DeleteArray(Html); Html = Copy ? NewStr(Txt, Bytes) : (char*)Txt; if (Txt && !Html) return false; } if (Cs != HtmlCharset) { DeleteArray(HtmlCharset); if (!(HtmlCharset = NewStr(Cs))) return false; } return true; } int MailMessage::EncodeBase64(GStreamI &Out, GStreamI &In) { int64 Start = LgiCurrentTime(); int Status = 0; int BufSize = 4 << 10; char *InBuf = new char[BufSize]; char *OutBuf = new char[BufSize]; if (InBuf && OutBuf) { int InLen = (int)In.GetSize(); // file remaining to read int InUsed = 0; int InDone = 0; int OutUsed = 0; do { if (InUsed - InDone < 256 && InLen > 0) { // Move any bit left over down to the start memmove(InBuf, InBuf + InDone, InUsed - InDone); InUsed -= InDone; InDone = 0; // Read in as much data as we can int Max = min(BufSize-InUsed, InLen); int r = In.Read(InBuf + InUsed, Max); if (r <= 0) break; // FilePos += r; InUsed += r; InLen -= r; } if (OutUsed > BufSize - 256) { int w = Out.Write(OutBuf, OutUsed); if (w > 0) { OutUsed = 0; Status += w; } else { break; } } int OutLen = ConvertBinaryToBase64( OutBuf + OutUsed, 76, (uchar*)InBuf + InDone, InUsed - InDone); int In = OutLen * 3 / 4; InDone += In; OutUsed += OutLen; OutBuf[OutUsed++] = '\r'; OutBuf[OutUsed++] = '\n'; } while (InDone < InUsed); if (OutUsed > 0) { int w = Out.Write(OutBuf, OutUsed); if (w >= 0) Status += w; w = Out.Write((char*)"\r\n", 2); if (w >= 0) Status += w; } #if 0 double Sec = (double)((int64)LgiCurrentTime() - Start) / 1000.0; double Kb = (double)FileDes->Sizeof() / 1024.0; LgiTrace("rate: %ikb/s\n", (int)(Kb / Sec)); #endif } else Log->Print("%s:%i - Error allocating buffers\n", _FL); DeleteArray(InBuf); DeleteArray(OutBuf); return Status; } int MailMessage::EncodeQuotedPrintable(GStreamI &Out, GStreamI &In) { int Status = 0; char OutBuf[100], InBuf[1024]; int ch = 0; int InLen; // Read the input data one chunk at a time while ((InLen = In.Read(InBuf, sizeof(InBuf))) > 0) { // For all the input bytes we just got for (char *s = InBuf; s - InBuf < InLen; ) { if (*s == '\n') { ch += sprintf_s(OutBuf+ch, sizeof(OutBuf)-ch, "\r\n"); int w = Out.Write(OutBuf, ch); if (w <= 0) break; ch = 0; Status += w; } else if (*s == '.') { // If the '.' character happens to fall at the // end of a paragraph and gets pushed onto the next line it // forms the magic \r\n.\r\n sequence that ends an SMTP data // session. Which is bad. The solution taken here is to // hex encode it if it falls at the start of the line. // Otherwise allow it through unencoded. if (ch == 0) { ch += sprintf_s(OutBuf+ch, sizeof(OutBuf)-ch, "=%2.2X", (uchar)*s); } else { OutBuf[ch++] = *s; } } else if (*s & 0x80 || *s == '=') { // Require hex encoding of 8-bit chars and the equals itself. ch += sprintf_s(OutBuf+ch, sizeof(OutBuf)-ch, "=%2.2X", (uchar)*s); } else if (*s != '\r') { OutBuf[ch++] = *s; } s++; if (ch > 73) { // time for a new line. ch += sprintf_s(OutBuf+ch, sizeof(OutBuf)-ch, "=\r\n"); int w = Out.Write(OutBuf, ch); if (w <= 0) break; ch = 0; Status += w; } } } ch += sprintf_s(OutBuf+ch, sizeof(OutBuf)-ch, "\r\n"); int w = Out.Write(OutBuf, ch); if (w > 0) Status += w; return Status; } int MailMessage::EncodeText(GStreamI &Out, GStreamI &In) { int Status = 0; char InBuf[4096]; int InLen, InUsed = 0; const char *Eol = "\r\n"; while ((InLen = In.Read(InBuf+InUsed, sizeof(InBuf)-InUsed)) > 0) { InUsed += InLen; char *s; for (s = InBuf; s - InBuf < InUsed; ) { // Do we have a complete line? int RemainingBytes = InUsed - (s - InBuf); char *NewLine = strnchr(s, '\n', RemainingBytes); if (NewLine) { // Yes... write that out. int Len = NewLine - s; if (Len > 0 && s[Len-1] == '\r') Len--; if (Len == 1 && s[0] == '.') { // this removes the sequence ".\n" // which is the END OF MAIL in the SMTP protocol. int w = Out.Write((char*)". ", 2); if (w <= 0) break; Status += w; } else if (Len) { int w = Out.Write(s, Len); if (w <= 0) break; Status += w; } int w = Out.Write(Eol, 2); if (w <= 0) break; s = NewLine + 1; Status += w; } else { // No... move the data down to the start of the buffer memmove(InBuf, s, RemainingBytes); InUsed = RemainingBytes; s = 0; break; } } if (s) InUsed -= s - InBuf; } if (InUsed) { int w = Out.Write(InBuf, InUsed); if (w > 0) Status += w; w = Out.Write(Eol, 2); if (w > 0) Status += w; } return Status; } #define SEND_BUF_SIZE (16 << 10) class SendBuf : public GStream { GStreamI *Out; int Used; uchar Buf[SEND_BUF_SIZE]; public: bool Status; SendBuf(GStreamI *out) { Out = out; Used = 0; Status = true; } ~SendBuf() { Flush(); } int64 GetSize() { return Used; } int64 SetSize(int64 Size) { return Out->SetSize(Size); } int Read(void *Buffer, int Size, int Flags = 0) { return -1; } void Flush() { if (Used > 0) { int w = Out->Write(Buf, Used, 0); if (w < Used) { Status = false; } Used = 0; } } int Write(const void *Buffer, int Size, int Flags = 0) { int64 w = 0; uchar *Ptr = (uchar*)Buffer; while (Ptr && Size > 0) { if (Size + Used >= SEND_BUF_SIZE) { int Chunk = SEND_BUF_SIZE - Used; memcpy(Buf + Used, Ptr, Chunk); int s = Out->Write(Buf, SEND_BUF_SIZE, 0); if (s < SEND_BUF_SIZE) { return -1; break; } Ptr += Chunk; Size -= Chunk; w += Chunk; Used = 0; } else { memcpy(Buf + Used, Ptr, Size); Used += Size; w += Size; Size = 0; } } return (int)w; } }; // Encode the whole email bool MailMessage::Encode(GStreamI &Out, GStream *HeadersSink, MailProtocol *Protocol, bool Mime) { GStringPipe p; bool Status = EncodeHeaders(p, Protocol, Mime); if (Status) { int Len = (int)p.GetSize(); char *Headers = p.NewStr(); if (HeadersSink) { HeadersSink->Write(Headers, Len); } else { InternetHeader = NewStr(Headers); } if (Headers && Out.Write(Headers, Len)) { SendBuf *Buf = new SendBuf(&Out); if (Buf) { Status = EncodeBody(*Buf, Protocol, Mime); if (Status) { Buf->Flush(); Status = Buf->Status; if (!Status) Log->Print("%s:%i - Buffer status failed.\n", _FL); } else Log->Print("%s:%i - EncodeBody failed.\n", _FL); DeleteObj(Buf); } } else Log->Print("%s:%i - Headers output failed.\n", _FL); DeleteArray(Headers); } else Log->Print("%s:%i - EncodeHeaders failed.\n", _FL); return Status; } #define WriteOutput() \ if (Out.Write(Buffer, Len) != Len) \ { \ Log->Print("%s:%i - Write failed.\n", _FL); \ Status = false; \ } // This encodes the main headers but not the headers relating to the // actual content. Thats done by the ::EncodeBody function. bool MailMessage::EncodeHeaders(GStreamI &Out, MailProtocol *Protocol, bool Mime) { bool Status = true; // Setup char Buffer[1025]; // Construct date const char *Weekday[7] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; const char *Month[12] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; LDateTime Dt; int TimeZone = Dt.SystemTimeZone(); Dt.SetNow(); int Len = sprintf_s(Buffer, sizeof(Buffer), "Date: %s, %i %s %i %i:%2.2i:%2.2i %s%2.2d%2.2d\r\n", Weekday[Dt.DayOfWeek()], Dt.Day(), Month[Dt.Month()-1], Dt.Year(), Dt.Hours(), Dt.Minutes(), Dt.Seconds(), (TimeZone >= 0) ? "+" : "", TimeZone / 60, abs(TimeZone) % 60); WriteOutput(); if (Protocol && Protocol->ProgramName) { // X-Mailer: Len = sprintf_s(Buffer, sizeof(Buffer), "X-Mailer: %s\r\n", Protocol->ProgramName.Get()); WriteOutput(); } if (Protocol && Protocol->ExtraOutgoingHeaders) { for (char *s=Protocol->ExtraOutgoingHeaders; s && *s; ) { char *e = s; while (*e && *e != '\r' && *e != '\n') e++; int l = e-s; if (l > 0) { Status &= Out.Write(s, l) > 0; Status &= Out.Write((char*)"\r\n", 2) > 0; } while (*e && (*e == '\r' || *e == '\n')) e++; s = e; } if (!Status) Log->Print("%s:%i - Writing ExtraOutgoingHeaders failed.\n", _FL); } if (Priority != MAIL_PRIORITY_NORMAL) { // X-Priority: Len = sprintf_s(Buffer, sizeof(Buffer), "X-Priority: %i\r\n", Priority); WriteOutput(); } if (MarkColour >= 0) { // X-Color (HTML Colour Ref for email marking) Len = sprintf_s(Buffer, sizeof(Buffer), "X-Color: #%2.2X%2.2X%2.2X\r\n", R24(MarkColour), G24(MarkColour), B24(MarkColour)); WriteOutput(); } // Message-ID: if (MessageID) { for (char *m=MessageID; *m; m++) { if (*m <= ' ') { printf("%s:%i - Bad message ID '%s'\n", _FL, MessageID.Get()); return false; } } Len = sprintf_s(Buffer, sizeof(Buffer), "Message-ID: %s\r\n", MessageID.Get()); WriteOutput(); } // References: if (ValidStr(References)) { char *Dir = strrchr(References, '/'); GAutoString a; char *Ref = 0; if (Dir) { GUri u; a = u.Decode(Dir + 1); Ref = a; } else Ref = References; int Len = sprintf_s(Buffer, sizeof(Buffer), "References: <%s>\r\n", Ref); WriteOutput(); } // To: char *ToAddr = CreateAddressTag(To, 0, &Protocol->CharsetPrefs); if (ToAddr) { Status &= Out.Write(ToAddr, strlen(ToAddr)) > 0; DeleteArray(ToAddr); if (!Status) Log->Print("%s:%i - Writing ToAddr failed.\n", _FL); } char *CcAddr = CreateAddressTag(To, 1, &Protocol->CharsetPrefs); if (CcAddr) { Status &= Out.Write(CcAddr, strlen(CcAddr)) > 0; DeleteArray(CcAddr); if (!Status) Log->Print("%s:%i - Writing CcAddr failed.\n", _FL); } // From: if (From && From->Addr) { Len = sprintf_s(Buffer, sizeof(Buffer), "From: "); char *Nme = EncodeRfc2047(NewStr(From->Name), 0, &Protocol->CharsetPrefs); if (Nme) { if (strchr(Nme, '\"')) Len += sprintf_s(Buffer+Len, sizeof(Buffer)-Len, "'%s' ", Nme); else Len += sprintf_s(Buffer+Len, sizeof(Buffer)-Len, "\"%s\" ", Nme); DeleteArray(Nme); } Len += sprintf_s(Buffer+Len, sizeof(Buffer)-Len, "<%s>\r\n", From->Addr); WriteOutput(); } else { Log->Print("%s:%i - No 'from' address to send email.\n", _FL); return false; } // Reply-To: if (Reply && ValidStr(Reply->Addr)) { Len = sprintf_s(Buffer, sizeof(Buffer), "Reply-To: "); char *Nme = EncodeRfc2047(NewStr(Reply->Name), 0, &Protocol->CharsetPrefs); if (Nme) { if (strchr(Nme, '\"')) Len += sprintf_s(Buffer+Len, sizeof(Buffer)-Len, "'%s' ", Nme); else Len += sprintf_s(Buffer+Len, sizeof(Buffer)-Len, "\"%s\" ", Nme); DeleteArray(Nme); } Len += sprintf_s(Buffer+Len, sizeof(Buffer)-Len, "<%s>\r\n", Reply->Addr); WriteOutput(); } // Subject: char *Subj = EncodeRfc2047(NewStr(Subject), 0, &Protocol->CharsetPrefs, 9); Len = sprintf_s(Buffer, sizeof(Buffer), "Subject: %s\r\n", (Subj) ? Subj : ""); WriteOutput(); DeleteArray(Subj); // DispositionNotificationTo if (DispositionNotificationTo) { Len = sprintf_s(Buffer, sizeof(Buffer), "Disposition-Notification-To:"); char *Nme = EncodeRfc2047(NewStr(From->Name), 0, &Protocol->CharsetPrefs); if (Nme) { Len += sprintf_s(Buffer+Len, sizeof(Buffer)-Len, " \"%s\"", Nme); DeleteArray(Nme); } Len += sprintf_s(Buffer+Len, sizeof(Buffer)-Len, " <%s>\r\n", From->Addr); WriteOutput(); } return Status; } bool MailMessage::EncodeBody(GStreamI &Out, MailProtocol *Protocol, bool Mime) { bool Status = true; char Buffer[1025]; if (Mime) { bool MultiPart = ((Text ? 1 : 0) + (Html ? 1 : 0) + FileDesc.Length()) > 1; bool MultipartAlternate = ValidStr(Text) && ValidStr(Html); bool MultipartMixed = FileDesc.Length() > 0; uint64 Now = LgiCurrentTime(); char Separator[256]; sprintf_s(Separator, sizeof(Separator), "----=_NextPart_%8.8X.%8.8X", (uint32)Now, (unsigned)(int64)LgiGetCurrentThread()); int Len = sprintf_s(Buffer, sizeof(Buffer), "MIME-Version: 1.0\r\n"); Status &= Out.Write(Buffer, Len) > 0; if (MultiPart) { const char *Type = MultipartMixed ? EncryptedMsg ? sMultipartEncrypted : sMultipartMixed : sMultipartAlternative; Len = sprintf_s(Buffer, sizeof(Buffer), "Content-Type: %s;\r\n\tboundary=\"%s\"\r\n", Type, Separator); Status &= Out.Write(Buffer, Len) > 0; } if (ValidStr(Text) || ValidStr(Html)) { char AlternateBoundry[128] = ""; if (MultiPart) { Len = sprintf_s(Buffer, sizeof(Buffer), "\r\n--%s\r\n", Separator); Status &= Out.Write(Buffer, Len) > 0; if (MultipartMixed && MultipartAlternate) { sprintf_s(AlternateBoundry, sizeof(AlternateBoundry), "----=_NextPart_%8.8X.%8.8X", (uint32)++Now, (uint32)(int64)LgiGetCurrentThread()); Len = sprintf_s(Buffer, sizeof(Buffer), "Content-Type: %s;\r\n\tboundary=\"%s\"\r\n", sMultipartAlternative, AlternateBoundry); Status &= Out.Write(Buffer, Len) > 0; Len = sprintf_s(Buffer, sizeof(Buffer), "\r\n--%s\r\n", AlternateBoundry); Status &= Out.Write(Buffer, Len) > 0; } } if (!Status) Log->Print("%s:%i - Status=%i\n", _FL, Status); if (ValidStr(Text)) { const char *Cs = 0; char *Txt = Text, *Mem = 0; // Detect charset if (!TextCharset || _stricmp(TextCharset, "utf-8") == 0) { Cs = LgiDetectCharset(Text, -1, Protocol ? &Protocol->CharsetPrefs : 0); if (Cs) { Mem = Txt = (char*)LgiNewConvertCp(Cs, Text, "utf-8", -1); } } if (!Cs) Cs = TextCharset; // Content type Len = sprintf_s(Buffer, sizeof(Buffer), "Content-Type: text/plain; charset=\"%s\"\r\n", Cs ? Cs : "us-ascii"); Status &= Out.Write(Buffer, Len) > 0; if (!Status) Log->Print("%s:%i - Status=%i\n", _FL, Status); // Transfer encoding if (Txt && (Is8Bit(Txt) || MaxLineLen(Txt) >= 80)) { char QuotPrint[] = "Content-Transfer-Encoding: quoted-printable\r\n\r\n"; Status &= Out.Write(QuotPrint, strlen(QuotPrint)) > 0; if (!Status) Log->Print("%s:%i - Status=%i\n", _FL, Status); GMemStream TxtStr(Txt, strlen(Txt), false); Status &= EncodeQuotedPrintable(Out, TxtStr) > 0; if (!Status) Log->Print("%s:%i - Status=%i\n", _FL, Status); } else { char Cte[] = "Content-Transfer-Encoding: 7bit\r\n\r\n"; Status &= Out.Write(Cte, strlen(Cte)) > 0; if (!Status) Log->Print("%s:%i - Status=%i\n", _FL, Status); GMemStream TxtStr(Txt, strlen(Txt), false); Status &= EncodeText(Out, TxtStr) > 0; if (!Status) Log->Print("%s:%i - Status=%i\n", _FL, Status); } DeleteArray(Mem); } if (!Status) Log->Print("%s:%i - Status=%i\n", _FL, Status); // Break alternate part if (AlternateBoundry[0]) { Len = sprintf_s(Buffer, sizeof(Buffer), "--%s\r\n", AlternateBoundry); Status &= Out.Write(Buffer, Len) > 0; } else if (MultipartAlternate) { Len = sprintf_s(Buffer, sizeof(Buffer), "--%s\r\n", Separator); Status &= Out.Write(Buffer, Len) > 0; } if (ValidStr(Html)) { // Content type Len = sprintf_s(Buffer, sizeof(Buffer), "Content-Type: text/html; charset=\"%s\"\r\n", TextCharset ? TextCharset : "us-ascii"); Status &= Out.Write(Buffer, Len) > 0; // Transfer encoding if (Is8Bit(Html) || MaxLineLen(Html) >= 80) { char Qp[] = "Content-Transfer-Encoding: quoted-printable\r\n\r\n"; Status &= Out.Write(Qp, strlen(Qp)) > 0; GMemStream HtmlStr(Html, strlen(Html), false); Status &= EncodeQuotedPrintable(Out, HtmlStr) > 0; } else { char Sb[] = "Content-Transfer-Encoding: 7bit\r\n\r\n"; Status &= Out.Write(Sb, strlen(Sb)) > 0; GMemStream HtmlStr(Html, strlen(Html), false); Status &= EncodeText(Out, HtmlStr) > 0; } } if (!Status) Log->Print("%s:%i - Status=%i\n", _FL, Status); if (AlternateBoundry[0]) { // End alternate part Len = sprintf_s(Buffer, sizeof(Buffer), "\r\n--%s--\r\n", AlternateBoundry); Status &= Out.Write(Buffer, Len) > 0; } } if (!Status) Log->Print("%s:%i - Status=%i\n", _FL, Status); int SizeEst = 1024; FileDescriptor *FileDes = FileDesc.First(); for (; FileDes; FileDes=FileDesc.Next()) { SizeEst += FileDes->Sizeof() * 4 / 3; } Out.SetSize(SizeEst); FileDes = FileDesc.First(); while (FileDes) { GStreamI *F = FileDes->GotoObject(); // write a MIME segment for this attachment if (MultiPart) { Len = sprintf_s(Buffer, sizeof(Buffer), "--%s\r\n", Separator); Status &= Out.Write(Buffer, Len) > 0; } char FileName[256]; char *s = FileDes->Name(), *d = FileName; if (!s) Log->Print("%s:%i - File descriptor has no name.\n", _FL); else { while (*s) { if (*s != '\\') { *d++ = *s++; } else { *d++ = '\\'; *d++ = '\\'; s++; } } *d = 0; char *FName = EncodeRfc2047(NewStr(FileName), 0, &Protocol->CharsetPrefs); Len = sprintf_s(Buffer, sizeof(Buffer), "Content-Type: %s; name=\"%s\"\r\n" "Content-Disposition: attachment\r\n", FileDes->GetMimeType() ? FileDes->GetMimeType() : "application/x-zip-compressed", (FName) ? FName : FileName); Status &= Out.Write(Buffer, Len) > 0; DeleteArray(FName); if (FileDes->GetContentId()) { Len = sprintf_s(Buffer, sizeof(Buffer), "Content-Id: %s\r\n", FileDes->GetContentId()); Status &= Out.Write(Buffer, Len) > 0; } Len = sprintf_s(Buffer, sizeof(Buffer), "Content-Transfer-Encoding: base64\r\n\r\n"); Status &= Out.Write(Buffer, Len) > 0; Status &= F ? EncodeBase64(Out, *F) > 0 : false; } FileDes = FileDesc.Next(); } if (!Status) Log->Print("%s:%i - Status=%i\n", _FL, Status); if (MultiPart) { // write final separator Len = sprintf_s(Buffer, sizeof(Buffer), "--%s--\r\n\r\n", Separator); Status &= Out.Write(Buffer, Len) > 0; } } else { // send content type int Len = sprintf_s(Buffer, sizeof(Buffer), "Content-Type: text/plain; charset=\"%s\"\r\n", TextCharset ? TextCharset : "us-ascii"); Status &= Out.Write(Buffer, Len) > 0; if (Is8Bit(Text)) { // send the encoding and a blank line char Qp[] = "Content-Transfer-Encoding: quoted-printable\r\n\r\n"; Status &= Out.Write(Qp, strlen(Qp)) > 0; // send message text GMemStream TextStr(Text, strlen(Text), false); Status &= EncodeQuotedPrintable(Out, TextStr) > 0; } else { // send a blank line Status &= Out.Write((char*)"\r\n", 2) > 0; // send message text GMemStream TextStr(Text, strlen(Text), false); Status &= EncodeText(Out, TextStr) > 0; } } if (!Status) Log->Print("%s:%i - Status=%i\n", _FL, Status); return true; } */ \ No newline at end of file diff --git a/src/common/INet/libntlm-0.4.2/build-win32/stdint.h b/src/common/INet/libntlm-0.4.2/build-win32/_stdint.h rename from src/common/INet/libntlm-0.4.2/build-win32/stdint.h rename to src/common/INet/libntlm-0.4.2/build-win32/_stdint.h diff --git a/src/common/INet/libntlm-0.4.2/smbencrypt.c b/src/common/INet/libntlm-0.4.2/smbencrypt.c --- a/src/common/INet/libntlm-0.4.2/smbencrypt.c +++ b/src/common/INet/libntlm-0.4.2/smbencrypt.c @@ -1,239 +1,239 @@ /* * Copyright (C) 2005, 2006, 2007, 2008 Simon Josefsson * Copyright (C) 1998-1999 Brian Bruns * Copyright (C) 2004 Frediano Ziglio * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public License * as published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301, USA. */ #include "config.h" #include #include #include #include #ifndef WINDOWS #include #endif #include #include "ntlm.h" #include "md4.h" #include "../../Hash/md5/md5.h" #include "des.h" /* C89 compliant way to cast 'char' to 'unsigned char'. */ -static inline unsigned char +static unsigned char to_uchar (char ch) { return ch; } /* * The following code is based on some psuedo-C code from ronald@innovation.ch */ static void ntlm_encrypt_answer (char *hash, const char *challenge, char *answer); static void ntlm_convert_key (char *key_56, gl_des_ctx * ks); void ntlm_smb_encrypt (const char *passwd, const uint8 * challenge, uint8 * answer) { #define MAX_PW_SZ 14 int len; int i; static const char magic[8] = { 0x4B, 0x47, 0x53, 0x21, 0x40, 0x23, 0x24, 0x25 }; gl_des_ctx ks; char hash[24]; char passwd_up[MAX_PW_SZ]; /* convert password to upper and pad to 14 chars */ memset (passwd_up, 0, MAX_PW_SZ); len = strlen (passwd); if (len > MAX_PW_SZ) len = MAX_PW_SZ; for (i = 0; i < len; i++) passwd_up[i] = toupper (passwd[i]); /* hash the first 7 characters */ ntlm_convert_key (passwd_up, &ks); gl_des_ecb_encrypt (&ks, magic, hash + 0); /* hash the second 7 characters */ ntlm_convert_key (passwd_up + 7, &ks); gl_des_ecb_encrypt (&ks, magic, hash + 8); memset (hash + 16, 0, 5); ntlm_encrypt_answer (hash, challenge, answer); /* with security is best be pedantic */ memset (&ks, 0, sizeof (ks)); memset (hash, 0, sizeof (hash)); memset (passwd_up, 0, sizeof (passwd_up)); } void ntlm_smb_nt_encrypt (const char *passwd, const uint8 * challenge, uint8 * answer) { size_t len, i; unsigned char hash[24]; unsigned char nt_pw[256]; /* NT resp */ len = strlen (passwd); if (len > 128) len = 128; for (i = 0; i < len; ++i) { nt_pw[2 * i] = passwd[i]; nt_pw[2 * i + 1] = 0; } md4_buffer (nt_pw, len * 2, hash); memset (hash + 16, 0, 5); ntlm_encrypt_answer (hash, challenge, answer); /* with security is best be pedantic */ memset (hash, 0, sizeof (hash)); memset (nt_pw, 0, sizeof (nt_pw)); } /* * takes a 21 byte array and treats it as 3 56-bit DES keys. The * 8 byte plaintext is encrypted with each key and the resulting 24 * bytes are stored in the results array. */ static void ntlm_encrypt_answer (char *hash, const char *challenge, char *answer) { gl_des_ctx ks; ntlm_convert_key (hash, &ks); gl_des_ecb_encrypt (&ks, challenge, answer); ntlm_convert_key (&hash[7], &ks); gl_des_ecb_encrypt (&ks, challenge, &answer[8]); ntlm_convert_key (&hash[14], &ks); gl_des_ecb_encrypt (&ks, challenge, &answer[16]); memset (&ks, 0, sizeof (ks)); } /* * turns a 56 bit key into the 64 bit, and sets the key schedule ks. */ static void ntlm_convert_key (char *key_56, gl_des_ctx * ks) { char key[8]; key[0] = to_uchar (key_56[0]); key[1] = ((to_uchar (key_56[0]) << 7) & 0xFF) | (to_uchar (key_56[1]) >> 1); key[2] = ((to_uchar (key_56[1]) << 6) & 0xFF) | (to_uchar (key_56[2]) >> 2); key[3] = ((to_uchar (key_56[2]) << 5) & 0xFF) | (to_uchar (key_56[3]) >> 3); key[4] = ((to_uchar (key_56[3]) << 4) & 0xFF) | (to_uchar (key_56[4]) >> 4); key[5] = ((to_uchar (key_56[4]) << 3) & 0xFF) | (to_uchar (key_56[5]) >> 5); key[6] = ((to_uchar (key_56[5]) << 2) & 0xFF) | (to_uchar (key_56[6]) >> 6); key[7] = (to_uchar (key_56[6]) << 1) & 0xFF; gl_des_setkey (ks, key); memset (&key, 0, sizeof (key)); } void hmac_md5 ( unsigned char *text, /* pointer to data stream */ int text_len, /* length of data stream */ unsigned char *key, /* pointer to authentication key */ int key_len, /* length of authentication key */ unsigned char *digest/* caller digest to be filled in */ ) { struct md5_state_s context; unsigned char k_ipad[65]; /* inner padding - * key XORd with ipad */ unsigned char k_opad[65]; /* outer padding - * key XORd with opad */ unsigned char tk[16]; int i; /* if key is longer than 64 bytes reset it to key=MD5(key) */ if (key_len > 64) { struct md5_state_s tctx; md5_init(&tctx); md5_append(&tctx, key, key_len); md5_finish(&tctx, tk); key = tk; key_len = 16; } /* * the HMAC_MD5 transform looks like: * * MD5(K XOR opad, MD5(K XOR ipad, text)) * * where K is an n byte key * ipad is the byte 0x36 repeated 64 times * opad is the byte 0x5c repeated 64 times * and text is the data being protected */ /* start out by storing key in pads */ memset( k_ipad, 0, sizeof k_ipad); memset( k_opad, 0, sizeof k_opad); memcpy( k_ipad, key, key_len); memcpy( k_opad, key, key_len); /* XOR key with ipad and opad values */ for (i=0; i<64; i++) { k_ipad[i] ^= 0x36; k_opad[i] ^= 0x5c; } /* * perform inner MD5 */ md5_init(&context); /* init context for 1st pass */ md5_append(&context, k_ipad, 64); /* start with inner pad */ md5_append(&context, text, text_len); /* then text of datagram */ md5_finish(&context, digest); /* finish up 1st pass */ /* * perform outer MD5 */ md5_init(&context); /* init context for 2nd * pass */ md5_append(&context, k_opad, 64); /* start with outer pad */ md5_append(&context, digest, 16); /* then results of 1st * hash */ md5_finish(&context, digest); /* finish up 2nd pass */ } /** \@} */ diff --git a/src/common/INet/libntlm-0.4.2/smbutil.c b/src/common/INet/libntlm-0.4.2/smbutil.c --- a/src/common/INet/libntlm-0.4.2/smbutil.c +++ b/src/common/INet/libntlm-0.4.2/smbutil.c @@ -1,654 +1,654 @@ /* smbutil.c --- Main library functions. * Copyright (C) 2002, 2004, 2005, 2006, 2008 Simon Josefsson * Copyright (C) 1999-2001 Grant Edwards * Copyright (C) 2004 Frediano Ziglio * * This file is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This file is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this file; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301, USA. * */ #include #include #include #include #include #ifndef WINDOWS #include #endif #include #include "ntlm.h" #include "md4.h" #include "../../src/common/Hash/md5/md5.h" #include #include "des.h" #include "md4.h" char versionString[] = PACKAGE_STRING; /* Utility routines that handle NTLM auth structures. */ /* * Must be multiple of two * We use a statis buffer of 1024 bytes for message * At maximun we but 48 bytes (ntlm responses) and 3 unicode strings so * NTLM_BUFSIZE * 3 + 48 <= 1024 */ #define NTLM_BUFSIZE 320 /* * all bytes in our structures are aligned so just swap bytes so * we have just to swap order */ #ifdef WORDS_BIGENDIAN # define UI16LE(n) bswap_16(n) # define UI32LE(n) bswap_32(n) #else # define UI16LE(n) (n) # define UI32LE(n) (n) #endif /* I am not crazy about these macros -- they seem to have gotten * a bit complex. A new scheme for handling string/buffer fields * in the structures probably needs to be designed */ #define AddBytes(ptr, header, buf, count) \ { \ if (NTLM_VER(ptr) == 2) { \ ptr->v2.header.len = ptr->v2.header.maxlen = UI16LE(count); \ ptr->v2.header.offset = UI32LE((ptr->v2.buffer - ((uint8*)ptr)) + ptr->v2.bufIndex); \ memcpy(ptr->v2.buffer+ptr->v2.bufIndex, buf, count); \ ptr->v2.bufIndex += count; \ } else { \ ptr->v1.header.len = ptr->v1.header.maxlen = UI16LE(count); \ ptr->v1.header.offset = UI32LE((ptr->v1.buffer - ((uint8*)ptr)) + ptr->v1.bufIndex); \ memcpy(ptr->v1.buffer+ptr->v1.bufIndex, buf, count); \ ptr->v1.bufIndex += count; \ } \ } #define AddString(ptr, header, string) \ { \ const char *p = (string); \ size_t len = p ? strlen(p) : 0; \ AddBytes(ptr, header, p, len); \ } #define AddStringLen(ptr, header, string, len) \ { \ AddBytes(ptr, header, string, len); \ } #define AddUnicodeStringLen(ptr, header, string, len) \ { \ unsigned char buf[NTLM_BUFSIZE]; \ unsigned char *b = strToUnicode(string, len, buf); \ AddBytes(ptr, header, b, len*2); \ } #define AddUnicodeString(ptr, header, string) \ { \ size_t len = string ? strlen(string) : 0; \ AddUnicodeStringLen(ptr, header, string, len); \ } #define GetUnicodeString(structPtr, header, output) \ NTLM_VER(structPtr) == 2 ? \ getUnicodeString(UI32LE(structPtr->v2.header.offset), \ UI16LE(structPtr->v2.header.len), \ ((char*)structPtr), \ (structPtr->v2.buffer - (uint8*) structPtr), \ sizeof(structPtr->v2.buffer), \ output) : \ getUnicodeString(UI32LE(structPtr->v1.header.offset), \ UI16LE(structPtr->v1.header.len), \ ((char*)structPtr), \ (structPtr->v1.buffer - (uint8*) structPtr), \ sizeof(structPtr->v1.buffer), \ output) #define GetString(structPtr, header, output) \ getString(structPtr, &structPtr->v1.header, output) #define DumpBuffer(fp, structPtr, header) \ NTLM_VER(structPtr) == 2 ? \ dumpBuffer( fp, \ UI32LE(structPtr->v2.header.offset), \ UI16LE(structPtr->v2.header.len), \ ((char*)structPtr), \ (structPtr->v2.buffer - (uint8*) structPtr), \ sizeof(structPtr->v2.buffer)) : \ dumpBuffer( fp, \ UI32LE(structPtr->v1.header.offset), \ UI16LE(structPtr->v1.header.len), \ ((char*)structPtr), \ (structPtr->v1.buffer - (uint8*) structPtr), \ sizeof(structPtr->v1.buffer)) static void dumpRaw (FILE * fp, const unsigned char *buf, size_t len) { size_t i; for (i = 0; i < len; ++i) fprintf (fp, "%02x ", buf[i]); fprintf (fp, "\n"); } -static inline void -dumpBuffer (FILE * fp, uint32 offset, uint32 len, char *structPtr, +static void +dumpBuffer (FILE * fp, uint32_t offset, uint32_t len, char *structPtr, size_t buf_start, size_t buf_len) { /* prevent buffer reading overflow */ if (offset < buf_start || offset > buf_len + buf_start || offset + len > buf_len + buf_start) len = 0; dumpRaw (fp, structPtr + offset, len); } static char * unicodeToString (const char *p, size_t len, char *buf) { const char *e = p + len; char *out = buf; if (len >= NTLM_BUFSIZE) len = NTLM_BUFSIZE - 1; while (p < e) { *out++ = *p & 0x7f; p += 2; } *out = '\0'; return buf; } -static inline char * +static char * getUnicodeString(uint32 offset, uint32 len, char *structPtr, size_t buf_start, size_t buf_len, char *output) { /* prevent buffer reading overflow */ if (offset < buf_start || offset > buf_len + buf_start || offset + len > buf_len + buf_start) len = 0; return unicodeToString (structPtr + offset, len / 2, output); } static unsigned char * strToUnicode (const char *p, size_t l, unsigned char *buf) { if (p) { int i = 0; if (l > (NTLM_BUFSIZE / 2)) l = (NTLM_BUFSIZE / 2); while (l--) { buf[i++] = *p++; buf[i++] = 0; } } return buf; } static char * toString (const char *p, size_t len, char *buf) { if (len >= NTLM_BUFSIZE) len = NTLM_BUFSIZE - 1; memcpy (buf, p, len); buf[len] = 0; return buf; } typedef struct { char ident[8]; uint32 msgType; } SmbCommonFields; char *getString(void *ptr, tSmbStrHeader *hdr, char *output) { SmbCommonFields *common = (SmbCommonFields*) ptr; uint8 *buffer = 0; tSmbNtlmFlagBits *flags = 0; int buf_start, unicode = 0; switch (common->msgType) { case 1: { tSmbNtlmAuthNegotiate *p = (tSmbNtlmAuthNegotiate*)ptr; buffer = p->v1.flagBits.NEGOTIATE_VERSION ? p->v2.buffer : p->v1.buffer; flags = &p->v1.flagBits; break; } case 2: { tSmbNtlmAuthChallenge *p = (tSmbNtlmAuthChallenge*)ptr; buffer = p->v1.flagBits.NEGOTIATE_VERSION ? p->v2.buffer : p->v1.buffer; flags = &p->v1.flagBits; break; } case 3: { tSmbNtlmAuthResponse *p = (tSmbNtlmAuthResponse*)ptr; buffer = p->v1.flagBits.NEGOTIATE_VERSION ? p->v2.buffer : p->v1.buffer; flags = &p->v1.flagBits; break; } } buf_start = buffer - (uint8*)ptr; if (hdr->offset < buf_start || hdr->offset > NTLM_BUF_SIZE + buf_start || hdr->offset + hdr->len > NTLM_BUF_SIZE + buf_start) hdr->len = 0; if (flags->NEGOTIATE_UNICODE) return unicodeToString ((uint8*)ptr + hdr->offset, hdr->len, output); else return toString ((uint8*)ptr + hdr->offset, hdr->len, output); } /* #define GetString(structPtr, header, output) \ NTLM_VER(structPtr) == 2 ? \ getString( UI32LE(structPtr->v2.header.offset), \ UI16LE(structPtr->v2.header.len), \ ((char*)structPtr), \ (structPtr->v2.buffer - (uint8*) structPtr), \ sizeof(structPtr->v2.buffer), output) : \ getString( UI32LE(structPtr->v1.header.offset), \ UI16LE(structPtr->v1.header.len), \ ((char*)structPtr), \ (structPtr->v1.buffer - (uint8*) structPtr), \ sizeof(structPtr->v1.buffer), output) static inline char * getString ( uint32 offset, uint32 len, char *structPtr, size_t buf_start, size_t buf_len, char *output) { if (offset < buf_start || offset > buf_len + buf_start || offset + len > buf_len + buf_start) len = 0; return toString (structPtr + offset, len, output); } */ void dumpSmbNtlmAuthNegotiate (FILE * fp, tSmbNtlmAuthNegotiate * request) { char buf1[NTLM_BUFSIZE], buf2[NTLM_BUFSIZE]; fprintf (fp, "NTLM Request:\n" " Ident = %.8s\n" " mType = %d\n" " Flags = %08x\n" " User = %s\n" " Domain = %s\n", request->v1.ident, UI32LE (request->v1.msgType), UI32LE (request->v1.flags), GetString (request, domainName, buf1), GetString (request, workStation, buf2)); } void dumpSmbNtlmAuthChallenge (FILE * fp, tSmbNtlmAuthChallenge * challenge) { unsigned char buf[NTLM_BUFSIZE]; fprintf (fp, "NTLM Challenge:\n" " Ident = %.8s\n" " mType = %d\n" " Domain = %s\n" " Flags = %08x\n" " Challenge = ", challenge->v1.ident, UI32LE (challenge->v1.msgType), GetUnicodeString (challenge, targetName, buf), UI32LE (challenge->v1.flags)); dumpRaw (fp, challenge->v1.challengeData, 8); } void dumpSmbNtlmAuthResponse (FILE * fp, tSmbNtlmAuthResponse * response) { unsigned char buf1[NTLM_BUFSIZE], buf2[NTLM_BUFSIZE], buf3[NTLM_BUFSIZE]; fprintf (fp, "NTLM Response:\n" " Ident = %.8s\n" " mType = %d\n" " LmResp = ", response->v1.ident, UI32LE (response->v1.msgType)); DumpBuffer (fp, response, lmResponse); fprintf (fp, " NTResp = "); DumpBuffer (fp, response, ntResponse); fprintf (fp, " Domain = %s\n" " User = %s\n" " Wks = %s\n" " sKey = ", GetUnicodeString (response, domainName, buf1), GetUnicodeString (response, user, buf2), GetUnicodeString (response, workStation, buf3)); DumpBuffer (fp, response, sessionKey); fprintf (fp, " Flags = %08x\n", UI32LE (response->v1.flags)); } static void buildSmbNtlmAuthNegotiate_userlen(tSmbNtlmAuthNegotiate * request, const char *user, size_t user_len, const char *domain) { request->v1.bufIndex = 0; memcpy (request->v1.ident, "NTLMSSP\0\0\0", 8); request->v1.msgType = UI32LE (1); request->v1.flags = UI32LE( NTLMSSP_NEGOTIATE_UNICODE | NTLM_NEGOTIATE_OEM | NTLMSSP_REQUEST_TARGET | NTLMSSP_NEGOTIATE_NTLM | NTLMSSP_NEGOTIATE_ALWAYS_SIGN | NTLMSSP_NEGOTIATE_NTLM2 ); if (user) AddBytes (request, workStation, user, user_len); if (domain) AddString (request, domainName, domain); } void buildSmbNtlmAuthNegotiate(tSmbNtlmAuthNegotiate * request, const char *user, const char *domain) { const char *p = user ? strchr(user, '@') : 0; size_t user_len = user ? strlen(user) : 0; if (p) { if (!domain) domain = p + 1; user_len = p - user; } buildSmbNtlmAuthNegotiate_userlen(request, user, user_len, domain); } void buildSmbNtlmAuthNegotiate_noatsplit( tSmbNtlmAuthNegotiate * request, const char *user, const char *domain) { buildSmbNtlmAuthNegotiate_userlen(request, user, strlen (user), domain); } /* The general idea: UserDom = ConcatenationOf ( Uppercase(User), UserDom ) ResponseKeyLM = HMAC_MD5 ( MD4(UNICODE(Passwd)), UserDom ) ClientChallenge = {0, 0, 0, 0, 0, 0, 0, 0} // wtf is this supposed to be? ServerChallenge = challenge->v2.challengeData ResponseRVersion = {0x01} HiResponseRVersion = {0x01} Time = The 8-byte little-endian time in GMT... ??? Temp = ConcatenationOf ( ResponseRVersion, HiResponseRVersion, Z(6), Time, ClientChallenge, Z(4), ServerName, Z(4) ) LmChallengeResponse = ConcatenationOf ( HMAC_MD5 ( ResponseKeyLM, ConcatenationOf ( ServerChallenge, ClientChallenge ) ), ClientChallenge ) NtChallengeResponse = ConcatenationOf ( HMAC_MD5 ( ResponseKeyNT, ConcatenationOf ( ServerChallenge, Temp ) ), Temp ) */ #define Z(num) memset(t, 0, num); t += num void NTOWFv2(const char *password, const char *user, const char *domain, uint8 *output) { uint16 uPass[256] = {0}; int uPassLen = 0; uint8 PassMD4[16]; char UserDom[256] = ""; if (password) { // convert password to unicode const char *in = password; uint16 *out = uPass; while (*in) *out++ = *in++ & 0x7f; uPassLen = (out - uPass) * sizeof(uPass[0]); *out++ = 0; } strcpy(UserDom, user); strupr(UserDom); if (domain) strcat(UserDom, domain); md4_buffer((const char *)uPass, uPassLen, PassMD4); hmac_md5( PassMD4, 16, UserDom, strlen(UserDom), output); } #define LM_HASH_LEN 16 #define LM_RESP_LEN 24 #define NTLM_HASH_LEN 16 #define NTLM_RESP_LEN 24 void md5sum(uint8 *in, int len, uint8 *out) { md5_state_t s; md5_init(&s); md5_append(&s, in, len); md5_finish(&s, out); } bool NTLM_Hash(const char *password, unsigned char *hash) { uint16 uPass[256] = {0}; int uPassLen = 0; const char *in = password; uint16 *out = uPass; if (!password) return false; // convert password to unicode while (*in) *out++ = *in++ & 0x7f; uPassLen = (out - uPass) * sizeof(uPass[0]); *out++ = 0; // md4 it md4_buffer((const char *)uPass, uPassLen, hash); return true; } static uint8 des_setkeyparity(uint8 x) { if ((((x >> 7) ^ (x >> 6) ^ (x >> 5) ^ (x >> 4) ^ (x >> 3) ^ (x >> 2) ^ (x >> 1)) & 0x01) == 0) x |= 0x01; else x &= 0xfe; return x; } static void des_makekey(const uint8 *raw, uint8 *key) { key[0] = des_setkeyparity(raw[0]); key[1] = des_setkeyparity((uint8) ((raw[0] << 7) | (raw[1] >> 1)) ); key[2] = des_setkeyparity((uint8) ((raw[1] << 6) | (raw[2] >> 2)) ); key[3] = des_setkeyparity((uint8) ((raw[2] << 5) | (raw[3] >> 3)) ); key[4] = des_setkeyparity((uint8) ((raw[3] << 4) | (raw[4] >> 4)) ); key[5] = des_setkeyparity((uint8) ((raw[4] << 3) | (raw[5] >> 5)) ); key[6] = des_setkeyparity((uint8) ((raw[5] << 2) | (raw[6] >> 6)) ); key[7] = des_setkeyparity((uint8) (raw[6] << 1)); } static void des_encrypt(const uint8 *key, const uint8 *src, uint8 *hash) { gl_des_ctx c; gl_des_setkey(&c, key); gl_des_ecb_encrypt(&c, src, hash); } void LM_Response(const uint8 *hash, const uint8 *challenge, uint8 *response) { uint8 keybytes[21], k1[8], k2[8], k3[8]; memcpy(keybytes, hash, 16); memset(keybytes + 16, 0, 5); des_makekey(keybytes , k1); des_makekey(keybytes + 7, k2); des_makekey(keybytes + 14, k3); des_encrypt(k1, challenge, response); des_encrypt(k2, challenge, response + 8); des_encrypt(k3, challenge, response + 16); } void buildSmbNtlmAuthResponse(tSmbNtlmAuthChallenge * challenge, tSmbNtlmAuthResponse * response, const char *user, const char *workstation, const char *domain, const char *password, const uint8 time[8]) { uint8 lmResp[LM_RESP_LEN], ntlmResp[NTLM_RESP_LEN], ntlmHash[NTLM_HASH_LEN]; if (challenge->v2.flagBits.NEGOTIATE_NTLM2) { uint8 sessionHash[16], temp[16]; GenerateRandom(lmResp, 8); memset(lmResp + 8, 0, LM_RESP_LEN - 8); memcpy(temp, challenge->v2.challengeData, 8); memcpy(temp + 8, lmResp, 8); md5sum(temp, 16, sessionHash); NTLM_Hash(password, ntlmHash); LM_Response(ntlmHash, sessionHash, ntlmResp); } else { NTLM_Hash(password, ntlmHash); LM_Response(ntlmHash, challenge->v2.challengeData, ntlmResp); } response->v1.flags = NTLMSSP_NEGOTIATE_UNICODE | NTLMSSP_REQUEST_TARGET | NTLMSSP_NEGOTIATE_NTLM | NTLMSSP_NEGOTIATE_ALWAYS_SIGN | NTLMSSP_NEGOTIATE_NTLM2; memcpy (response->v1.ident, "NTLMSSP\0\0\0", 8); response->v1.msgType = UI32LE (3); if (NTLM_VER(response) == 2) response->v2.bufIndex = 0; else response->v1.bufIndex = 0; if (response->v1.flagBits.NEGOTIATE_UNICODE) { AddUnicodeString(response, domainName, domain); AddUnicodeString(response, user, user); AddUnicodeString(response, workStation, workstation); } else { AddString (response, domainName, domain); AddString(response, user, user); AddString(response, workStation, workstation); } AddBytes(response, lmResponse, lmResp, sizeof(lmResp)); AddBytes(response, ntResponse, ntlmResp, sizeof(ntlmResp)); // AddString(response, sessionKey, NULL); } diff --git a/src/common/Text/Emoji/EmojiMap.cpp b/src/common/Text/Emoji/EmojiMap.cpp --- a/src/common/Text/Emoji/EmojiMap.cpp +++ b/src/common/Text/Emoji/EmojiMap.cpp @@ -1,858 +1,858 @@ // Generated by EmojiMapper // fret@memecode.com #include "Lgi.h" #include "Emoji.h" -int EmojiToIconIndex(const uint32 *Str, ssize_t Len) +int EmojiToIconIndex(const uint32_t *Str, ssize_t Len) { switch (*Str) { // case 0xa9: return 0; // case 0xae: return 1; case 0x203c: return 2; case 0x2049: return 3; case 0x2122: return 4; case 0x2139: return 5; case 0x2194: return 6; case 0x2195: return 7; case 0x2196: return 8; case 0x2197: return 9; case 0x2198: return 10; case 0x2199: return 11; case 0x21a9: return 12; case 0x21aa: return 13; case 0x231a: return 14; case 0x231b: return 15; case 0x23e9: return 16; case 0x23ea: return 17; case 0x23eb: return 18; case 0x23ec: return 19; case 0x23f0: return 20; case 0x23f3: return 21; case 0x24c2: return 22; case 0x25aa: return 23; case 0x25ab: return 24; case 0x25b6: return 25; case 0x25c0: return 26; case 0x25fb: return 27; case 0x25fc: return 28; case 0x2600: return 29; case 0x2601: return 30; case 0x260e: return 31; case 0x2611: return 32; case 0x2614: return 33; case 0x2615: return 34; case 0x261d: return 35; case 0x263a: return 36; case 0x2648: return 37; case 0x2649: return 38; case 0x264a: return 39; case 0x264b: return 40; case 0x264c: return 41; case 0x264d: return 42; case 0x264e: return 43; case 0x264f: return 44; case 0x2650: return 45; case 0x2651: return 46; case 0x2652: return 47; case 0x2653: return 48; case 0x2660: return 49; case 0x2663: return 50; case 0x2665: return 51; case 0x2666: return 52; case 0x2668: return 53; case 0x267b: return 54; case 0x267f: return 55; case 0x2693: return 56; case 0x26a0: return 57; case 0x26a1: return 58; case 0x26aa: return 59; case 0x26ab: return 60; case 0x26bd: return 61; case 0x26be: return 62; case 0x26c4: return 63; case 0x26c5: return 64; case 0x26ce: return 65; case 0x26d4: return 66; case 0x26ea: return 67; case 0x26f2: return 68; case 0x26f3: return 69; case 0x26f5: return 70; case 0x26fa: return 71; case 0x26fd: return 72; case 0x2702: return 73; case 0x2705: return 74; case 0x2708: return 75; case 0x2709: return 76; case 0x270a: return 77; case 0x270b: return 78; case 0x270c: return 79; case 0x270f: return 80; case 0x2712: return 81; case 0x2714: return 82; case 0x2716: return 83; case 0x2728: return 84; case 0x2733: return 85; case 0x2734: return 86; case 0x2744: return 87; case 0x2747: return 88; case 0x274c: return 89; case 0x274e: return 90; case 0x2753: return 91; case 0x2754: return 92; case 0x2755: return 93; case 0x2757: return 94; case 0x2764: return 95; case 0x2795: return 96; case 0x2796: return 97; case 0x2797: return 98; case 0x27a1: return 99; case 0x27b0: return 100; case 0x27bf: return 101; case 0x2934: return 102; case 0x2935: return 103; case 0x2b05: return 104; case 0x2b06: return 105; case 0x2b07: return 106; case 0x2b1b: return 107; case 0x2b1c: return 108; case 0x2b50: return 109; case 0x2b55: return 110; case 0x3030: return 111; case 0x303d: return 112; case 0x3297: return 113; case 0x3299: return 114; case 0x1f004: return 115; case 0x1f0cf: return 116; case 0x1f170: return 117; case 0x1f171: return 118; case 0x1f17e: return 119; case 0x1f17f: return 120; case 0x1f18e: return 121; case 0x1f191: return 122; case 0x1f192: return 123; case 0x1f193: return 124; case 0x1f194: return 125; case 0x1f195: return 126; case 0x1f196: return 127; case 0x1f197: return 128; case 0x1f198: return 129; case 0x1f199: return 130; case 0x1f19a: return 131; case 0x1f201: return 132; case 0x1f202: return 133; case 0x1f21a: return 134; case 0x1f22f: return 135; case 0x1f232: return 136; case 0x1f233: return 137; case 0x1f234: return 138; case 0x1f235: return 139; case 0x1f236: return 140; case 0x1f237: return 141; case 0x1f238: return 142; case 0x1f239: return 143; case 0x1f23a: return 144; case 0x1f250: return 145; case 0x1f251: return 146; case 0x1f300: return 147; case 0x1f301: return 148; case 0x1f302: return 149; case 0x1f303: return 150; case 0x1f304: return 151; case 0x1f305: return 152; case 0x1f306: return 153; case 0x1f307: return 154; case 0x1f308: return 155; case 0x1f309: return 156; case 0x1f30a: return 157; case 0x1f30b: return 158; case 0x1f30c: return 159; case 0x1f30d: return 160; case 0x1f30e: return 161; case 0x1f30f: return 162; case 0x1f310: return 163; case 0x1f311: return 164; case 0x1f312: return 165; case 0x1f313: return 166; case 0x1f314: return 167; case 0x1f315: return 168; case 0x1f316: return 169; case 0x1f317: return 170; case 0x1f318: return 171; case 0x1f319: return 172; case 0x1f31a: return 173; case 0x1f31b: return 174; case 0x1f31c: return 175; case 0x1f31d: return 176; case 0x1f31e: return 177; case 0x1f31f: return 178; case 0x1f320: return 179; case 0x1f330: return 180; case 0x1f331: return 181; case 0x1f332: return 182; case 0x1f333: return 183; case 0x1f334: return 184; case 0x1f335: return 185; case 0x1f337: return 186; case 0x1f338: return 187; case 0x1f339: return 188; case 0x1f33a: return 189; case 0x1f33b: return 190; case 0x1f33c: return 191; case 0x1f33d: return 192; case 0x1f33e: return 193; case 0x1f33f: return 194; case 0x1f340: return 195; case 0x1f341: return 196; case 0x1f342: return 197; case 0x1f343: return 198; case 0x1f344: return 199; case 0x1f345: return 200; case 0x1f346: return 201; case 0x1f347: return 202; case 0x1f348: return 203; case 0x1f349: return 204; case 0x1f34a: return 205; case 0x1f34b: return 206; case 0x1f34c: return 207; case 0x1f34d: return 208; case 0x1f34e: return 209; case 0x1f34f: return 210; case 0x1f350: return 211; case 0x1f351: return 212; case 0x1f352: return 213; case 0x1f353: return 214; case 0x1f354: return 215; case 0x1f355: return 216; case 0x1f356: return 217; case 0x1f357: return 218; case 0x1f358: return 219; case 0x1f359: return 220; case 0x1f35a: return 221; case 0x1f35b: return 222; case 0x1f35c: return 223; case 0x1f35d: return 224; case 0x1f35e: return 225; case 0x1f35f: return 226; case 0x1f360: return 227; case 0x1f361: return 228; case 0x1f362: return 229; case 0x1f363: return 230; case 0x1f364: return 231; case 0x1f365: return 232; case 0x1f366: return 233; case 0x1f367: return 234; case 0x1f368: return 235; case 0x1f369: return 236; case 0x1f36a: return 237; case 0x1f36b: return 238; case 0x1f36c: return 239; case 0x1f36d: return 240; case 0x1f36e: return 241; case 0x1f36f: return 242; case 0x1f370: return 243; case 0x1f371: return 244; case 0x1f372: return 245; case 0x1f373: return 246; case 0x1f374: return 247; case 0x1f375: return 248; case 0x1f376: return 249; case 0x1f377: return 250; case 0x1f378: return 251; case 0x1f379: return 252; case 0x1f37a: return 253; case 0x1f37b: return 254; case 0x1f37c: return 255; case 0x1f380: return 256; case 0x1f381: return 257; case 0x1f382: return 258; case 0x1f383: return 259; case 0x1f384: return 260; case 0x1f385: return 261; case 0x1f386: return 262; case 0x1f387: return 263; case 0x1f388: return 264; case 0x1f389: return 265; case 0x1f38a: return 266; case 0x1f38b: return 267; case 0x1f38c: return 268; case 0x1f38d: return 269; case 0x1f38e: return 270; case 0x1f38f: return 271; case 0x1f390: return 272; case 0x1f391: return 273; case 0x1f392: return 274; case 0x1f393: return 275; case 0x1f3a0: return 276; case 0x1f3a1: return 277; case 0x1f3a2: return 278; case 0x1f3a3: return 279; case 0x1f3a4: return 280; case 0x1f3a5: return 281; case 0x1f3a6: return 282; case 0x1f3a7: return 283; case 0x1f3a8: return 284; case 0x1f3a9: return 285; case 0x1f3aa: return 286; case 0x1f3ab: return 287; case 0x1f3ac: return 288; case 0x1f3ad: return 289; case 0x1f3ae: return 290; case 0x1f3af: return 291; case 0x1f3b0: return 292; case 0x1f3b1: return 293; case 0x1f3b2: return 294; case 0x1f3b3: return 295; case 0x1f3b4: return 296; case 0x1f3b5: return 297; case 0x1f3b6: return 298; case 0x1f3b7: return 299; case 0x1f3b8: return 300; case 0x1f3b9: return 301; case 0x1f3ba: return 302; case 0x1f3bb: return 303; case 0x1f3bc: return 304; case 0x1f3bd: return 305; case 0x1f3be: return 306; case 0x1f3bf: return 307; case 0x1f3c0: return 308; case 0x1f3c1: return 309; case 0x1f3c2: return 310; case 0x1f3c3: return 311; case 0x1f3c4: return 312; case 0x1f3c6: return 313; case 0x1f3c7: return 314; case 0x1f3c8: return 315; case 0x1f3c9: return 316; case 0x1f3ca: return 317; case 0x1f3e0: return 318; case 0x1f3e1: return 319; case 0x1f3e2: return 320; case 0x1f3e3: return 321; case 0x1f3e4: return 322; case 0x1f3e5: return 323; case 0x1f3e6: return 324; case 0x1f3e7: return 325; case 0x1f3e8: return 326; case 0x1f3e9: return 327; case 0x1f3ea: return 328; case 0x1f3eb: return 329; case 0x1f3ec: return 330; case 0x1f3ed: return 331; case 0x1f3ee: return 332; case 0x1f3ef: return 333; case 0x1f3f0: return 334; case 0x1f400: return 335; case 0x1f401: return 336; case 0x1f402: return 337; case 0x1f403: return 338; case 0x1f404: return 339; case 0x1f405: return 340; case 0x1f406: return 341; case 0x1f407: return 342; case 0x1f408: return 343; case 0x1f409: return 344; case 0x1f40a: return 345; case 0x1f40b: return 346; case 0x1f40c: return 347; case 0x1f40d: return 348; case 0x1f40e: return 349; case 0x1f40f: return 350; case 0x1f410: return 351; case 0x1f411: return 352; case 0x1f412: return 353; case 0x1f413: return 354; case 0x1f414: return 355; case 0x1f415: return 356; case 0x1f416: return 357; case 0x1f417: return 358; case 0x1f418: return 359; case 0x1f419: return 360; case 0x1f41a: return 361; case 0x1f41b: return 362; case 0x1f41c: return 363; case 0x1f41d: return 364; case 0x1f41e: return 365; case 0x1f41f: return 366; case 0x1f420: return 367; case 0x1f421: return 368; case 0x1f422: return 369; case 0x1f423: return 370; case 0x1f424: return 371; case 0x1f425: return 372; case 0x1f426: return 373; case 0x1f427: return 374; case 0x1f428: return 375; case 0x1f429: return 376; case 0x1f42a: return 377; case 0x1f42b: return 378; case 0x1f42c: return 379; case 0x1f42d: return 380; case 0x1f42e: return 381; case 0x1f42f: return 382; case 0x1f430: return 383; case 0x1f431: return 384; case 0x1f432: return 385; case 0x1f433: return 386; case 0x1f434: return 387; case 0x1f435: return 388; case 0x1f436: return 389; case 0x1f437: return 390; case 0x1f438: return 391; case 0x1f439: return 392; case 0x1f43a: return 393; case 0x1f43b: return 394; case 0x1f43c: return 395; case 0x1f43d: return 396; case 0x1f43e: return 397; case 0x1f440: return 398; case 0x1f442: return 399; case 0x1f443: return 400; case 0x1f444: return 401; case 0x1f445: return 402; case 0x1f446: return 403; case 0x1f447: return 404; case 0x1f448: return 405; case 0x1f449: return 406; case 0x1f44a: return 407; case 0x1f44b: return 408; case 0x1f44c: return 409; case 0x1f44d: return 410; case 0x1f44e: return 411; case 0x1f44f: return 412; case 0x1f450: return 413; case 0x1f451: return 414; case 0x1f452: return 415; case 0x1f453: return 416; case 0x1f454: return 417; case 0x1f455: return 418; case 0x1f456: return 419; case 0x1f457: return 420; case 0x1f458: return 421; case 0x1f459: return 422; case 0x1f45a: return 423; case 0x1f45b: return 424; case 0x1f45c: return 425; case 0x1f45d: return 426; case 0x1f45e: return 427; case 0x1f45f: return 428; case 0x1f460: return 429; case 0x1f461: return 430; case 0x1f462: return 431; case 0x1f463: return 432; case 0x1f464: return 433; case 0x1f465: return 434; case 0x1f466: return 435; case 0x1f467: return 436; case 0x1f468: return 437; case 0x1f469: return 438; case 0x1f46a: return 439; case 0x1f46b: return 440; case 0x1f46c: return 441; case 0x1f46d: return 442; case 0x1f46e: return 443; case 0x1f46f: return 444; case 0x1f470: return 445; case 0x1f471: return 446; case 0x1f472: return 447; case 0x1f473: return 448; case 0x1f474: return 449; case 0x1f475: return 450; case 0x1f476: return 451; case 0x1f477: return 452; case 0x1f478: return 453; case 0x1f479: return 454; case 0x1f47a: return 455; case 0x1f47b: return 456; case 0x1f47c: return 457; case 0x1f47d: return 458; case 0x1f47e: return 459; case 0x1f47f: return 460; case 0x1f480: return 461; case 0x1f481: return 462; case 0x1f482: return 463; case 0x1f483: return 464; case 0x1f484: return 465; case 0x1f485: return 466; case 0x1f486: return 467; case 0x1f487: return 468; case 0x1f488: return 469; case 0x1f489: return 470; case 0x1f48a: return 471; case 0x1f48b: return 472; case 0x1f48c: return 473; case 0x1f48d: return 474; case 0x1f48e: return 475; case 0x1f48f: return 476; case 0x1f490: return 477; case 0x1f491: return 478; case 0x1f492: return 479; case 0x1f493: return 480; case 0x1f494: return 481; case 0x1f495: return 482; case 0x1f496: return 483; case 0x1f497: return 484; case 0x1f498: return 485; case 0x1f499: return 486; case 0x1f49a: return 487; case 0x1f49b: return 488; case 0x1f49c: return 489; case 0x1f49d: return 490; case 0x1f49e: return 491; case 0x1f49f: return 492; case 0x1f4a0: return 493; case 0x1f4a1: return 494; case 0x1f4a2: return 495; case 0x1f4a3: return 496; case 0x1f4a4: return 497; case 0x1f4a5: return 498; case 0x1f4a6: return 499; case 0x1f4a7: return 500; case 0x1f4a8: return 501; case 0x1f4a9: return 502; case 0x1f4aa: return 503; case 0x1f4ab: return 504; case 0x1f4ac: return 505; case 0x1f4ad: return 506; case 0x1f4ae: return 507; case 0x1f4af: return 508; case 0x1f4b0: return 509; case 0x1f4b1: return 510; case 0x1f4b2: return 511; case 0x1f4b3: return 512; case 0x1f4b4: return 513; case 0x1f4b5: return 514; case 0x1f4b6: return 515; case 0x1f4b7: return 516; case 0x1f4b8: return 517; case 0x1f4b9: return 518; case 0x1f4ba: return 519; case 0x1f4bb: return 520; case 0x1f4bc: return 521; case 0x1f4bd: return 522; case 0x1f4be: return 523; case 0x1f4bf: return 524; case 0x1f4c0: return 525; case 0x1f4c1: return 526; case 0x1f4c2: return 527; case 0x1f4c3: return 528; case 0x1f4c4: return 529; case 0x1f4c5: return 530; case 0x1f4c6: return 531; case 0x1f4c7: return 532; case 0x1f4c8: return 533; case 0x1f4c9: return 534; case 0x1f4ca: return 535; case 0x1f4cb: return 536; case 0x1f4cc: return 537; case 0x1f4cd: return 538; case 0x1f4ce: return 539; case 0x1f4cf: return 540; case 0x1f4d0: return 541; case 0x1f4d1: return 542; case 0x1f4d2: return 543; case 0x1f4d3: return 544; case 0x1f4d4: return 545; case 0x1f4d5: return 546; case 0x1f4d6: return 547; case 0x1f4d7: return 548; case 0x1f4d8: return 549; case 0x1f4d9: return 550; case 0x1f4da: return 551; case 0x1f4db: return 552; case 0x1f4dc: return 553; case 0x1f4dd: return 554; case 0x1f4de: return 555; case 0x1f4df: return 556; case 0x1f4e0: return 557; case 0x1f4e1: return 558; case 0x1f4e2: return 559; case 0x1f4e3: return 560; case 0x1f4e4: return 561; case 0x1f4e5: return 562; case 0x1f4e6: return 563; case 0x1f4e7: return 564; case 0x1f4e8: return 565; case 0x1f4e9: return 566; case 0x1f4ea: return 567; case 0x1f4eb: return 568; case 0x1f4ec: return 569; case 0x1f4ed: return 570; case 0x1f4ee: return 571; case 0x1f4ef: return 572; case 0x1f4f0: return 573; case 0x1f4f1: return 574; case 0x1f4f2: return 575; case 0x1f4f3: return 576; case 0x1f4f4: return 577; case 0x1f4f5: return 578; case 0x1f4f6: return 579; case 0x1f4f7: return 580; case 0x1f4f9: return 581; case 0x1f4fa: return 582; case 0x1f4fb: return 583; case 0x1f4fc: return 584; case 0x1f500: return 585; case 0x1f501: return 586; case 0x1f502: return 587; case 0x1f503: return 588; case 0x1f504: return 589; case 0x1f505: return 590; case 0x1f506: return 591; case 0x1f507: return 592; case 0x1f508: return 593; case 0x1f509: return 594; case 0x1f50a: return 595; case 0x1f50b: return 596; case 0x1f50c: return 597; case 0x1f50d: return 598; case 0x1f50e: return 599; case 0x1f50f: return 600; case 0x1f510: return 601; case 0x1f511: return 602; case 0x1f512: return 603; case 0x1f513: return 604; case 0x1f514: return 605; case 0x1f515: return 606; case 0x1f516: return 607; case 0x1f517: return 608; case 0x1f518: return 609; case 0x1f519: return 610; case 0x1f51a: return 611; case 0x1f51b: return 612; case 0x1f51c: return 613; case 0x1f51d: return 614; case 0x1f51e: return 615; case 0x1f51f: return 616; case 0x1f520: return 617; case 0x1f521: return 618; case 0x1f522: return 619; case 0x1f523: return 620; case 0x1f524: return 621; case 0x1f525: return 622; case 0x1f526: return 623; case 0x1f527: return 624; case 0x1f528: return 625; case 0x1f529: return 626; case 0x1f52a: return 627; case 0x1f52b: return 628; case 0x1f52c: return 629; case 0x1f52d: return 630; case 0x1f52e: return 631; case 0x1f52f: return 632; case 0x1f530: return 633; case 0x1f531: return 634; case 0x1f532: return 635; case 0x1f533: return 636; case 0x1f534: return 637; case 0x1f535: return 638; case 0x1f536: return 639; case 0x1f537: return 640; case 0x1f538: return 641; case 0x1f539: return 642; case 0x1f53a: return 643; case 0x1f53b: return 644; case 0x1f53c: return 645; case 0x1f53d: return 646; case 0x1f550: return 647; case 0x1f551: return 648; case 0x1f552: return 649; case 0x1f553: return 650; case 0x1f554: return 651; case 0x1f555: return 652; case 0x1f556: return 653; case 0x1f557: return 654; case 0x1f558: return 655; case 0x1f559: return 656; case 0x1f55a: return 657; case 0x1f55b: return 658; case 0x1f55c: return 659; case 0x1f55d: return 660; case 0x1f55e: return 661; case 0x1f55f: return 662; case 0x1f560: return 663; case 0x1f561: return 664; case 0x1f562: return 665; case 0x1f563: return 666; case 0x1f564: return 667; case 0x1f565: return 668; case 0x1f566: return 669; case 0x1f567: return 670; case 0x1f5fb: return 671; case 0x1f5fc: return 672; case 0x1f5fd: return 673; case 0x1f5fe: return 674; case 0x1f5ff: return 675; case 0x1f600: return 676; case 0x1f601: return 677; case 0x1f602: return 678; case 0x1f603: return 679; case 0x1f604: return 680; case 0x1f605: return 681; case 0x1f606: return 682; case 0x1f607: return 683; case 0x1f608: return 684; case 0x1f609: return 685; case 0x1f60a: return 686; case 0x1f60b: return 687; case 0x1f60c: return 688; case 0x1f60d: return 689; case 0x1f60e: return 690; case 0x1f60f: return 691; case 0x1f610: return 692; case 0x1f611: return 693; case 0x1f612: return 694; case 0x1f613: return 695; case 0x1f614: return 696; case 0x1f615: return 697; case 0x1f616: return 698; case 0x1f617: return 699; case 0x1f618: return 700; case 0x1f619: return 701; case 0x1f61a: return 702; case 0x1f61b: return 703; case 0x1f61c: return 704; case 0x1f61d: return 705; case 0x1f61e: return 706; case 0x1f61f: return 707; case 0x1f620: return 708; case 0x1f621: return 709; case 0x1f622: return 710; case 0x1f623: return 711; case 0x1f624: return 712; case 0x1f625: return 713; case 0x1f626: return 714; case 0x1f627: return 715; case 0x1f628: return 716; case 0x1f629: return 717; case 0x1f62a: return 718; case 0x1f62b: return 719; case 0x1f62c: return 720; case 0x1f62d: return 721; case 0x1f62e: return 722; case 0x1f62f: return 723; case 0x1f630: return 724; case 0x1f631: return 725; case 0x1f632: return 726; case 0x1f633: return 727; case 0x1f634: return 728; case 0x1f635: return 729; case 0x1f636: return 730; case 0x1f637: return 731; case 0x1f638: return 732; case 0x1f639: return 733; case 0x1f63a: return 734; case 0x1f63b: return 735; case 0x1f63c: return 736; case 0x1f63d: return 737; case 0x1f63e: return 738; case 0x1f63f: return 739; case 0x1f640: return 740; case 0x1f645: return 741; case 0x1f646: return 742; case 0x1f647: return 743; case 0x1f648: return 744; case 0x1f649: return 745; case 0x1f64a: return 746; case 0x1f64b: return 747; case 0x1f64c: return 748; case 0x1f64d: return 749; case 0x1f64e: return 750; case 0x1f64f: return 751; case 0x1f680: return 752; case 0x1f681: return 753; case 0x1f682: return 754; case 0x1f683: return 755; case 0x1f684: return 756; case 0x1f685: return 757; case 0x1f686: return 758; case 0x1f687: return 759; case 0x1f688: return 760; case 0x1f689: return 761; case 0x1f68a: return 762; case 0x1f68b: return 763; case 0x1f68c: return 764; case 0x1f68d: return 765; case 0x1f68e: return 766; case 0x1f68f: return 767; case 0x1f690: return 768; case 0x1f691: return 769; case 0x1f692: return 770; case 0x1f693: return 771; case 0x1f694: return 772; case 0x1f695: return 773; case 0x1f696: return 774; case 0x1f697: return 775; case 0x1f698: return 776; case 0x1f699: return 777; case 0x1f69a: return 778; case 0x1f69b: return 779; case 0x1f69c: return 780; case 0x1f69d: return 781; case 0x1f69e: return 782; case 0x1f69f: return 783; case 0x1f6a0: return 784; case 0x1f6a1: return 785; case 0x1f6a2: return 786; case 0x1f6a3: return 787; case 0x1f6a4: return 788; case 0x1f6a5: return 789; case 0x1f6a6: return 790; case 0x1f6a7: return 791; case 0x1f6a8: return 792; case 0x1f6a9: return 793; case 0x1f6aa: return 794; case 0x1f6ab: return 795; case 0x1f6ac: return 796; case 0x1f6ad: return 797; case 0x1f6ae: return 798; case 0x1f6af: return 799; case 0x1f6b0: return 800; case 0x1f6b1: return 801; case 0x1f6b2: return 802; case 0x1f6b3: return 803; case 0x1f6b4: return 804; case 0x1f6b5: return 805; case 0x1f6b6: return 806; case 0x1f6b7: return 807; case 0x1f6b8: return 808; case 0x1f6b9: return 809; case 0x1f6ba: return 810; case 0x1f6bb: return 811; case 0x1f6bc: return 812; case 0x1f6bd: return 813; case 0x1f6be: return 814; case 0x1f6bf: return 815; case 0x1f6c0: return 816; case 0x1f6c1: return 817; case 0x1f6c2: return 818; case 0x1f6c3: return 819; case 0x1f6c4: return 820; case 0x1f6c5: return 821; case 0x23: return (Len > 1 && Str[1] == 0x20e3) ? 822 : -1; case 0x30: return (Len > 1 && Str[1] == 0x20e3) ? 823 : -1; case 0x31: return (Len > 1 && Str[1] == 0x20e3) ? 824 : -1; case 0x32: return (Len > 1 && Str[1] == 0x20e3) ? 825 : -1; case 0x33: return (Len > 1 && Str[1] == 0x20e3) ? 826 : -1; case 0x34: return (Len > 1 && Str[1] == 0x20e3) ? 827 : -1; case 0x35: return (Len > 1 && Str[1] == 0x20e3) ? 828 : -1; case 0x36: return (Len > 1 && Str[1] == 0x20e3) ? 829 : -1; case 0x37: return (Len > 1 && Str[1] == 0x20e3) ? 830 : -1; case 0x38: return (Len > 1 && Str[1] == 0x20e3) ? 831 : -1; case 0x39: return (Len > 1 && Str[1] == 0x20e3) ? 832 : -1; case 0x1f1e8: return (Len > 1 && Str[1] == 0x1f1f3) ? 833 : -1; case 0x1f1e9: return (Len > 1 && Str[1] == 0x1f1ea) ? 834 : -1; case 0x1f1ea: return (Len > 1 && Str[1] == 0x1f1f8) ? 835 : -1; case 0x1f1eb: return (Len > 1 && Str[1] == 0x1f1f7) ? 836 : -1; case 0x1f1ec: return (Len > 1 && Str[1] == 0x1f1e7) ? 837 : -1; case 0x1f1ee: return (Len > 1 && Str[1] == 0x1f1f9) ? 838 : -1; case 0x1f1ef: return (Len > 1 && Str[1] == 0x1f1f5) ? 839 : -1; case 0x1f1f0: return (Len > 1 && Str[1] == 0x1f1f7) ? 840 : -1; case 0x1f1f7: return (Len > 1 && Str[1] == 0x1f1fa) ? 841 : -1; case 0x1f1fa: return (Len > 1 && Str[1] == 0x1f1f8) ? 842 : -1; default: break; } return -1; } diff --git a/src/common/Text/Emoji/EmojiTools.cpp b/src/common/Text/Emoji/EmojiTools.cpp --- a/src/common/Text/Emoji/EmojiTools.cpp +++ b/src/common/Text/Emoji/EmojiTools.cpp @@ -1,254 +1,254 @@ // http://code.iamcal.com/php/emoji/ #include #include "Lgi.h" #include "Emoji.h" #include "GVariant.h" #include "GDocView.h" #ifdef WIN32 -typedef uint32 WChar; +typedef uint32_t WChar; #else typedef wchar_t WChar; #endif bool HasEmoji(char *Txt) { if (!Txt) return false; GUtf8Ptr p(Txt); WChar u; while ((u = p++)) { - int IcoIdx = EmojiToIconIndex((uint32*)&u, 1); + int IcoIdx = EmojiToIconIndex((uint32_t*)&u, 1); if (IcoIdx >= 0) return true; } return false; } -bool HasEmoji(uint32 *Txt) +bool HasEmoji(uint32_t *Txt) { if (!Txt) return false; for (uint32 *s = Txt; *s; s++) { int IcoIdx = EmojiToIconIndex(s, 2); if (IcoIdx >= 0) return true; } return false; } /* #ifdef WIN32 #define snwprintf _snwprintf #else #include */ template ssize_t my_snwprintf(T *ptr, int ptr_size, const char16 *fmt, ...) { T *start = ptr; T *end = ptr + ptr_size - 1; va_list ap; va_start(ap, fmt); // int a = 1; while (ptr < end && *fmt) { if (*fmt == '%') { int len = -1; fmt++; if (*fmt == '.') fmt++; if (*fmt == '*') { len = va_arg(ap, int); fmt++; } if (*fmt == 's') { T *in = va_arg(ap, T*); if (len >= 0) while (ptr < end && in && len-- > 0) *ptr++ = *in++; else while (ptr < end && in && *in) *ptr++ = *in++; } else if (*fmt == 'S') { char *in = va_arg(ap, char*); if (len >= 0) while (ptr < end && in && len-- > 0) *ptr++ = *in++; else while (ptr < end && in && *in) *ptr++ = *in++; } else if (*fmt == 'i') { char tmp[32]; int n = 0; int in = va_arg(ap, int); if (in) while (in) { tmp[n++] = '0' + (in % 10); in /= 10; } else tmp[n++] = '0'; while (ptr < end && n > 0) *ptr++ = tmp[--n]; } else LgiAssert(!"Unknown format specifier"); fmt++; } else *ptr++ = *fmt++; } *ptr++ = 0; va_end(ap); return ptr - start - 1; } // #endif #define BUF_SIZE 256 const char16 *h1 = L"\n"; const char16 *h2 = L"\n"; const char16 *newline = L"
\n"; const char16 *mail_link = L"%.*s"; const char16 *anchor = L"%.*s"; const char16 *img = L""; struct EmojiMemQ : GMemQueue { EmojiMemQ() : GMemQueue(1024) { } #ifdef WINDOWS int WriteWide(const char16 *s, ssize_t bytes) { - GAutoPtr c((uint32*)LgiNewConvertCp("utf-32", s, LGI_WideCharset, bytes)); + GAutoPtr c((uint32_t*)LgiNewConvertCp("utf-32", s, LGI_WideCharset, bytes)); int len = Strlen(c.Get()); - return GMemQueue::Write(c, len * sizeof(uint32)); + return GMemQueue::Write(c, len * sizeof(uint32_t)); } #endif ssize_t WriteWide(const WChar *s, ssize_t bytes) { return GMemQueue::Write(s, bytes); } }; -GAutoWString TextToEmoji(uint32 *Txt, bool IsHtml) +GAutoWString TextToEmoji(uint32_t *Txt, bool IsHtml) { EmojiMemQ p; GArray Links; int Lnk = 0; ssize_t Ch; WChar Buf[BUF_SIZE]; char EmojiPng[MAX_PATH]; #ifdef MAC LgiGetExeFile(EmojiPng, sizeof(EmojiPng)); LgiMakePath(EmojiPng, sizeof(EmojiPng), EmojiPng, "Contents/Resources/EmojiMap.png"); #else LGetSystemPath(LSP_APP_INSTALL, EmojiPng, sizeof(EmojiPng)); LgiMakePath(EmojiPng, sizeof(EmojiPng), EmojiPng, "resources/EmojiMap.png"); #endif LgiAssert(sizeof(WChar) == sizeof(uint32)); if (!IsHtml) { LgiDetectLinks(Links, Txt); Ch = my_snwprintf(Buf, BUF_SIZE, h1); if (Ch > 0) p.Write(Buf, (int) (Ch * sizeof(*Buf))); } WChar *Start = (WChar*)Txt; WChar *s = (WChar*)Txt; for (; *s; s++) { if (Lnk < (int)Links.Length() && s - (WChar*)Txt == Links[Lnk].Start) { GLinkInfo &l = Links[Lnk]; // Start of embedded link, convert into if (s > Start) p.Write(Start, (int) ((s - Start) * sizeof(*s))); if (l.Email) Ch = my_snwprintf(Buf, BUF_SIZE, mail_link, l.Len, s, l.Len, s); else Ch = my_snwprintf(Buf, BUF_SIZE, anchor, l.Len, s, l.Len, s); if (Ch > 0) p.Write(Buf, (int) (Ch * sizeof(*Buf))); Start = s + l.Len; Lnk++; } else if (!IsHtml && *s == '\n') { // Eol if (s > Start) p.Write(Start, (int) ((s - Start) * sizeof(*s))); Ch = my_snwprintf(Buf, BUF_SIZE, newline); if (Ch > 0) p.Write(Buf, (int) (Ch * sizeof(*Buf))); Start = s + 1; } else { int IcoIdx = EmojiToIconIndex((uint32*)s, 2); if (IcoIdx >= 0) { // Emoji character, convert to if (s > Start) p.Write(Start, (int) ((s - Start) * sizeof(*s))); int XChar = IcoIdx % EMOJI_GROUP_X; int YChar = IcoIdx / EMOJI_GROUP_X; GRect rc; rc.ZOff(EMOJI_CELL_SIZE - 1, EMOJI_CELL_SIZE - 1); rc.Offset(XChar * EMOJI_CELL_SIZE, YChar * EMOJI_CELL_SIZE); Ch = my_snwprintf(Buf, BUF_SIZE, img, EmojiPng, rc.GetStr()); if (Ch > 0) p.Write(Buf, (int) (Ch * sizeof(*Buf))); Start = s + 1; if (*Start == 0xfe0f) { s++; Start++; } } } } if (s > Start) p.Write(Start, (int) ((s - Start) * sizeof(*s))); if (!IsHtml) { Ch = my_snwprintf(Buf, BUF_SIZE, h2); if (Ch > 0) p.Write(Buf, (int) (Ch * sizeof(*Buf))); } GAutoPtr WideVer( (WChar*)p.New(sizeof(*s)) ); GAutoWString Final( (char16*)LgiNewConvertCp(LGI_WideCharset, WideVer, "utf-32") ); return Final; } diff --git a/src/common/Text/vCard-vCal.cpp b/src/common/Text/vCard-vCal.cpp --- a/src/common/Text/vCard-vCal.cpp +++ b/src/common/Text/vCard-vCal.cpp @@ -1,1402 +1,1402 @@ #include #include #include #include "Lgi.h" #include "vCard-vCal.h" #include "GToken.h" #include "ScribeDefs.h" #define Push(s) Write(s, (int)strlen(s)) #define ClearFields() \ Field.Empty(); \ Params.Empty(); \ Data.Empty() #define IsType(str) (Params.Find(str) != 0) #if 1 bool IsVar(char *field, const char *s) { if (!s) return false; char *dot = strchr(field, '.'); if (dot) return _stricmp(dot + 1, s) == 0; return _stricmp(field, s) == 0; } #else #define IsVar(field, str) (field != 0 && _stricmp(field, str) == 0) #endif char *DeEscape(char *s, bool QuotedPrintable) { if (!s) return 0; char *i = s; char *o = s; while (*i) { if (*i == '\\') { i++; switch (*i) { case 'n': case 'N': *o++ = '\n'; break; case ',': case ';': case ':': *o++ = *i; break; default: *o++ = '\\'; i--; break; } } else if (QuotedPrintable && *i == '=' && i[1] && i[2]) { i++; char h[3] = { i[0], i[1], 0}; *o++ = htoi(h); i++; } else { *o++ = *i; } i++; } *o = 0; return s; } ///////////////////////////////////////////////////////////// // General IO class class VIoPriv { public: GStringPipe Buf; }; VIo::VIo() : d(new VIoPriv) { } VIo::~VIo() { DeleteObj(d); } bool VIo::ParseDate(LDateTime &Out, char *In) { bool Status = false; if (In) { Out.SetTimeZone(0, false); GToken v(In, "T"); if (v.Length() > 0) { char *d = v[0]; if (d && strlen(d) == 8) { char Year[5] = {d[0], d[1], d[2], d[3], 0}; char Month[3] = {d[4], d[5], 0}; char Day[3] = {d[6], d[7], 0}; Out.Year(atoi(Year)); Out.Month(atoi(Month)); Out.Day(atoi(Day)); Status = true; } char *t = v[1]; if (t && strlen(t) >= 6) { char Hour[3] = {t[0], t[1], 0}; char Minute[3] = {t[2], t[3], 0}; char Second[3] = {t[4], t[5], 0}; Out.Hours(atoi(Hour)); Out.Minutes(atoi(Minute)); Out.Seconds(atoi(Second)); Status = true; } } } return Status; } bool VIo::ParseDuration(LDateTime &Out, int &Sign, char *In) { bool Status = false; if (In) { Sign = 1; if (*In == '-') { Sign = -1; In++; } if (toupper(*In++) == 'P' && toupper(*In++) == 'T') { while (*In) { int i = atoi(In); while (IsDigit(*In)) In++; switch (toupper(*In++)) { case 'W': { Out.Day(Out.Day() + (i * 7)); break; } case 'D': { Out.Day(Out.Day() + i); break; } case 'H': { Out.Hours(Out.Hours() + i); break; } case 'M': { Out.Minutes(Out.Minutes() + i); break; } case 'S': { Out.Seconds(Out.Seconds() + i); break; } } } Status = true; } } return Status; } void VIo::Fold(GStreamI &o, char *i, int pre_chars) { int x = pre_chars; for (char *s=i; s && *s;) { if (x >= 74) { // wrapping o.Write(i, (int)(s-i)); o.Write((char*)"\r\n\t", 3); x = 0; i = s; } - else if (*s == '=' || ((((uint8)*s) & 0x80) != 0)) + else if (*s == '=' || ((((uint8_t)*s) & 0x80) != 0)) { // quoted printable o.Write(i, (int)(s-i)); - GStreamPrint(&o, "=%02.2x", (uint8)*s); + GStreamPrint(&o, "=%02.2x", (uint8_t)*s); x += 3; i = ++s; } else if (*s == '\n') { // new line o.Write(i, (int)(s-i)); o.Write((char*)"\\n", 2); x += 2; i = ++s; } else if (*s == '\r') { o.Write(i, (int)(s-i)); i = ++s; } else { s++; x++; } } o.Write(i, (int)strlen(i)); } char *VIo::Unfold(char *In) { if (In) { GStringPipe p(256); for (char *i=In; i && *i; i++) { if (*i == '\n') { if (i[1] && strchr(" \t", i[1])) { i++; } else { p.Write(i, 1); } } else { p.Write(i, 1); } } return p.NewStr(); } return 0; } char *VIo::UnMultiLine(char *In) { if (In) { GStringPipe p; char *n; for (char *i=In; i && *i; i=n) { n = stristr(i, "\\n"); if (n) { p.Write(i, (int)(n-i)); p.Push((char*)"\n"); n += 2; } else { p.Push(i); } } return p.NewStr(); } return 0; } ///////////////////////////////////////////////////////////// // VCard class bool VCard::Import(GDataPropI *c, GStreamI *s) { bool Status = false; if (!c || !s) return false; GString Field; ParamArray Params; GString Data; ssize_t PrefEmail = -1; GArray Emails; while (ReadField(*s, Field, &Params, Data)) { if (_stricmp(Field, "begin") == 0 && _stricmp(Data, "vcard") == 0) { while (ReadField(*s, Field, &Params, Data)) { if (_stricmp(Field, "end") == 0 && _stricmp(Data, "vcard") == 0) goto ExitLoop; if (IsVar(Field, "n")) { GToken Name(Data, ";", false); char *First = Name[1]; char *Last = Name[0]; char *Title = Name[3]; if (First) { c->SetStr(FIELD_FIRST_NAME, First); Status = true; } if (Last) { c->SetStr(FIELD_LAST_NAME, Last); Status = true; } if (Title) { c->SetStr(FIELD_TITLE, Title); Status = true; } } else if (IsVar(Field, "nickname")) { c->SetStr(FIELD_NICK, Data); } else if (IsVar(Field, "tel")) { GToken Phone(Data, ";", false); - for (uint32 p=0; pSetStr(FIELD_WORK_MOBILE, Phone[p]); } else { c->SetStr(FIELD_HOME_MOBILE, Phone[p]); } } else if (IsType("fax")) { if (IsType("Work")) { c->SetStr(FIELD_WORK_FAX, Phone[p]); } else { c->SetStr(FIELD_HOME_FAX, Phone[p]); } } else { if (IsType("Work")) { c->SetStr(FIELD_WORK_PHONE, Phone[p]); } else { c->SetStr(FIELD_HOME_PHONE, Phone[p]); } } } } else if (IsVar(Field, "email")) { if (IsType("pref")) { PrefEmail = Emails.Length(); } Emails.Add(NewStr(Data)); } else if (IsVar(Field, "org")) { GToken Org(Data, ";", false); if (Org[0]) c->SetStr(FIELD_COMPANY, Org[0]); } else if (IsVar(Field, "adr")) { bool IsWork = IsType("work"); // bool IsHome = IsType("home"); GToken Addr(Data, ";", false); if (Addr[2]) { GToken A(Addr[2], "\r\n"); if (A.Length() > 1) { c->SetStr(IsWork ? FIELD_WORK_STREET : FIELD_HOME_STREET, A[0]); if (A[1]) c->SetStr(IsWork ? FIELD_WORK_SUBURB : FIELD_HOME_SUBURB, A[1]); if (A[2]) c->SetStr(IsWork ? FIELD_WORK_COUNTRY : FIELD_HOME_COUNTRY, A[2]); } else { c->SetStr(IsWork ? FIELD_WORK_STREET : FIELD_HOME_STREET, Addr[2]); } } if (Addr[3]) c->SetStr(IsWork ? FIELD_WORK_SUBURB : FIELD_HOME_SUBURB, Addr[3]); if (Addr[4]) c->SetStr(IsWork ? FIELD_WORK_STATE : FIELD_HOME_STATE, Addr[4]); if (Addr[5]) c->SetStr(IsWork ? FIELD_WORK_POSTCODE : FIELD_HOME_POSTCODE, Addr[5]); if (Addr[6]) c->SetStr(IsWork ? FIELD_WORK_COUNTRY : FIELD_HOME_COUNTRY, Addr[6]); } else if (IsVar(Field, "note")) { c->SetStr(FIELD_NOTE, Data); } else if (IsVar(Field, "uid")) { GToken n(Data, ";", false); c->SetStr(FIELD_UID, n[0]); } else if (IsVar(Field, "x-perm")) { int Perms = atoi(Data); c->SetInt(FIELD_PERMISSIONS, Perms); } else if (IsVar(Field, "url")) { bool IsWork = IsType("work"); bool IsHome = IsType("home"); if (IsWork) { c->SetStr(FIELD_HOME_WEBPAGE, Data); } else if (IsHome) { c->SetStr(FIELD_WORK_WEBPAGE, Data); } } else if (IsVar(Field, "nickname")) { c->SetStr(FIELD_NICK, Data); } else if (IsVar(Field, "photo")) { size_t B64Len = strlen(Data); ssize_t BinLen = BufferLen_64ToBin(B64Len); - GAutoPtr Bin(new uint8[BinLen]); + GAutoPtr Bin(new uint8_t[BinLen]); if (Bin) { ssize_t Bytes = ConvertBase64ToBinary(Bin.Get(), BinLen, Data, B64Len); GVariant v; if (v.SetBinary(Bytes, Bin.Release(), true)) { c->SetVar(FIELD_CONTACT_IMAGE, &v); } } } } } } ExitLoop: if (Emails.Length()) { if (PrefEmail < 0) PrefEmail = 0; c->SetStr(FIELD_EMAIL, Emails[PrefEmail]); Emails.DeleteAt(PrefEmail); if (Emails.Length()) { GStringPipe p; - for (uint32 i=0; iSetStr(FIELD_ALT_EMAIL, v); DeleteArray(v); } } } ClearFields(); return Status; } bool VIo::ReadField(GStreamI &s, GString &Name, ParamArray *Params, GString &Data) { bool Status = false; ParamArray LocalParams; Name.Empty(); Data.Empty(); if (Params) Params->Empty(); else Params = &LocalParams; char Temp[256]; GArray p; bool Done = false; while (!Done) { bool EatNext = false; ReadNextLine: Temp[0] = 0; int64 r = d->Buf.Pop(Temp, sizeof(Temp)); if (r <= 0) { // Try reading more data... r = s.Read(Temp, sizeof(Temp)); if (r > 0) d->Buf.Write(Temp, (int)r); else break; r = d->Buf.Pop(Temp, sizeof(Temp)); } if (r <= 0) break; // Unfold for (char *c = Temp; *c; c++) { if (*c == '\r') { // do nothing } else if (*c == '\n') { char Next; r = d->Buf.Peek((uchar*) &Next, 1); if (r == 0) { r = s.Read(Temp, sizeof(Temp)); if (r <= 0) break; d->Buf.Write(Temp, (int)r); r = d->Buf.Peek((uchar*) &Next, 1); } if (r == 1) { if (Next == ' ' || Next == '\t') { // Wrapped, do nothing EatNext = true; goto ReadNextLine; } else { Done = true; break; } } else { break; } } else if (EatNext) { EatNext = false; } else { p.Add(*c); } } } p.Add(0); char *f = p.Length() > 1 ? &p[0] : 0; if (f) { char *e = strchr(f, ':'); if (e) { *e++ = 0; GToken t(f, ";"); if (t.Length() > 0) { Name = t[0]; for (uint32 i=1; iFind("charset"); if (Charset) { GAutoString u((char*)LgiNewConvertCp("utf-8", e, Charset)); Data = u.Get(); } else { Data = e; } Status = Name.Length() > 0; } return Status; } void VIo::WriteField(GStreamI &s, const char *Name, ParamArray *Params, char *Data) { if (Name && Data) { int64 Size = s.GetSize(); GStreamPrint(&s, "%s", Name); if (Params) { - for (uint32 i=0; iLength(); i++) + for (uint32_t i=0; iLength(); i++) { Parameter &p = (*Params)[i]; GStreamPrint(&s, "%s%s=%s", i?"":";", p.Field.Get(), p.Value.Get()); } } bool Is8Bit = false; bool HasEq = false; - for (uint8 *c = (uint8*)Data; *c; c++) + for (uint8_t *c = (uint8_t*)Data; *c; c++) { if ((*c & 0x80) != 0) { Is8Bit = true; } else if (*c == '=') { HasEq = true; } } if (Is8Bit || HasEq) { if (Is8Bit) s.Write((char*)";charset=utf-8", 14); s.Write((char*)";encoding=quoted-printable", 26); } s.Write((char*)":", 1); Fold(s, Data, (int) (s.GetSize() - Size)); s.Write((char*)"\r\n", 2); } } bool VCard::Export(GDataPropI *c, GStreamI *o) { if (!c || !o) return false; bool Status = true; char s[512]; const char *Empty = ""; o->Push("begin:vcard\r\n"); o->Push("version:3.0\r\n"); char *First = 0, *Last = 0, *Title = 0; First = c->GetStr(FIELD_FIRST_NAME); Last = c->GetStr(FIELD_LAST_NAME); Title = c->GetStr(FIELD_TITLE); if (First || Last) { sprintf_s(s, sizeof(s), "%s;%s;%s", Last?Last:Empty, First?First:Empty, Title?Title:Empty); WriteField(*o, "n", 0, s); } #define OutputTypedField(Field, Name, Type) \ { char *str = 0; \ if ((str = c->GetStr(Name))) \ { \ WriteField(*o, Field, Type, str); \ } } #define OutputField(Field, Name) \ { char *str = 0; \ if ((str = c->GetStr(Name))) \ { \ WriteField(*o, Field, 0, str); \ } } ParamArray Work("Work"), WorkCell("Work,Cell"), WorkFax("Work,Fax"); ParamArray Home("Home"), HomeCell("Home,Cell"), HomeFax("Home,Fax"); ParamArray InetPref("internet,pref"), Inet("internet"); OutputTypedField("tel", FIELD_WORK_PHONE, &Work); OutputTypedField("tel", FIELD_WORK_MOBILE, &WorkCell); OutputTypedField("tel", FIELD_WORK_FAX, &WorkFax); OutputTypedField("tel", FIELD_HOME_PHONE, &Home); OutputTypedField("tel", FIELD_HOME_MOBILE, &HomeCell); OutputTypedField("tel", FIELD_HOME_FAX, &HomeFax); OutputField("org", FIELD_COMPANY); OutputTypedField("email", FIELD_EMAIL, &InetPref); char *Alt; if ((Alt = c->GetStr(FIELD_ALT_EMAIL))) { GToken t(Alt, ","); for (unsigned i=0; iGetStr(FIELD_UID))) { GStreamPrint(o, "UID:%s\r\n", Uid); } char *Street, *Suburb, *PostCode, *State, *Country; Street = Suburb = PostCode = State = Country = 0; Street = c->GetStr(FIELD_HOME_STREET); Suburb = c->GetStr(FIELD_HOME_SUBURB); PostCode = c->GetStr(FIELD_HOME_POSTCODE); State = c->GetStr(FIELD_HOME_STATE); Country = c->GetStr(FIELD_HOME_COUNTRY); if (Street || Suburb || PostCode || State || Country) { sprintf_s(s, sizeof(s), ";;%s;%s;%s;%s;%s", Street?Street:Empty, Suburb?Suburb:Empty, State?State:Empty, PostCode?PostCode:Empty, Country?Country:Empty); WriteField(*o, "adr", &Home, s); } Street = Suburb = PostCode = State = Country = 0; Street = c->GetStr(FIELD_WORK_STREET); Suburb = c->GetStr(FIELD_WORK_SUBURB); PostCode = c->GetStr(FIELD_WORK_POSTCODE); State = c->GetStr(FIELD_WORK_STATE); Country = c->GetStr(FIELD_WORK_COUNTRY); if (Street || Suburb || PostCode || State || Country) { sprintf_s(s, sizeof(s), ";;%s;%s;%s;%s;%s", Street?Street:Empty, Suburb?Suburb:Empty, State?State:Empty, PostCode?PostCode:Empty, Country?Country:Empty); WriteField(*o, "adr", &Work, s); } // OutputField("X-Perm", FIELD_PERMISSIONS); char *Url; if ((Url = c->GetStr(FIELD_HOME_WEBPAGE))) { WriteField(*o, "url", &Home, Url); } if ((Url = c->GetStr(FIELD_WORK_WEBPAGE))) { WriteField(*o, "url", &Work, Url); } char *Nick; if ((Nick = c->GetStr(FIELD_NICK))) { WriteField(*o, "nickname", 0, Nick); } char *Note; if ((Note = c->GetStr(FIELD_NOTE))) { WriteField(*o, "note", 0, Note); } GVariant *Photo = c->GetVar(FIELD_CONTACT_IMAGE); if (Photo && Photo->Type == GV_BINARY) { ssize_t B64Len = BufferLen_BinTo64(Photo->Value.Binary.Length); GAutoPtr B64Buf(new char[B64Len]); if (B64Buf) { ssize_t Bytes = ConvertBinaryToBase64(B64Buf, B64Len, (uchar*)Photo->Value.Binary.Data, Photo->Value.Binary.Length); if (Bytes > 0) { GStreamPrint(o, "photo;type=jpeg;encoding=base64:\r\n"); int LineChar = 76; for (int i=0; i LineChar ? LineChar : Remain; o->Write(" ", 1); o->Write(B64Buf + i, Wr); o->Write("\r\n", 2); i += Wr; } o->Write("\r\n", 2); } } } o->Push("end:vcard\r\n"); return Status; } ///////////////////////////////////////////////////////////// // VCal class int StringToWeekDay(const char *s) { const char *days[] = {"SU","MO","TU","WE","TH","FR","SA"}; for (unsigned i=0; i TzInfos; TimeZoneInfo *TzInfo = NULL; bool IsNormalTz = false, IsDaylightTz = false; while (ReadField(*In, Field, &Params, Data)) { if (!_stricmp(Field, "begin")) { if (_stricmp(Data, "vevent") == 0 || _stricmp(Data, "vtodo") == 0) { IsEvent = true; SectionType = Data; int Type = _stricmp(Data, "vtodo") == 0 ? 1 : 0; c->SetInt(FIELD_CAL_TYPE, Type); } else if (!_stricmp(Data, "vtimezone")) { IsTimeZone = true; TzInfo = &TzInfos.New(); } else if (_stricmp(Data, "vcalendar") == 0) IsCal = true; else if (!_stricmp(Data, "standard")) IsNormalTz = true; else if (!_stricmp(Data, "daylight")) IsDaylightTz = true; } else if (_stricmp(Field, "end") == 0) { if (_stricmp(Data, "vcalendar") == 0) { IsCal = false; } else if (SectionType && _stricmp(Data, SectionType) == 0) { Status = true; IsEvent = false; break; // exit loop } else if (!_stricmp(Data, "vtimezone")) { IsTimeZone = false; TzInfo = NULL; } else if (!_stricmp(Data, "standard")) IsNormalTz = false; else if (!_stricmp(Data, "daylight")) IsDaylightTz = false; } else if (IsEvent) { if (IsVar(Field, "dtstart")) { ParseDate(EventStart, Data); StartTz = Params.Find("TZID"); } else if (IsVar(Field, "dtend")) { ParseDate(EventEnd, Data); EndTz = Params.Find("TZID"); } else if (IsVar(Field, "summary")) { c->SetStr(FIELD_CAL_SUBJECT, Data); } else if (IsVar(Field, "description")) { GAutoString Sum(UnMultiLine(Data)); if (Sum) c->SetStr(FIELD_CAL_NOTES, Sum); } else if (IsVar(Field, "location")) { c->SetStr(FIELD_CAL_LOCATION, Data); } else if (IsVar(Field, "uid")) { char *Uid = Data; c->SetStr(FIELD_UID, Uid); } else if (IsVar(Field, "x-showas")) { char *n = Data; if (_stricmp(n, "TENTATIVE") == 0) { c->SetInt(FIELD_CAL_SHOW_TIME_AS, 1); } else if (_stricmp(n, "BUSY") == 0) { c->SetInt(FIELD_CAL_SHOW_TIME_AS, 2); } else if (_stricmp(n, "OUT") == 0) { c->SetInt(FIELD_CAL_SHOW_TIME_AS, 3); } else { c->SetInt(FIELD_CAL_SHOW_TIME_AS, 0); } } else if (IsVar(Field, "attendee")) { char *e = stristr(Data, "mailto="); if (e) { e += 7; /* char *Name = Variables["CN"]; char *Role = Variables["Role"]; GDataPropI *a = c->CreateAttendee(); if (a) { a->SetStr(FIELD_ATTENDEE_NAME, Name); a->SetStr(FIELD_ATTENDEE_EMAIL, e); if (_stricmp(Role, "CHAIR") == 0) { a->SetStr(FIELD_ATTENDEE_ATTENDENCE, 1); } else if (_stricmp(Role, "REQ-PARTICIPANT") == 0) { a->SetStr(FIELD_ATTENDEE_ATTENDENCE, 2); } else if (_stricmp(Role, "OPT-PARTICIPANT") == 0) { a->SetStr(FIELD_ATTENDEE_ATTENDENCE, 3); } } */ } } } else if (IsTimeZone && TzInfo) { /* e.g.: TZID:Pacific Standard Time BEGIN:STANDARD DTSTART:16010101T020000 TZOFFSETFROM:-0700 TZOFFSETTO:-0800 RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=1SU;BYMONTH=11 END:STANDARD BEGIN:DAYLIGHT DTSTART:16010101T020000 TZOFFSETFROM:-0800 TZOFFSETTO:-0700 RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=2SU;BYMONTH=3 END:DAYLIGHT */ if (IsVar(Field, "TZID")) { TzInfo->Name = Data; } else if (IsNormalTz || IsDaylightTz) { TimeZoneSection &Sect = IsNormalTz ? TzInfo->Normal : TzInfo->Daylight; if (IsVar(Field, "DTSTART")) ParseDate(Sect.Start, Data); else if (IsVar(Field, "TZOFFSETFROM")) Sect.From = (int)Data.Int(); else if (IsVar(Field, "TZOFFSETTO")) Sect.To = (int)Data.Int(); else if (IsVar(Field, "RRULE")) Sect.Rule = Data; } } } if (StartTz || EndTz) { // Did we get a timezone defn? TimeZoneInfo *Match = NULL; for (unsigned i=0; iNormal, EventStart.Year()) && EvalRule(Dst, Match->Daylight, EventStart.Year())) { bool IsDst = false; if (Dst < Norm) { // DST in the middle of the year // |Jan----DST------Norm----Dec| if (EventStart >= Dst && EventStart <= Norm) IsDst = true; } else { // DST over the start and end of the year // |Jan----Norm------DST----Dec| if (EventStart >= Norm && EventStart <= Dst) IsDst = false; else IsDst = true; } #ifdef _DEBUG LgiTrace("Eval Start=%s, Norm=%s, Dst=%s, IsDst=%i\n", EventStart.Get().Get(), Norm.Get().Get(), Dst.Get().Get(), IsDst); #endif EffectiveTz = IsDst ? Match->Daylight.To : Match->Normal.To; GString sTz; sTz.Printf("%4.4i,%s", EffectiveTz, StartTz.Get()); c->SetStr(FIELD_CAL_TIMEZONE, sTz); } else goto StoreStringTz; } else { // Store whatever they gave us StoreStringTz: if (StartTz.Equals(EndTz)) c->SetStr(FIELD_CAL_TIMEZONE, StartTz); else if (StartTz.Get() && EndTz.Get()) { GString s; s.Printf("%s,%s", StartTz.Get(), EndTz.Get()); c->SetStr(FIELD_CAL_TIMEZONE, s); } else if (StartTz) c->SetStr(FIELD_CAL_TIMEZONE, StartTz); else if (EndTz) c->SetStr(FIELD_CAL_TIMEZONE, EndTz); if (StartTz) { EffectiveTz = atoi(StartTz); } } if (EffectiveTz) { // Convert the event to UTC int e = abs(EffectiveTz); int Mins = (((e / 100) * 60) + (e % 100)) * (EffectiveTz < 0 ? -1 : 1); #ifdef _DEBUG LgiTrace("%s:%i - EffectiveTz=%i, Mins=%i\n", _FL, EffectiveTz, Mins); #endif if (EventStart.IsValid()) { #ifdef _DEBUG LgiTrace("EventStart=%s\n", EventStart.Get().Get()); #endif EventStart.AddMinutes(-Mins); #ifdef _DEBUG LgiTrace("EventStart=%s\n", EventStart.Get().Get()); #endif } if (EventEnd.IsValid()) EventEnd.AddMinutes(-Mins); } } if (EventStart.IsValid()) c->SetDate(FIELD_CAL_START_UTC, &EventStart); if (EventEnd.IsValid()) c->SetDate(FIELD_CAL_END_UTC, &EventEnd); ClearFields(); return Status; } bool VCal::Export(GDataPropI *c, GStreamI *o) { if (!c || !o) return false; int64 Type = c->GetInt(FIELD_CAL_TYPE); char *TypeStr = Type == 1 ? (char*)"VTODO" : (char*)"VEVENT"; o->Push((char*)"BEGIN:VCALENDAR\r\n"); o->Push((char*)"VERSION:1.0\r\n"); if (c) { GStreamPrint(o, "BEGIN:%s\r\n", TypeStr); #define OutputStr(Field, Name) \ { \ char *_s = 0; \ if ((_s = c->GetStr(Field))) \ { \ WriteField(*o, Name, 0, _s); \ } \ } OutputStr(FIELD_CAL_SUBJECT, "SUMMARY"); OutputStr(FIELD_CAL_LOCATION, "LOCATION"); OutputStr(FIELD_CAL_NOTES, "DESCRIPTION"); int64 ShowAs; if ((ShowAs = c->GetInt(FIELD_CAL_SHOW_TIME_AS))) { switch (ShowAs) { default: case 0: { o->Push((char*)"X-SHOWAS:FREE\r\n"); break; } case 1: { o->Push((char*)"X-SHOWAS:TENTATIVE\r\n"); break; } case 2: { o->Push((char*)"X-SHOWAS:BUSY\r\n"); break; } case 3: { o->Push((char*)"X-SHOWAS:OUT\r\n"); break; } } } char *Uid; if ((Uid = c->GetStr(FIELD_UID))) { GStreamPrint(o, "UID:%s\r\n", Uid); } LDateTime *Dt; if ((Dt = c->GetDate(FIELD_CAL_START_UTC))) { Dt->ToUtc(); GStreamPrint(o, "DTSTART:%04.4i%02.2i%02.2iT%02.2i%02.2i%02.2i\r\n", Dt->Year(), Dt->Month(), Dt->Day(), Dt->Hours(), Dt->Minutes(), Dt->Seconds()); } if ((Dt = c->GetDate(FIELD_CAL_END_UTC))) { Dt->ToUtc(); GStreamPrint(o, "DTEND:%04.4i%02.2i%02.2iT%02.2i%02.2i%02.2i\r\n", Dt->Year(), Dt->Month(), Dt->Day(), Dt->Hours(), Dt->Minutes(), Dt->Seconds()); } #ifdef _MSC_VER #pragma message("FIXME: add export for reminder.") #else #warning "FIXME: add export for reminder." #endif /* int64 AlarmAction; int AlarmTime; if ((AlarmAction = c->GetInt(FIELD_CAL_REMINDER_ACTION)) && AlarmAction && (AlarmTime = (int)c->GetInt(FIELD_CAL_REMINDER_TIME))) { o->Push((char*)"BEGIN:VALARM\r\n"); GStreamPrint(o, "TRIGGER:%sPT%iM\r\n", (AlarmTime<0) ? "-" : "", abs(AlarmTime)); o->Push((char*)"END:VALARM\r\n"); } List Attendees; if (c->GetAttendees(Attendees)) { for (GDataPropI *a=Attendees.First(); a; a=Attendees.Next()) { // ATTENDEE;CN="Matthew Allen";ROLE=REQ-PARTICIPANT;RSVP=TRUE:MAILTO:matthew@cisra.canon.com.au int Attend = 2; char *Name = 0; char *Email = 0; int Response = 0; a->GetStr(FIELD_ATTENDEE_ATTENDENCE, Attend); a->GetStr(FIELD_ATTENDEE_NAME, Name); a->GetStr(FIELD_ATTENDEE_EMAIL, Email); a->GetStr(FIELD_ATTENDEE_RESPONSE, Response); if (Email) { strcpy(s, "ATTENDEE"); if (Name) { int len = strlen(s); sprintf_s(s+len, sizeof(s)-len, ";CN=\"%s\"", Name); } switch (Attend) { case 1: { strcat(s, ";ROLE=CHAIR"); break; } default: case 2: { strcat(s, ";ROLE=REQ-PARTICIPANT"); break; } case 3: { strcat(s, ";ROLE=OPT-PARTICIPANT"); break; } } if (Email) { int len = strlen(s) sprintf_s(s+len, sizeof(s)-len, ":MAILTO=%s", Email); } strcat(s, "\r\n"); o->Push(s); } } } */ GStreamPrint(o, "END:%s\r\n", TypeStr); } o->Push((char*)"END:VCALENDAR\r\n"); return true; } diff --git a/src/common/Widgets/Editor/GRichTextEdit.cpp b/src/common/Widgets/Editor/GRichTextEdit.cpp --- a/src/common/Widgets/Editor/GRichTextEdit.cpp +++ b/src/common/Widgets/Editor/GRichTextEdit.cpp @@ -1,3032 +1,3032 @@ #include #include #include #include "Lgi.h" #include "GRichTextEdit.h" #include "GInput.h" #include "GScrollBar.h" #ifdef WIN32 #include #endif #include "GClipBoard.h" #include "GDisplayString.h" #include "GViewPriv.h" #include "GCssTools.h" #include "GFontCache.h" #include "GUnicode.h" #include "GDropFiles.h" #include "GHtmlCommon.h" #include "GHtmlParser.h" #include "LgiRes.h" #define DefaultCharset "utf-8" #define GDCF_UTF8 -1 #define POUR_DEBUG 0 #define PROFILE_POUR 0 #define ALLOC_BLOCK 64 #define IDC_VS 1000 #define PAINT_BORDER Back #define PAINT_AFTER_LINE Back #if !defined(WIN32) && !defined(toupper) #define toupper(c) (((c)>='a'&&(c)<='z') ? (c)-'a'+'A' : (c)) #endif // static char SelectWordDelim[] = " \t\n.,()[]<>=?/\\{}\"\';:+=-|!@#$%^&*"; #include "GRichTextEditPriv.h" ////////////////////////////////////////////////////////////////////// GRichTextEdit::GRichTextEdit( int Id, int x, int y, int cx, int cy, GFontType *FontType) : ResObject(Res_Custom) { // init vars GView::d->Css.Reset(new GRichTextPriv(this, &d)); // setup window SetId(Id); SetTabStop(true); // default options #if WINNATIVE CrLf = true; SetDlgCode(DLGC_WANTALLKEYS); #else CrLf = false; #endif d->Padding(GCss::Len(GCss::LenPx, 4)); #if 0 d->BackgroundColor(GCss::ColorDef(GColour::Green)); #else d->BackgroundColor(GCss::ColorDef(GCss::ColorRgb, Rgb24To32(LC_WORKSPACE))); #endif SetFont(SysFont); #if 0 // def _DEBUG Name("\n" "\n" " This is some bold text to test with.
\n" " A second line of text for testing.\n" "\n" "\n"); #endif } GRichTextEdit::~GRichTextEdit() { // 'd' is owned by the GView CSS autoptr. } bool GRichTextEdit::SetSpellCheck(GSpellCheck *sp) { if ((d->SpellCheck = sp)) { if (IsAttached()) d->SpellCheck->EnumLanguages(AddDispatch()); // else call that OnCreate } return d->SpellCheck != NULL; } /* bool GRichTextEdit::NeedsCapability(const char *Name, const char *Param) { for (unsigned i=0; iNeedsCap.Length(); i++) { if (d->NeedsCap[i].Name.Equals(Name)) return true; } d->NeedsCap.New().Set(Name, Param); Invalidate(); return true; } void GRichTextEdit::OnInstall(CapsHash *Caps, bool Status) { OnCloseInstaller(); } void GRichTextEdit::OnCloseInstaller() { d->NeedsCap.Length(0); Invalidate(); } */ bool GRichTextEdit::IsDirty() { return d->Dirty; } void GRichTextEdit::IsDirty(bool dirty) { if (d->Dirty ^ dirty) { d->Dirty = dirty; } } void GRichTextEdit::SetFixedWidthFont(bool i) { if (FixedWidthFont ^ i) { if (i) { GFontType Type; if (Type.GetSystemFont("Fixed")) { GDocView::SetFixedWidthFont(i); } } OnFontChange(); Invalidate(); } } void GRichTextEdit::SetReadOnly(bool i) { GDocView::SetReadOnly(i); #if WINNATIVE SetDlgCode(i ? DLGC_WANTARROWS : DLGC_WANTALLKEYS); #endif } GRect GRichTextEdit::GetArea(RectType Type) { return Type >= ContentArea && Type <= MaxArea ? d->Areas[Type] : GRect(0, 0, -1, -1); } bool GRichTextEdit::ShowStyleTools() { return d->ShowTools; } void GRichTextEdit::ShowStyleTools(bool b) { if (d->ShowTools ^ b) { d->ShowTools = b; Invalidate(); } } -void GRichTextEdit::SetTabSize(uint8 i) +void GRichTextEdit::SetTabSize(uint8_t i) { TabSize = limit(i, 2, 32); OnFontChange(); OnPosChange(); Invalidate(); } void GRichTextEdit::SetWrapType(LDocWrapType i) { GDocView::SetWrapType(i); OnPosChange(); Invalidate(); } GFont *GRichTextEdit::GetFont() { return d->Font; } void GRichTextEdit::SetFont(GFont *f, bool OwnIt) { if (!f) return; if (OwnIt) { d->Font.Reset(f); } else if (d->Font.Reset(new GFont)) { *d->Font = *f; d->Font->Create(NULL, 0, 0); } OnFontChange(); } void GRichTextEdit::OnFontChange() { } void GRichTextEdit::PourText(ssize_t Start, ssize_t Length /* == 0 means it's a delete */) { } void GRichTextEdit::PourStyle(ssize_t Start, ssize_t EditSize) { } bool GRichTextEdit::Insert(int At, char16 *Data, int Len) { return false; } bool GRichTextEdit::Delete(int At, int Len) { return false; } bool GRichTextEdit::DeleteSelection(char16 **Cut) { AutoTrans t(new GRichTextPriv::Transaction); if (!d->DeleteSelection(t, Cut)) return false; return d->AddTrans(t); } int64 GRichTextEdit::Value() { char *n = Name(); #ifdef _MSC_VER return (n) ? _atoi64(n) : 0; #else return (n) ? atoll(n) : 0; #endif } void GRichTextEdit::Value(int64 i) { char Str[32]; sprintf_s(Str, sizeof(Str), LPrintfInt64, i); Name(Str); } bool GRichTextEdit::GetFormattedContent(const char *MimeType, GString &Out, GArray *Media) { if (!MimeType || _stricmp(MimeType, "text/html")) return false; if (!d->ToHtml(Media)) return false; Out = d->UtfNameCache; return true; } char *GRichTextEdit::Name() { d->ToHtml(); return d->UtfNameCache; } const char *GRichTextEdit::GetCharset() { return d->Charset; } void GRichTextEdit::SetCharset(const char *s) { d->Charset = s; } bool GRichTextEdit::GetVariant(const char *Name, GVariant &Value, char *Array) { GDomProperty p = LgiStringToDomProp(Name); switch (p) { case HtmlImagesLinkCid: { Value = d->HtmlLinkAsCid; break; } case SpellCheckLanguage: { Value = d->SpellLang.Get(); break; } case SpellCheckDictionary: { Value = d->SpellDict.Get(); break; } default: return false; } return true; } bool GRichTextEdit::SetVariant(const char *Name, GVariant &Value, char *Array) { GDomProperty p = LgiStringToDomProp(Name); switch (p) { case HtmlImagesLinkCid: { d->HtmlLinkAsCid = Value.CastInt32() != 0; break; } case SpellCheckLanguage: { d->SpellLang = Value.Str(); break; } case SpellCheckDictionary: { d->SpellDict = Value.Str(); break; } default: return false; } return true; } static GHtmlElement *FindElement(GHtmlElement *e, HtmlTag TagId) { if (e->TagId == TagId) return e; for (unsigned i = 0; i < e->Children.Length(); i++) { GHtmlElement *c = FindElement(e->Children[i], TagId); if (c) return c; } return NULL; } void GRichTextEdit::OnAddStyle(const char *MimeType, const char *Styles) { if (d->CreationCtx) { d->CreationCtx->StyleStore.Parse(Styles); } } bool GRichTextEdit::Name(const char *s) { d->Empty(); d->OriginalText = s; GHtmlElement Root(NULL); if (!d->CreationCtx.Reset(new GRichTextPriv::CreateContext(d))) return false; if (!d->GHtmlParser::Parse(&Root, s)) return d->Error(_FL, "Failed to parse HTML."); GHtmlElement *Body = FindElement(&Root, TAG_BODY); if (!Body) Body = &Root; bool Status = d->FromHtml(Body, *d->CreationCtx); // d->DumpBlocks(); if (!d->Blocks.Length()) { d->EmptyDoc(); } else { // Clear out any zero length blocks. for (unsigned i=0; iBlocks.Length(); i++) { GRichTextPriv::Block *b = d->Blocks[i]; if (b->Length() == 0) { d->Blocks.DeleteAt(i--, true); DeleteObj(b); } } } if (Status) SetCursor(0, false); Invalidate(); return Status; } char16 *GRichTextEdit::NameW() { d->WideNameCache.Reset(Utf8ToWide(Name())); return d->WideNameCache; } bool GRichTextEdit::NameW(const char16 *s) { GAutoString a(WideToUtf8(s)); return Name(a); } char *GRichTextEdit::GetSelection() { if (!HasSelection()) return NULL; GArray Text; if (!d->GetSelection(&Text, NULL)) return NULL; return WideToUtf8(&Text[0]); } bool GRichTextEdit::HasSelection() { return d->Selection.Get() != NULL; } void GRichTextEdit::SelectAll() { AutoCursor Start(new BlkCursor(d->Blocks.First(), 0, 0)); d->SetCursor(Start); GRichTextPriv::Block *Last = d->Blocks.Length() ? d->Blocks.Last() : NULL; if (Last) { AutoCursor End(new BlkCursor(Last, Last->Length(), Last->GetLines()-1)); d->SetCursor(End, true); } else d->Selection.Reset(); Invalidate(); } void GRichTextEdit::UnSelectAll() { bool Update = HasSelection(); if (Update) { d->Selection.Reset(); Invalidate(); } } void GRichTextEdit::SetStylePrefix(GString s) { d->SetPrefix(s); } size_t GRichTextEdit::GetLines() { uint32 Count = 0; for (size_t i=0; iBlocks.Length(); i++) { GRichTextPriv::Block *b = d->Blocks[i]; Count += b->GetLines(); } return Count; } int GRichTextEdit::GetLine() { if (!d->Cursor) return -1; ssize_t Idx = d->Blocks.IndexOf(d->Cursor->Blk); if (Idx < 0) { LgiAssert(0); return -1; } int Count = 0; // Count lines in blocks before the cursor... for (int i=0; iBlocks[i]; Count += b->GetLines(); } // Add the lines in the cursor's block... if (d->Cursor->LineHint) { Count += d->Cursor->LineHint; } else { GArray BlockLine; if (d->Cursor->Blk->OffsetToLine(d->Cursor->Offset, NULL, &BlockLine)) Count += BlockLine.First(); else { // Hmmm... LgiAssert(!"Can't find block line."); return -1; } } return Count; } void GRichTextEdit::SetLine(int i) { int Count = 0; // Count lines in blocks before the cursor... for (int i=0; i<(int)d->Blocks.Length(); i++) { GRichTextPriv::Block *b = d->Blocks[i]; int Lines = b->GetLines(); if (i >= Count && i < Count + Lines) { int BlockLine = i - Count; int Offset = b->LineToOffset(BlockLine); if (Offset >= 0) { AutoCursor c(new BlkCursor(b, Offset, BlockLine)); d->SetCursor(c); break; } } Count += Lines; } } void GRichTextEdit::GetTextExtent(int &x, int &y) { x = d->DocumentExtent.x; y = d->DocumentExtent.y; } bool GRichTextEdit::GetLineColumnAtIndex(GdcPt2 &Pt, ssize_t Index) { ssize_t Offset = -1; int BlockLines = -1; GRichTextPriv::Block *b = d->GetBlockByIndex(Index, &Offset, NULL, &BlockLines); if (!b) return false; int Cols; GArray Lines; if (b->OffsetToLine(Offset, &Cols, &Lines)) return false; Pt.x = Cols; Pt.y = BlockLines + Lines.First(); return true; } ssize_t GRichTextEdit::GetCaret(bool Cur) { if (!d->Cursor) return -1; int CharPos = 0; for (unsigned i=0; iBlocks.Length(); i++) { GRichTextPriv::Block *b = d->Blocks[i]; if (d->Cursor->Blk == b) return CharPos + d->Cursor->Offset; CharPos += b->Length(); } LgiAssert(!"Cursor block not found."); return -1; } bool GRichTextEdit::IndexAt(int x, int y, ssize_t &Off, int &LineHint) { GdcPt2 Doc = d->ScreenToDoc(x, y); Off = d->HitTest(Doc.x, Doc.y, LineHint); return Off >= 0; } ssize_t GRichTextEdit::IndexAt(int x, int y) { ssize_t Idx; int Line; if (!IndexAt(x, y, Idx, Line)) return -1; return Idx; } void GRichTextEdit::SetCursor(int i, bool Select, bool ForceFullUpdate) { ssize_t Offset = -1; GRichTextPriv::Block *Blk = d->GetBlockByIndex(i, &Offset); if (Blk) { AutoCursor c(new BlkCursor(Blk, Offset, -1)); if (c) d->SetCursor(c, Select); } } bool GRichTextEdit::Cut() { if (!HasSelection()) return false; char16 *Txt = NULL; if (!DeleteSelection(&Txt)) return false; bool Status = true; if (Txt) { GClipBoard Cb(this); Status = Cb.TextW(Txt); DeleteArray(Txt); } SendNotify(GNotifyDocChanged); return Status; } bool GRichTextEdit::Copy() { if (!HasSelection()) return false; GArray PlainText; GAutoString Html; if (!d->GetSelection(&PlainText, &Html)) return false; // Put on the clipboard GClipBoard Cb(this); bool Status = Cb.TextW(PlainText.AddressOf()); Cb.Html(Html, false); return Status; } bool GRichTextEdit::Paste() { GString Html; GAutoWString Text; GAutoPtr Img; { GClipBoard Cb(this); Html = Cb.Html(); if (!Html) { Text.Reset(NewStrW(Cb.TextW())); if (!Text) Img.Reset(Cb.Bitmap()); } } if (!Html && !Text && !Img) return false; if (!d->Cursor || !d->Cursor->Blk) { LgiAssert(0); return false; } AutoTrans Trans(new GRichTextPriv::Transaction); if (HasSelection()) { if (!d->DeleteSelection(Trans, NULL)) return false; } if (Html) { GHtmlElement Root(NULL); if (!d->CreationCtx.Reset(new GRichTextPriv::CreateContext(d))) return false; if (!d->GHtmlParser::Parse(&Root, Html)) return d->Error(_FL, "Failed to parse HTML."); GHtmlElement *Body = FindElement(&Root, TAG_BODY); if (!Body) Body = &Root; if (d->Cursor) { auto *b = d->Cursor->Blk; ssize_t BlkIdx = d->Blocks.IndexOf(b); GRichTextPriv::Block *After = NULL; ssize_t AddIndex = BlkIdx;; // Split 'b' to make room for pasted objects if (d->Cursor->Offset > 0) { After = b->Split(Trans, d->Cursor->Offset); AddIndex = BlkIdx+1; } // else Insert before cursor block auto *PastePoint = new GRichTextPriv::TextBlock(d); if (PastePoint) { d->Blocks.AddAt(AddIndex++, PastePoint); if (After) d->Blocks.AddAt(AddIndex++, After); d->CreationCtx->Tb = PastePoint; d->FromHtml(Body, *d->CreationCtx); } } } else if (Text) { GAutoPtr Utf32((uint32*)LgiNewConvertCp("utf-32", Text, LGI_WideCharset)); ptrdiff_t Len = Strlen(Utf32.Get()); if (!d->Cursor->Blk->AddText(Trans, d->Cursor->Offset, Utf32.Get(), (int)Len)) { LgiAssert(0); return false; } d->Cursor->Offset += Len; d->Cursor->LineHint = -1; } else if (Img) { GRichTextPriv::Block *b = d->Cursor->Blk; ssize_t BlkIdx = d->Blocks.IndexOf(b); GRichTextPriv::Block *After = NULL; ssize_t AddIndex; LgiAssert(BlkIdx >= 0); // Split 'b' to make room for the image if (d->Cursor->Offset > 0) { After = b->Split(Trans, d->Cursor->Offset); AddIndex = BlkIdx+1; } else { // Insert before.. AddIndex = BlkIdx; } GRichTextPriv::ImageBlock *ImgBlk = new GRichTextPriv::ImageBlock(d); if (ImgBlk) { d->Blocks.AddAt(AddIndex++, ImgBlk); if (After) d->Blocks.AddAt(AddIndex++, After); Img->MakeOpaque(); ImgBlk->SetImage(Img); AutoCursor c(new BlkCursor(ImgBlk, 1, -1)); d->SetCursor(c); } } Invalidate(); SendNotify(GNotifyDocChanged); return d->AddTrans(Trans); } bool GRichTextEdit::ClearDirty(bool Ask, char *FileName) { if (1 /*dirty*/) { int Answer = (Ask) ? LgiMsg(this, LgiLoadString(L_TEXTCTRL_ASK_SAVE, "Do you want to save your changes to this document?"), LgiLoadString(L_TEXTCTRL_SAVE, "Save"), MB_YESNOCANCEL) : IDYES; if (Answer == IDYES) { GFileSelect Select; Select.Parent(this); if (!FileName && Select.Save()) { FileName = Select.Name(); } Save(FileName); } else if (Answer == IDCANCEL) { return false; } } return true; } bool GRichTextEdit::Open(const char *Name, const char *CharSet) { bool Status = false; GFile f; if (f.Open(Name, O_READ|O_SHARE)) { size_t Bytes = (size_t)f.GetSize(); SetCursor(0, false); char *c8 = new char[Bytes + 4]; if (c8) { if (f.Read(c8, (int)Bytes) == Bytes) { char *DataStart = c8; c8[Bytes] = 0; c8[Bytes+1] = 0; c8[Bytes+2] = 0; c8[Bytes+3] = 0; if ((uchar)c8[0] == 0xff && (uchar)c8[1] == 0xfe) { // utf-16 if (!CharSet) { CharSet = "utf-16"; DataStart += 2; } } } DeleteArray(c8); } else { } Invalidate(); } return Status; } bool GRichTextEdit::Save(const char *FileName, const char *CharSet) { GFile f; if (!FileName || !f.Open(FileName, O_WRITE)) return false; f.SetSize(0); char *Nm = Name(); if (!Nm) return false; size_t Len = strlen(Nm); return f.Write(Nm, (int)Len) == Len; } void GRichTextEdit::UpdateScrollBars(bool Reset) { if (VScroll) { //GRect Before = GetClient(); } } bool GRichTextEdit::DoCase(bool Upper) { if (!HasSelection()) return false; bool Cf = d->CursorFirst(); GRichTextPriv::BlockCursor *Start = Cf ? d->Cursor : d->Selection; GRichTextPriv::BlockCursor *End = Cf ? d->Selection : d->Cursor; if (Start->Blk == End->Blk) { // In the same block... ssize_t Len = End->Offset - Start->Offset; Start->Blk->DoCase(NoTransaction, Start->Offset, Len, Upper); } else { // Multi-block delete... // 1) Delete all the content to the end of the first block ssize_t StartLen = Start->Blk->Length(); if (Start->Offset < StartLen) Start->Blk->DoCase(NoTransaction, Start->Offset, StartLen - Start->Offset, Upper); // 2) Delete any blocks between 'Start' and 'End' ssize_t i = d->Blocks.IndexOf(Start->Blk); if (i >= 0) { for (++i; d->Blocks[i] != End->Blk && i < (int)d->Blocks.Length(); ) { GRichTextPriv::Block *b = d->Blocks[i]; b->DoCase(NoTransaction, 0, -1, Upper); } } else { LgiAssert(0); return false; } // 3) Delete any text up to the Cursor in the 'End' block End->Blk->DoCase(NoTransaction, 0, End->Offset, Upper); } // Update the screen d->Dirty = true; Invalidate(); return true; } bool GRichTextEdit::DoGoto() { GInput Dlg(this, "", LgiLoadString(L_TEXTCTRL_GOTO_LINE, "Goto line:"), "Text"); if (Dlg.DoModal() == IDOK) { GString s = Dlg.GetStr(); int64 i = s.Int(); if (i >= 0) SetLine((int)i); } return true; } GDocFindReplaceParams *GRichTextEdit::CreateFindReplaceParams() { return new GDocFindReplaceParams3; } void GRichTextEdit::SetFindReplaceParams(GDocFindReplaceParams *Params) { if (Params) { } } bool GRichTextEdit::DoFindNext() { return false; } bool RichText_FindCallback(GFindReplaceCommon *Dlg, bool Replace, void *User) { return ((GRichTextEdit*)User)->OnFind(Dlg); } ////////////////////////////////////////////////////////////////////////////////// FIND bool GRichTextEdit::DoFind() { GArray Sel; if (HasSelection()) d->GetSelection(&Sel, NULL); GAutoString u(Sel.Length() ? WideToUtf8(&Sel.First()) : NULL); GFindDlg Dlg(this, u, RichText_FindCallback, this); Dlg.DoModal(); Focus(true); return false; } bool GRichTextEdit::OnFind(GFindReplaceCommon *Params) { if (!Params || !d->Cursor) { LgiAssert(0); return false; } GAutoPtr w((uint32*)LgiNewConvertCp("utf-32", Params->Find, "utf-8", Params->Find.Length())); ssize_t Idx = d->Blocks.IndexOf(d->Cursor->Blk); if (Idx < 0) { LgiAssert(0); return false; } for (unsigned n = 0; n < d->Blocks.Length(); n++) { ssize_t i = Idx + n; GRichTextPriv::Block *b = d->Blocks[i % d->Blocks.Length()]; ssize_t At = n ? 0 : d->Cursor->Offset; ssize_t Result = b->FindAt(At, w, Params); if (Result >= At) { ptrdiff_t Len = Strlen(w.Get()); AutoCursor Sel(new BlkCursor(b, Result, -1)); d->SetCursor(Sel, false); AutoCursor Cur(new BlkCursor(b, Result + Len, -1)); return d->SetCursor(Cur, true); } } return false; } ////////////////////////////////////////////////////////////////////////////////// REPLACE bool GRichTextEdit::DoReplace() { return false; } bool GRichTextEdit::OnReplace(GFindReplaceCommon *Params) { return false; } ////////////////////////////////////////////////////////////////////////////////// void GRichTextEdit::SelectWord(size_t From) { int BlockIdx; ssize_t Start, End; GRichTextPriv::Block *b = d->GetBlockByIndex(From, &Start, &BlockIdx); if (!b) return; GArray Txt; if (!b->CopyAt(0, b->Length(), &Txt)) return; End = Start; while (Start > 0 && !IsWordBreakChar(Txt[Start-1])) Start--; while ( End < b->Length() && ( End == Txt.Length() || !IsWordBreakChar(Txt[End]) ) ) End++; AutoCursor c(new BlkCursor(b, Start, -1)); d->SetCursor(c); c.Reset(new BlkCursor(b, End, -1)); d->SetCursor(c, true); } bool GRichTextEdit::OnMultiLineTab(bool In) { return false; } void GRichTextEdit::OnSetHidden(int Hidden) { } void GRichTextEdit::OnPosChange() { static bool Processing = false; if (!Processing) { Processing = true; GLayout::OnPosChange(); // GRect c = GetClient(); Processing = false; } } int GRichTextEdit::WillAccept(List &Formats, GdcPt2 Pt, int KeyState) { const char *Fd = LGI_FileDropFormat; for (char *s = Formats.First(); s; ) { if (!_stricmp(s, Fd) || !_stricmp(s, "UniformResourceLocatorW")) { s = Formats.Next(); } else { // LgiTrace("Ignoring format '%s'\n", s); Formats.Delete(s); DeleteArray(s); s = Formats.Current(); } } return Formats.Length() ? DROPEFFECT_COPY : DROPEFFECT_NONE; } int GRichTextEdit::OnDrop(GArray &Data, GdcPt2 Pt, int KeyState) { int Effect = DROPEFFECT_NONE; for (unsigned i=0; iAreas[ContentArea].Overlap(Pt.x, Pt.y)) { int AddIndex = -1; GdcPt2 TestPt( Pt.x - d->Areas[ContentArea].x1, Pt.y - d->Areas[ContentArea].y1); GDropFiles Df(dd); for (unsigned n=0; nHitTest(TestPt.x, TestPt.y, LineHint); if (Idx >= 0) { ssize_t BlkOffset; int BlkIdx; GRichTextPriv::Block *b = d->GetBlockByIndex(Idx, &BlkOffset, &BlkIdx); if (b) { GRichTextPriv::Block *After = NULL; // Split 'b' to make room for the image if (BlkOffset > 0) { After = b->Split(NoTransaction, BlkOffset); AddIndex = BlkIdx+1; } else { // Insert before.. AddIndex = BlkIdx; } GRichTextPriv::ImageBlock *ImgBlk = new GRichTextPriv::ImageBlock(d); if (ImgBlk) { d->Blocks.AddAt(AddIndex++, ImgBlk); if (After) d->Blocks.AddAt(AddIndex++, After); ImgBlk->Load(f); Effect = DROPEFFECT_COPY; } } } } } } break; } } if (Effect != DROPEFFECT_NONE) { Invalidate(); SendNotify(GNotifyDocChanged); } return Effect; } void GRichTextEdit::OnCreate() { SetWindow(this); DropTarget(true); if (Focus()) SetPulse(RTE_PULSE_RATE); if (d->SpellCheck) d->SpellCheck->EnumLanguages(AddDispatch()); } void GRichTextEdit::OnEscape(GKey &K) { } bool GRichTextEdit::OnMouseWheel(double l) { if (VScroll) { VScroll->Value(VScroll->Value() + (int64)l); Invalidate(); } return true; } void GRichTextEdit::OnFocus(bool f) { Invalidate(); SetPulse(f ? RTE_PULSE_RATE : -1); } ssize_t GRichTextEdit::HitTest(int x, int y) { int Line = -1; return d->HitTest(x, y, Line); } void GRichTextEdit::Undo() { if (d->UndoPos > 0) d->SetUndoPos(d->UndoPos - 1); } void GRichTextEdit::Redo() { if (d->UndoPos < (int)d->UndoQue.Length()) d->SetUndoPos(d->UndoPos + 1); } #ifdef _DEBUG class NodeView : public GWindow { public: GTree *Tree; NodeView(GViewI *w) { GRect r(0, 0, 500, 600); SetPos(r); MoveSameScreen(w); Attach(0); if ((Tree = new GTree(100, 0, 0, 100, 100))) { Tree->SetPourLargest(true); Tree->Attach(this); } } }; #endif void GRichTextEdit::DoContextMenu(GMouse &m) { GMenuItem *i; GSubMenu RClick; GAutoString ClipText; { GClipBoard Clip(this); ClipText.Reset(NewStr(Clip.Text())); } GRichTextPriv::Block *Over = NULL; GRect &Content = d->Areas[ContentArea]; GdcPt2 Doc = d->ScreenToDoc(m.x, m.y); // int BlockIndex = -1; ssize_t Offset = -1; if (Content.Overlap(m.x, m.y)) { int LineHint; Offset = d->HitTest(Doc.x, Doc.y, LineHint, &Over); } if (Over) Over->DoContext(RClick, Doc, Offset, true); RClick.AppendItem(LgiLoadString(L_TEXTCTRL_CUT, "Cut"), IDM_RTE_CUT, HasSelection()); RClick.AppendItem(LgiLoadString(L_TEXTCTRL_COPY, "Copy"), IDM_RTE_COPY, HasSelection()); RClick.AppendItem(LgiLoadString(L_TEXTCTRL_PASTE, "Paste"), IDM_RTE_PASTE, ClipText != 0); RClick.AppendSeparator(); RClick.AppendItem(LgiLoadString(L_TEXTCTRL_UNDO, "Undo"), IDM_RTE_UNDO, false /* UndoQue.CanUndo() */); RClick.AppendItem(LgiLoadString(L_TEXTCTRL_REDO, "Redo"), IDM_RTE_REDO, false /* UndoQue.CanRedo() */); RClick.AppendSeparator(); #if 0 i = RClick.AppendItem(LgiLoadString(L_TEXTCTRL_FIXED, "Fixed Width Font"), IDM_FIXED, true); if (i) i->Checked(GetFixedWidthFont()); #endif i = RClick.AppendItem(LgiLoadString(L_TEXTCTRL_AUTO_INDENT, "Auto Indent"), IDM_AUTO_INDENT, true); if (i) i->Checked(AutoIndent); i = RClick.AppendItem(LgiLoadString(L_TEXTCTRL_SHOW_WHITESPACE, "Show Whitespace"), IDM_SHOW_WHITE, true); if (i) i->Checked(ShowWhiteSpace); i = RClick.AppendItem(LgiLoadString(L_TEXTCTRL_HARD_TABS, "Hard Tabs"), IDM_HARD_TABS, true); if (i) i->Checked(HardTabs); RClick.AppendItem(LgiLoadString(L_TEXTCTRL_INDENT_SIZE, "Indent Size"), IDM_INDENT_SIZE, true); RClick.AppendItem(LgiLoadString(L_TEXTCTRL_TAB_SIZE, "Tab Size"), IDM_TAB_SIZE, true); GSubMenu *Src = RClick.AppendSub("Source"); if (Src) { Src->AppendItem("Copy Original", IDM_COPY_ORIGINAL, d->OriginalText.Get() != NULL); Src->AppendItem("Copy Current", IDM_COPY_CURRENT); #ifdef _DEBUG Src->AppendItem("Dump Nodes", IDM_DUMP_NODES); // Edit->DumpNodes(Tree); #endif } if (Over) { #ifdef _DEBUG // RClick.AppendItem(Over->GetClass(), -1, false); #endif Over->DoContext(RClick, Doc, Offset, false); } if (Environment) Environment->AppendItems(&RClick); int Id = 0; m.ToScreen(); switch (Id = RClick.Float(this, m.x, m.y)) { case IDM_FIXED: { SetFixedWidthFont(!GetFixedWidthFont()); SendNotify(GNotifyFixedWidthChanged); break; } case IDM_RTE_CUT: { Cut(); break; } case IDM_RTE_COPY: { Copy(); break; } case IDM_RTE_PASTE: { Paste(); break; } case IDM_RTE_UNDO: { Undo(); break; } case IDM_RTE_REDO: { Redo(); break; } case IDM_AUTO_INDENT: { AutoIndent = !AutoIndent; break; } case IDM_SHOW_WHITE: { ShowWhiteSpace = !ShowWhiteSpace; Invalidate(); break; } case IDM_HARD_TABS: { HardTabs = !HardTabs; break; } case IDM_INDENT_SIZE: { char s[32]; sprintf_s(s, sizeof(s), "%i", IndentSize); GInput i(this, s, "Indent Size:", "Text"); if (i.DoModal()) { IndentSize = i.GetStr().Int(); } break; } case IDM_TAB_SIZE: { char s[32]; sprintf_s(s, sizeof(s), "%i", TabSize); GInput i(this, s, "Tab Size:", "Text"); if (i.DoModal()) { SetTabSize(i.GetStr().Int()); } break; } case IDM_COPY_ORIGINAL: { GClipBoard c(this); c.Text(d->OriginalText); break; } case IDM_COPY_CURRENT: { GClipBoard c(this); c.Text(Name()); break; } case IDM_DUMP_NODES: { #ifdef _DEBUG NodeView *nv = new NodeView(GetWindow()); DumpNodes(nv->Tree); nv->Visible(true); #endif break; } default: { if (Over) { GMessage Cmd(M_COMMAND, Id); if (Over->OnEvent(&Cmd)) break; } if (Environment) { Environment->OnMenu(this, Id, 0); } break; } } } void GRichTextEdit::OnMouseClick(GMouse &m) { bool Processed = false; RectType Clicked = d->PosToButton(m); if (m.Down()) { Focus(true); if (m.IsContextMenu()) { DoContextMenu(m); return; } else { Focus(true); if (d->Areas[ToolsArea].Overlap(m.x, m.y) // || d->Areas[CapabilityArea].Overlap(m.x, m.y) ) { if (Clicked != MaxArea) { if (d->BtnState[Clicked].IsPress) { d->BtnState[d->ClickedBtn = Clicked].Pressed = true; Invalidate(d->Areas + Clicked); Capture(true); } else { Processed |= d->ClickBtn(m, Clicked); } } } else { d->WordSelectMode = !Processed && m.Double(); AutoCursor c(new BlkCursor(NULL, 0, 0)); GdcPt2 Doc = d->ScreenToDoc(m.x, m.y); ssize_t Idx = -1; if (d->CursorFromPos(Doc.x, Doc.y, &c, &Idx)) { d->ClickedBtn = ContentArea; d->SetCursor(c, m.Shift()); if (d->WordSelectMode) SelectWord(Idx); } } } } else if (IsCapturing()) { Capture(false); if (d->ClickedBtn != MaxArea) { d->BtnState[d->ClickedBtn].Pressed = false; Invalidate(d->Areas + d->ClickedBtn); Processed |= d->ClickBtn(m, Clicked); } d->ClickedBtn = MaxArea; } if (!Processed) { Capture(m.Down()); } } int GRichTextEdit::OnHitTest(int x, int y) { #ifdef WIN32 if (GetClient().Overlap(x, y)) { return HTCLIENT; } #endif return GView::OnHitTest(x, y); } void GRichTextEdit::OnMouseMove(GMouse &m) { GRichTextEdit::RectType OverBtn = d->PosToButton(m); if (d->OverBtn != OverBtn) { if (d->OverBtn < MaxArea) { d->BtnState[d->OverBtn].MouseOver = false; Invalidate(&d->Areas[d->OverBtn]); } d->OverBtn = OverBtn; if (d->OverBtn < MaxArea) { d->BtnState[d->OverBtn].MouseOver = true; Invalidate(&d->Areas[d->OverBtn]); } } if (IsCapturing()) { if (d->ClickedBtn == ContentArea) { AutoCursor c; GdcPt2 Doc = d->ScreenToDoc(m.x, m.y); ssize_t Idx = -1; if (d->CursorFromPos(Doc.x, Doc.y, &c, &Idx) && c) { if (d->WordSelectMode && d->Selection) { // Extend the selection to include the whole word if (!d->CursorFirst()) { // Extend towards the end of the doc... GArray Txt; if (c->Blk->CopyAt(0, c->Blk->Length(), &Txt)) { while ( c->Offset < (int)Txt.Length() && !IsWordBreakChar(Txt[c->Offset]) ) c->Offset++; } } else { // Extend towards the start of the doc... GArray Txt; if (c->Blk->CopyAt(0, c->Blk->Length(), &Txt)) { while ( c->Offset > 0 && !IsWordBreakChar(Txt[c->Offset-1]) ) c->Offset--; } } } d->SetCursor(c, m.Left()); } } } #ifdef WIN32 GRect c = GetClient(); c.Offset(-c.x1, -c.y1); if (c.Overlap(m.x, m.y)) { /* GStyle *s = HitStyle(Hit); TCHAR *c = (s) ? s->GetCursor() : 0; if (!c) c = IDC_IBEAM; ::SetCursor(LoadCursor(0, MAKEINTRESOURCE(c))); */ } #endif } bool GRichTextEdit::OnKey(GKey &k) { if (k.Down() && d->Cursor) d->Cursor->Blink = true; // k.Trace("GRichTextEdit::OnKey"); if (k.IsContextMenu()) { GMouse m; DoContextMenu(m); } else if (k.IsChar) { switch (k.c16) { default: { // process single char input if ( !GetReadOnly() && ( (k.c16 >= ' ' || k.c16 == VK_TAB) && k.c16 != 127 ) ) { if (k.Down() && d->Cursor && d->Cursor->Blk) { // letter/number etc GRichTextPriv::Block *b = d->Cursor->Blk; GNamedStyle *AddStyle = NULL; if (d->StyleDirty.Length() > 0) { GAutoPtr Mod(new GCss); if (Mod) { // Get base styles at the cursor.. GNamedStyle *Base = b->GetStyle(d->Cursor->Offset); if (Base && Mod) *Mod = *Base; // Apply dirty toolbar styles... if (d->StyleDirty.HasItem(FontFamilyBtn)) Mod->FontFamily(GCss::StringsDef(d->Values[FontFamilyBtn].Str())); if (d->StyleDirty.HasItem(FontSizeBtn)) Mod->FontSize(GCss::Len(GCss::LenPt, (float) d->Values[FontSizeBtn].CastDouble())); if (d->StyleDirty.HasItem(BoldBtn)) Mod->FontWeight(d->Values[BoldBtn].CastInt32() ? GCss::FontWeightBold : GCss::FontWeightNormal); if (d->StyleDirty.HasItem(ItalicBtn)) Mod->FontStyle(d->Values[ItalicBtn].CastInt32() ? GCss::FontStyleItalic : GCss::FontStyleNormal); if (d->StyleDirty.HasItem(UnderlineBtn)) Mod->TextDecoration(d->Values[UnderlineBtn].CastInt32() ? GCss::TextDecorUnderline : GCss::TextDecorNone); if (d->StyleDirty.HasItem(ForegroundColourBtn)) Mod->Color(GCss::ColorDef(GCss::ColorRgb, (uint32)d->Values[ForegroundColourBtn].CastInt64())); if (d->StyleDirty.HasItem(BackgroundColourBtn)) Mod->BackgroundColor(GCss::ColorDef(GCss::ColorRgb, (uint32)d->Values[BackgroundColourBtn].CastInt64())); AddStyle = d->AddStyleToCache(Mod); } d->StyleDirty.Length(0); } AutoTrans Trans(new GRichTextPriv::Transaction); d->DeleteSelection(Trans, NULL); uint32 Ch = k.c16; if (b->AddText(Trans, d->Cursor->Offset, &Ch, 1, AddStyle)) { d->Cursor->Set(d->Cursor->Offset + 1); Invalidate(); SendNotify(GNotifyDocChanged); d->AddTrans(Trans); } } return true; } break; } case VK_RETURN: { if (GetReadOnly()) break; if (k.Down() && k.IsChar) OnEnter(k); return true; } case VK_BACKSPACE: { if (GetReadOnly()) break; bool Changed = false; AutoTrans Trans(new GRichTextPriv::Transaction); if (k.Ctrl()) { // Ctrl+H } else if (k.Down()) { GRichTextPriv::Block *b; if (HasSelection()) { Changed = d->DeleteSelection(Trans, NULL); } else if (d->Cursor && (b = d->Cursor->Blk)) { if (d->Cursor->Offset > 0) { Changed = b->DeleteAt(Trans, d->Cursor->Offset-1, 1) > 0; if (Changed) { // Has block size reached 0? if (b->Length() == 0) { // Then delete it... GRichTextPriv::Block *n = d->Next(b); if (n) { d->Blocks.Delete(b, true); d->Cursor.Reset(new GRichTextPriv::BlockCursor(n, 0, 0)); } } else { d->Cursor->Set(d->Cursor->Offset - 1); } } } else { // At the start of a block: GRichTextPriv::Block *Prev = d->Prev(d->Cursor->Blk); if (Prev) { // Try and merge the two blocks... ssize_t Len = Prev->Length(); d->Merge(Trans, Prev, d->Cursor->Blk); AutoCursor c(new BlkCursor(Prev, Len, -1)); d->SetCursor(c); } else // at the start of the doc... { // Don't send the doc changed... return true; } } } } if (Changed) { Invalidate(); d->AddTrans(Trans); SendNotify(GNotifyDocChanged); } return true; } } } else // not a char { switch (k.vkey) { case VK_TAB: return true; case VK_RETURN: return !GetReadOnly(); case VK_BACKSPACE: { if (!GetReadOnly()) { if (k.Alt()) { if (k.Down()) { if (k.Ctrl()) Redo(); else Undo(); } } else if (k.Ctrl()) { if (k.Down()) { // Implement delete by word LgiAssert(!"Impl backspace by word"); } } return true; } break; } case VK_F3: { if (k.Down()) DoFindNext(); return true; } case VK_LEFT: { if (k.Alt()) return false; if (k.Down()) { if (HasSelection() && !k.Shift()) { GRect r = d->SelectionRect(); Invalidate(&r); AutoCursor c(new BlkCursor(d->CursorFirst() ? *d->Cursor : *d->Selection)); d->SetCursor(c); } else { #ifdef MAC if (k.System()) goto Jump_StartOfLine; else #endif d->Seek(d->Cursor, k.Ctrl() ? GRichTextPriv::SkLeftWord : GRichTextPriv::SkLeftChar, k.Shift()); } } return true; } case VK_RIGHT: { if (k.Alt()) return false; if (k.Down()) { if (HasSelection() && !k.Shift()) { GRect r = d->SelectionRect(); Invalidate(&r); AutoCursor c(new BlkCursor(d->CursorFirst() ? *d->Selection : *d->Cursor)); d->SetCursor(c); } else { #ifdef MAC if (k.System()) goto Jump_EndOfLine; #endif d->Seek(d->Cursor, k.Ctrl() ? GRichTextPriv::SkRightWord : GRichTextPriv::SkRightChar, k.Shift()); } } return true; } case VK_UP: { if (k.Alt()) return false; if (k.Down()) { #ifdef MAC if (k.Ctrl()) goto GTextView4_PageUp; #endif d->Seek(d->Cursor, GRichTextPriv::SkUpLine, k.Shift()); } return true; } case VK_DOWN: { if (k.Alt()) return false; if (k.Down()) { #ifdef MAC if (k.Ctrl()) goto GTextView4_PageDown; #endif d->Seek(d->Cursor, GRichTextPriv::SkDownLine, k.Shift()); } return true; } case VK_END: { if (k.Down()) { #ifdef MAC if (!k.Ctrl()) Jump_EndOfLine: #endif d->Seek(d->Cursor, k.Ctrl() ? GRichTextPriv::SkDocEnd : GRichTextPriv::SkLineEnd, k.Shift()); } return true; } case VK_HOME: { if (k.Down()) { #ifdef MAC if (!k.Ctrl()) Jump_StartOfLine: #endif d->Seek(d->Cursor, k.Ctrl() ? GRichTextPriv::SkDocStart : GRichTextPriv::SkLineStart, k.Shift()); } return true; } case VK_PAGEUP: { #ifdef MAC GTextView4_PageUp: #endif if (k.Down()) { d->Seek(d->Cursor, GRichTextPriv::SkUpPage, k.Shift()); } return true; break; } case VK_PAGEDOWN: { #ifdef MAC GTextView4_PageDown: #endif if (k.Down()) { d->Seek(d->Cursor, GRichTextPriv::SkDownPage, k.Shift()); } return true; break; } case VK_INSERT: { if (k.Down()) { if (k.Ctrl()) { Copy(); } else if (k.Shift()) { if (!GetReadOnly()) { Paste(); } } } return true; break; } case VK_DELETE: { if (GetReadOnly()) break; if (!k.Down()) return true; bool Changed = false; GRichTextPriv::Block *b; AutoTrans Trans(new GRichTextPriv::Transaction); if (HasSelection()) { if (k.Shift()) Changed |= Cut(); else Changed |= d->DeleteSelection(Trans, NULL); } else if (d->Cursor && (b = d->Cursor->Blk)) { if (d->Cursor->Offset >= b->Length()) { // Cursor is at the end of this block, pull the styles // from the next block into this one. GRichTextPriv::Block *next = d->Next(b); if (!next) { // No next block, therefor nothing to delete break; } // Try and merge the blocks if (d->Merge(Trans, b, next)) Changed = true; else { // If the cursor is on the last empty line of a text block, // we should delete that '\n' first GRichTextPriv::TextBlock *tb = dynamic_cast(b); if (tb && tb->IsEmptyLine(d->Cursor)) Changed = tb->StripLast(Trans); // move the cursor to the next block d->Cursor.Reset(new GRichTextPriv::BlockCursor(b = next, 0, 0)); } } if (!Changed && b->DeleteAt(Trans, d->Cursor->Offset, 1)) { if (b->Length() == 0) { GRichTextPriv::Block *n = d->Next(b); if (n) { d->Blocks.Delete(b, true); d->Cursor.Reset(new GRichTextPriv::BlockCursor(n, 0, 0)); } } Changed = true; } } if (Changed) { Invalidate(); d->AddTrans(Trans); SendNotify(GNotifyDocChanged); } return true; } default: { if (k.c16 == 17) break; if (k.c16 == ' ' && k.Ctrl() && k.Alt() && d->Cursor && d->Cursor->Blk) { if (k.Down()) { // letter/number etc GRichTextPriv::Block *b = d->Cursor->Blk; uint32 Nbsp[] = {0xa0}; if (b->AddText(NoTransaction, d->Cursor->Offset, Nbsp, 1)) { d->Cursor->Set(d->Cursor->Offset + 1); Invalidate(); SendNotify(GNotifyDocChanged); } } break; } if (k.Modifier() && !k.Alt()) { switch (k.GetChar()) { case 0xbd: // Ctrl+'-' { /* if (k.Down() && Font->PointSize() > 1) { Font->PointSize(Font->PointSize() - 1); OnFontChange(); Invalidate(); } */ break; } case 0xbb: // Ctrl+'+' { /* if (k.Down() && Font->PointSize() < 100) { Font->PointSize(Font->PointSize() + 1); OnFontChange(); Invalidate(); } */ break; } case 'a': case 'A': { if (k.Down()) { // select all SelectAll(); } return true; break; } case 'b': case 'B': { if (k.Down()) { // Bold selection GMouse m; GetMouse(m); d->ClickBtn(m, BoldBtn); } return true; break; } case 'i': case 'I': { if (k.Down()) { // Italic selection GMouse m; GetMouse(m); d->ClickBtn(m, ItalicBtn); } return true; break; } case 'y': case 'Y': { if (!GetReadOnly()) { if (k.Down()) { Redo(); } return true; } break; } case 'z': case 'Z': { if (!GetReadOnly()) { if (k.Down()) { if (k.Shift()) { Redo(); } else { Undo(); } } return true; } break; } case 'x': case 'X': { if (!GetReadOnly()) { if (k.Down()) { Cut(); } return true; } break; } case 'c': case 'C': { if (k.Shift()) return false; if (k.Down()) Copy(); return true; break; } case 'v': case 'V': { if (!k.Shift() && !GetReadOnly()) { if (k.Down()) { Paste(); } return true; } break; } case 'f': { if (k.Down()) DoFind(); return true; } case 'g': case 'G': { if (k.Down()) { DoGoto(); } return true; break; } case 'h': case 'H': { if (k.Down()) { DoReplace(); } return true; break; } case 'u': case 'U': { if (!GetReadOnly()) { if (k.Down()) { DoCase(k.Shift()); } return true; } break; } case VK_RETURN: { if (!GetReadOnly() && !k.Shift()) { if (k.Down()) { OnEnter(k); } return true; } break; } } } break; } } } return false; } void GRichTextEdit::OnEnter(GKey &k) { AutoTrans Trans(new GRichTextPriv::Transaction); // Enter key handling bool Changed = false; if (HasSelection()) Changed |= d->DeleteSelection(Trans, NULL); if (d->Cursor && d->Cursor->Blk) { GRichTextPriv::Block *b = d->Cursor->Blk; const uint32 Nl[] = {'\n'}; if (b->AddText(Trans, d->Cursor->Offset, Nl, 1)) { d->Cursor->Set(d->Cursor->Offset + 1); Changed = true; } else { // Some blocks don't take text. However a new block can be created or // the text added to the start of the next block if (d->Cursor->Offset == 0) { GRichTextPriv::Block *Prev = d->Prev(b); if (Prev) Changed = Prev->AddText(Trans, Prev->Length(), Nl, 1); else // No previous... must by first block... create new block: { GRichTextPriv::TextBlock *tb = new GRichTextPriv::TextBlock(d); if (tb) { Changed = true; // tb->AddText(Trans, 0, Nl, 1); d->Blocks.AddAt(0, tb); } } } else if (d->Cursor->Offset == b->Length()) { GRichTextPriv::Block *Next = d->Next(b); if (Next) { if ((Changed = Next->AddText(Trans, 0, Nl, 1))) d->Cursor->Set(Next, 0, -1); } else // No next block. Create one: { GRichTextPriv::TextBlock *tb = new GRichTextPriv::TextBlock(d); if (tb) { Changed = true; // tb->AddText(Trans, 0, Nl, 1); d->Blocks.Add(tb); } } } } } if (Changed) { Invalidate(); d->AddTrans(Trans); SendNotify(GNotifyDocChanged); } } void GRichTextEdit::OnPaintLeftMargin(GSurface *pDC, GRect &r, GColour &colour) { pDC->Colour(colour); pDC->Rectangle(&r); } void GRichTextEdit::OnPaint(GSurface *pDC) { GRect r = GetClient(); if (!r.Valid()) return; #if 0 pDC->Colour(GColour(255, 0, 255)); pDC->Rectangle(); #endif int FontY = GetFont()->GetHeight(); GCssTools ct(d, d->Font); r = ct.PaintBorder(pDC, r); bool HasSpace = r.Y() > (FontY * 3); /* if (d->NeedsCap.Length() > 0 && HasSpace) { d->Areas[CapabilityArea] = r; d->Areas[CapabilityArea].y2 = d->Areas[CapabilityArea].y1 + 4 + ((FontY + 4) * (int)d->NeedsCap.Length()); r.y1 = d->Areas[CapabilityArea].y2 + 1; d->Areas[CapabilityBtn] = d->Areas[CapabilityArea]; d->Areas[CapabilityBtn].Size(2, 2); d->Areas[CapabilityBtn].x1 = d->Areas[CapabilityBtn].x2 - 30; } else { d->Areas[CapabilityArea].ZOff(-1, -1); d->Areas[CapabilityBtn].ZOff(-1, -1); } */ if (d->ShowTools && HasSpace) { d->Areas[ToolsArea] = r; d->Areas[ToolsArea].y2 = d->Areas[ToolsArea].y1 + (FontY + 8) - 1; r.y1 = d->Areas[ToolsArea].y2 + 1; } else { d->Areas[ToolsArea].ZOff(-1, -1); } d->Areas[ContentArea] = r; #if 0 CGAffineTransform t1 = CGContextGetCTM(pDC->Handle()); CGRect rc = CGContextGetClipBoundingBox(pDC->Handle()); LgiTrace("d->Areas[ContentArea]=%s %f,%f,%f,%f\n", d->Areas[ContentArea].GetStr(), rc.origin.x, rc.origin.y, rc.size.width, rc.size.height); if (rc.size.width < 20) { int asd=0; } #endif if (d->Layout(VScroll)) d->Paint(pDC, VScroll); // else the scroll bars changed, wait for re-paint } GMessage::Result GRichTextEdit::OnEvent(GMessage *Msg) { switch (MsgCode(Msg)) { case M_CUT: { Cut(); break; } case M_COPY: { Copy(); break; } case M_PASTE: { Paste(); break; } case M_BLOCK_MSG: { GRichTextPriv::Block *b = (GRichTextPriv::Block*)Msg->A(); GAutoPtr msg((GMessage*)Msg->B()); if (d->Blocks.HasItem(b) && msg) { b->OnEvent(msg); } else printf("%s:%i - No block to receive M_BLOCK_MSG.\n", _FL); break; } case M_ENUMERATE_LANGUAGES: { GAutoPtr< GArray > Languages((GArray*)Msg->A()); if (!Languages) { LgiTrace("%s:%i - M_ENUMERATE_LANGUAGES no param\n", _FL); break; } // LgiTrace("%s:%i - Got M_ENUMERATE_LANGUAGES %s\n", _FL, d->SpellLang.Get()); bool Match = false; for (unsigned i=0; iLength(); i++) { GSpellCheck::LanguageId &s = (*Languages)[i]; if (s.LangCode.Equals(d->SpellLang) || s.EnglishName.Equals(d->SpellLang)) { // LgiTrace("%s:%i - EnumDict called %s\n", _FL, s.LangCode.Get()); d->SpellCheck->EnumDictionaries(AddDispatch(), s.LangCode); Match = true; break; } } if (!Match) LgiTrace("%s:%i - EnumDict not called %s\n", _FL, d->SpellLang.Get()); break; } case M_ENUMERATE_DICTIONARIES: { GAutoPtr< GArray > Dictionaries((GArray*)Msg->A()); if (!Dictionaries) break; bool Match = false; for (unsigned i=0; iLength(); i++) { GSpellCheck::DictionaryId &s = (*Dictionaries)[i]; if (s.Dict.Equals(d->SpellDict)) { // LgiTrace("%s:%i - M_ENUMERATE_DICTIONARIES: %s, %s\n", _FL, s.Dict.Get(), d->SpellDict.Get()); d->SpellCheck->SetDictionary(AddDispatch(), s.Lang, s.Dict); Match = true; break; } } if (!Match) LgiTrace("%s:%i - No match in M_ENUMERATE_DICTIONARIES: %s\n", _FL, d->SpellDict.Get()); break; } case M_SET_DICTIONARY: { d->SpellDictionaryLoaded = Msg->A() != 0; // LgiTrace("%s:%i - M_SET_DICTIONARY=%i\n", _FL, d->SpellDictionaryLoaded); if (d->SpellDictionaryLoaded) { AutoTrans Trans(new GRichTextPriv::Transaction); // Get any loaded text blocks to check their spelling bool Status = false; for (unsigned i=0; iBlocks.Length(); i++) { Status |= d->Blocks[i]->OnDictionary(Trans); } if (Status) d->AddTrans(Trans); } break; } case M_CHECK_TEXT: { GAutoPtr Ct((GSpellCheck::CheckText*)Msg->A()); if (!Ct || Ct->User.Length() > 1) { LgiAssert(0); break; } GRichTextPriv::Block *b = (GRichTextPriv::Block*)Ct->User[SpellBlockPtr].CastVoidPtr(); if (!d->Blocks.HasItem(b)) break; b->SetSpellingErrors(Ct->Errors, *Ct); Invalidate(); break; } #if defined WIN32 case WM_GETTEXTLENGTH: { return 0 /*Size*/; } case WM_GETTEXT: { int Chars = (int)MsgA(Msg); char *Out = (char*)MsgB(Msg); if (Out) { char *In = (char*)LgiNewConvertCp(LgiAnsiToLgiCp(), NameW(), LGI_WideCharset, Chars); if (In) { int Len = (int)strlen(In); memcpy(Out, In, Len); DeleteArray(In); return Len; } } return 0; } case M_COMPONENT_INSTALLED: { GAutoPtr Comp((GString*)Msg->A()); if (Comp) d->OnComponentInstall(*Comp); break; } /* This is broken... the IME returns garbage in the buffer. :( case WM_IME_COMPOSITION: { if (Msg->b & GCS_RESULTSTR) { HIMC hIMC = ImmGetContext(Handle()); if (hIMC) { int Size = ImmGetCompositionString(hIMC, GCS_RESULTSTR, NULL, 0); char *Buf = new char[Size]; if (Buf) { ImmGetCompositionString(hIMC, GCS_RESULTSTR, Buf, Size); char16 *Utf = (char16*)LgiNewConvertCp(LGI_WideCharset, Buf, LgiAnsiToLgiCp(), Size); if (Utf) { Insert(Cursor, Utf, StrlenW(Utf)); DeleteArray(Utf); } DeleteArray(Buf); } ImmReleaseContext(Handle(), hIMC); } return 0; } break; } */ #endif } return GLayout::OnEvent(Msg); } int GRichTextEdit::OnNotify(GViewI *Ctrl, int Flags) { if (Ctrl->GetId() == IDC_VSCROLL && VScroll) { Invalidate(d->Areas + ContentArea); } return 0; } void GRichTextEdit::OnPulse() { if (!ReadOnly && d->Cursor) { uint64 n = LgiCurrentTime(); if (d->BlinkTs - n >= RTE_CURSOR_BLINK_RATE) { d->BlinkTs = n; d->Cursor->Blink = !d->Cursor->Blink; d->InvalidateDoc(&d->Cursor->Pos); } // Do autoscroll while the user has clicked and dragged off the control: if (VScroll && IsCapturing() && d->ClickedBtn == GRichTextEdit::ContentArea) { GMouse m; GetMouse(m); // Is the mouse outside the content window GRect &r = d->Areas[ContentArea]; if (!r.Overlap(m.x, m.y)) { AutoCursor c(new BlkCursor(NULL, 0, 0)); GdcPt2 Doc = d->ScreenToDoc(m.x, m.y); ssize_t Idx = -1; if (d->CursorFromPos(Doc.x, Doc.y, &c, &Idx)) { d->SetCursor(c, true); if (d->WordSelectMode) SelectWord(Idx); } // Update the screen. d->InvalidateDoc(NULL); } } } } void GRichTextEdit::OnUrl(char *Url) { if (Environment) { Environment->OnNavigate(this, Url); } } bool GRichTextEdit::OnLayout(GViewLayoutInfo &Inf) { Inf.Width.Min = 32; Inf.Width.Max = -1; // Inf.Height.Min = (Font ? Font->GetHeight() : 18) + 4; Inf.Height.Max = -1; return true; } #if _DEBUG void GRichTextEdit::SelectNode(GString Param) { GRichTextPriv::Block *b = (GRichTextPriv::Block*) Param.Int(16); bool Valid = false; for (auto i : d->Blocks) { if (i == b) Valid = true; i->DrawDebug = false; } if (Valid) { b->DrawDebug = true; Invalidate(); } } void GRichTextEdit::DumpNodes(GTree *Root) { d->DumpNodes(Root); } #endif /////////////////////////////////////////////////////////////////////////////// SelectColour::SelectColour(GRichTextPriv *priv, GdcPt2 p, GRichTextEdit::RectType t) : GPopup(priv->View) { d = priv; Type = t; int Px = 16; int PxSp = Px + 2; int x = 6; int y = 6; // Do grey ramp for (int i=0; i<8; i++) { Entry &en = e.New(); int Grey = i * 255 / 7; en.r.ZOff(Px-1, Px-1); en.r.Offset(x + (i * PxSp), y); en.c.Rgb(Grey, Grey, Grey); } // Do colours y += PxSp + 4; int SatRange = 255 - 64; int SatStart = 255 - 32; int HueStep = 360 / 8; for (int sat=0; sat<8; sat++) { for (int hue=0; hue<8; hue++) { GColour c; c.SetHLS(hue * HueStep, SatStart - ((sat * SatRange) / 7), 255); c.ToRGB(); Entry &en = e.New(); en.r.ZOff(Px-1, Px-1); en.r.Offset(x + (hue * PxSp), y); en.c = c; } y += PxSp; } SetParent(d->View); GRect r(0, 0, 12 + (8 * PxSp) - 1, y + 6 - 1); r.Offset(p.x, p.y); SetPos(r); Visible(true); } void SelectColour::OnPaint(GSurface *pDC) { pDC->Colour(LC_MED, 24); pDC->Rectangle(); for (unsigned i=0; iColour(e[i].c); pDC->Rectangle(&e[i].r); } } void SelectColour::OnMouseClick(GMouse &m) { if (m.Down()) { for (unsigned i=0; iValues[Type] = (int64)e[i].c.c32(); d->View->Invalidate(d->Areas + Type); d->OnStyleChange(Type); Visible(false); break; } } } } void SelectColour::Visible(bool i) { GPopup::Visible(i); if (!i) { d->View->Focus(true); delete this; } } /////////////////////////////////////////////////////////////////////////////// #define EMOJI_PAD 2 #include "Emoji.h" int EmojiMenu::Cur = 0; EmojiMenu::EmojiMenu(GRichTextPriv *priv, GdcPt2 p) : GPopup(priv->View) { d = priv; d->GetEmojiImage(); int MaxIdx = 0; GRange EmojiBlocks[2] = { GRange(0x203c, 0x3299 - 0x203c + 1), GRange(0x1f004, 0x1f6c5 - 0x1f004 + 1) }; LHashTbl, int> Map; for (int b=0; b= 0) { Map.Add(Idx, u); MaxIdx = MAX(MaxIdx, Idx); } } } int Sz = EMOJI_CELL_SIZE - 1; int PaneCount = 5; int PaneSz = Map.Length() / PaneCount; int ImgIdx = 0; int PaneSelectSz = SysFont->GetHeight() * 2; int Rows = (PaneSz + EMOJI_GROUP_X - 1) / EMOJI_GROUP_X; GRect r(0, 0, (EMOJI_CELL_SIZE + EMOJI_PAD) * EMOJI_GROUP_X + EMOJI_PAD, (EMOJI_CELL_SIZE + EMOJI_PAD) * Rows + EMOJI_PAD + PaneSelectSz); r.Offset(p.x, p.y); SetPos(r); for (int pi = 0; pi < PaneCount; pi++) { Pane &p = Panes[pi]; int Wid = X() - (EMOJI_PAD*2); p.Btn.x1 = EMOJI_PAD + (pi * Wid / PaneCount); p.Btn.y1 = EMOJI_PAD; p.Btn.x2 = EMOJI_PAD + ((pi + 1) * Wid / PaneCount) - 1; p.Btn.y2 = EMOJI_PAD + PaneSelectSz; int Dx = EMOJI_PAD; int Dy = p.Btn.y2 + 1; while ((int)p.e.Length() < PaneSz && ImgIdx <= MaxIdx) { uint32 u = Map.Find(ImgIdx); if (u) { Emoji &Ch = p.e.New(); Ch.u = u; int Sx = ImgIdx % EMOJI_GROUP_X; int Sy = ImgIdx / EMOJI_GROUP_X; Ch.Src.ZOff(Sz, Sz); Ch.Src.Offset(Sx * EMOJI_CELL_SIZE, Sy * EMOJI_CELL_SIZE); Ch.Dst.ZOff(Sz, Sz); Ch.Dst.Offset(Dx, Dy); Dx += EMOJI_PAD + EMOJI_CELL_SIZE; if (Dx + EMOJI_PAD + EMOJI_CELL_SIZE >= r.X()) { Dx = EMOJI_PAD; Dy += EMOJI_PAD + EMOJI_CELL_SIZE; } } ImgIdx++; } } SetParent(d->View); Visible(true); } void EmojiMenu::OnPaint(GSurface *pDC) { GAutoPtr DblBuf; if (!pDC->SupportsAlphaCompositing()) DblBuf.Reset(new GDoubleBuffer(pDC)); pDC->Colour(LC_MED, 24); pDC->Rectangle(); GSurface *EmojiImg = d->GetEmojiImage(); if (EmojiImg) { pDC->Op(GDC_ALPHA); for (unsigned i=0; iColour(LC_LIGHT, 24); pDC->Rectangle(&p.Btn); } SysFont->Fore(LC_TEXT); SysFont->Transparent(true); Ds.Draw(pDC, p.Btn.x1 + ((p.Btn.X()-Ds.X())>>1), p.Btn.y1 + ((p.Btn.Y()-Ds.Y())>>1)); } Pane &p = Panes[Cur]; for (unsigned i=0; iBlt(g.Dst.x1, g.Dst.y1, EmojiImg, &g.Src); } } else { GRect c = GetClient(); GDisplayString Ds(SysFont, "Loading..."); SysFont->Colour(LC_TEXT, LC_MED); SysFont->Transparent(true); Ds.Draw(pDC, (c.X()-Ds.X())>>1, (c.Y()-Ds.Y())>>1); } } bool EmojiMenu::InsertEmoji(uint32 Ch) { if (!d->Cursor || !d->Cursor->Blk) return false; AutoTrans Trans(new GRichTextPriv::Transaction); if (!d->Cursor->Blk->AddText(NoTransaction, d->Cursor->Offset, &Ch, 1, NULL)) return false; AutoCursor c(new BlkCursor(*d->Cursor)); c->Offset++; d->SetCursor(c); d->AddTrans(Trans); d->Dirty = true; d->InvalidateDoc(NULL); d->View->SendNotify(GNotifyDocChanged); return true; } void EmojiMenu::OnMouseClick(GMouse &m) { if (m.Down()) { for (unsigned i=0; iView->Focus(true); delete this; } } /////////////////////////////////////////////////////////////////////////////// class GRichTextEdit_Factory : public GViewFactory { GView *NewView(const char *Class, GRect *Pos, const char *Text) { if (_stricmp(Class, "GRichTextEdit") == 0) { return new GRichTextEdit(-1, 0, 0, 2000, 2000); } return 0; } } RichTextEdit_Factory; diff --git a/src/common/Widgets/Editor/GRichTextEditPriv.cpp b/src/common/Widgets/Editor/GRichTextEditPriv.cpp --- a/src/common/Widgets/Editor/GRichTextEditPriv.cpp +++ b/src/common/Widgets/Editor/GRichTextEditPriv.cpp @@ -1,2484 +1,2484 @@ #include "Lgi.h" #include "GRichTextEdit.h" #include "GRichTextEditPriv.h" #include "GScrollBar.h" #include "GCssTools.h" /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -bool Utf16to32(GArray &Out, const uint16 *In, int Len) +bool Utf16to32(GArray &Out, const uint16_t *In, int Len) { if (Len == 0) { Out.Length(0); return true; } // Count the length of utf32 chars... const uint16 *Ptr = In; ssize_t Bytes = sizeof(*In) * Len; int Chars = 0; while ( Bytes >= sizeof(*In) && LgiUtf16To32(Ptr, Bytes) > 0) Chars++; // Set the output buffer size.. if (!Out.Length(Chars)) return false; // Convert the string... Ptr = (uint16*)In; Bytes = sizeof(*In) * Len; uint32 *o = &Out[0]; uint32 *e = o + Out.Length(); while ( Bytes >= sizeof(*In)) { *o++ = LgiUtf16To32(Ptr, Bytes); } LgiAssert(o == e); return true; } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// const char *GRichEditElemContext::GetElement(GRichEditElem *obj) { return obj->Tag; } const char *GRichEditElemContext::GetAttr(GRichEditElem *obj, const char *Attr) { const char *a = NULL; obj->Get(Attr, a); return a; } bool GRichEditElemContext::GetClasses(GString::Array &Classes, GRichEditElem *obj) { const char *c; if (!obj->Get("class", c)) return false; GString cls = c; Classes = cls.Split(" "); return Classes.Length() > 0; } GRichEditElem *GRichEditElemContext::GetParent(GRichEditElem *obj) { return dynamic_cast(obj->Parent); } GArray GRichEditElemContext::GetChildren(GRichEditElem *obj) { GArray a; for (unsigned i=0; iChildren.Length(); i++) a.Add(dynamic_cast(obj->Children[i])); return a; } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// GCssCache::GCssCache() { Idx = 1; } GCssCache::~GCssCache() { Styles.DeleteObjects(); } -uint32 GCssCache::GetStyles() +uint32_t GCssCache::GetStyles() { uint32 c = 0; for (unsigned i=0; iRefCount != 0; } return c; } void GCssCache::ZeroRefCounts() { for (unsigned i=0; iRefCount = 0; } } bool GCssCache::OutputStyles(GStream &s, int TabDepth) { char Tabs[64]; memset(Tabs, '\t', TabDepth); Tabs[TabDepth] = 0; for (unsigned i=0; iRefCount > 0) { s.Print("%s.%s {\n", Tabs, ns->Name.Get()); GAutoString a = ns->ToString(); GString all = a.Get(); GString::Array lines = all.Split("\n"); for (unsigned n=0; n &s) { if (!s) return NULL; // Look through existing styles for a match... for (unsigned i=0; iName.Printf("%sStyle%i", p?p:"", Idx++); *(GCss*)ns = *s.Get(); Styles.Add(ns); #if 0 // _DEBUG GAutoString ss = ns->ToString(); if (ss) LgiTrace("%s = %s\n", ns->Name.Get(), ss.Get()); #endif } return ns; } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// BlockCursorState::BlockCursorState(bool cursor, GRichTextPriv::BlockCursor *c) { Cursor = cursor; Offset = c->Offset; LineHint = c->LineHint; BlockUid = c->Blk->GetUid(); } bool BlockCursorState::Apply(GRichTextPriv *Ctx, bool Forward) { GAutoPtr &Bc = Cursor ? Ctx->Cursor : Ctx->Selection; if (!Bc) return false; ssize_t o = Bc->Offset; int lh = Bc->LineHint; int uid = Bc->Blk->GetUid(); int Index; GRichTextPriv::Block *b; if (!Ctx->GetBlockByUid(b, BlockUid, &Index)) return false; if (b != Bc->Blk) Bc.Reset(new GRichTextPriv::BlockCursor(b, Offset, LineHint)); else { Bc->Offset = Offset; Bc->LineHint = LineHint; } Offset = o; LineHint = lh; BlockUid = uid; return true; } /// This is the simplest form of undo, just save the entire block state, and restore it if needed CompleteTextBlockState::CompleteTextBlockState(GRichTextPriv *Ctx, GRichTextPriv::TextBlock *Tb) { if (Ctx->Cursor) Cur.Reset(new BlockCursorState(true, Ctx->Cursor)); if (Ctx->Selection) Sel.Reset(new BlockCursorState(false, Ctx->Selection)); if (Tb) { Uid = Tb->GetUid(); Blk.Reset(new GRichTextPriv::TextBlock(Tb)); } } bool CompleteTextBlockState::Apply(GRichTextPriv *Ctx, bool Forward) { int Index; GRichTextPriv::TextBlock *b; if (!Ctx->GetBlockByUid(b, Uid, &Index)) return false; // Swap the local state with the block in the ctx Blk->UpdateSpellingAndLinks(NULL, GRange(0, Blk->Length())); Ctx->Blocks[Index] = Blk.Release(); Blk.Reset(b); // Update cursors if (Cur) Cur->Apply(Ctx, Forward); if (Sel) Sel->Apply(Ctx, Forward); return true; } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// MultiBlockState::MultiBlockState(GRichTextPriv *ctx, ssize_t Start) { Ctx = ctx; Index = Start; Length = -1; } bool MultiBlockState::Apply(GRichTextPriv *Ctx, bool Forward) { if (Index < 0 || Length < 0) { LgiAssert(!"Missing parameters"); return false; } // Undo: Swap 'Length' blocks Ctx->Blocks with Blks ssize_t OldLen = Blks.Length(); bool Status = Blks.SwapRange(GRange(0, OldLen), Ctx->Blocks, GRange(Index, Length)); if (Status) Length = OldLen; return Status; } bool MultiBlockState::Copy(ssize_t Idx) { if (!Ctx->Blocks.AddressOf(Idx)) return false; GRichTextPriv::Block *b = Ctx->Blocks[Idx]->Clone(); if (!b) return false; Blks.Add(b); return true; } bool MultiBlockState::Cut(ssize_t Idx) { if (!Ctx->Blocks.AddressOf(Idx)) return false; GRichTextPriv::Block *b = Ctx->Blocks[Idx]; if (!b) return false; Blks.Add(b); return Ctx->Blocks.DeleteAt(Idx, true); } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// GRichTextPriv::GRichTextPriv(GRichTextEdit *view, GRichTextPriv **Ptr) : GHtmlParser(view), GFontCache(SysFont) { if (Ptr) *Ptr = this; BlinkTs = 0; View = view; Log = &LogBuffer; NextUid = 1; UndoPos = 0; WordSelectMode = false; Dirty = false; ScrollOffsetPx = 0; ShowTools = true; ScrollChange = false; DocumentExtent.x = 0; DocumentExtent.y = 0; SpellCheck = NULL; SpellDictionaryLoaded = false; HtmlLinkAsCid = false; ScrollLinePx = SysFont->GetHeight(); if (Font.Reset(new GFont)) *Font = *SysFont; for (unsigned i=0; i 1000) { LgiTrace("%s:%i - Waiting for blocks: %i\n", _FL, (int)(Now-Start)); Msg = Now; } if (Now - Start > 10000) { LgiAssert(0); Start = Now; } } } Empty(); } bool GRichTextPriv::DeleteSelection(Transaction *Trans, char16 **Cut) { if (!Cursor || !Selection) return false; GArray DeletedText; GArray *DelTxt = Cut ? &DeletedText : NULL; bool Cf = CursorFirst(); GRichTextPriv::BlockCursor *Start = Cf ? Cursor : Selection; GRichTextPriv::BlockCursor *End = Cf ? Selection : Cursor; if (Start->Blk == End->Blk) { // In the same block... just delete the text ssize_t Len = End->Offset - Start->Offset; GRichTextPriv::Block *NextBlk = Next(Start->Blk); if (Len >= Start->Blk->Length() && NextBlk) { // Delete entire block ssize_t i = Blocks.IndexOf(Start->Blk); GAutoPtr MultiState(new MultiBlockState(this, i)); MultiState->Cut(i); MultiState->Length = 0; Start->Set(NextBlk, 0, 0); Trans->Add(MultiState.Release()); } else { Start->Blk->DeleteAt(Trans, Start->Offset, Len, DelTxt); } } else { // Multi-block delete... ssize_t i = Blocks.IndexOf(Start->Blk); ssize_t e = Blocks.IndexOf(End->Blk); GAutoPtr MultiState(new MultiBlockState(this, i)); // 1) Delete all the content to the end of the first block ssize_t StartLen = Start->Blk->Length(); if (Start->Offset < StartLen) { MultiState->Copy(i++); Start->Blk->DeleteAt(NoTransaction, Start->Offset, StartLen - Start->Offset, DelTxt); } else if (Start->Offset == StartLen) { // This can happen because there is an implied '\n' at the end of each block. The // next block always starts on a new line. We just increment the index 'i' to avoid // the next loop deleting the start block. i++; // If the start and end blocks can be merged, the new line will eventually get removed // then. If not, then... well whatevs. } else { LgiAssert(0); return Error(_FL, "Cursor outside start block."); } // 2) Delete any blocks between 'Start' and 'End' if (i >= 0) { while (Blocks[i] != End->Blk && i < (int)Blocks.Length()) { GRichTextPriv::Block *b = Blocks[i]; b->CopyAt(0, -1, DelTxt); printf("%s:%i - inter, %i\n", _FL, (int)i); MultiState->Cut(i); e--; } } else { LgiAssert(0); return Error(_FL, "Start block has no index.");; } if (End->Offset > 0) { // 3) Delete any text up to the Cursor in the 'End' block MultiState->Copy(i); printf("%s:%i - end, %i-%i, %i\n", _FL, (int)0, (int)End->Offset, (int)End->Blk->Length()); End->Blk->DeleteAt(NoTransaction, 0, End->Offset, DelTxt); } // Try and merge the start and end blocks bool MergeOk = Merge(NoTransaction, Start->Blk, End->Blk); MultiState->Length = MergeOk ? 1 : 2; Trans->Add(MultiState.Release()); } // Set the cursor and update the screen Cursor->Set(Start->Blk, Start->Offset, Start->LineHint); Selection.Reset(); View->Invalidate(); if (Cut) { DelTxt->Add(0); *Cut = (char16*)LgiNewConvertCp(LGI_WideCharset, &DelTxt->First(), "utf-32", DelTxt->Length()*sizeof(uint32)); } return true; } GRichTextPriv::Block *GRichTextPriv::Next(Block *b) { ssize_t Idx = Blocks.IndexOf(b); if (Idx < 0) return NULL; if (++Idx >= (int)Blocks.Length()) return NULL; return Blocks[Idx]; } GRichTextPriv::Block *GRichTextPriv::Prev(Block *b) { ssize_t Idx = Blocks.IndexOf(b); if (Idx <= 0) return NULL; return Blocks[--Idx]; } bool GRichTextPriv::AddTrans(GAutoPtr &t) { // Delete any transaction history after 'UndoPos' for (ssize_t i=UndoPos; i UndoPos) { // Forward in que Transaction *t = UndoQue[UndoPos]; if (!t->Apply(this, true)) return false; UndoPos++; } else if (Pos < UndoPos) { Transaction *t = UndoQue[UndoPos-1]; if (!t->Apply(this, false)) return false; UndoPos--; } else break; // We are done } Dirty = true; InvalidateDoc(NULL); return true; } bool GRichTextPriv::IsBusy(bool Stop) { for (unsigned i=0; iIsBusy(Stop)) return true; } return false; } bool GRichTextPriv::Error(const char *file, int line, const char *fmt, ...) { va_list Arg; va_start(Arg, fmt); GString s; LgiPrintf(s, fmt, Arg); va_end(Arg); GString Err; Err.Printf("%s:%i - Error: %s\n", file, line, s.Get()); Log->Write(Err, Err.Length()); Err = LogBuffer.NewGStr(); LgiTrace("%.*s", Err.Length(), Err.Get()); LgiAssert(0); return false; } void GRichTextPriv::UpdateStyleUI() { if (!Cursor || !Cursor->Blk) { Error(_FL, "Not a valid cursor."); return; } TextBlock *b = dynamic_cast(Cursor->Blk); GArray Styles; if (b) b->GetTextAt(Cursor->Offset, Styles); StyleText *st = Styles.Length() ? Styles.First() : NULL; GFont *f = NULL; if (st) f = GetFont(st->GetStyle()); else if (View) f = View->GetFont(); else if (LgiApp) f = SysFont; if (f) { Values[GRichTextEdit::FontFamilyBtn] = f->Face(); Values[GRichTextEdit::FontSizeBtn] = f->PointSize(); Values[GRichTextEdit::FontSizeBtn].CastString(); Values[GRichTextEdit::BoldBtn] = f->Bold(); Values[GRichTextEdit::ItalicBtn] = f->Italic(); Values[GRichTextEdit::UnderlineBtn] = f->Underline(); } else { Values[GRichTextEdit::FontFamilyBtn] = "(Unknown)"; } Values[GRichTextEdit::ForegroundColourBtn] = (int64) (st && st->Colours.Fore.IsValid() ? st->Colours.Fore.c32() : TextColour.c32()); Values[GRichTextEdit::BackgroundColourBtn] = (int64) (st && st->Colours.Back.IsValid() ? st->Colours.Back.c32() : 0); if (View) View->Invalidate(Areas + GRichTextEdit::ToolsArea); } void GRichTextPriv::ScrollTo(GRect r) { GRect Content = Areas[GRichTextEdit::ContentArea]; Content.Offset(-Content.x1, ScrollOffsetPx-Content.y1); if (ScrollLinePx > 0) { if (r.y1 < Content.y1) { int OffsetPx = MAX(r.y1, 0); View->SetScrollPos(0, OffsetPx / ScrollLinePx); InvalidateDoc(NULL); } if (r.y2 > Content.y2) { int OffsetPx = r.y2 - Content.Y(); View->SetScrollPos(0, (OffsetPx + ScrollLinePx - 1) / ScrollLinePx); InvalidateDoc(NULL); } } } void GRichTextPriv::InvalidateDoc(GRect *r) { // Transform the coordinates from doc to screen space GRect &c = Areas[GRichTextEdit::ContentArea]; if (r) { GRect t = *r; t.Offset(c.x1, c.y1 - ScrollOffsetPx); View->Invalidate(&t); } else View->Invalidate(&c); } void GRichTextPriv::EmptyDoc() { Block *Def = new TextBlock(this); if (Def) { Blocks.Add(Def); Cursor.Reset(new BlockCursor(Def, 0, 0)); UpdateStyleUI(); } } void GRichTextPriv::Empty() { // Delete cursors first to avoid hanging references Cursor.Reset(); Selection.Reset(); // Clear the block list.. Blocks.DeleteObjects(); } bool GRichTextPriv::Seek(BlockCursor *In, SeekType Dir, bool Select) { if (!In || !In->Blk || Blocks.Length() == 0) return Error(_FL, "Not a valid 'In' cursor, Blks=%i", Blocks.Length()); GAutoPtr c; bool Status = false; switch (Dir) { case SkLineEnd: case SkLineStart: case SkUpLine: case SkDownLine: { if (!c.Reset(new BlockCursor(*In))) break; Block *b = c->Blk; Status = b->Seek(Dir, *c); if (Status) break; if (Dir == SkUpLine) { // No more lines in the current block... // Move to the next block. ssize_t CurIdx = Blocks.IndexOf(b); ssize_t NewIdx = CurIdx - 1; if (NewIdx >= 0) { Block *b = Blocks[NewIdx]; if (!b) return Error(_FL, "No block at %i", NewIdx); c.Reset(new BlockCursor(b, b->Length(), b->GetLines() - 1)); LgiAssert(c->Offset >= 0); Status = true; } } else if (Dir == SkDownLine) { // No more lines in the current block... // Move to the next block. ssize_t CurIdx = Blocks.IndexOf(b); ssize_t NewIdx = CurIdx + 1; if ((unsigned)NewIdx < Blocks.Length()) { Block *b = Blocks[NewIdx]; if (!b) return Error(_FL, "No block at %i", NewIdx); c.Reset(new BlockCursor(b, 0, 0)); LgiAssert(c->Offset >= 0); Status = true; } } break; } case SkDocStart: { if (!c.Reset(new BlockCursor(Blocks[0], 0, 0))) break; Status = true; break; } case SkDocEnd: { if (Blocks.Length() == 0) break; Block *l = Blocks.Last(); if (!c.Reset(new BlockCursor(l, l->Length(), -1))) break; Status = true; break; } case SkLeftChar: { if (!c.Reset(new BlockCursor(*In))) break; if (c->Offset > 0) { GArray Ln; c->Blk->OffsetToLine(c->Offset, NULL, &Ln); if (Ln.Length() == 2 && c->LineHint == Ln.Last()) { c->LineHint = Ln.First(); } else { c->Offset--; if (c->Blk->OffsetToLine(c->Offset, NULL, &Ln)) c->LineHint = Ln.First(); } Status = true; } else // Seek to previous block { SeekPrevBlock: ssize_t Idx = Blocks.IndexOf(c->Blk); if (Idx < 0) { LgiAssert(0); break; } if (Idx == 0) break; // Beginning of document Block *b = Blocks[--Idx]; if (!b) { LgiAssert(0); break; } if (!c.Reset(new BlockCursor(b, b->Length(), b->GetLines()-1))) break; Status = true; } break; } case SkLeftWord: { if (!c.Reset(new BlockCursor(*In))) break; if (c->Offset > 0) { GArray a; c->Blk->CopyAt(0, c->Offset, &a); ssize_t i = c->Offset; while (i > 0 && IsWordBreakChar(a[i-1])) i--; while (i > 0 && !IsWordBreakChar(a[i-1])) i--; c->Offset = i; GArray Ln; if (c->Blk->OffsetToLine(c->Offset, NULL, &Ln)) c->LineHint = Ln[0]; Status = true; } else // Seek into previous block? { goto SeekPrevBlock; } break; } case SkRightChar: { if (!c.Reset(new BlockCursor(*In))) break; if (c->Offset < c->Blk->Length()) { GArray Ln; if (c->Blk->OffsetToLine(c->Offset, NULL, &Ln) && Ln.Length() == 2 && c->LineHint == Ln.First()) { c->LineHint = Ln.Last(); } else { c->Offset++; if (c->Blk->OffsetToLine(c->Offset, NULL, &Ln)) c->LineHint = Ln.First(); } Status = true; } else // Seek to next block { SeekNextBlock: ssize_t Idx = Blocks.IndexOf(c->Blk); if (Idx < 0) return Error(_FL, "Block ptr index error."); if (Idx >= (int)Blocks.Length() - 1) break; // End of document Block *b = Blocks[++Idx]; if (!b) return Error(_FL, "No block at %i.", Idx); if (!c.Reset(new BlockCursor(b, 0, 0))) break; Status = true; } break; } case SkRightWord: { if (!c.Reset(new BlockCursor(*In))) break; if (c->Offset < c->Blk->Length()) { GArray a; ssize_t RemainingCh = c->Blk->Length() - c->Offset; c->Blk->CopyAt(c->Offset, RemainingCh, &a); int i = 0; while (i < RemainingCh && !IsWordBreakChar(a[i])) i++; while (i < RemainingCh && IsWordBreakChar(a[i])) i++; c->Offset += i; GArray Ln; if (c->Blk->OffsetToLine(c->Offset, NULL, &Ln)) c->LineHint = Ln.Last(); else c->LineHint = -1; Status = true; } else // Seek into next block? { goto SeekNextBlock; } break; } case SkUpPage: { GRect &Content = Areas[GRichTextEdit::ContentArea]; int LineHint = -1; int TargetY = In->Pos.y1 - Content.Y(); ssize_t Idx = HitTest(In->Pos.x1, MAX(TargetY, 0), LineHint); if (Idx >= 0) { ssize_t Offset = -1; Block *b = GetBlockByIndex(Idx, &Offset); if (b) { if (!c.Reset(new BlockCursor(b, Offset, LineHint))) break; Status = true; } } break; } case SkDownPage: { GRect &Content = Areas[GRichTextEdit::ContentArea]; int LineHint = -1; int TargetY = In->Pos.y1 + Content.Y(); ssize_t Idx = HitTest(In->Pos.x1, MIN(TargetY, DocumentExtent.y-1), LineHint); if (Idx >= 0) { ssize_t Offset = -1; int BlkIdx = -1; ssize_t CursorBlkIdx = Blocks.IndexOf(Cursor->Blk); Block *b = GetBlockByIndex(Idx, &Offset, &BlkIdx); if (!b || BlkIdx < CursorBlkIdx || (BlkIdx == CursorBlkIdx && Offset < Cursor->Offset)) { LgiAssert(!"GetBlockByIndex failed.\n"); LgiTrace("%s:%i - GetBlockByIndex failed.\n", _FL); } if (b) { if (!c.Reset(new BlockCursor(b, Offset, LineHint))) break; Status = true; } } break; } default: { return Error(_FL, "Unknown seek type."); } } if (Status) SetCursor(c, Select); return Status; } bool GRichTextPriv::CursorFirst() { if (!Cursor || !Selection) return true; ssize_t CIdx = Blocks.IndexOf(Cursor->Blk); ssize_t SIdx = Blocks.IndexOf(Selection->Blk); if (CIdx != SIdx) return CIdx < SIdx; return Cursor->Offset < Selection->Offset; } bool GRichTextPriv::SetCursor(GAutoPtr c, bool Select) { GRect InvalidRc(0, 0, -1, -1); if (!c || !c->Blk) return Error(_FL, "Invalid cursor."); if (Select && !Selection) { // Selection starting... save cursor as selection end point if (Cursor) InvalidRc = Cursor->Line; Selection = Cursor; } else if (!Select && Selection) { // Selection ending... invalidate selection region and delete // selection end point GRect r = SelectionRect(); InvalidateDoc(&r); Selection.Reset(); // LgiTrace("Ending selection delete(sel) Idx=%i\n", i); } else if (Select && Cursor) { // Changing selection... InvalidRc = Cursor->Line; } if (Cursor && !Select) { // Just moving cursor InvalidateDoc(&Cursor->Pos); } if (!Cursor) Cursor.Reset(new BlockCursor(*c)); else Cursor = c; // LgiTrace("%s:%i - SetCursor: %i, line: %i\n", _FL, Cursor->Offset, Cursor->LineHint); if (Cursor && Selection && *Cursor == *Selection) Selection.Reset(); Cursor->Blk->GetPosFromIndex(Cursor); UpdateStyleUI(); #if DEBUG_OUTLINE_CUR_DISPLAY_STR || DEBUG_OUTLINE_CUR_STYLE_TEXT InvalidateDoc(NULL); #else if (Select) InvalidRc.Union(&Cursor->Line); else InvalidateDoc(&Cursor->Pos); if (InvalidRc.Valid()) { // Update the screen InvalidateDoc(&InvalidRc); } #endif // Check the cursor is on the visible part of the document. if (Cursor->Pos.Valid()) ScrollTo(Cursor->Pos); return true; } GRect GRichTextPriv::SelectionRect() { GRect SelRc; if (Cursor) { SelRc = Cursor->Line; if (Selection) SelRc.Union(&Selection->Line); } else if (Selection) { SelRc = Selection->Line; } return SelRc; } ssize_t GRichTextPriv::IndexOfCursor(BlockCursor *c) { if (!c || !c->Blk) { Error(_FL, "Invalid cursor param."); return -1; } int CharPos = 0; for (unsigned i=0; iBlk == b) return CharPos + c->Offset; CharPos += b->Length(); } LgiAssert(0); return -1; } GdcPt2 GRichTextPriv::ScreenToDoc(int x, int y) { GRect &Content = Areas[GRichTextEdit::ContentArea]; return GdcPt2(x - Content.x1, y - Content.y1 + ScrollOffsetPx); } GdcPt2 GRichTextPriv::DocToScreen(int x, int y) { GRect &Content = Areas[GRichTextEdit::ContentArea]; return GdcPt2(x + Content.x1, y + Content.y1 - ScrollOffsetPx); } bool GRichTextPriv::Merge(Transaction *Trans, Block *a, Block *b) { TextBlock *ta = dynamic_cast(a); TextBlock *tb = dynamic_cast(b); if (!ta || !tb) return false; ta->Txt.Add(tb->Txt); ta->LayoutDirty = true; ta->Len += tb->Len; tb->Txt.Length(0); Blocks.Delete(b, true); Dirty = true; return true; } GSurface *GEmojiContext::GetEmojiImage() { if (!EmojiImg) { GString p = LGetSystemPath(LSP_APP_INSTALL); if (!p) { LgiTrace("%s:%i - No app install path.\n", _FL); return NULL; } char File[MAX_PATH] = ""; LgiMakePath(File, sizeof(File), p, "..\\src\\common\\Text\\Emoji\\EmojiMap.png"); GAutoString a; if (!FileExists(File)) a.Reset(LgiFindFile("EmojiMap.png")); EmojiImg.Reset(GdcD->Load(a ? a : File, false)); } return EmojiImg; } ssize_t GRichTextPriv::HitTest(int x, int y, int &LineHint, Block **Blk) { int CharPos = 0; HitTestResult r(x, y); if (Blocks.Length() == 0) { Error(_FL, "No blocks."); return -1; } Block *b = Blocks.First(); GRect rc = b->GetPos(); if (y < rc.y1) { if (Blk) *Blk = b; return 0; } for (unsigned i=0; iGetPos(); bool Over = y >= p.y1 && y <= p.y2; if (b->HitTest(r)) { LineHint = r.LineHint; if (Blk) *Blk = b; return CharPos + r.Idx; } else if (Over) { Error(_FL, "Block failed to hit, i=%i, pos=%s, y=%i.", i, p.GetStr(), y); } CharPos += b->Length(); } b = Blocks.Last(); rc = b->GetPos(); if (y > rc.y2) { if (Blk) *Blk = b; return CharPos + b->Length(); } return -1; } bool GRichTextPriv::CursorFromPos(int x, int y, GAutoPtr *Cursor, ssize_t *GlobalIdx) { int CharPos = 0; HitTestResult r(x, y); for (unsigned i=0; iHitTest(r)) { if (Cursor) Cursor->Reset(new BlockCursor(b, r.Idx, r.LineHint)); if (GlobalIdx) *GlobalIdx = CharPos + r.Idx; return true; } CharPos += b->Length(); } return false; } GRichTextPriv::Block *GRichTextPriv::GetBlockByIndex(ssize_t Index, ssize_t *Offset, int *BlockIdx, int *LineCount) { int CharPos = 0; int Lines = 0; for (unsigned i=0; iLength(); int Ln = b->GetLines(); if (Index >= CharPos && Index < CharPos + Len) { if (BlockIdx) *BlockIdx = i; if (Offset) *Offset = Index - CharPos; return b; } CharPos += b->Length(); Lines += Ln; } Block *b = Blocks.Last(); if (Offset) *Offset = b->Length(); if (BlockIdx) *BlockIdx = (int)Blocks.Length() - 1; if (LineCount) *LineCount = Lines; return b; } bool GRichTextPriv::Layout(GScrollBar *&ScrollY) { Flow f(this); ScrollLinePx = View->GetFont()->GetHeight(); LgiAssert(ScrollLinePx > 0); if (ScrollLinePx <= 0) ScrollLinePx = 16; GRect Client = Areas[GRichTextEdit::ContentArea]; Client.Offset(-Client.x1, -Client.y1); DocumentExtent.x = Client.X(); GCssTools Ct(this, Font); GRect Content = Ct.ApplyPadding(Client); f.Left = Content.x1; f.Right = Content.x2; f.Top = f.CurY = Content.y1; for (unsigned i=0; iOnLayout(f); if ((f.CurY > Client.Y()) && ScrollY==NULL && !ScrollChange) { // We need to add a scroll bar View->SetScrollBars(false, true); View->Invalidate(); ScrollChange = true; return false; } } DocumentExtent.y = f.CurY + (Client.y2 - Content.y2); if (ScrollY) { int Lines = (f.CurY + ScrollLinePx - 1) / ScrollLinePx; int PageLines = (Client.Y() + ScrollLinePx - 1) / ScrollLinePx; ScrollY->SetPage(PageLines); ScrollY->SetLimits(0, Lines); } if (Cursor) { LgiAssert(Cursor->Blk != NULL); if (Cursor->Blk) Cursor->Blk->GetPosFromIndex(Cursor); } return true; } void GRichTextPriv::OnStyleChange(GRichTextEdit::RectType t) { GCss s; switch (t) { case GRichTextEdit::FontFamilyBtn: { GCss::StringsDef Fam(Values[t].Str()); s.FontFamily(Fam); if (!ChangeSelectionStyle(&s, true)) StyleDirty.Add(t); break; } case GRichTextEdit::FontSizeBtn: { double Pt = Values[t].CastDouble(); s.FontSize(GCss::Len(GCss::LenPt, (float)Pt)); if (!ChangeSelectionStyle(&s, true)) StyleDirty.Add(t); break; } case GRichTextEdit::BoldBtn: { s.FontWeight(GCss::FontWeightBold); if (!ChangeSelectionStyle(&s, Values[t].CastBool())) StyleDirty.Add(t); break; } case GRichTextEdit::ItalicBtn: { s.FontStyle(GCss::FontStyleItalic); if (!ChangeSelectionStyle(&s, Values[t].CastBool())) StyleDirty.Add(t); break; } case GRichTextEdit::UnderlineBtn: { s.TextDecoration(GCss::TextDecorUnderline); if (ChangeSelectionStyle(&s, Values[t].CastBool())) StyleDirty.Add(t); break; } case GRichTextEdit::ForegroundColourBtn: { s.Color(GCss::ColorDef(GCss::ColorRgb, (uint32) Values[t].Value.Int64)); if (!ChangeSelectionStyle(&s, true)) StyleDirty.Add(t); break; } case GRichTextEdit::BackgroundColourBtn: { s.BackgroundColor(GCss::ColorDef(GCss::ColorRgb, (uint32) Values[t].Value.Int64)); if (!ChangeSelectionStyle(&s, true)) StyleDirty.Add(t); break; } default: break; } } bool GRichTextPriv::ChangeSelectionStyle(GCss *Style, bool Add) { if (!Selection) return false; GAutoPtr Trans(new Transaction); bool Cf = CursorFirst(); GRichTextPriv::BlockCursor *Start = Cf ? Cursor : Selection; GRichTextPriv::BlockCursor *End = Cf ? Selection : Cursor; if (Start->Blk == End->Blk) { // Change style in the same block... ssize_t Len = End->Offset - Start->Offset; if (!Start->Blk->ChangeStyle(Trans, Start->Offset, Len, Style, Add)) return false; } else { // Multi-block style change... // 1) Change style on the content to the end of the first block Start->Blk->ChangeStyle(Trans, Start->Offset, -1, Style, Add); // 2) Change style on blocks between 'Start' and 'End' ssize_t i = Blocks.IndexOf(Start->Blk); if (i >= 0) { for (++i; Blocks[i] != End->Blk && i < (int)Blocks.Length(); i++) { GRichTextPriv::Block *&b = Blocks[i]; if (!b->ChangeStyle(Trans, 0, -1, Style, Add)) return false; } } else { return Error(_FL, "Start block has no index."); } // 3) Change style up to the Cursor in the 'End' block if (!End->Blk->ChangeStyle(Trans, 0, End->Offset, Style, Add)) return false; } Cursor->Pos.ZOff(-1, -1); InvalidateDoc(NULL); AddTrans(Trans); return true; } void GRichTextPriv::PaintBtn(GSurface *pDC, GRichTextEdit::RectType t) { GRect r = Areas[t]; GVariant &v = Values[t]; bool Down = (v.Type == GV_BOOL && v.Value.Bool) || (BtnState[t].IsPress && BtnState[t].Pressed && BtnState[t].MouseOver); SysFont->Colour(LC_TEXT, BtnState[t].MouseOver ? LC_LIGHT : LC_MED); SysFont->Transparent(false); GColour Low(96, 96, 96); pDC->Colour(Down ? GColour::White : Low); pDC->Line(r.x1, r.y2, r.x2, r.y2); pDC->Line(r.x2, r.y1, r.x2, r.y2); pDC->Colour(Down ? Low : GColour::White); pDC->Line(r.x1, r.y1, r.x2, r.y1); pDC->Line(r.x1, r.y1, r.x1, r.y2); r.Size(1, 1); switch (v.Type) { case GV_STRING: { GDisplayString Ds(SysFont, v.Str()); Ds.Draw(pDC, r.x1 + ((r.X()-Ds.X())>>1) + Down, r.y1 + ((r.Y()-Ds.Y())>>1) + Down, &r); break; } case GV_INT64: { if (v.Value.Int64) { pDC->Colour((uint32)v.Value.Int64, 32); pDC->Rectangle(&r); } else { // Transparent int g[2] = { 128, 192 }; pDC->ClipRgn(&r); for (int y=0; y>1)%2) ^ ((x>>1)%2); pDC->Colour(GColour(g[i],g[i],g[i])); pDC->Rectangle(r.x1+x, r.y1+y, r.x1+x+1, r.y1+y+1); } } pDC->ClipRgn(NULL); } break; } case GV_BOOL: { const char *Label = NULL; switch (t) { case GRichTextEdit::BoldBtn: Label = "B"; break; case GRichTextEdit::ItalicBtn: Label = "I"; break; case GRichTextEdit::UnderlineBtn: Label = "U"; break; default: break; } if (!Label) break; GDisplayString Ds(SysFont, Label); Ds.Draw(pDC, r.x1 + ((r.X()-Ds.X())>>1) + Down, r.y1 + ((r.Y()-Ds.Y())>>1) + Down, &r); break; } default: break; } } bool GRichTextPriv::MakeLink(TextBlock *tb, ssize_t Offset, ssize_t Len, GString Link) { if (!tb) return false; GArray st; if (!tb->GetTextAt(Offset, st)) return false; GAutoPtr ns(new GNamedStyle); if (ns) { if (st.Last()->GetStyle()) *ns = *st.Last()->GetStyle(); ns->TextDecoration(GCss::TextDecorUnderline); ns->Color(GCss::ColorDef(GCss::ColorRgb, GColour::Blue.c32())); GAutoPtr Trans(new Transaction); tb->ChangeStyle(Trans, Offset, Len, ns, true); if (tb->GetTextAt(Offset + 1, st)) { st.First()->Element = TAG_A; st.First()->Param = Link; } AddTrans(Trans); } return true; } bool GRichTextPriv::ClickBtn(GMouse &m, GRichTextEdit::RectType t) { switch (t) { case GRichTextEdit::FontFamilyBtn: { GString::Array Fonts; if (!GFontSystem::Inst()->EnumerateFonts(Fonts)) return Error(_FL, "EnumerateFonts failed."); bool UseSub = (SysFont->GetHeight() * Fonts.Length()) > (GdcD->Y() * 0.8); GSubMenu s; GSubMenu *Cur = NULL; int Idx = 1; char Last = 0; for (unsigned i=0; iAppendItem(f, Idx++); else break; } else { s.AppendItem(f, Idx++); } } GdcPt2 p(Areas[t].x1, Areas[t].y2 + 1); View->PointToScreen(p); int Result = s.Float(View, p.x, p.y, true); if (Result) { Values[t] = Fonts[Result-1]; View->Invalidate(Areas+t); OnStyleChange(t); } break; } case GRichTextEdit::FontSizeBtn: { static const char *Sizes[] = { "6", "7", "8", "9", "10", "11", "12", "14", "16", "18", "20", "24", "28", "32", "40", "50", "60", "80", "100", "120", 0 }; GSubMenu s; for (int Idx = 0; Sizes[Idx]; Idx++) s.AppendItem(Sizes[Idx], Idx+1); GdcPt2 p(Areas[t].x1, Areas[t].y2 + 1); View->PointToScreen(p); int Result = s.Float(View, p.x, p.y, true); if (Result) { Values[t] = Sizes[Result-1]; View->Invalidate(Areas+t); OnStyleChange(t); } break; } case GRichTextEdit::BoldBtn: case GRichTextEdit::ItalicBtn: case GRichTextEdit::UnderlineBtn: { Values[t] = !Values[t].CastBool(); View->Invalidate(Areas+t); OnStyleChange(t); break; } case GRichTextEdit::ForegroundColourBtn: case GRichTextEdit::BackgroundColourBtn: { GdcPt2 p(Areas[t].x1, Areas[t].y2 + 1); View->PointToScreen(p); new SelectColour(this, p, t); break; } case GRichTextEdit::MakeLinkBtn: { if (!Cursor || !Cursor->Blk) break; TextBlock *tb = dynamic_cast(Cursor->Blk); if (!tb) break; GArray st; if (!tb->GetTextAt(Cursor->Offset, st)) break; StyleText *a = st.Length() > 1 && st[1]->Element == TAG_A ? st[1] : st.First()->Element == TAG_A ? st[0] : NULL; if (a) { // Edit the existing link... GInput i(View, a->Param, "Edit link:", "Link"); if (i.DoModal()) { a->Param = i.GetStr(); } } else if (Selection) { // Turn current selection into link GInput i(View, NULL, "Edit link:", "Link"); if (i.DoModal()) { BlockCursor *Start = CursorFirst() ? Cursor : Selection; BlockCursor *End = CursorFirst() ? Selection : Cursor; if (!Start || !End) return false; if (Start->Blk != End->Blk) { LgiMsg(View, "Selection too large.", "Error"); return false; } MakeLink(tb, Start->Offset, End->Offset - Start->Offset, i.GetStr()); } } break; } case GRichTextEdit::RemoveLinkBtn: { if (!Cursor || !Cursor->Blk) break; TextBlock *tb = dynamic_cast(Cursor->Blk); if (!tb) break; GArray st; if (!tb->GetTextAt(Cursor->Offset, st)) break; StyleText *a = st.Length() > 1 && st[1]->Element == TAG_A ? st[1] : st.First()->Element == TAG_A ? st[0] : NULL; if (a) { // Remove the existing link... a->Element = CONTENT; a->Param.Empty(); GAutoPtr s(new GCss); GNamedStyle *Ns = a->GetStyle(); if (Ns) *s = *Ns; if (s->TextDecoration() == GCss::TextDecorUnderline) s->DeleteProp(GCss::PropTextDecoration); if ((GColour)s->Color() == GColour::Blue) s->DeleteProp(GCss::PropColor); Ns = AddStyleToCache(s); a->SetStyle(Ns); tb->LayoutDirty = true; InvalidateDoc(NULL); } break; } case GRichTextEdit::RemoveStyleBtn: { GCss s; ChangeSelectionStyle(&s, false); break; } /* case GRichTextEdit::CapabilityBtn: { View->OnCloseInstaller(); break; } */ case GRichTextEdit::EmojiBtn: { GdcPt2 p(Areas[t].x1, Areas[t].y2 + 1); View->PointToScreen(p); new EmojiMenu(this, p); break; } case GRichTextEdit::HorzRuleBtn: { InsertHorzRule(); break; } default: return false; } return true; } bool GRichTextPriv::InsertHorzRule() { if (!Cursor || !Cursor->Blk) return false; TextBlock *tb = dynamic_cast(Cursor->Blk); if (!tb) return false; GAutoPtr Trans(new Transaction); DeleteSelection(Trans, NULL); ssize_t InsertIdx = Blocks.IndexOf(tb) + 1; GRichTextPriv::Block *After = NULL; if (Cursor->Offset == 0) { InsertIdx--; } else if (Cursor->Offset < tb->Length()) { After = tb->Split(Trans, Cursor->Offset); if (!After) return false; tb->StripLast(Trans); } HorzRuleBlock *Hr = new HorzRuleBlock(this); if (!Hr) return false; Blocks.AddAt(InsertIdx++, Hr); if (After) Blocks.AddAt(InsertIdx++, After); AddTrans(Trans); InvalidateDoc(NULL); GAutoPtr c(new BlockCursor(Hr, 0, 0)); return SetCursor(c); } void GRichTextPriv::Paint(GSurface *pDC, GScrollBar *&ScrollY) { /* if (Areas[GRichTextEdit::CapabilityArea].Valid()) { GRect &t = Areas[GRichTextEdit::CapabilityArea]; pDC->Colour(GColour::Red); pDC->Rectangle(&t); int y = t.y1 + 4; for (unsigned i=0; iTransparent(true); SysFont->Colour(GColour::White, GColour::Red); Ds.Draw(pDC, t.x1 + 4, y); y += Ds.Y() + 4; } PaintBtn(pDC, GRichTextEdit::CapabilityBtn); } */ if (Areas[GRichTextEdit::ToolsArea].Valid()) { // Draw tools area... GRect &t = Areas[GRichTextEdit::ToolsArea]; #ifdef WIN32 GDoubleBuffer Buf(pDC, &t); #endif pDC->Colour(GColour(180, 180, 206)); pDC->Rectangle(&t); GRect r = t; r.Size(3, 3); #define AllocPx(sz, border) \ GRect(r.x1, r.y1, r.x1 + (int)(sz) - 1, r.y2); r.x1 += (int)(sz) + border Areas[GRichTextEdit::FontFamilyBtn] = AllocPx(100, 6); Areas[GRichTextEdit::FontSizeBtn] = AllocPx(40, 6); Areas[GRichTextEdit::BoldBtn] = AllocPx(r.Y(), 0); Areas[GRichTextEdit::ItalicBtn] = AllocPx(r.Y(), 0); Areas[GRichTextEdit::UnderlineBtn] = AllocPx(r.Y(), 6); Areas[GRichTextEdit::ForegroundColourBtn] = AllocPx(r.Y()*1.5, 0); Areas[GRichTextEdit::BackgroundColourBtn] = AllocPx(r.Y()*1.5, 6); { GDisplayString Ds(SysFont, TEXT_LINK); Areas[GRichTextEdit::MakeLinkBtn] = AllocPx(Ds.X() + 12, 0); } { GDisplayString Ds(SysFont, TEXT_REMOVE_LINK); Areas[GRichTextEdit::RemoveLinkBtn] = AllocPx(Ds.X() + 12, 6); } { GDisplayString Ds(SysFont, TEXT_REMOVE_STYLE); Areas[GRichTextEdit::RemoveStyleBtn] = AllocPx(Ds.X() + 12, 6); } for (unsigned int i=GRichTextEdit::EmojiBtn; iGetOrigin(Origin.x, Origin.y); GRect r = Areas[GRichTextEdit::ContentArea]; #if defined(WINDOWS) && !DEBUG_NO_DOUBLE_BUF GMemDC Mem(r.X(), r.Y(), pDC->GetColourSpace()); GSurface *pScreen = pDC; pDC = &Mem; r.Offset(-r.x1, -r.y1); #else pDC->ClipRgn(&r); #endif ScrollOffsetPx = ScrollY ? (int)(ScrollY->Value() * ScrollLinePx) : 0; pDC->SetOrigin(Origin.x-r.x1, Origin.y-r.y1+ScrollOffsetPx); int DrawPx = ScrollOffsetPx + Areas[GRichTextEdit::ContentArea].Y(); int ExtraPx = DrawPx > DocumentExtent.y ? DrawPx - DocumentExtent.y : 0; r.Set(0, 0, DocumentExtent.x-1, DocumentExtent.y-1); // Fill the padding... GCssTools ct(this, Font); r = ct.PaintPadding(pDC, r); // Fill the background... #if DEBUG_COVERAGE_CHECK pDC->Colour(GColour(255, 0, 255)); #else GCss::ColorDef cBack = BackgroundColor(); // pDC->Colour(cBack.IsValid() ? (GColour)cBack : GColour(LC_WORKSPACE, 24)); #endif pDC->Rectangle(&r); if (ExtraPx) pDC->Rectangle(0, DocumentExtent.y, DocumentExtent.x-1, DocumentExtent.y+ExtraPx); PaintContext Ctx; Ctx.pDC = pDC; Ctx.Cursor = Cursor; Ctx.Select = Selection; Ctx.Colours[Unselected].Fore.Set(LC_TEXT, 24); Ctx.Colours[Unselected].Back.Set(LC_WORKSPACE, 24); if (View->Focus()) { Ctx.Colours[Selected].Fore.Set(LC_FOCUS_SEL_FORE, 24); Ctx.Colours[Selected].Back.Set(LC_FOCUS_SEL_BACK, 24); } else { Ctx.Colours[Selected].Fore.Set(LC_NON_FOCUS_SEL_FORE, 24); Ctx.Colours[Selected].Back.Set(LC_NON_FOCUS_SEL_BACK, 24); } for (unsigned i=0; iOnPaint(Ctx); #if DEBUG_OUTLINE_BLOCKS pDC->Colour(GColour(192, 192, 192)); pDC->LineStyle(GSurface::LineDot); pDC->Box(&b->GetPos()); pDC->LineStyle(GSurface::LineSolid); #endif } } #ifdef _DEBUG pDC->Colour(GColour::Green); for (unsigned i=0; iBox(&DebugRects[i]); } #endif #if 0 // Outline the line the cursor is on if (Cursor) { pDC->Colour(GColour::Blue); pDC->LineStyle(GSurface::LineDot); pDC->Box(&Cursor->Line); } #endif #if defined(WINDOWS) && !DEBUG_NO_DOUBLE_BUF Mem.SetOrigin(0, 0); pScreen->Blt(Areas[GRichTextEdit::ContentArea].x1, Areas[GRichTextEdit::ContentArea].y1, &Mem); #endif } GHtmlElement *GRichTextPriv::CreateElement(GHtmlElement *Parent) { return new GRichEditElem(Parent); } bool GRichTextPriv::ToHtml(GArray *Media, BlockCursor *From, BlockCursor *To) { UtfNameCache.Reset(); if (!Blocks.Length()) return false; GStringPipe p(256); p.Print("\n" "\n" "\t\n"); ZeroRefCounts(); ssize_t Start = From ? Blocks.IndexOf(From->Blk) : 0; ssize_t End = To ? Blocks.IndexOf(To->Blk) : Blocks.Length() - 1; ssize_t StartIdx = From ? From->Offset : 0; ssize_t EndIdx = To ? To->Offset : Blocks.Last()->Length(); for (size_t i=Start; i<=End; i++) { Blocks[i]->IncAllStyleRefs(); } if (GetStyles()) { p.Print("\t\n"); } p.Print("\n" "\n"); for (size_t i=Start; i<=End; i++) { Block *b = Blocks[i]; GRange r; if (i == Start) r.Start = StartIdx; if (i == End) r.Len = EndIdx - r.Start; b->ToHtml(p, Media, r.Valid() ? &r : NULL); } p.Print("\n\n"); return UtfNameCache.Reset(p.NewStr()); } void GRichTextPriv::DumpBlocks() { LgiTrace("GRichTextPriv Blocks=%i\n", Blocks.Length()); for (unsigned i=0; iGetClass(), b->GetStyle(), b->GetStyle() ? b->GetStyle()->Name.Get() : NULL); b->Dump(); LgiTrace("}\n"); } } struct HtmlElementCb : public GCss::ElementCallback { const char *GetElement(GHtmlElement *obj) { return obj->Tag; } const char *GetAttr(GHtmlElement *obj, const char *Attr) { const char *Val = NULL; return obj->Get(Attr, Val) ? Val : NULL; } bool GetClasses(GString::Array &Classes, GHtmlElement *obj) { const char *Cls = NULL; if (!obj->Get("class", Cls)) return false; Classes = GString(Cls).SplitDelimit(); return true; } GHtmlElement *GetParent(GHtmlElement *obj) { return obj->Parent; } GArray GetChildren(GHtmlElement *obj) { return obj->Children; } }; bool GRichTextPriv::FromHtml(GHtmlElement *e, CreateContext &ctx, GCss *ParentStyle, int Depth) { char Sp[48]; int SpLen = MIN(Depth << 1, sizeof(Sp) - 1); memset(Sp, ' ', SpLen); Sp[SpLen] = 0; for (unsigned i = 0; i < e->Children.Length(); i++) { GHtmlElement *c = e->Children[i]; GAutoPtr Style; if (ParentStyle) Style.Reset(new GCss(*ParentStyle)); GCss::SelArray Matches; HtmlElementCb Cb; if (ctx.StyleStore.Match(Matches, &Cb, c) && Matches.Length() > 0 && (Style.Get() || Style.Reset(new GCss))) { for (auto s : Matches) { const char *p = s->Style; Style->Parse(p, GCss::ParseRelaxed); } } // Check to see if the element is block level and end the previous // paragraph if so. c->Info = c->Tag ? GHtmlStatic::Inst->GetTagInfo(c->Tag) : NULL; bool IsBlock = c->Info != NULL && c->Info->Block(); switch (c->TagId) { case TAG_STYLE: { char16 *Style = e->GetText(); if (ValidStrW(Style)) LgiAssert(!"Impl me."); continue; break; } case TAG_B: { if (!Style) Style.Reset(new GCss); if (Style) Style->FontWeight(GCss::FontWeightBold); break; } case TAG_I: { if (!Style) Style.Reset(new GCss); if (Style) Style->FontStyle(GCss::FontStyleItalic); break; } case TAG_BLOCKQUOTE: { if (!Style) Style.Reset(new GCss); if (Style) { Style->MarginTop(GCss::Len("0.5em")); Style->MarginBottom(GCss::Len("0.5em")); Style->MarginLeft(GCss::Len("1em")); if (ctx.Tb) ctx.Tb->StripLast(NoTransaction); } break; } case TAG_A: { if (!Style) Style.Reset(new GCss); if (Style) { Style->TextDecoration(GCss::TextDecorUnderline); Style->Color(GCss::ColorDef(GCss::ColorRgb, GColour::Blue.c32())); } break; } case TAG_HR: { if (ctx.Tb) ctx.Tb->StripLast(NoTransaction); // Fall through } case TAG_IMG: { ctx.Tb = NULL; IsBlock = true; break; } default: { break; } } const char *Css, *Class; if (c->Get("style", Css)) { if (!Style) Style.Reset(new GCss); if (Style) Style->Parse(Css, ParseRelaxed); } if (c->Get("class", Class)) { GCss::SelArray Selectors; GRichEditElemContext StyleCtx; if (ctx.StyleStore.Match(Selectors, &StyleCtx, dynamic_cast(c))) { for (unsigned n=0; nStyle; if (s) { if (!Style) Style.Reset(new GCss); if (Style) Style->Parse(s, GCss::ParseRelaxed); } } } } } GNamedStyle *CachedStyle = AddStyleToCache(Style); if ( (IsBlock && ctx.LastChar != '\n') || c->TagId == TAG_BR ) { if (!ctx.Tb && c->TagId == TAG_BR) { // Don't do this for IMG and HR layout. Blocks.Add(ctx.Tb = new TextBlock(this)); if (CachedStyle && ctx.Tb) ctx.Tb->SetStyle(CachedStyle); } if (ctx.Tb) { const uint32 Nl[] = {'\n', 0}; ctx.Tb->AddText(NoTransaction, -1, Nl, 1, NULL); ctx.LastChar = '\n'; ctx.StartOfLine = true; } } bool EndStyleChange = false; if (c->TagId == TAG_IMG) { Blocks.Add(ctx.Ib = new ImageBlock(this)); if (ctx.Ib) { const char *s; if (c->Get("src", s)) ctx.Ib->Source = s; if (c->Get("width", s)) { GCss::Len Sz(s); int Px = Sz.ToPx(); if (Px) ctx.Ib->Size.x = Px; } if (c->Get("height", s)) { GCss::Len Sz(s); int Px = Sz.ToPx(); if (Px) ctx.Ib->Size.y = Px; } if (CachedStyle) ctx.Ib->SetStyle(CachedStyle); ctx.Ib->Load(); } } else if (c->TagId == TAG_HR) { Blocks.Add(ctx.Hrb = new HorzRuleBlock(this)); } else if (c->TagId == TAG_A) { ctx.StartOfLine |= ctx.AddText(CachedStyle, c->GetText()); if (ctx.Tb && ctx.Tb->Txt.Length()) { StyleText *st = ctx.Tb->Txt.Last(); st->Element = TAG_A; const char *Link; if (c->Get("href", Link)) st->Param = Link; } } else { if (IsBlock && ctx.Tb != NULL) { if (CachedStyle != ctx.Tb->GetStyle()) { if (ctx.Tb->Length() == 0) { ctx.Tb->SetStyle(CachedStyle); } else { // Start a new block because the styles are different... EndStyleChange = true; auto Idx = Blocks.IndexOf(ctx.Tb); ctx.Tb = new TextBlock(this); if (Idx >= 0) Blocks.AddAt(Idx+1, ctx.Tb); else Blocks.Add(ctx.Tb); if (CachedStyle) ctx.Tb->SetStyle(CachedStyle); } } } char16 *Txt = c->GetText(); if ( Txt && ( !ctx.StartOfLine || ValidStrW(Txt) ) ) { if (!ctx.Tb) { Blocks.Add(ctx.Tb = new TextBlock(this)); ctx.Tb->SetStyle(CachedStyle); } ctx.AddText(CachedStyle, Txt); ctx.StartOfLine = false; } } if (!FromHtml(c, ctx, Style, Depth + 1)) return false; if (EndStyleChange) ctx.Tb = NULL; if (IsBlock) ctx.StartOfLine = true; } return true; } bool GRichTextPriv::GetSelection(GArray *Text, GAutoString *Html) { if (!Text && !Html) return false; GArray Utf32; bool Cf = CursorFirst(); GRichTextPriv::BlockCursor *Start = Cf ? Cursor : Selection; GRichTextPriv::BlockCursor *End = Cf ? Selection : Cursor; if (Html) { if (ToHtml(NULL, Start, End)) *Html = UtfNameCache; } if (Text) { if (Start->Blk == End->Blk) { // In the same block... just copy ssize_t Len = End->Offset - Start->Offset; Start->Blk->CopyAt(Start->Offset, Len, &Utf32); } else { // Multi-block copy... // 1) Copy the content to the end of the first block Start->Blk->CopyAt(Start->Offset, -1, &Utf32); // 2) Copy any blocks between 'Start' and 'End' ssize_t i = Blocks.IndexOf(Start->Blk); ssize_t EndIdx = Blocks.IndexOf(End->Blk); if (i >= 0 && EndIdx >= i) { for (++i; Blocks[i] != End->Blk && i < (int)Blocks.Length(); i++) { GRichTextPriv::Block *&b = Blocks[i]; b->CopyAt(0, -1, &Utf32); } } else return Error(_FL, "Blocks missing index: %i, %i.", i, EndIdx); // 3) Delete any text up to the Cursor in the 'End' block End->Blk->CopyAt(0, End->Offset, &Utf32); } char16 *w = (char16*)LgiNewConvertCp(LGI_WideCharset, &Utf32[0], "utf-32", Utf32.Length() * sizeof(uint32)); if (!w) return Error(_FL, "Failed to convert %i utf32 to wide.", Utf32.Length()); Text->Add(w, Strlen(w)); Text->Add(0); } return true; } GRichTextEdit::RectType GRichTextPriv::PosToButton(GMouse &m) { if (Areas[GRichTextEdit::ToolsArea].Overlap(m.x, m.y) // || Areas[GRichTextEdit::CapabilityArea].Overlap(m.x, m.y) ) { for (unsigned i=GRichTextEdit::FontFamilyBtn; iOnComponentInstall(Name); } } #ifdef _DEBUG void GRichTextPriv::DumpNodes(GTree *Root) { if (Cursor) { GTreeItem *ti = new GTreeItem; ti->SetText("Cursor"); PrintNode(ti, "Offset=%i", Cursor->Offset); PrintNode(ti, "Pos=%s", Cursor->Pos.GetStr()); PrintNode(ti, "LineHint=%i", Cursor->LineHint); PrintNode(ti, "Blk=%i", Cursor->Blk ? Blocks.IndexOf(Cursor->Blk) : -2); Root->Insert(ti); } if (Selection) { GTreeItem *ti = new GTreeItem; ti->SetText("Selection"); PrintNode(ti, "Offset=%i", Selection->Offset); PrintNode(ti, "Pos=%s", Selection->Pos.GetStr()); PrintNode(ti, "LineHint=%i", Selection->LineHint); PrintNode(ti, "Blk=%i", Selection->Blk ? Blocks.IndexOf(Selection->Blk) : -2); Root->Insert(ti); } for (unsigned i=0; iDumpNodes(ti); GString s; s.Printf("[%i] %s", i, ti->GetText()); ti->SetText(s); Root->Insert(ti); } } GTreeItem *PrintNode(GTreeItem *Parent, const char *Fmt, ...) { GTreeItem *i = new GTreeItem; GString s; va_list Arg; va_start(Arg, Fmt); s.Printf(Arg, Fmt); va_end(Arg); s = s.Replace("\n", "\\n"); i->SetText(s); Parent->Insert(i); return i; } #endif diff --git a/src/common/Widgets/Editor/GRichTextEditPriv.h b/src/common/Widgets/Editor/GRichTextEditPriv.h --- a/src/common/Widgets/Editor/GRichTextEditPriv.h +++ b/src/common/Widgets/Editor/GRichTextEditPriv.h @@ -1,1380 +1,1380 @@ /* Rich text design notes: - The document is an array of Blocks (Blocks have no hierarchy) - Blocks have a length in characters. New lines are considered as one '\n' char. - The main type of block is the TextBlock - TextBlock contains: - array of StyleText: This is the source text. Each run of text has a style associated with it. This forms the input to the layout algorithm and is what the user is editing. - array of TextLine: Contains all the info needed to render one line of text. Essentially the output of the layout engine. Contains an array of DisplayStr objects. i.e. Characters in the exact same style as each other. It will regularly be deleted and re-flowed from the StyleText objects. - For a plaint text document the entire thing is contained by the one TextBlock. - There is an Image block, where the image is treated as one character object. - Also a horizontal rule block. */ #ifndef _RICH_TEXT_EDIT_PRIV_H_ #define _RICH_TEXT_EDIT_PRIV_H_ #include "GHtmlCommon.h" #include "GHtmlParser.h" #include "GFontCache.h" #include "GDisplayString.h" #include "GColourSpace.h" #include "GPopup.h" #include "Emoji.h" #include "LgiSpellCheck.h" #define DEBUG_LOG_CURSOR_COUNT 0 #define DEBUG_OUTLINE_CUR_DISPLAY_STR 0 #define DEBUG_OUTLINE_CUR_STYLE_TEXT 0 #define DEBUG_OUTLINE_BLOCKS 0 #define DEBUG_NO_DOUBLE_BUF 0 #define DEBUG_COVERAGE_CHECK 0 #define DEBUG_NUMBERED_LAYOUTS 0 #if 0 // _DEBUG #define LOG_FN LgiTrace #else #define LOG_FN d->Log->Print #endif #define TEXT_LINK "Link" #define TEXT_REMOVE_LINK "X" #define TEXT_REMOVE_STYLE "Remove Style" #define TEXT_CAP_BTN "Ok" #define TEXT_EMOJI ":)" #define TEXT_HORZRULE "HR" #define RTE_CURSOR_BLINK_RATE 1000 #define RTE_PULSE_RATE 200 #define RICH_TEXT_RESIZED_JPEG_QUALITY 83 // out of 100, high = better quality #define NoTransaction NULL #define IsWordBreakChar(ch) \ ( \ ( \ (ch) == ' ' || (ch) == '\t' || (ch) == '\r' || (ch) == '\n' \ ) \ || \ ( \ EmojiToIconIndex(&(ch), 1) >= 0 \ ) \ ) enum RteCommands { // IDM_OPEN = 10, IDM_NEW = 2000, IDM_RTE_COPY, IDM_RTE_CUT, IDM_RTE_PASTE, IDM_RTE_UNDO, IDM_RTE_REDO, IDM_COPY_URL, IDM_AUTO_INDENT, IDM_UTF8, IDM_PASTE_NO_CONVERT, IDM_FIXED, IDM_SHOW_WHITE, IDM_HARD_TABS, IDM_INDENT_SIZE, IDM_TAB_SIZE, IDM_DUMP, IDM_RTL, IDM_COPY_ORIGINAL, IDM_COPY_CURRENT, IDM_DUMP_NODES, IDM_CLOCKWISE, IDM_ANTI_CLOCKWISE, IDM_X_FLIP, IDM_Y_FLIP, IDM_SCALE_IMAGE, CODEPAGE_BASE = 100, CONVERT_CODEPAGE_BASE = 200, SPELLING_BASE = 300 }; ////////////////////////////////////////////////////////////////////// #define PtrCheckBreak(ptr) if (!ptr) { LgiAssert(!"Invalid ptr"); break; } #undef FixedToInt #define FixedToInt(fixed) ((fixed)>>GDisplayString::FShift) #undef IntToFixed #define IntToFixed(val) ((val)<, GString> Attr; public: GRichEditElem(GHtmlElement *parent) : GHtmlElement(parent) { } bool Get(const char *attr, const char *&val) { if (!attr) return false; GString s = Attr.Find(attr); if (!s) return false; val = s; return true; } void Set(const char *attr, const char *val) { if (!attr) return; Attr.Add(attr, GString(val)); } void SetStyle() { } }; struct GRichEditElemContext : public GCss::ElementCallback { /// Returns the element name const char *GetElement(GRichEditElem *obj); /// Returns the document unque element ID const char *GetAttr(GRichEditElem *obj, const char *Attr); /// Returns the class bool GetClasses(GString::Array &Classes, GRichEditElem *obj); /// Returns the parent object GRichEditElem *GetParent(GRichEditElem *obj); /// Returns an array of child objects GArray GetChildren(GRichEditElem *obj); }; class GDocFindReplaceParams3 : public GDocFindReplaceParams { public: // Find/Replace History char16 *LastFind; char16 *LastReplace; bool MatchCase; bool MatchWord; bool SelectionOnly; GDocFindReplaceParams3() { LastFind = 0; LastReplace = 0; MatchCase = false; MatchWord = false; SelectionOnly = false; } ~GDocFindReplaceParams3() { DeleteArray(LastFind); DeleteArray(LastReplace); } }; struct GNamedStyle : public GCss { int RefCount; GString Name; GNamedStyle() { RefCount = 0; } }; class GCssCache { int Idx; GArray Styles; GString Prefix; public: GCssCache(); ~GCssCache(); void SetPrefix(GString s) { Prefix = s; } - uint32 GetStyles(); + uint32_t GetStyles(); void ZeroRefCounts(); bool OutputStyles(GStream &s, int TabDepth); GNamedStyle *AddStyleToCache(GAutoPtr &s); }; class GRichTextPriv; class SelectColour : public GPopup { GRichTextPriv *d; GRichTextEdit::RectType Type; struct Entry { GRect r; GColour c; }; GArray e; public: SelectColour(GRichTextPriv *priv, GdcPt2 p, GRichTextEdit::RectType t); const char *GetClass() { return "SelectColour"; } void OnPaint(GSurface *pDC); void OnMouseClick(GMouse &m); void Visible(bool i); }; class EmojiMenu : public GPopup { GRichTextPriv *d; struct Emoji { GRect Src, Dst; - uint32 u; + uint32_t u; }; struct Pane { GRect Btn; GArray e; }; GArray Panes; static int Cur; public: EmojiMenu(GRichTextPriv *priv, GdcPt2 p); void OnPaint(GSurface *pDC); void OnMouseClick(GMouse &m); void Visible(bool i); - bool InsertEmoji(uint32 Ch); + bool InsertEmoji(uint32_t Ch); }; struct CtrlCap { GString Name, Param; void Set(const char *name, const char *param) { Name = name; Param = param; } }; struct ButtonState { - uint8 IsMenu : 1; - uint8 IsPress : 1; + uint8_t IsMenu : 1; + uint8_t IsPress : 1; uint8 Pressed : 1; uint8 MouseOver : 1; }; -extern bool Utf16to32(GArray &Out, const uint16 *In, int Len); +extern bool Utf16to32(GArray &Out, const uint16_t *In, int Len); class GEmojiContext { GAutoPtr EmojiImg; public: GSurface *GetEmojiImage(); }; class GRichTextPriv : public GCss, public GHtmlParser, public GHtmlStaticInst, public GCssCache, public GFontCache, public GEmojiContext { GStringPipe LogBuffer; public: enum SelectModeType { Unselected = 0, Selected = 1, }; enum SeekType { SkUnknown, SkLineStart, SkLineEnd, SkDocStart, SkDocEnd, // Horizontal navigation SkLeftChar, SkLeftWord, SkRightChar, SkRightWord, // Vertical navigation SkUpPage, SkUpLine, SkCurrentLine, SkDownLine, SkDownPage, }; struct DisplayStr; struct BlockCursor; class Block; GRichTextEdit *View; GString OriginalText; GAutoWString WideNameCache; GAutoString UtfNameCache; GAutoPtr Font; bool WordSelectMode; bool Dirty; GdcPt2 DocumentExtent; // Px GString Charset; GHtmlStaticInst Inst; int NextUid; GStream *Log; bool HtmlLinkAsCid; uint64 BlinkTs; // Spell check support GSpellCheck *SpellCheck; bool SpellDictionaryLoaded; GString SpellLang, SpellDict; // This is set when the user changes a style without a selection, // indicating that we should start a new run when new text is entered GArray StyleDirty; // Toolbar bool ShowTools; GRichTextEdit::RectType ClickedBtn, OverBtn; ButtonState BtnState[GRichTextEdit::MaxArea]; GRect Areas[GRichTextEdit::MaxArea]; GVariant Values[GRichTextEdit::MaxArea]; // Scrolling int ScrollLinePx; int ScrollOffsetPx; bool ScrollChange; // Capabilities // GArray NeedsCap; // Debug stuff GArray DebugRects; // Constructor GRichTextPriv(GRichTextEdit *view, GRichTextPriv **Ptr); ~GRichTextPriv(); bool Error(const char *file, int line, const char *fmt, ...); bool IsBusy(bool Stop = false); struct Flow { GRichTextPriv *d; GSurface *pDC; // Used for printing. int Left, Right;// Left and right margin positions as measured in px // from the left of the page (controls client area). int Top; int CurY; // Current y position down the page in document co-ords bool Visible; // true if the current block overlaps the visible page // If false, the implementation can take short cuts and // guess various dimensions. Flow(GRichTextPriv *priv) { d = priv; pDC = NULL; Left = 0; Top = 0; Right = 1000; CurY = 0; Visible = true; } int X() { return Right - Left + 1; } GString Describe() { GString s; s.Printf("Left=%i Right=%i CurY=%i", Left, Right, CurY); return s; } }; struct ColourPair { GColour Fore, Back; void Empty() { Fore.Empty(); Back.Empty(); } }; /// This is a run of text, all of the same style - class StyleText : public GArray + class StyleText : public GArray { GNamedStyle *Style; // owned by the CSS cache public: ColourPair Colours; HtmlTag Element; GString Param; bool Emoji; StyleText(const StyleText *St); - StyleText(const uint32 *t = NULL, ssize_t Chars = -1, GNamedStyle *style = NULL); - uint32 *At(ssize_t i); + StyleText(const uint32_t *t = NULL, ssize_t Chars = -1, GNamedStyle *style = NULL); + uint32_t *At(ssize_t i); GNamedStyle *GetStyle(); void SetStyle(GNamedStyle *s); }; struct PaintContext { int Index; GSurface *pDC; SelectModeType Type; ColourPair Colours[2]; BlockCursor *Cursor, *Select; // Cursor stuff int CurEndPoint; GArray EndPoints; PaintContext() { Index = 0; pDC = NULL; Type = Unselected; Cursor = NULL; Select = NULL; CurEndPoint = 0; } GColour &Fore() { return Colours[Type].Fore; } GColour &Back() { return Colours[Type].Back; } void DrawBox(GRect &r, GRect &Edge, GColour &c) { if (Edge.x1 > 0 || Edge.x2 > 0 || Edge.y1 > 0 || Edge.y2 > 0) { pDC->Colour(c); if (Edge.x1) { pDC->Rectangle(r.x1, r.y1, r.x1 + Edge.x1 - 1, r.y2); r.x1 += Edge.x1; } if (Edge.y1) { pDC->Rectangle(r.x1, r.y1, r.x2, r.y1 + Edge.y1 - 1); r.y1 += Edge.y1; } if (Edge.y2) { pDC->Rectangle(r.x1, r.y2 - Edge.y2 + 1, r.x2, r.y2); r.y2 -= Edge.y2; } if (Edge.x2) { pDC->Rectangle(r.x2 - Edge.x2 + 1, r.y1, r.x2, r.y2); r.x2 -= Edge.x2; } } } // This handles calculating the selection stuff for simple "one char" blocks // like images and HR. Call this at the start of the OnPaint. // \return TRUE if the content should be drawn selected. bool SelectBeforePaint(class GRichTextPriv::Block *b) { CurEndPoint = 0; if (b->Cursors > 0 && Select) { // Selection end point checks... if (Cursor && Cursor->Blk == b) EndPoints.Add(Cursor->Offset); if (Select && Select->Blk == b) EndPoints.Add(Select->Offset); // Sort the end points if (EndPoints.Length() > 1 && EndPoints[0] > EndPoints[1]) { ssize_t ep = EndPoints[0]; EndPoints[0] = EndPoints[1]; EndPoints[1] = ep; } } // Before selection end point if (CurEndPoint < (ssize_t)EndPoints.Length() && EndPoints[CurEndPoint] == 0) { Type = Type == Selected ? Unselected : Selected; CurEndPoint++; } return Type == Selected; } // Call this after the OnPaint // \return TRUE if the content after the block is selected. bool SelectAfterPaint(class GRichTextPriv::Block *b) { // After image selection end point if (CurEndPoint < (ssize_t)EndPoints.Length() && EndPoints[CurEndPoint] == 1) { Type = Type == Selected ? Unselected : Selected; CurEndPoint++; } return Type == Selected; } }; struct HitTestResult { GdcPt2 In; Block *Blk; DisplayStr *Ds; ssize_t Idx; int LineHint; bool Near; HitTestResult(int x, int y) { In.x = x; In.y = y; Blk = NULL; Ds = NULL; Idx = -1; LineHint = -1; Near = false; } }; ////////////////////////////////////////////////////////////////////////////////////////////// // Undo structures... struct DocChange { virtual ~DocChange() {} virtual bool Apply(GRichTextPriv *Ctx, bool Forward) = 0; }; class Transaction { public: GArray Changes; ~Transaction() { Changes.DeleteObjects(); } void Add(DocChange *Dc) { Changes.Add(Dc); } bool Apply(GRichTextPriv *Ctx, bool Forward) { for (unsigned i=0; iApply(Ctx, Forward)) return false; } return true; } }; GArray UndoQue; ssize_t UndoPos; bool AddTrans(GAutoPtr &t); bool SetUndoPos(ssize_t Pos); template bool GetBlockByUid(T *&Ptr, int Uid, int *Idx = NULL) { for (unsigned i=0; iGetUid() == Uid) { if (Idx) *Idx = i; return (Ptr = dynamic_cast(b)) != NULL; } } if (Idx) *Idx = -1; return false; } ////////////////////////////////////////////////////////////////////////////////////////////// // A Block is like a DIV in HTML, it's as wide as the page and // always starts and ends on a whole line. class Block : public GEventSinkI, public GEventTargetI { protected: int BlockUid; GRichTextPriv *d; public: /// This is the number of cursors current referencing this Block. int8 Cursors; /// Draw debug selection bool DrawDebug; Block(GRichTextPriv *priv) { d = priv; DrawDebug = false; BlockUid = d->NextUid++; Cursors = 0; } Block(const Block *blk) { d = blk->d; DrawDebug = false; BlockUid = blk->GetUid(); Cursors = 0; } virtual ~Block() { // We must have removed cursors by the time we are deleted // otherwise there will be a hanging pointer in the cursor // object. LgiAssert(Cursors == 0); } // Events bool PostEvent(int Cmd, GMessage::Param a = 0, GMessage::Param b = 0) { bool r = d->View->PostEvent(M_BLOCK_MSG, (GMessage::Param)(Block*)this, (GMessage::Param)new GMessage(Cmd, a, b)); #if defined(_DEBUG) if (!r) LgiTrace("%s:%i - Warning: PostEvent failed..\n", _FL); #endif return r; } // If this returns non-zero further command processing is aborted. GMessage::Result OnEvent(GMessage *Msg) { return false; } /************************************************ * Get state methods, do not modify the block * ***********************************************/ virtual const char *GetClass() { return "Block"; } virtual GRect GetPos() = 0; virtual ssize_t Length() = 0; virtual bool HitTest(HitTestResult &htr) = 0; virtual bool GetPosFromIndex(BlockCursor *Cursor) = 0; virtual bool OnLayout(Flow &f) = 0; virtual void OnPaint(PaintContext &Ctx) = 0; virtual bool ToHtml(GStream &s, GArray *Media, GRange *Rgn) = 0; virtual bool OffsetToLine(ssize_t Offset, int *ColX, GArray *LineY) = 0; virtual int LineToOffset(int Line) = 0; virtual int GetLines() = 0; - virtual ssize_t FindAt(ssize_t StartIdx, const uint32 *Str, GFindReplaceCommon *Params) = 0; + virtual ssize_t FindAt(ssize_t StartIdx, const uint32_t *Str, GFindReplaceCommon *Params) = 0; virtual void SetSpellingErrors(GArray &Errors, GRange r) {} virtual void IncAllStyleRefs() {} virtual void Dump() {} virtual GNamedStyle *GetStyle(ssize_t At = -1) = 0; virtual int GetUid() const { return BlockUid; } virtual bool DoContext(GSubMenu &s, GdcPt2 Doc, ssize_t Offset, bool Spelling) { return false; } #ifdef _DEBUG virtual void DumpNodes(GTreeItem *Ti) = 0; #endif virtual bool IsValid() { return false; } virtual bool IsBusy(bool Stop = false) { return false; } virtual Block *Clone() = 0; virtual void OnComponentInstall(GString Name) {} // Copy some or all of the text out - virtual ssize_t CopyAt(ssize_t Offset, ssize_t Chars, GArray *Text) { return false; } + virtual ssize_t CopyAt(ssize_t Offset, ssize_t Chars, GArray *Text) { return false; } /// This method moves a cursor index. /// \returns the new cursor index or -1 on error. virtual bool Seek ( /// [In] true if the next line is needed, false for the previous line SeekType To, /// [In/Out] The starting cursor. BlockCursor &Cursor ) = 0; /************************************************ * Change state methods, require a transaction * ***********************************************/ // Add some text at a given position virtual bool AddText ( /// Current transaction Transaction *Trans, /// The index to add at (-1 = the end) ssize_t AtOffset, /// The text itself - const uint32 *Str, + const uint32_t *Str, /// [Optional] The number of characters ssize_t Chars = -1, /// [Optional] Style to give the text, NULL means "use the existing style" GNamedStyle *Style = NULL ) { return false; } /// Delete some chars /// \returns the number of chars actually removed virtual ssize_t DeleteAt ( Transaction *Trans, ssize_t Offset, ssize_t Chars, - GArray *DeletedText = NULL + GArray *DeletedText = NULL ) { return false; } /// Changes the style of a range of characters virtual bool ChangeStyle ( Transaction *Trans, ssize_t Offset, ssize_t Chars, GCss *Style, bool Add ) { return false; } virtual bool DoCase ( /// Current transaction Transaction *Trans, /// Start index of text to change ssize_t StartIdx, /// Number of chars to change ssize_t Chars, /// True if upper case is desired bool Upper ) { return false; } // Split a block virtual Block *Split ( /// Current transaction Transaction *Trans, /// The index to add at (-1 = the end) ssize_t AtOffset ) { return NULL; } // Event called on dictionary load virtual bool OnDictionary(Transaction *Trans) { return false; } }; struct BlockCursor { // The block the cursor is in. Block *Blk; // This is the character offset of the cursor relative to // the start of 'Blk'. ssize_t Offset; // In wrapped text, a given offset can either be at the end // of one line or the start of the next line. This tells the // text block which line the cursor is actually on. int LineHint; // This is the position on the screen in doc coords. GRect Pos; // This is the position line that the cursor is on. This is // used to calculate the bounds for screen updates. GRect Line; // Cursor is currently blinking on bool Blink; BlockCursor(const BlockCursor &c); BlockCursor(Block *b, ssize_t off, int line); ~BlockCursor(); BlockCursor &operator =(const BlockCursor &c); void Set(ssize_t off); void Set(Block *b, ssize_t off, int line); bool operator ==(const BlockCursor &c) { return Blk == c.Blk && Offset == c.Offset; } #ifdef _DEBUG void DumpNodes(GTreeItem *Ti); #endif }; GAutoPtr Cursor, Selection; /// This is part or all of a Text run struct DisplayStr : public GDisplayString { StyleText *Src; ssize_t Chars; // The number of UTF-32 characters. This can be different to // GDisplayString::Length() in the case that GDisplayString // is using UTF-16 (i.e. Windows). int OffsetY; // Offset of this string from the TextLine's box in the Y axis - DisplayStr(StyleText *src, GFont *f, const uint32 *s, ssize_t l = -1, GSurface *pdc = NULL) : + DisplayStr(StyleText *src, GFont *f, const uint32_t *s, ssize_t l = -1, GSurface *pdc = NULL) : GDisplayString(f, #ifndef WINDOWS (char16*) #endif s, l, pdc) { Src = src; OffsetY = 0; #if defined(_MSC_VER) Chars = l < 0 ? Strlen(s) : l; #else Chars = len; #endif } template T *Utf16Seek(T *s, int i) { T *e = s + i; while (s < e) { uint16 n = *s & 0xfc00; if (n == 0xd800) { s++; if (s >= e) break; n = *s & 0xfc00; if (n != 0xdc00) { LgiAssert(!"Unexpected surrogate"); continue; } // else skip over the 2nd surrogate } s++; } return s; } // Make a sub-string of this display string virtual GAutoPtr Clone(ssize_t Start, ssize_t Len = -1) { GAutoPtr c; if (len > 0 && Len != 0) { const char16 *Str = *this; if (Len < 0) Len = len - Start; if (Start >= 0 && Start < (int)len && Start + Len <= (int)len) { #if defined(_MSC_VER) LgiAssert(Str != NULL); const char16 *s = Utf16Seek(Str, Start); const char16 *e = Utf16Seek(s, Len); GArray Tmp; if (Utf16to32(Tmp, (const uint16*)s, e - s)) c.Reset(new DisplayStr(Src, GetFont(), &Tmp[0], Tmp.Length(), pDC)); #else c.Reset(new DisplayStr(Src, GetFont(), (uint32*)Str + Start, Len, pDC)); #endif } } return c; } virtual void Paint(GSurface *pDC, int &FixX, int FixY, GColour &Back) { FDraw(pDC, FixX, FixY); FixX += FX(); } virtual double GetAscent() { return Font->Ascent(); } virtual ssize_t PosToIndex(int x, bool Nearest) { return CharAt(x); } }; struct EmojiDisplayStr : public DisplayStr { GArray SrcRect; GSurface *Img; #if defined(_MSC_VER) - GArray Utf32; + GArray Utf32; #endif - EmojiDisplayStr(StyleText *src, GSurface *img, GFont *f, const uint32 *s, ssize_t l = -1); + EmojiDisplayStr(StyleText *src, GSurface *img, GFont *f, const uint32_t *s, ssize_t l = -1); GAutoPtr Clone(ssize_t Start, ssize_t Len = -1); void Paint(GSurface *pDC, int &FixX, int FixY, GColour &Back); double GetAscent(); ssize_t PosToIndex(int XPos, bool Nearest); }; /// This structure is a layout of a full line of text. Made up of one or more /// display string objects. struct TextLine { /// This is a position relative to the parent Block GRect PosOff; /// The array of display strings GArray Strs; /// Is '1' for lines that have a new line character at the end. - uint8 NewLine; + uint8_t NewLine; TextLine(int XOffsetPx, int WidthPx, int YOffsetPx); int Length(); /// This runs after the layout line has been filled with display strings. /// It measures the line and works out the right offsets for each strings /// so that their baselines all match up correctly. void LayoutOffsets(int DefaultFontHt); }; class TextBlock : public Block { GNamedStyle *Style; GArray SpellingErrors; int PaintErrIdx, ClickErrIdx; GSpellCheck::SpellingError *SpErr; bool PreEdit(Transaction *Trans); void DrawDisplayString(GSurface *pDC, DisplayStr *Ds, int &FixX, int FixY, GColour &Bk, int &Pos); public: // Runs of characters in the same style: pre-layout. GArray Txt; // Runs of characters (display strings) of potentially different styles on the same line: post-layout. GArray Layout; // True if the 'Layout' data is out of date. bool LayoutDirty; // Size of the edges GRect Margin, Border, Padding; // Default font for the block GFont *Fnt; // Chars in the whole block (sum of all Text lengths) ssize_t Len; // Position in document co-ordinates GRect Pos; TextBlock(GRichTextPriv *priv); TextBlock(const TextBlock *Copy); ~TextBlock(); bool IsValid(); // No state change methods const char *GetClass() { return "TextBlock"; } int GetLines(); bool OffsetToLine(ssize_t Offset, int *ColX, GArray *LineY); int LineToOffset(int Line); GRect GetPos() { return Pos; } void Dump(); GNamedStyle *GetStyle(ssize_t At = -1); void SetStyle(GNamedStyle *s); ssize_t Length(); bool ToHtml(GStream &s, GArray *Media, GRange *Rng); bool GetPosFromIndex(BlockCursor *Cursor); bool HitTest(HitTestResult &htr); void OnPaint(PaintContext &Ctx); bool OnLayout(Flow &flow); ssize_t GetTextAt(ssize_t Offset, GArray &t); - ssize_t CopyAt(ssize_t Offset, ssize_t Chars, GArray *Text); + ssize_t CopyAt(ssize_t Offset, ssize_t Chars, GArray *Text); bool Seek(SeekType To, BlockCursor &Cursor); - ssize_t FindAt(ssize_t StartIdx, const uint32 *Str, GFindReplaceCommon *Params); + ssize_t FindAt(ssize_t StartIdx, const uint32_t *Str, GFindReplaceCommon *Params); void IncAllStyleRefs(); void SetSpellingErrors(GArray &Errors, GRange r); bool DoContext(GSubMenu &s, GdcPt2 Doc, ssize_t Offset, bool Spelling); #ifdef _DEBUG void DumpNodes(GTreeItem *Ti); #endif Block *Clone(); bool IsEmptyLine(BlockCursor *Cursor); void UpdateSpellingAndLinks(Transaction *Trans, GRange r); // Events GMessage::Result OnEvent(GMessage *Msg); // Transactional changes - bool AddText(Transaction *Trans, ssize_t AtOffset, const uint32 *Str, ssize_t Chars = -1, GNamedStyle *Style = NULL); + bool AddText(Transaction *Trans, ssize_t AtOffset, const uint32_t *Str, ssize_t Chars = -1, GNamedStyle *Style = NULL); bool ChangeStyle(Transaction *Trans, ssize_t Offset, ssize_t Chars, GCss *Style, bool Add); - ssize_t DeleteAt(Transaction *Trans, ssize_t BlkOffset, ssize_t Chars, GArray *DeletedText = NULL); + ssize_t DeleteAt(Transaction *Trans, ssize_t BlkOffset, ssize_t Chars, GArray *DeletedText = NULL); bool DoCase(Transaction *Trans, ssize_t StartIdx, ssize_t Chars, bool Upper); Block *Split(Transaction *Trans, ssize_t AtOffset); bool StripLast(Transaction *Trans, const char *Set = " \t\r\n"); // Strip trailing new line if present.. bool OnDictionary(Transaction *Trans); }; class HorzRuleBlock : public Block { GRect Pos; bool IsDeleted; public: HorzRuleBlock(GRichTextPriv *priv); HorzRuleBlock(const HorzRuleBlock *Copy); ~HorzRuleBlock(); bool IsValid(); // No state change methods const char *GetClass() { return "HorzRuleBlock"; } int GetLines(); bool OffsetToLine(ssize_t Offset, int *ColX, GArray *LineY); int LineToOffset(int Line); GRect GetPos() { return Pos; } void Dump(); GNamedStyle *GetStyle(ssize_t At = -1); void SetStyle(GNamedStyle *s); ssize_t Length(); bool ToHtml(GStream &s, GArray *Media, GRange *Rng); bool GetPosFromIndex(BlockCursor *Cursor); bool HitTest(HitTestResult &htr); void OnPaint(PaintContext &Ctx); bool OnLayout(Flow &flow); ssize_t GetTextAt(ssize_t Offset, GArray &t); - ssize_t CopyAt(ssize_t Offset, ssize_t Chars, GArray *Text); + ssize_t CopyAt(ssize_t Offset, ssize_t Chars, GArray *Text); bool Seek(SeekType To, BlockCursor &Cursor); - ssize_t FindAt(ssize_t StartIdx, const uint32 *Str, GFindReplaceCommon *Params); + ssize_t FindAt(ssize_t StartIdx, const uint32_t *Str, GFindReplaceCommon *Params); void IncAllStyleRefs(); bool DoContext(GSubMenu &s, GdcPt2 Doc, ssize_t Offset, bool Spelling); #ifdef _DEBUG void DumpNodes(GTreeItem *Ti); #endif Block *Clone(); // Events GMessage::Result OnEvent(GMessage *Msg); // Transactional changes - bool AddText(Transaction *Trans, ssize_t AtOffset, const uint32 *Str, ssize_t Chars = -1, GNamedStyle *Style = NULL); + bool AddText(Transaction *Trans, ssize_t AtOffset, const uint32_t *Str, ssize_t Chars = -1, GNamedStyle *Style = NULL); bool ChangeStyle(Transaction *Trans, ssize_t Offset, ssize_t Chars, GCss *Style, bool Add); - ssize_t DeleteAt(Transaction *Trans, ssize_t BlkOffset, ssize_t Chars, GArray *DeletedText = NULL); + ssize_t DeleteAt(Transaction *Trans, ssize_t BlkOffset, ssize_t Chars, GArray *DeletedText = NULL); bool DoCase(Transaction *Trans, ssize_t StartIdx, ssize_t Chars, bool Upper); Block *Split(Transaction *Trans, ssize_t AtOffset); }; class ImageBlock : public Block { public: struct ScaleInf { GdcPt2 Sz; GString MimeType; GAutoPtr Compressed; int Percent; ScaleInf() { Sz.x = Sz.y = 0; Percent = 0; } }; int ThreadHnd; protected: GNamedStyle *Style; int Scale; GRect SourceValid; GString FileName; GString ContentId; GString StreamMimeType; GAutoString FileMimeType; GArray Scales; int ResizeIdx; int ThreadBusy; bool IsDeleted; void UpdateThreadBusy(const char *File, int Line, int Off); int GetThreadHandle(); void UpdateDisplay(int y); void UpdateDisplayImg(); public: GAutoPtr SourceImg, DisplayImg, SelectImg; GRect Margin, Border, Padding; GString Source; GdcPt2 Size; bool LayoutDirty; GRect Pos; // position in document co-ordinates GRect ImgPos; ImageBlock(GRichTextPriv *priv); ImageBlock(const ImageBlock *Copy); ~ImageBlock(); bool IsValid(); bool IsBusy(bool Stop = false); bool Load(const char *Src = NULL); bool SetImage(GAutoPtr Img); // No state change methods int GetLines(); bool OffsetToLine(ssize_t Offset, int *ColX, GArray *LineY); int LineToOffset(int Line); GRect GetPos() { return Pos; } void Dump(); GNamedStyle *GetStyle(ssize_t At = -1); void SetStyle(GNamedStyle *s); ssize_t Length(); bool ToHtml(GStream &s, GArray *Media, GRange *Rng); bool GetPosFromIndex(BlockCursor *Cursor); bool HitTest(HitTestResult &htr); void OnPaint(PaintContext &Ctx); bool OnLayout(Flow &flow); ssize_t GetTextAt(ssize_t Offset, GArray &t); - ssize_t CopyAt(ssize_t Offset, ssize_t Chars, GArray *Text); + ssize_t CopyAt(ssize_t Offset, ssize_t Chars, GArray *Text); bool Seek(SeekType To, BlockCursor &Cursor); - ssize_t FindAt(ssize_t StartIdx, const uint32 *Str, GFindReplaceCommon *Params); + ssize_t FindAt(ssize_t StartIdx, const uint32_t *Str, GFindReplaceCommon *Params); void IncAllStyleRefs(); bool DoContext(GSubMenu &s, GdcPt2 Doc, ssize_t Offset, bool Spelling); #ifdef _DEBUG void DumpNodes(GTreeItem *Ti); #endif Block *Clone(); void OnComponentInstall(GString Name); // Events GMessage::Result OnEvent(GMessage *Msg); // Transactional changes - bool AddText(Transaction *Trans, ssize_t AtOffset, const uint32 *Str, ssize_t Chars = -1, GNamedStyle *Style = NULL); + bool AddText(Transaction *Trans, ssize_t AtOffset, const uint32_t *Str, ssize_t Chars = -1, GNamedStyle *Style = NULL); bool ChangeStyle(Transaction *Trans, ssize_t Offset, ssize_t Chars, GCss *Style, bool Add); - ssize_t DeleteAt(Transaction *Trans, ssize_t BlkOffset, ssize_t Chars, GArray *DeletedText = NULL); + ssize_t DeleteAt(Transaction *Trans, ssize_t BlkOffset, ssize_t Chars, GArray *DeletedText = NULL); bool DoCase(Transaction *Trans, ssize_t StartIdx, ssize_t Chars, bool Upper); }; GArray Blocks; Block *Next(Block *b); Block *Prev(Block *b); void InvalidateDoc(GRect *r); void ScrollTo(GRect r); void UpdateStyleUI(); void EmptyDoc(); void Empty(); bool Seek(BlockCursor *In, SeekType Dir, bool Select); bool CursorFirst(); bool SetCursor(GAutoPtr c, bool Select = false); GRect SelectionRect(); bool GetSelection(GArray *Text, GAutoString *Html); ssize_t IndexOfCursor(BlockCursor *c); ssize_t HitTest(int x, int y, int &LineHint, Block **Blk = NULL); bool CursorFromPos(int x, int y, GAutoPtr *Cursor, ssize_t *GlobalIdx); Block *GetBlockByIndex(ssize_t Index, ssize_t *Offset = NULL, int *BlockIdx = NULL, int *LineCount = NULL); bool Layout(GScrollBar *&ScrollY); void OnStyleChange(GRichTextEdit::RectType t); bool ChangeSelectionStyle(GCss *Style, bool Add); void PaintBtn(GSurface *pDC, GRichTextEdit::RectType t); bool MakeLink(TextBlock *tb, ssize_t Offset, ssize_t Len, GString Link); bool ClickBtn(GMouse &m, GRichTextEdit::RectType t); bool InsertHorzRule(); void Paint(GSurface *pDC, GScrollBar *&ScrollY); GHtmlElement *CreateElement(GHtmlElement *Parent); GdcPt2 ScreenToDoc(int x, int y); GdcPt2 DocToScreen(int x, int y); bool Merge(Transaction *Trans, Block *a, Block *b); bool DeleteSelection(Transaction *t, char16 **Cut); GRichTextEdit::RectType PosToButton(GMouse &m); void OnComponentInstall(GString Name); struct CreateContext { TextBlock *Tb; ImageBlock *Ib; HorzRuleBlock *Hrb; - GArray Buf; + GArray Buf; char16 LastChar; GFontCache *FontCache; GCss::Store StyleStore; bool StartOfLine; CreateContext(GFontCache *fc) { Tb = NULL; Ib = NULL; Hrb = NULL; LastChar = '\n'; FontCache = fc; StartOfLine = true; } bool AddText(GNamedStyle *Style, char16 *Str) { if (!Str || !Tb) return false; int Used = 0; char16 *s = Str; char16 *e = s + StrlenW(s); while (s < e) { if (*s == '\r') { s++; continue; } if (IsWhiteSpace(*s)) { Buf[Used++] = ' '; while (s < e && IsWhiteSpace(*s)) s++; } else { #ifdef WINDOWS ssize_t Len = s[0] && s[1] ? 4 : (s[0] ? 2 : 0); Buf[Used++] = LgiUtf16To32((const uint16 *&)s, Len); #else Buf[Used++] = *s++; #endif while (s < e && !IsWhiteSpace(*s)) { #ifdef WINDOWS Len = s[0] && s[1] ? 4 : (s[0] ? 2 : 0); Buf[Used++] = LgiUtf16To32((const uint16 *&)s, Len); #else Buf[Used++] = *s++; #endif } } } bool Status = false; if (Used > 0) { Status = Tb->AddText(NoTransaction, -1, &Buf[0], Used, Style); LastChar = Buf[Used-1]; } return Status; } }; GAutoPtr CreationCtx; bool ToHtml(GArray *Media = NULL, BlockCursor *From = NULL, BlockCursor *To = NULL); void DumpBlocks(); bool FromHtml(GHtmlElement *e, CreateContext &ctx, GCss *ParentStyle = NULL, int Depth = 0); #ifdef _DEBUG void DumpNodes(GTree *Root); #endif }; struct BlockCursorState { bool Cursor; ssize_t Offset; int LineHint; int BlockUid; BlockCursorState(bool cursor, GRichTextPriv::BlockCursor *c); bool Apply(GRichTextPriv *Ctx, bool Forward); }; struct CompleteTextBlockState : public GRichTextPriv::DocChange { int Uid; GAutoPtr Cur, Sel; GAutoPtr Blk; CompleteTextBlockState(GRichTextPriv *Ctx, GRichTextPriv::TextBlock *Tb); bool Apply(GRichTextPriv *Ctx, bool Forward); }; struct MultiBlockState : public GRichTextPriv::DocChange { GRichTextPriv *Ctx; ssize_t Index; // Number of blocks before the edit ssize_t Length; // Of the other version currently in the Ctx stack GArray Blks; MultiBlockState(GRichTextPriv *ctx, ssize_t Start); bool Apply(GRichTextPriv *Ctx, bool Forward); bool Copy(ssize_t Idx); bool Cut(ssize_t Idx); }; #ifdef _DEBUG GTreeItem *PrintNode(GTreeItem *Parent, const char *Fmt, ...); #endif typedef GRichTextPriv::BlockCursor BlkCursor; typedef GAutoPtr AutoCursor; typedef GAutoPtr AutoTrans; #endif \ No newline at end of file diff --git a/src/common/Widgets/Editor/HorzRuleBlock.cpp b/src/common/Widgets/Editor/HorzRuleBlock.cpp --- a/src/common/Widgets/Editor/HorzRuleBlock.cpp +++ b/src/common/Widgets/Editor/HorzRuleBlock.cpp @@ -1,248 +1,248 @@ #include "Lgi.h" #include "GRichTextEdit.h" #include "GRichTextEditPriv.h" #include "GDocView.h" GRichTextPriv::HorzRuleBlock::HorzRuleBlock(GRichTextPriv *priv) : Block(priv) { IsDeleted = false; } GRichTextPriv::HorzRuleBlock::HorzRuleBlock(const HorzRuleBlock *Copy) : Block(Copy->d) { IsDeleted = Copy->IsDeleted; } GRichTextPriv::HorzRuleBlock::~HorzRuleBlock() { LgiAssert(Cursors == 0); } bool GRichTextPriv::HorzRuleBlock::IsValid() { return true; } int GRichTextPriv::HorzRuleBlock::GetLines() { return 1; } bool GRichTextPriv::HorzRuleBlock::OffsetToLine(ssize_t Offset, int *ColX, GArray *LineY) { if (ColX) *ColX = Offset > 0; if (LineY) LineY->Add(0); return true; } int GRichTextPriv::HorzRuleBlock::LineToOffset(int Line) { return 0; } void GRichTextPriv::HorzRuleBlock::Dump() { } GNamedStyle *GRichTextPriv::HorzRuleBlock::GetStyle(ssize_t At) { return NULL; } void GRichTextPriv::HorzRuleBlock::SetStyle(GNamedStyle *s) { } ssize_t GRichTextPriv::HorzRuleBlock::Length() { return IsDeleted ? 0 : 1; } bool GRichTextPriv::HorzRuleBlock::ToHtml(GStream &s, GArray *Media, GRange *Rng) { s.Print("
\n"); return true; } bool GRichTextPriv::HorzRuleBlock::GetPosFromIndex(BlockCursor *Cursor) { if (!Cursor) return d->Error(_FL, "No cursor param."); Cursor->Pos = Pos; Cursor->Line = Pos; if (Cursor->Offset == 0) Cursor->Pos.x2 = Cursor->Pos.x1 + 1; else Cursor->Pos.x1 = Cursor->Pos.x2 - 1; return true; } bool GRichTextPriv::HorzRuleBlock::HitTest(HitTestResult &htr) { if (htr.In.y < Pos.y1 || htr.In.y > Pos.y2) return false; htr.Near = false; htr.LineHint = 0; int Cx = Pos.x1 + (Pos.X() / 2); if (htr.In.x < Cx) htr.Idx = 0; else htr.Idx = 1; return true; } void GRichTextPriv::HorzRuleBlock::OnPaint(PaintContext &Ctx) { Ctx.SelectBeforePaint(this); GColour Fore, Back = Ctx.Back(); Fore = Ctx.Fore().Mix(Back, 0.75f); Ctx.pDC->Colour(Back); Ctx.pDC->Rectangle(&Pos); Ctx.pDC->Colour(Fore); int Cy = Pos.y1 + (Pos.Y() >> 1); Ctx.pDC->Rectangle(Pos.x1, Cy-1, Pos.x2, Cy); if (Ctx.Cursor != NULL && Ctx.Cursor->Blk == (Block*)this && Ctx.Cursor->Blink && d->View->Focus()) { GRect &p = Ctx.Cursor->Pos; Ctx.pDC->Colour(Ctx.Fore()); Ctx.pDC->Rectangle(&p); } Ctx.SelectAfterPaint(this); } bool GRichTextPriv::HorzRuleBlock::OnLayout(Flow &flow) { Pos.x1 = flow.Left; Pos.y1 = flow.CurY; Pos.x2 = flow.Right; Pos.y2 = flow.CurY + 15; // Start with a 16px height. flow.CurY = Pos.y2 + 1; return true; } ssize_t GRichTextPriv::HorzRuleBlock::GetTextAt(ssize_t Offset, GArray &t) { return 0; } -ssize_t GRichTextPriv::HorzRuleBlock::CopyAt(ssize_t Offset, ssize_t Chars, GArray *Text) +ssize_t GRichTextPriv::HorzRuleBlock::CopyAt(ssize_t Offset, ssize_t Chars, GArray *Text) { return 0; } bool GRichTextPriv::HorzRuleBlock::Seek(SeekType To, BlockCursor &Cursor) { switch (To) { case SkLineStart: { Cursor.Offset = 0; Cursor.LineHint = 0; break; } case SkLineEnd: { Cursor.Offset = 1; Cursor.LineHint = 0; break; } case SkLeftChar: { if (Cursor.Offset != 1) return false; Cursor.Offset = 0; Cursor.LineHint = 0; break; } case SkRightChar: { if (Cursor.Offset != 0) return false; Cursor.Offset = 1; Cursor.LineHint = 0; break; } default: { return false; break; } } return true; } -ssize_t GRichTextPriv::HorzRuleBlock::FindAt(ssize_t StartIdx, const uint32 *Str, GFindReplaceCommon *Params) +ssize_t GRichTextPriv::HorzRuleBlock::FindAt(ssize_t StartIdx, const uint32_t *Str, GFindReplaceCommon *Params) { return 0; } void GRichTextPriv::HorzRuleBlock::IncAllStyleRefs() { } bool GRichTextPriv::HorzRuleBlock::DoContext(GSubMenu &s, GdcPt2 Doc, ssize_t Offset, bool Spelling) { return false; } #ifdef _DEBUG void GRichTextPriv::HorzRuleBlock::DumpNodes(GTreeItem *Ti) { Ti->SetText("HorzRuleBlock"); } #endif GRichTextPriv::Block *GRichTextPriv::HorzRuleBlock::Clone() { return new HorzRuleBlock(this); } GMessage::Result GRichTextPriv::HorzRuleBlock::OnEvent(GMessage *Msg) { return false; } -bool GRichTextPriv::HorzRuleBlock::AddText(Transaction *Trans, ssize_t AtOffset, const uint32 *Str, ssize_t Chars, GNamedStyle *Style) +bool GRichTextPriv::HorzRuleBlock::AddText(Transaction *Trans, ssize_t AtOffset, const uint32_t *Str, ssize_t Chars, GNamedStyle *Style) { return false; } bool GRichTextPriv::HorzRuleBlock::ChangeStyle(Transaction *Trans, ssize_t Offset, ssize_t Chars, GCss *Style, bool Add) { return false; } -ssize_t GRichTextPriv::HorzRuleBlock::DeleteAt(Transaction *Trans, ssize_t BlkOffset, ssize_t Chars, GArray *DeletedText) +ssize_t GRichTextPriv::HorzRuleBlock::DeleteAt(Transaction *Trans, ssize_t BlkOffset, ssize_t Chars, GArray *DeletedText) { IsDeleted = BlkOffset == 0; if (IsDeleted) return true; return false; } bool GRichTextPriv::HorzRuleBlock::DoCase(Transaction *Trans, ssize_t StartIdx, ssize_t Chars, bool Upper) { return false; } GRichTextPriv::Block *GRichTextPriv::HorzRuleBlock::Split(Transaction *Trans, ssize_t AtOffset) { return NULL; } diff --git a/src/common/Widgets/Editor/ImageBlock.cpp b/src/common/Widgets/Editor/ImageBlock.cpp --- a/src/common/Widgets/Editor/ImageBlock.cpp +++ b/src/common/Widgets/Editor/ImageBlock.cpp @@ -1,1350 +1,1350 @@ #include "Lgi.h" #include "GRichTextEdit.h" #include "GRichTextEditPriv.h" #include "GdcTools.h" #include "GToken.h" #define LOADER_THREAD_LOGGING 1 #define TIMEOUT_LOAD_PROGRESS 100 // ms int ImgScales[] = { 15, 25, 50, 75, 100 }; class ImageLoader : public GEventTargetThread, public Progress { GString File; GEventSinkI *Sink; GSurface *Img; GAutoPtr Filter; bool SurfaceSent; int64 Ts; GAutoPtr In; public: ImageLoader(GEventSinkI *s) : GEventTargetThread("ImageLoader") { Sink = s; Img = NULL; SurfaceSent = false; Ts = 0; } ~ImageLoader() { Cancel(true); #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - ~ImageLoader\n", _FL); #endif } void Value(int64 v) { Progress::Value(v); if (!SurfaceSent) { SurfaceSent = true; PostSink(M_IMAGE_SET_SURFACE, (GMessage::Param)Img, (GMessage::Param)In.Release()); } int64 Now = LgiCurrentTime(); if (Now - Ts > TIMEOUT_LOAD_PROGRESS) { Ts = Now; PostSink(M_IMAGE_PROGRESS, (GMessage::Param)v); } } bool PostSink(int Cmd, GMessage::Param a = 0, GMessage::Param b = 0) { for (int i=0; i<50; i++) { if (Sink->PostEvent(Cmd, a, b)) return true; LgiSleep(1); } LgiAssert(!"PostSink failed."); return false; } GMessage::Result OnEvent(GMessage *Msg) { switch (Msg->Msg()) { case M_IMAGE_LOAD_FILE: { GAutoPtr Str((GString*)Msg->A()); File = *Str; #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Thread.Receive(M_IMAGE_LOAD_FILE): '%s'\n", _FL, File.Get()); #endif if (!Filter.Reset(GFilterFactory::New(File, O_READ, NULL))) { #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Thread.Send(M_IMAGE_ERROR): no filter\n", _FL); #endif return PostSink(M_IMAGE_ERROR); } if (!In.Reset(new GFile) || !In->Open(File, O_READ)) { #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Thread.Send(M_IMAGE_ERROR): can't read\n", _FL); #endif return PostSink(M_IMAGE_ERROR); } if (!(Img = new GMemDC)) { #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Thread.Send(M_IMAGE_ERROR): alloc err\n", _FL); #endif return PostSink(M_IMAGE_ERROR); } Filter->SetProgress(this); Ts = LgiCurrentTime(); GFilter::IoStatus Status = Filter->ReadImage(Img, In); if (Status != GFilter::IoSuccess) { if (Status == GFilter::IoComponentMissing) { GString *s = new GString(Filter->GetComponentName()); #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Thread.Send(M_IMAGE_COMPONENT_MISSING)\n", _FL); #endif return PostSink(M_IMAGE_COMPONENT_MISSING, (GMessage::Param)s); } #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Thread.Send(M_IMAGE_ERROR): Filter::ReadImage err\n", _FL); #endif return PostSink(M_IMAGE_ERROR); } if (!SurfaceSent) { #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Thread.Send(M_IMAGE_SET_SURFACE)\n", _FL); #endif PostSink(M_IMAGE_SET_SURFACE, (GMessage::Param)Img, (GMessage::Param)In.Release()); } #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Thread.Send(M_IMAGE_FINISHED)\n", _FL); #endif PostSink(M_IMAGE_FINISHED); break; } case M_IMAGE_LOAD_STREAM: { GAutoPtr Stream((GStreamI*)Msg->A()); GAutoPtr FileName((GString*)Msg->B()); #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Thread.Receive(M_IMAGE_LOAD_STREAM)\n", _FL); #endif if (!Stream) { LgiAssert(!"No stream."); return PostSink(M_IMAGE_ERROR); } GMemStream *Mem = new GMemStream(Stream, 0, -1); In.Reset(Mem); if (!Filter.Reset(GFilterFactory::New(FileName ? *FileName : 0, O_READ, (const uchar*)Mem->GetBasePtr()))) { #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Thread.Send(M_IMAGE_ERROR): no filter\n", _FL); #endif return PostSink(M_IMAGE_ERROR); } if (!(Img = new GMemDC)) { #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Thread.Send(M_IMAGE_ERROR): alloc err\n", _FL); #endif return PostSink(M_IMAGE_ERROR); } Filter->SetProgress(this); Ts = LgiCurrentTime(); GFilter::IoStatus Status = Filter->ReadImage(Img, Mem); if (Status != GFilter::IoSuccess) { if (Status == GFilter::IoComponentMissing) { GString *s = new GString(Filter->GetComponentName()); #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Thread.Send(M_IMAGE_COMPONENT_MISSING)\n", _FL); #endif return PostSink(M_IMAGE_COMPONENT_MISSING, (GMessage::Param)s); } #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Thread.Send(M_IMAGE_ERROR): Filter::ReadImage err\n", _FL); #endif return PostSink(M_IMAGE_ERROR); } if (!SurfaceSent) { #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Thread.Send(M_IMAGE_SET_SURFACE)\n", _FL); #endif PostSink(M_IMAGE_SET_SURFACE, (GMessage::Param)Img, (GMessage::Param)In.Release()); } #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Thread.Send(M_IMAGE_FINISHED)\n", _FL); #endif PostSink(M_IMAGE_FINISHED); break; } case M_IMAGE_RESAMPLE: { GSurface *Dst = (GSurface*) Msg->A(); GSurface *Src = (GSurface*) Msg->B(); #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Thread.Receive(M_IMAGE_RESAMPLE)\n", _FL); #endif if (Src && Dst) { ResampleDC(Dst, Src); if (PostSink(M_IMAGE_RESAMPLE)) { #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Thread.Send(M_IMAGE_RESAMPLE)\n", _FL); #endif } else LgiTrace("%s:%i - Error sending re-sample msg.\n", _FL); } else { #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Thread.Send(M_IMAGE_ERROR): ptr err %p %p\n", _FL, Src, Dst); #endif return PostSink(M_IMAGE_ERROR); } break; } case M_IMAGE_COMPRESS: { GSurface *img = (GSurface*)Msg->A(); GRichTextPriv::ImageBlock::ScaleInf *si = (GRichTextPriv::ImageBlock::ScaleInf*)Msg->B(); #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Thread.Receive(M_IMAGE_COMPRESS)\n", _FL); #endif if (!img || !si) { #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Thread.Send(M_IMAGE_ERROR): invalid ptr\n", _FL); #endif PostSink(M_IMAGE_ERROR, (GMessage::Param) new GString("Invalid pointer.")); break; } GAutoPtr f(GFilterFactory::New("a.jpg", O_READ, NULL)); if (!f) { #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Thread.Send(M_IMAGE_ERROR): No JPEG filter available\n", _FL); #endif PostSink(M_IMAGE_ERROR, (GMessage::Param) new GString("No JPEG filter available.")); break; } GAutoPtr scaled; if (img->X() != si->Sz.x || img->Y() != si->Sz.y) { if (!scaled.Reset(new GMemDC(si->Sz.x, si->Sz.y, img->GetColourSpace()))) break; ResampleDC(scaled, img, NULL, NULL); img = scaled; } GXmlTag Props; f->Props = &Props; Props.SetAttr(LGI_FILTER_QUALITY, RICH_TEXT_RESIZED_JPEG_QUALITY); GAutoPtr jpg(new GMemStream(1024)); if (!f->WriteImage(jpg, img)) { #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Thread.Send(M_IMAGE_ERROR): Image compression failed\n", _FL); #endif PostSink(M_IMAGE_ERROR, (GMessage::Param) new GString("Image compression failed.")); break; } #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Thread.Send(M_IMAGE_COMPRESS)\n", _FL); #endif PostSink(M_IMAGE_COMPRESS, (GMessage::Param)jpg.Release(), (GMessage::Param)si); break; } case M_IMAGE_ROTATE: { #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Thread.Receive(M_IMAGE_ROTATE)\n", _FL); #endif GSurface *Img = (GSurface*)Msg->A(); if (!Img) { LgiAssert(!"No image."); break; } RotateDC(Img, Msg->B() == 1 ? 90 : 270); #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Thread.Send(M_IMAGE_ROTATE)\n", _FL); #endif PostSink(M_IMAGE_ROTATE); break; } case M_IMAGE_FLIP: { #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Thread.Receive(M_IMAGE_FLIP)\n", _FL); #endif GSurface *Img = (GSurface*)Msg->A(); if (!Img) { LgiAssert(!"No image."); break; } if (Msg->B() == 1) FlipXDC(Img); else FlipYDC(Img); #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Thread.Send(M_IMAGE_FLIP)\n", _FL); #endif PostSink(M_IMAGE_FLIP); break; } case M_CLOSE: { #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Thread.Receive(M_CLOSE)\n", _FL); #endif EndThread(); break; } } return 0; } }; GRichTextPriv::ImageBlock::ImageBlock(GRichTextPriv *priv) : Block(priv) { ThreadHnd = 0; IsDeleted = false; LayoutDirty = false; Pos.ZOff(-1, -1); Style = NULL; Size.x = 200; Size.y = 64; Scale = 1; SourceValid.ZOff(-1, -1); ResizeIdx = -1; ThreadBusy = 0; Margin.ZOff(0, 0); Border.ZOff(0, 0); Padding.ZOff(0, 0); } GRichTextPriv::ImageBlock::ImageBlock(const ImageBlock *Copy) : Block(Copy->d) { ThreadHnd = 0; ThreadBusy = 0; LayoutDirty = true; SourceImg.Reset(new GMemDC(Copy->SourceImg)); Size = Copy->Size; IsDeleted = false; Margin = Copy->Margin; Border = Copy->Border; Padding = Copy->Padding; } GRichTextPriv::ImageBlock::~ImageBlock() { LgiAssert(ThreadBusy == 0); if (ThreadHnd) PostThreadEvent(ThreadHnd, M_CLOSE); LgiAssert(Cursors == 0); } bool GRichTextPriv::ImageBlock::IsValid() { return true; } bool GRichTextPriv::ImageBlock::IsBusy(bool Stop) { return ThreadBusy != 0; } bool GRichTextPriv::ImageBlock::SetImage(GAutoPtr Img) { SourceImg = Img; if (!SourceImg) return false; Scales.Length(CountOf(ImgScales)); for (int i=0; iX() * ImgScales[i] / 100; si.Sz.y = SourceImg->Y() * ImgScales[i] / 100; si.Percent = ImgScales[i]; if (si.Sz.x == SourceImg->X() && si.Sz.y == SourceImg->Y()) { ResizeIdx = i; } } LayoutDirty = true; UpdateDisplayImg(); if (DisplayImg) { // Update the display image by scaling it from the source... if (PostThreadEvent(GetThreadHandle(), M_IMAGE_RESAMPLE, (GMessage::Param) DisplayImg.Get(), (GMessage::Param) SourceImg.Get())) UpdateThreadBusy(_FL, 1); } else LayoutDirty = true; // Also create a JPG for the current scale (needed before // we save to HTML). if (ResizeIdx >= 0 && ResizeIdx < (int)Scales.Length()) { ScaleInf &si = Scales[ResizeIdx]; if (PostThreadEvent(GetThreadHandle(), M_IMAGE_COMPRESS, (GMessage::Param)SourceImg.Get(), (GMessage::Param)&si)) UpdateThreadBusy(_FL, 1); } else LgiAssert(!"ResizeIdx should be valid."); return true; } bool GRichTextPriv::ImageBlock::Load(const char *Src) { if (Src) Source = Src; GAutoPtr Stream; GString::Array a = Source.Strip().Split(":", 1); if (a.Length() > 1 && a[0].Equals("cid")) { GDocumentEnv *Env = d->View->GetEnv(); if (!Env) return false; GDocumentEnv::LoadJob *j = Env->NewJob(); if (!j) return false; j->Uri.Reset(NewStr(Source)); j->Env = Env; j->Pref = GDocumentEnv::LoadJob::FmtStream; j->UserUid = d->View->GetDocumentUid(); GDocumentEnv::LoadType Result = Env->GetContent(j); if (Result == GDocumentEnv::LoadImmediate) { StreamMimeType = j->MimeType; ContentId = j->ContentId.Strip("<>"); FileName = j->Filename; if (j->Stream) { Stream = j->Stream; } else if (j->pDC) { SourceImg = j->pDC; return true; } } else if (Result == GDocumentEnv::LoadDeferred) { LgiAssert(!"Impl me?"); } DeleteObj(j); } else if (FileExists(Source)) { FileName = Source; FileMimeType = LgiApp->GetFileMimeType(Source); } else return false; if (!FileName && !Stream) return false; if (Stream) { #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Posting M_IMAGE_LOAD_STREAM\n", _FL); #endif if (PostThreadEvent(GetThreadHandle(), M_IMAGE_LOAD_STREAM, (GMessage::Param)Stream.Release(), (GMessage::Param) (FileName ? new GString(FileName) : NULL))) { UpdateThreadBusy(_FL, 1); return true; } } if (FileName) { #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Posting M_IMAGE_LOAD_FILE\n", _FL); #endif if (PostThreadEvent(GetThreadHandle(), M_IMAGE_LOAD_FILE, (GMessage::Param)new GString(FileName))) { UpdateThreadBusy(_FL, 1); return true; } } return false; } int GRichTextPriv::ImageBlock::GetLines() { return 1; } bool GRichTextPriv::ImageBlock::OffsetToLine(ssize_t Offset, int *ColX, GArray *LineY) { if (ColX) *ColX = Offset > 0; if (LineY) LineY->Add(0); return true; } int GRichTextPriv::ImageBlock::LineToOffset(int Line) { return 0; } void GRichTextPriv::ImageBlock::Dump() { } GNamedStyle *GRichTextPriv::ImageBlock::GetStyle(ssize_t At) { return Style; } void GRichTextPriv::ImageBlock::SetStyle(GNamedStyle *s) { if ((Style = s)) { GFont *Fnt = d->GetFont(s); LayoutDirty = true; LgiAssert(Fnt != NULL); Margin.x1 = Style->MarginLeft().ToPx(Pos.X(), Fnt); Margin.y1 = Style->MarginTop().ToPx(Pos.Y(), Fnt); Margin.x2 = Style->MarginRight().ToPx(Pos.X(), Fnt); Margin.y2 = Style->MarginBottom().ToPx(Pos.Y(), Fnt); Border.x1 = Style->BorderLeft().ToPx(Pos.X(), Fnt); Border.y1 = Style->BorderTop().ToPx(Pos.Y(), Fnt); Border.x2 = Style->BorderRight().ToPx(Pos.X(), Fnt); Border.y2 = Style->BorderBottom().ToPx(Pos.Y(), Fnt); Padding.x1 = Style->PaddingLeft().ToPx(Pos.X(), Fnt); Padding.y1 = Style->PaddingTop().ToPx(Pos.Y(), Fnt); Padding.x2 = Style->PaddingRight().ToPx(Pos.X(), Fnt); Padding.y2 = Style->PaddingBottom().ToPx(Pos.Y(), Fnt); } } ssize_t GRichTextPriv::ImageBlock::Length() { return IsDeleted ? 0 : 1; } bool GRichTextPriv::ImageBlock::ToHtml(GStream &s, GArray *Media, GRange *Rng) { if (Media) { bool ValidSourceFile = FileExists(Source); GDocView::ContentMedia &Cm = Media->New(); int Idx = LgiRand() % 10000; if (!ContentId) ContentId.Printf("%u@memecode.com", Idx); Cm.Id = ContentId; GString Style; ScaleInf *Si = ResizeIdx >= 0 && ResizeIdx < (int)Scales.Length() ? &Scales[ResizeIdx] : NULL; if (Si && Si->Compressed) { // Attach a copy of the resized JPEG... Si->Compressed->SetPos(0); Cm.Stream.Reset(new GMemStream(Si->Compressed, 0, -1)); Cm.MimeType = Si->MimeType; if (FileName) Cm.FileName = FileName; else if (Cm.MimeType.Equals("image/jpeg")) Cm.FileName.Printf("img%u.jpg", Idx); else if (Cm.MimeType.Equals("image/png")) Cm.FileName.Printf("img%u.png", Idx); else if (Cm.MimeType.Equals("image/tiff")) Cm.FileName.Printf("img%u.tiff", Idx); else if (Cm.MimeType.Equals("image/gif")) Cm.FileName.Printf("img%u.gif", Idx); else if (Cm.MimeType.Equals("image/bmp")) Cm.FileName.Printf("img%u.bmp", Idx); else { LgiAssert(!"Unknown image mime type?"); Cm.FileName.Printf("img%u", Idx); } } else if (ValidSourceFile) { // Attach the original file... GAutoString mt = LgiApp->GetFileMimeType(Source); Cm.MimeType = mt.Get(); Cm.FileName = LgiGetLeaf(Source); GFile *f = new GFile; if (f) { if (f->Open(Source, O_READ)) { Cm.Stream.Reset(f); } else { delete f; LgiTrace("%s:%i - Failed to open link image '%s'.\n", _FL, Source.Get()); } } } else { LgiTrace("%s:%i - No source or JPEG for saving image to HTML.\n", _FL); LgiAssert(!"No source file or compressed image."); return false; } LgiAssert(Cm.MimeType != NULL); if (DisplayImg && SourceImg && DisplayImg->X() != SourceImg->X()) { int Dx = DisplayImg->X(); Style.Printf(" style=\"width:%ipx\"", Dx); } if (Cm.Stream) { s.Print("HtmlLinkAsCid) s.Print("cid:%s", Cm.Id.Get()); else s.Print("%s", Cm.FileName.Get()); s.Print("\">\n"); LgiAssert(Cm.Valid()); return true; } } s.Print("\n", Source.Get()); return true; } bool GRichTextPriv::ImageBlock::GetPosFromIndex(BlockCursor *Cursor) { if (!Cursor) return d->Error(_FL, "No cursor param."); if (LayoutDirty) { Cursor->Pos.ZOff(-1, -1); // This is valid behaviour... need to // wait for layout before getting cursor // position. return false; } Cursor->Pos = ImgPos; Cursor->Line = Pos; if (Cursor->Offset == 0) { Cursor->Pos.x2 = Cursor->Pos.x1 + 1; } else if (Cursor->Offset == 1) { Cursor->Pos.x1 = Cursor->Pos.x2 - 1; } return true; } bool GRichTextPriv::ImageBlock::HitTest(HitTestResult &htr) { if (htr.In.y < Pos.y1 || htr.In.y > Pos.y2) return false; htr.Near = false; htr.LineHint = 0; int Cx = ImgPos.x1 + (ImgPos.X() / 2); if (htr.In.x < Cx) htr.Idx = 0; else htr.Idx = 1; return true; } void GRichTextPriv::ImageBlock::OnPaint(PaintContext &Ctx) { bool ImgSelected = Ctx.SelectBeforePaint(this); // Paint margins, borders and padding... GRect r = Pos; r.x1 -= Margin.x1; r.y1 -= Margin.y1; r.x2 -= Margin.x2; r.y2 -= Margin.y2; GCss::ColorDef BorderStyle; if (Style) BorderStyle = Style->BorderLeft().Color; GColour BorderCol(222, 222, 222); if (BorderStyle.Type == GCss::ColorRgb) BorderCol.Set(BorderStyle.Rgb32, 32); Ctx.DrawBox(r, Margin, Ctx.Colours[Unselected].Back); Ctx.DrawBox(r, Border, BorderCol); Ctx.DrawBox(r, Padding, Ctx.Colours[Unselected].Back); if (!DisplayImg && SourceImg && SourceImg->X() > r.X()) { UpdateDisplayImg(); } GSurface *Src = DisplayImg ? DisplayImg : SourceImg; if (Src) { if (SourceValid.Valid()) { GRect Bounds(0, 0, Size.x-1, Size.y-1); Bounds.Offset(r.x1, r.y1); Ctx.pDC->Colour(LC_MED, 24); Ctx.pDC->Box(&Bounds); Bounds.Size(1, 1); Ctx.pDC->Colour(LC_WORKSPACE, 24); Ctx.pDC->Rectangle(&Bounds); GRect rr(0, 0, Src->X()-1, SourceValid.y2 / Scale); Ctx.pDC->Blt(r.x1, r.y1, Src, &rr); } else { if (Ctx.Type == GRichTextPriv::Selected) { if (!SelectImg && SelectImg.Reset(new GMemDC(Src->X(), Src->Y(), System32BitColourSpace))) { SelectImg->Blt(0, 0, Src); int Op = SelectImg->Op(GDC_ALPHA); GColour c = Ctx.Colours[GRichTextPriv::Selected].Back; c.Rgb(c.r(), c.g(), c.b(), 0xa0); SelectImg->Colour(c); SelectImg->Rectangle(); SelectImg->Op(Op); } Ctx.pDC->Blt(r.x1, r.y1, SelectImg); } else { Ctx.pDC->Blt(r.x1, r.y1, Src); } } } else { // Drag missing image... r = ImgPos; GColour cBack(245, 245, 245); Ctx.pDC->Colour(ImgSelected ? cBack.Mix(Ctx.Colours[Selected].Back) : cBack); Ctx.pDC->Rectangle(&r); Ctx.pDC->Colour(LC_LOW, 24); uint Ls = Ctx.pDC->LineStyle(GSurface::LineAlternate); Ctx.pDC->Box(&r); Ctx.pDC->LineStyle(Ls); int Cx = r.x1 + (r.X() >> 1); int Cy = r.y1 + (r.Y() >> 1); Ctx.pDC->Colour(GColour::Red); int Sz = 5; Ctx.pDC->Line(Cx - Sz, Cy - Sz, Cx + Sz, Cy + Sz); Ctx.pDC->Line(Cx - Sz, Cy - Sz + 1, Cx + Sz - 1, Cy + Sz); Ctx.pDC->Line(Cx - Sz + 1, Cy - Sz, Cx + Sz, Cy + Sz - 1); Ctx.pDC->Line(Cx + Sz, Cy - Sz, Cx - Sz, Cy + Sz); Ctx.pDC->Line(Cx + Sz - 1, Cy - Sz, Cx - Sz, Cy + Sz - 1); Ctx.pDC->Line(Cx + Sz, Cy - Sz + 1, Cx - Sz + 1, Cy + Sz); } ImgSelected = Ctx.SelectAfterPaint(this); if (ImgSelected) { Ctx.pDC->Colour(Ctx.Colours[Selected].Back); Ctx.pDC->Rectangle(ImgPos.x2 + 1, ImgPos.y1, ImgPos.x2 + 7, ImgPos.y2); } if (Ctx.Cursor && Ctx.Cursor->Blk == this && Ctx.Cursor->Blink && d->View->Focus()) { Ctx.pDC->Colour(CursorColour); if (Ctx.Cursor->Pos.Valid()) Ctx.pDC->Rectangle(&Ctx.Cursor->Pos); else Ctx.pDC->Rectangle(Pos.x1, Pos.y1, Pos.x1, Pos.y2); } } bool GRichTextPriv::ImageBlock::OnLayout(Flow &flow) { LayoutDirty = false; flow.Left += Margin.x1; flow.Right -= Margin.x2; flow.CurY += Margin.y1; Pos.x1 = flow.Left; Pos.y1 = flow.CurY; Pos.x2 = flow.Right; Pos.y2 = flow.CurY-1; // Start with a 0px height. flow.Left += Border.x1 + Padding.x1; flow.Right -= Border.x2 + Padding.x2; flow.CurY += Border.y1 + Padding.y1; ImgPos.x1 = Pos.x1 + Padding.x1; ImgPos.y1 = Pos.y1 + Padding.y1; ImgPos.x2 = ImgPos.x1 + Size.x - 1; ImgPos.y2 = ImgPos.y1 + Size.y - 1; int Px2 = ImgPos.x2 + Padding.x2; if (Px2 < Pos.x2) Pos.x2 = ImgPos.x2 + Padding.x2; Pos.y2 = ImgPos.y2 + Padding.y2; flow.CurY = Pos.y2 + 1 + Margin.y2 + Border.y2 + Padding.y2; flow.Left -= Margin.x1 + Border.x1 + Padding.x1; flow.Right += Margin.x2 + Border.x2 + Padding.x2; return true; } ssize_t GRichTextPriv::ImageBlock::GetTextAt(ssize_t Offset, GArray &t) { // No text to get return 0; } -ssize_t GRichTextPriv::ImageBlock::CopyAt(ssize_t Offset, ssize_t Chars, GArray *Text) +ssize_t GRichTextPriv::ImageBlock::CopyAt(ssize_t Offset, ssize_t Chars, GArray *Text) { // No text to copy return 0; } bool GRichTextPriv::ImageBlock::Seek(SeekType To, BlockCursor &Cursor) { switch (To) { case SkLineStart: { Cursor.Offset = 0; Cursor.LineHint = 0; break; } case SkLineEnd: { Cursor.Offset = 1; Cursor.LineHint = 0; break; } case SkLeftChar: { if (Cursor.Offset != 1) return false; Cursor.Offset = 0; Cursor.LineHint = 0; break; } case SkRightChar: { if (Cursor.Offset != 0) return false; Cursor.Offset = 1; Cursor.LineHint = 0; break; } default: { return false; break; } } return true; } -ssize_t GRichTextPriv::ImageBlock::FindAt(ssize_t StartIdx, const uint32 *Str, GFindReplaceCommon *Params) +ssize_t GRichTextPriv::ImageBlock::FindAt(ssize_t StartIdx, const uint32_t *Str, GFindReplaceCommon *Params) { // No text to find in return -1; } void GRichTextPriv::ImageBlock::IncAllStyleRefs() { if (Style) Style->RefCount++; } bool GRichTextPriv::ImageBlock::DoContext(GSubMenu &s, GdcPt2 Doc, ssize_t Offset, bool Spelling) { if (SourceImg && !Spelling) { s.AppendSeparator(); GSubMenu *c = s.AppendSub("Transform Image"); if (c) { c->AppendItem("Rotate Clockwise", IDM_CLOCKWISE); c->AppendItem("Rotate Anti-clockwise", IDM_ANTI_CLOCKWISE); c->AppendItem("Horizontal Flip", IDM_X_FLIP); c->AppendItem("Vertical Flip", IDM_Y_FLIP); } c = s.AppendSub("Scale Image"); if (c) { for (unsigned i=0; iX() * ImgScales[i] / 100; si.Sz.y = SourceImg->Y() * ImgScales[i] / 100; si.Percent = ImgScales[i]; m.Printf("%i x %i, %i%% ", si.Sz.x, si.Sz.y, ImgScales[i]); if (si.Compressed) { char Sz[128]; LgiFormatSize(Sz, sizeof(Sz), si.Compressed->GetSize()); GString s; s.Printf(" (%s)", Sz); m += s; } GMenuItem *mi = c->AppendItem(m, IDM_SCALE_IMAGE+i, !IsBusy()); if (mi && ResizeIdx == i) { mi->Checked(true); } } } return true; } return false; } GRichTextPriv::Block *GRichTextPriv::ImageBlock::Clone() { return new ImageBlock(this); } void GRichTextPriv::ImageBlock::OnComponentInstall(GString Name) { if (Source && !SourceImg) { // Retry the load? Load(Source); } } void GRichTextPriv::ImageBlock::UpdateDisplay(int yy) { GRect s; if (DisplayImg && !SourceValid.Valid()) { SourceValid = SourceImg->Bounds(); SourceValid.y2 = yy; s = SourceValid; } else { s = SourceValid; s.y1 = s.y2 + 1; s.y2 = SourceValid.y2 = yy; } if (DisplayImg) { GRect d(0, s.y1 / Scale, DisplayImg->X()-1, s.y2 / Scale); // Do a quick and dirty nearest neighbor scale to // show the user some feed back. GSurface *Src = SourceImg; GSurface *Dst = DisplayImg; for (int y=d.y1; y<=d.y2; y++) { int sy = y * Scale; int sx = d.x1 * Scale; for (int x=d.x1; x<=d.x2; x++, sx+=Scale) { COLOUR c = Src->Get(sx, sy); Dst->Colour(c); Dst->Set(x, y); } } } LayoutDirty = true; this->d->InvalidateDoc(NULL); } int GRichTextPriv::ImageBlock::GetThreadHandle() { if (ThreadHnd == 0) { ImageLoader *il = new ImageLoader(this); if (il != NULL) ThreadHnd = il->GetHandle(); } return ThreadHnd; } void GRichTextPriv::ImageBlock::UpdateDisplayImg() { if (!SourceImg) return; Size.x = SourceImg->X(); Size.y = SourceImg->Y(); int ViewX = d->Areas[GRichTextEdit::ContentArea].X(); if (ViewX > 0) { int MaxX = (int) (ViewX * 0.9); if (SourceImg->X() > MaxX) { double Ratio = (double)SourceImg->X() / MAX(1, MaxX); Scale = (int)ceil(Ratio); Size.x = (int)ceil((double)SourceImg->X() / Scale); Size.y = (int)ceil((double)SourceImg->Y() / Scale); if (DisplayImg.Reset(new GMemDC(Size.x, Size.y, SourceImg->GetColourSpace()))) { DisplayImg->Colour(LC_MED, 24); DisplayImg->Rectangle(); } } } } void GRichTextPriv::ImageBlock::UpdateThreadBusy(const char *File, int Line, int Off) { if (ThreadBusy + Off >= 0) { ThreadBusy += Off; #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - ThreadBusy=%i\n", File, Line, ThreadBusy); #endif } else { #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Error: ThreadBusy=%i\n", File, Line, ThreadBusy, ThreadBusy + Off); #endif LgiAssert(0); } } GMessage::Result GRichTextPriv::ImageBlock::OnEvent(GMessage *Msg) { switch (Msg->Msg()) { case M_COMMAND: { if (!SourceImg) break; if (Msg->A() >= IDM_SCALE_IMAGE && Msg->A() < IDM_SCALE_IMAGE + CountOf(ImgScales)) { int i = (int)Msg->A() - IDM_SCALE_IMAGE; if (i >= 0 && i < (int)Scales.Length()) { ScaleInf &si = Scales[i]; ResizeIdx = i; #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Posting M_IMAGE_COMPRESS\n", _FL); #endif if (PostThreadEvent(GetThreadHandle(), M_IMAGE_COMPRESS, (GMessage::Param)SourceImg.Get(), (GMessage::Param)&si)) UpdateThreadBusy(_FL, 1); else LgiAssert(!"PostThreadEvent failed."); } } else switch (Msg->A()) { case IDM_CLOCKWISE: #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Posting M_IMAGE_ROTATE\n", _FL); #endif if (PostThreadEvent(GetThreadHandle(), M_IMAGE_ROTATE, (GMessage::Param) SourceImg.Get(), 1)) UpdateThreadBusy(_FL, 1); break; case IDM_ANTI_CLOCKWISE: #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Posting M_IMAGE_ROTATE\n", _FL); #endif if (PostThreadEvent(GetThreadHandle(), M_IMAGE_ROTATE, (GMessage::Param) SourceImg.Get(), -1)) UpdateThreadBusy(_FL, 1); break; case IDM_X_FLIP: #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Posting M_IMAGE_FLIP\n", _FL); #endif if (PostThreadEvent(GetThreadHandle(), M_IMAGE_FLIP, (GMessage::Param) SourceImg.Get(), 1)) UpdateThreadBusy(_FL, 1); break; case IDM_Y_FLIP: #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Posting M_IMAGE_FLIP\n", _FL); #endif if (PostThreadEvent(GetThreadHandle(), M_IMAGE_FLIP, (GMessage::Param) SourceImg.Get(), 0)) UpdateThreadBusy(_FL, 1); break; } break; } case M_IMAGE_COMPRESS: { GAutoPtr Jpg((GMemStream*)Msg->A()); ScaleInf *Si = (ScaleInf*)Msg->B(); if (!Jpg || !Si) { LgiAssert(0); #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Error: M_IMAGE_COMPRESS bad arg\n", _FL); #endif break; } #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Received M_IMAGE_COMPRESS\n", _FL); #endif Si->Compressed.Reset(Jpg.Release()); Si->MimeType = "image/jpeg"; UpdateThreadBusy(_FL, -1); break; } case M_IMAGE_ERROR: { GAutoPtr ErrMsg((GString*) Msg->A()); #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Received M_IMAGE_ERROR, posting M_CLOSE\n", _FL); #endif UpdateThreadBusy(_FL, -1); break; } case M_IMAGE_COMPONENT_MISSING: { GAutoPtr Component((GString*) Msg->A()); #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Received M_IMAGE_COMPONENT_MISSING, posting M_CLOSE\n", _FL); #endif UpdateThreadBusy(_FL, -1); if (Component) { GToken t(*Component, ","); for (int i=0; iView->NeedsCapability(t[i]); } else LgiAssert(!"Missing component name."); break; } case M_IMAGE_SET_SURFACE: { GAutoPtr File((GStream*)Msg->B()); #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Received M_IMAGE_SET_SURFACE\n", _FL); #endif if (SourceImg.Reset((GSurface*)Msg->A())) { Scales.Length(CountOf(ImgScales)); for (int i=0; iX() * ImgScales[i] / 100; si.Sz.y = SourceImg->Y() * ImgScales[i] / 100; si.Percent = ImgScales[i]; if (si.Sz.x == SourceImg->X() && si.Sz.y == SourceImg->Y()) { ResizeIdx = i; si.Compressed.Reset(File.Release()); if (StreamMimeType) { si.MimeType = StreamMimeType; } else if (FileMimeType) { si.MimeType = FileMimeType.Get(); FileMimeType.Reset(); } } } UpdateDisplayImg(); } break; } case M_IMAGE_PROGRESS: { #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Received M_IMAGE_PROGRESS\n", _FL); #endif UpdateDisplay((int)Msg->A()); break; } case M_IMAGE_FINISHED: { #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Received M_IMAGE_FINISHED\n", _FL); #endif UpdateDisplay(SourceImg->Y()-1); UpdateThreadBusy(_FL, -1); if (DisplayImg != NULL && PostThreadEvent(GetThreadHandle(), M_IMAGE_RESAMPLE, (GMessage::Param)DisplayImg.Get(), (GMessage::Param)SourceImg.Get())) UpdateThreadBusy(_FL, 1); break; } case M_IMAGE_RESAMPLE: { #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Received M_IMAGE_RESAMPLE\n", _FL); #endif LayoutDirty = true; UpdateThreadBusy(_FL, -1); d->InvalidateDoc(NULL); SourceValid.ZOff(-1, -1); break; } case M_IMAGE_ROTATE: case M_IMAGE_FLIP: { #if LOADER_THREAD_LOGGING LgiTrace("%s:%i - Received %s\n", _FL, Msg->Msg()==M_IMAGE_ROTATE?"M_IMAGE_ROTATE":"M_IMAGE_FLIP"); #endif GAutoPtr Img = SourceImg; UpdateThreadBusy(_FL, -1); SetImage(Img); break; } default: return false; } return true; } -bool GRichTextPriv::ImageBlock::AddText(Transaction *Trans, ssize_t AtOffset, const uint32 *Str, ssize_t Chars, GNamedStyle *Style) +bool GRichTextPriv::ImageBlock::AddText(Transaction *Trans, ssize_t AtOffset, const uint32_t *Str, ssize_t Chars, GNamedStyle *Style) { // Can't add text to image block return false; } bool GRichTextPriv::ImageBlock::ChangeStyle(Transaction *Trans, ssize_t Offset, ssize_t Chars, GCss *Style, bool Add) { // No styles to change... return false; } -ssize_t GRichTextPriv::ImageBlock::DeleteAt(Transaction *Trans, ssize_t BlkOffset, ssize_t Chars, GArray *DeletedText) +ssize_t GRichTextPriv::ImageBlock::DeleteAt(Transaction *Trans, ssize_t BlkOffset, ssize_t Chars, GArray *DeletedText) { // The image is one "character" IsDeleted = BlkOffset == 0; if (IsDeleted) return true; return false; } bool GRichTextPriv::ImageBlock::DoCase(Transaction *Trans, ssize_t StartIdx, ssize_t Chars, bool Upper) { // No text to change case... return false; } #ifdef _DEBUG void GRichTextPriv::ImageBlock::DumpNodes(GTreeItem *Ti) { GString s; s.Printf("ImageBlock style=%s", Style?Style->Name.Get():NULL); Ti->SetText(s); } #endif diff --git a/src/common/Widgets/Editor/TextBlock.cpp b/src/common/Widgets/Editor/TextBlock.cpp --- a/src/common/Widgets/Editor/TextBlock.cpp +++ b/src/common/Widgets/Editor/TextBlock.cpp @@ -1,2519 +1,2519 @@ #include "Lgi.h" #include "GRichTextEdit.h" #include "GRichTextEditPriv.h" #include "Emoji.h" #include "GDocView.h" #define DEBUG_LAYOUT 0 ////////////////////////////////////////////////////////////////////////////////////////////////// GRichTextPriv::StyleText::StyleText(const StyleText *St) { Emoji = St->Emoji; Style = NULL; Element = St->Element; Param = St->Param; if (St->Style) SetStyle(St->Style); Add((uint32*)&St->ItemAt(0), St->Length()); } -GRichTextPriv::StyleText::StyleText(const uint32 *t, ssize_t Chars, GNamedStyle *style) +GRichTextPriv::StyleText::StyleText(const uint32_t *t, ssize_t Chars, GNamedStyle *style) { Emoji = false; Style = NULL; Element = CONTENT; if (style) SetStyle(style); if (t) { if (Chars < 0) Chars = Strlen(t); Add((uint32*)t, (int)Chars); } } -uint32 *GRichTextPriv::StyleText::At(ssize_t i) +uint32_t *GRichTextPriv::StyleText::At(ssize_t i) { if (i >= 0 && i < (int)Length()) return &(*this)[i]; LgiAssert(0); return NULL; } GNamedStyle *GRichTextPriv::StyleText::GetStyle() { return Style; } void GRichTextPriv::StyleText::SetStyle(GNamedStyle *s) { if (Style != s) { Style = s; Colours.Empty(); if (Style) { GCss::ColorDef c = Style->Color(); if (c.Type == GCss::ColorRgb) Colours.Fore.Set(c.Rgb32, 32); c = Style->BackgroundColor(); if (c.Type == GCss::ColorRgb) Colours.Back.Set(c.Rgb32, 32); } } } ////////////////////////////////////////////////////////////////////////////////////////////////// -GRichTextPriv::EmojiDisplayStr::EmojiDisplayStr(StyleText *src, GSurface *img, GFont *f, const uint32 *s, ssize_t l) : +GRichTextPriv::EmojiDisplayStr::EmojiDisplayStr(StyleText *src, GSurface *img, GFont *f, const uint32_t *s, ssize_t l) : DisplayStr(src, NULL, s, l) { Img = img; #if defined(_MSC_VER) Utf16to32(Utf32, (const uint16*) StrCache.Get(), len); uint32 *u = &Utf32[0]; #else LgiAssert(sizeof(char16) == 4); uint32 *u = (uint32*)StrCache.Get(); Chars = Strlen(u); #endif for (int i=0; i= 0); if (Idx >= 0) { int x = Idx % EMOJI_GROUP_X; int y = Idx / EMOJI_GROUP_X; GRect &rc = SrcRect[i]; rc.ZOff(EMOJI_CELL_SIZE-1, EMOJI_CELL_SIZE-1); rc.Offset(x * EMOJI_CELL_SIZE, y * EMOJI_CELL_SIZE); } } x = (int)SrcRect.Length() * EMOJI_CELL_SIZE; y = EMOJI_CELL_SIZE; xf = IntToFixed(x); yf = IntToFixed(y); } GAutoPtr GRichTextPriv::EmojiDisplayStr::Clone(ssize_t Start, ssize_t Len) { if (Len < 0) Len = Chars - Start; #if defined(_MSC_VER) LgiAssert( Start >= 0 && Start < (int)Utf32.Length() && Start + Len <= (int)Utf32.Length()); #endif GAutoPtr s(new EmojiDisplayStr(Src, Img, NULL, #if defined(_MSC_VER) &Utf32[Start] #else (uint32*)(const char16*)(*this) #endif , Len)); return s; } void GRichTextPriv::EmojiDisplayStr::Paint(GSurface *pDC, int &FixX, int FixY, GColour &Back) { GRect f(0, 0, x-1, y-1); f.Offset(FixedToInt(FixX), FixedToInt(FixY)); pDC->Colour(Back); pDC->Rectangle(&f); int Op = pDC->Op(GDC_ALPHA); for (unsigned i=0; iBlt(f.x1, f.y1, Img, &SrcRect[i]); f.x1 += EMOJI_CELL_SIZE; FixX += IntToFixed(EMOJI_CELL_SIZE); } pDC->Op(Op); } double GRichTextPriv::EmojiDisplayStr::GetAscent() { return EMOJI_CELL_SIZE * 0.8; } ssize_t GRichTextPriv::EmojiDisplayStr::PosToIndex(int XPos, bool Nearest) { if (XPos >= (int)x) return Chars; if (XPos <= 0) return 0; return (XPos + (Nearest ? EMOJI_CELL_SIZE >> 1 : 0)) / EMOJI_CELL_SIZE; } ////////////////////////////////////////////////////////////////////////////////////////////////// GRichTextPriv::TextLine::TextLine(int XOffsetPx, int WidthPx, int YOffsetPx) { NewLine = 0; PosOff.ZOff(0, 0); PosOff.Offset(XOffsetPx, YOffsetPx); } int GRichTextPriv::TextLine::Length() { int Len = NewLine; for (unsigned i=0; iChars; return Len; } /// This runs after the layout line has been filled with display strings. /// It measures the line and works out the right offsets for each strings /// so that their baselines all match up correctly. void GRichTextPriv::TextLine::LayoutOffsets(int DefaultFontHt) { double BaseLine = 0.0; int HtPx = 0; for (unsigned i=0; iGetAscent(); BaseLine = MAX(BaseLine, Ascent); HtPx = MAX(HtPx, ds->Y()); } if (Strs.Length() == 0) HtPx = DefaultFontHt; else LgiAssert(HtPx > 0); for (unsigned i=0; iGetAscent(); if (Ascent > 0.0) ds->OffsetY = (int)(BaseLine - Ascent); LgiAssert(ds->OffsetY >= 0); HtPx = MAX(HtPx, ds->OffsetY+ds->Y()); } PosOff.y2 = PosOff.y1 + HtPx - 1; } ////////////////////////////////////////////////////////////////////////////////////////////////// GRichTextPriv::TextBlock::TextBlock(GRichTextPriv *priv) : Block(priv) { LayoutDirty = false; Len = 0; Pos.ZOff(-1, -1); Style = NULL; Fnt = NULL; ClickErrIdx = -1; Margin.ZOff(0, 0); Border.ZOff(0, 0); Padding.ZOff(0, 0); } GRichTextPriv::TextBlock::TextBlock(const TextBlock *Copy) : Block(Copy) { LayoutDirty = true; Len = Copy->Len; Pos = Copy->Pos; Style = Copy->Style; Fnt = Copy->Fnt; Margin = Copy->Margin; Border = Copy->Border; Padding = Copy->Padding; for (unsigned i=0; iTxt.Length(); i++) { Txt.Add(new StyleText(Copy->Txt.ItemAt(i))); } } GRichTextPriv::TextBlock::~TextBlock() { LgiAssert(Cursors == 0); Txt.DeleteObjects(); } void GRichTextPriv::TextBlock::Dump() { LgiTrace(" Txt.Len=%i, margin=%s, border=%s, padding=%s\n", Txt.Length(), Margin.GetStr(), Border.GetStr(), Padding.GetStr()); for (unsigned i=0; iLength() ? #ifndef WINDOWS (char16*) #endif t->At(0) : NULL, t->Length()); s = s.Strip(); LgiTrace(" %p: style=%p/%s, len=%i\n", t, t->GetStyle(), t->GetStyle() ? t->GetStyle()->Name.Get() : NULL, t->Length()); } } GNamedStyle *GRichTextPriv::TextBlock::GetStyle(ssize_t At) { if (At >= 0) { GArray t; if (GetTextAt(At, t)) return t[0]->GetStyle(); } return Style; } void GRichTextPriv::TextBlock::SetStyle(GNamedStyle *s) { if ((Style = s)) { Fnt = d->GetFont(s); LayoutDirty = true; LgiAssert(Fnt != NULL); Margin.x1 = Style->MarginLeft().ToPx(Pos.X(), Fnt); Margin.y1 = Style->MarginTop().ToPx(Pos.Y(), Fnt); Margin.x2 = Style->MarginRight().ToPx(Pos.X(), Fnt); Margin.y2 = Style->MarginBottom().ToPx(Pos.Y(), Fnt); Border.x1 = Style->BorderLeft().ToPx(Pos.X(), Fnt); Border.y1 = Style->BorderTop().ToPx(Pos.Y(), Fnt); Border.x2 = Style->BorderRight().ToPx(Pos.X(), Fnt); Border.y2 = Style->BorderBottom().ToPx(Pos.Y(), Fnt); Padding.x1 = Style->PaddingLeft().ToPx(Pos.X(), Fnt); Padding.y1 = Style->PaddingTop().ToPx(Pos.Y(), Fnt); Padding.x2 = Style->PaddingRight().ToPx(Pos.X(), Fnt); Padding.y2 = Style->PaddingBottom().ToPx(Pos.Y(), Fnt); } } ssize_t GRichTextPriv::TextBlock::Length() { return Len; } HtmlTag IsDefaultStyle(HtmlTag Id, GCss *Css) { if (!Css) return CONTENT; if (Css->Length() == 2) { GCss::ColorDef c = Css->Color(); if ((GColour)c != GColour::Blue) return CONTENT; GCss::TextDecorType td = Css->TextDecoration(); if (td != GCss::TextDecorUnderline) return CONTENT; return TAG_A; } else if (Css->Length() == 1) { GCss::FontWeightType fw = Css->FontWeight(); if (fw == GCss::FontWeightBold || fw == GCss::FontWeightBolder || fw >= GCss::FontWeight700) return TAG_B; GCss::TextDecorType td = Css->TextDecoration(); if (td == GCss::TextDecorUnderline) return TAG_U; GCss::FontStyleType fs = Css->FontStyle(); if (fs == GCss::FontStyleItalic) return TAG_I; } return CONTENT; } bool GRichTextPriv::TextBlock::ToHtml(GStream &s, GArray *Media, GRange *Rng) { s.Print("

"); GRange All(0, Length()); if (!Rng) Rng = &All; size_t Pos = 0; for (unsigned i=0; iGetStyle(); ssize_t tlen = t->Length(); if (!tlen) continue; GRange TxtRange(Pos, tlen); GRange Common = TxtRange.Overlap(*Rng); if (Common.Valid()) { GString utf( #ifndef WINDOWS (char16*) #endif t->At(Common.Start - Pos), Common.Len); char *str = utf; const char *ElemName = NULL; if (t->Element != CONTENT) { GHtmlElemInfo *e = d->Inst.Static->GetTagInfo(t->Element); if (!e) return false; ElemName = e->Tag; if (style) { HtmlTag tag = IsDefaultStyle(t->Element, style); if (tag == t->Element) style = NULL; } } else { HtmlTag tag = IsDefaultStyle(t->Element, style); if (tag != CONTENT) { GHtmlElemInfo *e = d->Inst.Static->GetTagInfo(tag); if (e) { ElemName = e->Tag; style = NULL; } } } if (style && !ElemName) ElemName = "span"; if (ElemName) s.Print("<%s", ElemName); if (style) s.Print(" class='%s'", style->Name.Get()); if (t->Element == TAG_A && t->Param) s.Print(" href='%s'", t->Param.Get()); if (ElemName) s.Print(">"); // Encode entities... GUtf8Ptr last(str); GUtf8Ptr cur(str); GUtf8Ptr end(str + utf.Length()); while (cur < end) { int32 ch = cur; switch (ch) { case '<': s.Print("%.*s<", cur - last, last.GetPtr()); last = ++cur; break; case '>': s.Print("%.*s>", cur - last, last.GetPtr()); last = ++cur; break; case '\n': s.Print("%.*s
\n", cur - last, last.GetPtr()); last = ++cur; break; case '&': s.Print("%.*s&", cur - last, last.GetPtr()); last = ++cur; break; case 0xa0: s.Print("%.*s ", cur - last, last.GetPtr()); last = ++cur; break; default: cur++; break; } } s.Print("%.*s", cur - last, last.GetPtr()); if (ElemName) s.Print("", ElemName); } Pos += tlen; } s.Print("

\n"); return true; } bool GRichTextPriv::TextBlock::GetPosFromIndex(BlockCursor *Cursor) { if (!Cursor) return d->Error(_FL, "No cursor param."); if (LayoutDirty) { Cursor->Pos.ZOff(-1, -1); // This is valid behaviour... need to // wait for layout before getting cursor // position. return false; } int CharPos = 0; int LastY = 0; for (unsigned i=0; iPosOff; r.Offset(Pos.x1, Pos.y1); int FixX = 0; for (unsigned n=0; nStrs.Length(); n++) { DisplayStr *ds = tl->Strs[n]; ssize_t dsChars = ds->Chars; if ( Cursor->Offset >= CharPos && Cursor->Offset <= CharPos + dsChars && ( Cursor->LineHint < 0 || Cursor->LineHint == i ) ) { ssize_t CharOffset = Cursor->Offset - CharPos; if (CharOffset == 0) { // First char Cursor->Pos.x1 = r.x1 + FixedToInt(FixX); } else if (CharOffset == dsChars) { // Last char Cursor->Pos.x1 = r.x1 + FixedToInt(FixX + ds->FX()); } else { // In the middle somewhere... GAutoPtr Tmp = ds->Clone(0, CharOffset); // GDisplayString Tmp(ds->GetFont(), *ds, CharOffset); if (Tmp) Cursor->Pos.x1 = r.x1 + FixedToInt(FixX + Tmp->FX()); } Cursor->Pos.y1 = r.y1 + ds->OffsetY; Cursor->Pos.y2 = Cursor->Pos.y1 + ds->Y() - 1; Cursor->Pos.x2 = Cursor->Pos.x1 + 1; Cursor->Line.Set(Pos.x1, r.y1, Pos.x2, r.y2); return true; } FixX += ds->FX(); CharPos += ds->Chars; } if ( ( tl->Strs.Length() == 0 || i == Layout.Length() - 1 ) && Cursor->Offset == CharPos ) { // Cursor at the start of empty line. Cursor->Pos.x1 = r.x1; Cursor->Pos.x2 = Cursor->Pos.x1 + 1; Cursor->Pos.y1 = r.y1; Cursor->Pos.y2 = r.y2; Cursor->Line.Set(Pos.x1, r.y1, Pos.x2, r.y2); return true; } CharPos += tl->NewLine; LastY = tl->PosOff.y2; } if (Cursor->Offset == 0 && Len == 0) { Cursor->Pos.x1 = Pos.x1; Cursor->Pos.x2 = Pos.x1 + 1; Cursor->Pos.y1 = Pos.y1; Cursor->Pos.y2 = Pos.y2; Cursor->Line = Pos; return true; } return false; } bool GRichTextPriv::TextBlock::HitTest(HitTestResult &htr) { if (htr.In.y < Pos.y1 || htr.In.y > Pos.y2) return false; int CharPos = 0; for (unsigned i=0; iPosOff; r.Offset(Pos.x1, Pos.y1); bool Over = r.Overlap(htr.In.x, htr.In.y); bool OnThisLine = htr.In.y >= r.y1 && htr.In.y <= r.y2; if (OnThisLine && htr.In.x <= r.x1) { htr.Near = true; htr.Idx = CharPos; htr.LineHint = i; LgiAssert(htr.Idx <= Length()); return true; } int FixX = 0; int InputX = IntToFixed(htr.In.x - Pos.x1 - tl->PosOff.x1); for (unsigned n=0; nStrs.Length(); n++) { DisplayStr *ds = tl->Strs[n]; int dsFixX = ds->FX(); if (Over && InputX >= FixX && InputX < FixX + dsFixX) { int OffFix = InputX - FixX; int OffPx = FixedToInt(OffFix); ssize_t OffChar = ds->PosToIndex(OffPx, true); // d->DebugRects[0].Set(Pos.x1, r.y1, Pos.x1 + InputX+1, r.y2); htr.Blk = this; htr.Ds = ds; htr.Idx = CharPos + OffChar; htr.LineHint = i; LgiAssert(htr.Idx <= Length()); return true; } FixX += ds->FX(); CharPos += ds->Chars; } if (OnThisLine) { htr.Near = true; htr.Idx = CharPos; htr.LineHint = i; LgiAssert(htr.Idx <= Length()); return true; } CharPos += tl->NewLine; } return false; } void DrawDecor(GSurface *pDC, GRichTextPriv::DisplayStr *Ds, int Fx, int Fy, ssize_t Start, ssize_t Len) { // GColour Old = pDC->Colour(GColour::Red); GDisplayString ds1(Ds->GetFont(), (const char16*)(*Ds), Start); GDisplayString ds2(Ds->GetFont(), (const char16*)(*Ds), Start+Len); int x = (Fx >> GDisplayString::FShift); int y = (Fy >> GDisplayString::FShift) + (int)Ds->GetAscent() + 1; int End = x + ds2.X(); x += ds1.X(); pDC->Colour(GColour::Red); while (x < End) { pDC->Set(x, y+(x%2)); x++; } } bool Overlap(GSpellCheck::SpellingError *e, int start, ssize_t len) { if (!e) return false; if (start+len <= e->Start) return false; if (start >= e->End()) return false; return true; } void GRichTextPriv::TextBlock::DrawDisplayString(GSurface *pDC, DisplayStr *Ds, int &FixX, int FixY, GColour &Bk, int &Pos) { int OldX = FixX; // Paint the string itself... Ds->Paint(pDC, FixX, FixY, Bk); // Does the a spelling error overlap this string? ssize_t DsEnd = Pos + Ds->Chars; while (Overlap(SpErr, Pos, Ds->Chars)) { // Yes, work out the region of characters and paint the decor ssize_t Start = MAX(SpErr->Start, Pos); ssize_t Len = MIN(SpErr->End(), Pos + Ds->Chars) - Start; // Draw the decor for the error DrawDecor(pDC, Ds, OldX, FixY, Start - Pos, Len); if (SpErr->End() < DsEnd) { // Are there more errors? SpErr = SpellingErrors.AddressOf(++PaintErrIdx); } else break; } while (SpErr && SpErr->End() < DsEnd) { // Are there more errors? SpErr = SpellingErrors.AddressOf(++PaintErrIdx); } Pos += Ds->Chars; } void GRichTextPriv::TextBlock::OnPaint(PaintContext &Ctx) { int CharPos = 0; int EndPoints = 0; ssize_t EndPoint[2] = {-1, -1}; int CurEndPoint = 0; if (Cursors > 0 && Ctx.Select) { // Selection end point checks... if (Ctx.Cursor && Ctx.Cursor->Blk == this) EndPoint[EndPoints++] = Ctx.Cursor->Offset; if (Ctx.Select && Ctx.Select->Blk == this) EndPoint[EndPoints++] = Ctx.Select->Offset; // Sort the end points if (EndPoints > 1 && EndPoint[0] > EndPoint[1]) { ssize_t ep = EndPoint[0]; EndPoint[0] = EndPoint[1]; EndPoint[1] = ep; } } // Paint margins, borders and padding... GRect r = Pos; r.x1 -= Margin.x1; r.y1 -= Margin.y1; r.x2 -= Margin.x2; r.y2 -= Margin.y2; GCss::ColorDef BorderStyle; if (Style) BorderStyle = Style->BorderLeft().Color; GColour BorderCol(222, 222, 222); if (BorderStyle.Type == GCss::ColorRgb) BorderCol.Set(BorderStyle.Rgb32, 32); Ctx.DrawBox(r, Margin, Ctx.Colours[Unselected].Back); Ctx.DrawBox(r, Border, BorderCol); Ctx.DrawBox(r, Padding, Ctx.Colours[Unselected].Back); int CurY = Pos.y1; PaintErrIdx = 0; SpErr = SpellingErrors.AddressOf(PaintErrIdx); for (unsigned i=0; iPosOff; LinePos.Offset(Pos.x1, Pos.y1); if (Line->PosOff.X() < Pos.X()) { Ctx.pDC->Colour(Ctx.Colours[Unselected].Back); Ctx.pDC->Rectangle(LinePos.x2, LinePos.y1, Pos.x2, LinePos.y2); } int FixX = IntToFixed(LinePos.x1); if (CurY < LinePos.y1) { // Fill padded area... Ctx.pDC->Colour(Ctx.Colours[Unselected].Back); Ctx.pDC->Rectangle(Pos.x1, CurY, Pos.x2, LinePos.y1 - 1); } CurY = LinePos.y1; GFont *Fnt = NULL; #if DEBUG_NUMBERED_LAYOUTS GString s; s.Printf("%i", Ctx.Index); Ctx.Index++; #endif for (unsigned n=0; nStrs.Length(); n++) { DisplayStr *Ds = Line->Strs[n]; GFont *DsFnt = Ds->GetFont(); ColourPair &Cols = Ds->Src->Colours; if (DsFnt && DsFnt != Fnt) { Fnt = DsFnt; Fnt->Transparent(false); } // If the current text part doesn't cover the full line height we have to // fill in the rest here... if (Ds->Y() < Line->PosOff.Y()) { Ctx.pDC->Colour(Ctx.Colours[Unselected].Back); int CurX = FixedToInt(FixX); if (Ds->OffsetY > 0) Ctx.pDC->Rectangle(CurX, CurY, CurX+Ds->X(), CurY+Ds->OffsetY-1); int DsY2 = Ds->OffsetY + Ds->Y(); if (DsY2 < Pos.Y()) Ctx.pDC->Rectangle(CurX, CurY+DsY2, CurX+Ds->X(), Pos.y2); } // Check for selection changes... int FixY = IntToFixed(CurY + Ds->OffsetY); #if DEBUG_OUTLINE_CUR_STYLE_TEXT GRect r(0, 0, -1, -1); if (Ctx.Cursor->Blk == (Block*)this) { GArray CurStyle; if (GetTextAt(Ctx.Cursor->Offset, CurStyle) && Ds->Src == CurStyle.First()) { r.ZOff(Ds->X()-1, Ds->Y()-1); r.Offset(FixedToInt(FixX), FixedToInt(FixY)); } } #endif if (CurEndPoint < EndPoints && EndPoint[CurEndPoint] >= CharPos && EndPoint[CurEndPoint] <= CharPos + Ds->Chars) { // Process string into parts based on the selection boundaries ssize_t Ch = EndPoint[CurEndPoint] - CharPos; int TmpPos = CharPos; GAutoPtr ds1 = Ds->Clone(0, Ch); // First part... GColour Bk = Ctx.Type == Unselected && Cols.Back.IsValid() ? Cols.Back : Ctx.Back(); if (DsFnt) DsFnt->Colour(Ctx.Type == Unselected && Cols.Fore.IsValid() ? Cols.Fore : Ctx.Fore(), Bk); if (ds1) DrawDisplayString(Ctx.pDC, ds1, FixX, FixY, Bk, TmpPos); Ctx.Type = Ctx.Type == Selected ? Unselected : Selected; CurEndPoint++; // Is there 3 parts? // // This happens when the selection starts and end in the one string. // // The alternative is that it starts or ends in the strings but the other // end point is in a different string. In which case there is only 2 strings // to draw. if (CurEndPoint < EndPoints && EndPoint[CurEndPoint] >= CharPos && EndPoint[CurEndPoint] <= CharPos + Ds->Chars) { // Yes.. ssize_t Ch2 = EndPoint[CurEndPoint] - CharPos; // Part 2 GAutoPtr ds2 = Ds->Clone(Ch, Ch2 - Ch); GColour Bk = Ctx.Type == Unselected && Cols.Back.IsValid() ? Cols.Back : Ctx.Back(); if (DsFnt) DsFnt->Colour(Ctx.Type == Unselected && Cols.Fore.IsValid() ? Cols.Fore : Ctx.Fore(), Bk); if (ds2) DrawDisplayString(Ctx.pDC, ds2, FixX, FixY, Bk, TmpPos); Ctx.Type = Ctx.Type == Selected ? Unselected : Selected; CurEndPoint++; // Part 3 if (Ch2 < Ds->Length()) { GAutoPtr ds3 = Ds->Clone(Ch2); Bk = Ctx.Type == Unselected && Cols.Back.IsValid() ? Cols.Back : Ctx.Back(); if (DsFnt) DsFnt->Colour(Ctx.Type == Unselected && Cols.Fore.IsValid() ? Cols.Fore : Ctx.Fore(), Bk); if (ds3) DrawDisplayString(Ctx.pDC, ds3, FixX, FixY, Bk, TmpPos); } } else if (Ch < Ds->Chars) { // No... draw 2nd part GAutoPtr ds2 = Ds->Clone(Ch); GColour Bk = Ctx.Type == Unselected && Cols.Back.IsValid() ? Cols.Back : Ctx.Back(); if (DsFnt) DsFnt->Colour(Ctx.Type == Unselected && Cols.Fore.IsValid() ? Cols.Fore : Ctx.Fore(), Bk); if (ds2) DrawDisplayString(Ctx.pDC, ds2, FixX, FixY, Bk, TmpPos); } } else { // No selection changes... draw the whole string GColour Bk = Ctx.Type == Unselected && Cols.Back.IsValid() ? Cols.Back : Ctx.Back(); if (DsFnt) DsFnt->Colour(Ctx.Type == Unselected && Cols.Fore.IsValid() ? Cols.Fore : Ctx.Fore(), Bk); #if DEBUG_OUTLINE_CUR_DISPLAY_STR int OldFixX = FixX; #endif int TmpPos = CharPos; DrawDisplayString(Ctx.pDC, Ds, FixX, FixY, Bk, TmpPos); #if DEBUG_OUTLINE_CUR_DISPLAY_STR if (Ctx.Cursor->Blk == (Block*)this && Ctx.Cursor->Offset >= CharPos && Ctx.Cursor->Offset < CharPos + Ds->Chars) { GRect r(0, 0, Ds->X()-1, Ds->Y()-1); r.Offset(FixedToInt(OldFixX), FixedToInt(FixY)); Ctx.pDC->Colour(GColour::Red); Ctx.pDC->Box(&r); } #endif } #if DEBUG_OUTLINE_CUR_STYLE_TEXT if (r.Valid()) { Ctx.pDC->Colour(GColour(192, 192, 192)); Ctx.pDC->LineStyle(GSurface::LineDot); Ctx.pDC->Box(&r); Ctx.pDC->LineStyle(GSurface::LineSolid); } #endif CharPos += Ds->Chars; } if (Line->Strs.Length() == 0) { if (CurEndPoint < EndPoints && EndPoint[CurEndPoint] == CharPos) { Ctx.Type = Ctx.Type == Selected ? Unselected : Selected; CurEndPoint++; } } if (Ctx.Type == Selected) { // Draw new line int x1 = FixedToInt(FixX); FixX += IntToFixed(5); int x2 = FixedToInt(FixX); Ctx.pDC->Colour(Ctx.Colours[Selected].Back); Ctx.pDC->Rectangle(x1, LinePos.y1, x2, LinePos.y2); } Ctx.pDC->Colour(Ctx.Colours[Unselected].Back); Ctx.pDC->Rectangle(FixedToInt(FixX), LinePos.y1, Pos.x2, LinePos.y2); #if DEBUG_NUMBERED_LAYOUTS GDisplayString Ds(SysFont, s); SysFont->Colour(GColour::Green, GColour::White); SysFont->Transparent(false); Ds.Draw(Ctx.pDC, LinePos.x1, LinePos.y1); /* Ctx.pDC->Colour(GColour::Blue); Ctx.pDC->Line(LinePos.x1, LinePos.y1,LinePos.x2,LinePos.y2); */ #endif CurY = LinePos.y2 + 1; CharPos += Line->NewLine; } if (CurY < Pos.y2) { // Fill padded area... Ctx.pDC->Colour(Ctx.Colours[Unselected].Back); Ctx.pDC->Rectangle(Pos.x1, CurY, Pos.x2, Pos.y2); } if (Ctx.Cursor && Ctx.Cursor->Blk == this && Ctx.Cursor->Blink && d->View->Focus()) { Ctx.pDC->Colour(CursorColour); if (Ctx.Cursor->Pos.Valid()) Ctx.pDC->Rectangle(&Ctx.Cursor->Pos); else Ctx.pDC->Rectangle(Pos.x1, Pos.y1, Pos.x1, Pos.y2); } #if 0 // def _DEBUG if (Ctx.Select && Ctx.Select->Blk == this) { Ctx.pDC->Colour(GColour(255, 0, 0)); Ctx.pDC->Rectangle(&Ctx.Select->Pos); } #endif } bool GRichTextPriv::TextBlock::OnLayout(Flow &flow) { if (Pos.X() == flow.X() && !LayoutDirty) { // Adjust position to match the flow, even if we are not dirty Pos.Offset(0, flow.CurY - Pos.y1); flow.CurY = Pos.y2 + 1; return true; } LayoutDirty = false; Layout.DeleteObjects(); flow.Left += Margin.x1; flow.Right -= Margin.x2; flow.CurY += Margin.y1; Pos.x1 = flow.Left; Pos.y1 = flow.CurY; Pos.x2 = flow.Right; Pos.y2 = flow.CurY-1; // Start with a 0px height. flow.Left += Border.x1 + Padding.x1; flow.Right -= Border.x2 + Padding.x2; flow.CurY += Border.y1 + Padding.y1; int FixX = 0; // Current x offset (fixed point) on the current line GAutoPtr CurLine(new TextLine(flow.Left - Pos.x1, flow.X(), flow.CurY - Pos.y1)); if (!CurLine) return flow.d->Error(_FL, "alloc failed."); int LayoutSize = 0; int TextSize = 0; for (unsigned i=0; iGetStyle(); LgiAssert(t->Length() >= 0); TextSize += t->Length(); if (t->Length() == 0) continue; int AvailableX = Pos.X() - CurLine->PosOff.x1; if (AvailableX < 0) AvailableX = 1; // Get the font for 't' GFont *f = flow.d->GetFont(t->GetStyle()); if (!f) return flow.d->Error(_FL, "font creation failed."); GCss::WordWrapType WrapType = tstyle ? tstyle->WordWrap() : GCss::WrapNormal; uint32 *sStart = t->At(0); uint32 *sEnd = sStart + t->Length(); for (unsigned Off = 0; Off < t->Length(); ) { // How much of 't' is on the same line? uint32 *s = sStart + Off; #if DEBUG_LAYOUT LgiTrace("Txt[%i][%i]: FixX=%i, Txt='%.*S'\n", i, Off, FixX, t->Length() - Off, s); #endif if (*s == '\n') { // New line handling... Off++; CurLine->PosOff.x2 = CurLine->PosOff.x1 + FixedToInt(FixX) - 1; FixX = 0; CurLine->LayoutOffsets(f->GetHeight()); Pos.y2 = MAX(Pos.y2, Pos.y1 + CurLine->PosOff.y2); CurLine->NewLine = 1; LayoutSize += CurLine->Length(); #if DEBUG_LAYOUT LgiTrace("\tNewLineChar, LayoutSize=%i, TextSize=%i\n", LayoutSize, TextSize); #endif Layout.Add(CurLine.Release()); CurLine.Reset(new TextLine(flow.Left - Pos.x1, flow.X(), Pos.Y())); if (Off == t->Length()) { // Empty line at the end of the StyleText const uint32 Empty[] = {0}; CurLine->Strs.Add(new DisplayStr(t, f, Empty, 0, flow.pDC)); } continue; } uint32 *e = s; /* printf("e=%i sEnd=%i len=%i\n", (int)(e - sStart), (int)(sEnd - sStart), (int)t->Length()); */ while (e < sEnd && *e != '\n') e++; // Add 't' to current line ssize_t Chars = MIN(1024, (int) (e - s)); GAutoPtr Ds ( t->Emoji ? new EmojiDisplayStr(t, d->GetEmojiImage(), f, s, Chars) : new DisplayStr(t, f, s, Chars, flow.pDC) ); if (!Ds) return flow.d->Error(_FL, "display str creation failed."); if (WrapType != GCss::WrapNone && FixX + Ds->FX() > IntToFixed(AvailableX)) { #if DEBUG_LAYOUT LgiTrace("\tNeedToWrap: %i, %i + %i > %i\n", WrapType, FixX, Ds->FX(), IntToFixed(AvailableX)); #endif // Wrap the string onto the line... int AvailablePx = AvailableX - FixedToInt(FixX); ssize_t FitChars = Ds->PosToIndex(AvailablePx, false); if (FitChars < 0) { #if DEBUG_LAYOUT LgiTrace("\tFitChars error: %i\n", FitChars); #endif flow.d->Error(_FL, "PosToIndex(%i) failed.", AvailablePx); LgiAssert(0); } else { // Wind back to the last break opportunity ssize_t ch = 0; for (ch = FitChars; ch > 0; ch--) { if (IsWordBreakChar(s[ch-1])) break; } #if DEBUG_LAYOUT LgiTrace("\tWindBack: %i\n", (int)ch); #endif if (ch == 0) { // One word minimum per line for (ch = 1; ch < Chars; ch++) { if (IsWordBreakChar(s[ch])) break; } Chars = ch; } else if (ch > (FitChars >> 2)) Chars = ch; else Chars = FitChars; // Create a new display string of the right size... if ( ! Ds.Reset ( t->Emoji ? new EmojiDisplayStr(t, d->GetEmojiImage(), f, s, Chars) : new DisplayStr(t, f, s, Chars, flow.pDC) ) ) return flow.d->Error(_FL, "failed to create wrapped display str."); // Finish off line CurLine->PosOff.x2 = CurLine->PosOff.x1 + FixedToInt(FixX + Ds->FX()) - 1; CurLine->Strs.Add(Ds.Release()); CurLine->LayoutOffsets(d->Font->GetHeight()); Pos.y2 = MAX(Pos.y2, Pos.y1 + CurLine->PosOff.y2); LayoutSize += CurLine->Length(); Layout.Add(CurLine.Release()); #if DEBUG_LAYOUT LgiTrace("\tWrap, LayoutSize=%i TextSize=%i\n", LayoutSize, TextSize); #endif // New line... CurLine.Reset(new TextLine(flow.Left - Pos.x1, flow.X(), Pos.Y())); FixX = 0; Off += Chars; continue; } } else { FixX += Ds->FX(); } if (!Ds) break; CurLine->PosOff.x2 = CurLine->PosOff.x1 + FixedToInt(FixX) - 1; CurLine->Strs.Add(Ds.Release()); Off += Chars; } } if (Txt.Length() == 0) { // Empty node case int y = Pos.y1 + flow.d->View->GetFont()->GetHeight() - 1; CurLine->PosOff.y2 = Pos.y2 = MAX(Pos.y2, y); LayoutSize += CurLine->Length(); Layout.Add(CurLine.Release()); } if (CurLine && CurLine->Strs.Length() > 0) { GFont *f = d->View ? d->View->GetFont() : SysFont; CurLine->LayoutOffsets(f->GetHeight()); Pos.y2 = MAX(Pos.y2, Pos.y1 + CurLine->PosOff.y2); LayoutSize += CurLine->Length(); #if DEBUG_LAYOUT LgiTrace("\tRemaining, LayoutSize=%i, TextSize=%i\n", LayoutSize, TextSize); #endif Layout.Add(CurLine.Release()); } LgiAssert(LayoutSize == Len); flow.CurY = Pos.y2 + 1 + Margin.y2 + Border.y2 + Padding.y2; flow.Left -= Margin.x1 + Border.x1 + Padding.x1; flow.Right += Margin.x2 + Border.x2 + Padding.x2; return true; } ssize_t GRichTextPriv::TextBlock::GetTextAt(ssize_t Offset, GArray &Out) { if (Txt.Length() == 0) return 0; StyleText **t = &Txt[0]; StyleText **e = t + Txt.Length(); Out.Length(0); uint32 Pos = 0; while (t < e) { ssize_t Len = (*t)->Length(); if (Offset >= Pos && Offset <= Pos + Len) Out.Add(*t); t++; Pos += Len; } LgiAssert(Pos == Len); return Out.Length(); } bool GRichTextPriv::TextBlock::IsValid() { int TxtLen = 0; for (unsigned i = 0; i < Txt.Length(); i++) { StyleText *t = Txt[i]; TxtLen += t->Length(); for (unsigned n = 0; n < t->Length(); n++) { if ((*t)[n] == 0) { LgiAssert(0); return false; } } } if (Len != TxtLen) return d->Error(_FL, "Txt.Len vs Len mismatch: %i, %i.", TxtLen, Len); return true; } int GRichTextPriv::TextBlock::GetLines() { return (int)Layout.Length(); } bool GRichTextPriv::TextBlock::OffsetToLine(ssize_t Offset, int *ColX, GArray *LineY) { if (LayoutDirty) return false; if (LineY) LineY->Length(0); if (Offset <= 0) { if (ColX) *ColX = 0; if (LineY) LineY->Add(0); return true; } bool Found = false; int Pos = 0; for (unsigned i=0; iLength(); if (Offset >= Pos && Offset <= Pos + Len - tl->NewLine) { if (ColX) *ColX = (int)(Offset - Pos); if (LineY) LineY->Add(i); Found = true; } Pos += Len; } return Found; } int GRichTextPriv::TextBlock::LineToOffset(int Line) { if (LayoutDirty) return -1; if (Line <= 0) return 0; int Pos = 0; for (unsigned i=0; iLength(); if (i == Line) return Pos; Pos = Len; } return (int)Length(); } bool GRichTextPriv::TextBlock::PreEdit(Transaction *Trans) { if (Trans) { bool HasThisBlock = false; for (unsigned i=0; iChanges.Length(); i++) { CompleteTextBlockState *c = dynamic_cast(Trans->Changes[i]); if (c) { if (c->Uid == BlockUid) { HasThisBlock = true; break; } } } if (!HasThisBlock) Trans->Add(new CompleteTextBlockState(d, this)); } return true; } -ssize_t GRichTextPriv::TextBlock::DeleteAt(Transaction *Trans, ssize_t BlkOffset, ssize_t Chars, GArray *DeletedText) +ssize_t GRichTextPriv::TextBlock::DeleteAt(Transaction *Trans, ssize_t BlkOffset, ssize_t Chars, GArray *DeletedText) { ssize_t Pos = 0; ssize_t Deleted = 0; PreEdit(Trans); for (unsigned i=0; i 0; i++) { StyleText *t = Txt[i]; ssize_t TxtOffset = BlkOffset - Pos; if (TxtOffset >= 0 && TxtOffset < (int)t->Length()) { ssize_t MaxChars = t->Length() - TxtOffset; ssize_t Remove = MIN(Chars, MaxChars); if (Remove <= 0) return 0; ssize_t Remaining = MaxChars - Remove; ssize_t NewLen = t->Length() - Remove; if (DeletedText) { DeletedText->Add(t->At(TxtOffset), Remove); } if (Remaining > 0) { // Copy down memmove(&(*t)[TxtOffset], &(*t)[TxtOffset + Remove], Remaining * sizeof(uint32)); (*t)[NewLen] = 0; } // Change length if (NewLen == 0) { // Remove run completely // LgiTrace("DelRun %p/%i '%.*S'\n", t, i, t->Length(), &(*t)[0]); Txt.DeleteAt(i--, true); DeleteObj(t); } else { // Shorten run t->Length(NewLen); // LgiTrace("ShortenRun %p/%i '%.*S'\n", t, i, t->Length(), &(*t)[0]); } LayoutDirty = true; Chars -= Remove; Len -= Remove; Deleted += Remove; } if (t) Pos += t->Length(); } if (Deleted > 0) { // Adjust start of existing spelling styles GRange r(BlkOffset, Deleted); for (auto &Err : SpellingErrors) { if (Err.Overlap(r).Valid()) { Err -= r; } else if (Err > r) { Err.Start -= Deleted; } } LayoutDirty = true; UpdateSpellingAndLinks(Trans, GRange(BlkOffset, 0)); } IsValid(); return Deleted; } GMessage::Result GRichTextPriv::TextBlock::OnEvent(GMessage *Msg) { switch (Msg->Msg()) { case M_COMMAND: { GSpellCheck::SpellingError *e = SpellingErrors.AddressOf(ClickErrIdx); if (e) { // Replacing text with spell check suggestion: int i = (int)Msg->A() - SPELLING_BASE; if (i >= 0 && i < (int)e->Suggestions.Length()) { auto Start = e->Start; GString s = e->Suggestions[i]; AutoTrans t(new GRichTextPriv::Transaction); // Delete the old text... DeleteAt(t, Start, e->Len); // 'e' might disappear here // Insert the new text.... GAutoPtr u((uint32*)LgiNewConvertCp("utf-32", s, "utf-8")); AddText(t, Start, u.Get(), Strlen(u.Get())); d->AddTrans(t); return true; } } break; } } return false; } -bool GRichTextPriv::TextBlock::AddText(Transaction *Trans, ssize_t AtOffset, const uint32 *InStr, ssize_t InChars, GNamedStyle *Style) +bool GRichTextPriv::TextBlock::AddText(Transaction *Trans, ssize_t AtOffset, const uint32_t *InStr, ssize_t InChars, GNamedStyle *Style) { if (!InStr) return d->Error(_FL, "No input text."); if (InChars < 0) InChars = Strlen(InStr); PreEdit(Trans); GArray EmojiIdx; EmojiIdx.Length(InChars); for (int i=0; i= 0 ? AtOffset : Len; int Chars = 0; // Length of run to insert int Pos = 0; // Current character position in this block uint32 TxtIdx = 0; // Index into Txt array for (int i = 0; i < InChars; i += Chars) { // Work out the run of chars that are either // emoji or not emoji... bool IsEmoji = EmojiIdx[i] >= 0; Chars = 1; for (int n = i + 1; n < InChars; n++) { if ( IsEmoji ^ (EmojiIdx[n] >= 0) ) break; Chars++; } // Now process 'Char' chars const uint32 *Str = InStr + i; if (AtOffset >= 0 && Txt.Length() > 0) { // Seek further into block? while ( Pos < AtOffset && TxtIdx < Txt.Length()) { StyleText *t = Txt[TxtIdx]; ssize_t Len = t->Length(); if (AtOffset <= Pos + Len) break; Pos += Len; TxtIdx++; } StyleText *t = TxtIdx >= Txt.Length() ? Txt.Last() : Txt[TxtIdx]; ssize_t TxtLen = t->Length(); if (AtOffset >= Pos && AtOffset <= Pos + TxtLen) { ssize_t StyleOffset = AtOffset - Pos; // Offset into 't' in which we need to potentially break the style // to insert the new content. bool UrlEdge = t->Element == TAG_A && *Str == '\n'; if (!Style && IsEmoji == t->Emoji && !UrlEdge) { // Insert/append to existing text run ssize_t After = t->Length() - StyleOffset; ssize_t NewSz = t->Length() + Chars; t->Length(NewSz); uint32 *c = &t->First(); LOG_FN("TextBlock(%i)::Add(%i,%i,%s)::Append StyleOffset=%i, After=%i\n", GetUid(), AtOffset, InChars, Style?Style->Name.Get():NULL, StyleOffset, After); // Do we need to move characters up to make space? if (After > 0) memmove(c + StyleOffset + Chars, c + StyleOffset, After * sizeof(*c)); // Insert the new string... memcpy(c + StyleOffset, Str, Chars * sizeof(*c)); Len += Chars; AtOffset += Chars; } else { // Break into 2 runs, with the new text in the middle... // Insert the new text+style StyleText *Run = new StyleText(Str, Chars, Style); if (!Run) return false; Run->Emoji = IsEmoji; /* This following code could be wrong. In terms of test cases I fixed this: A) Starting with basic empty email + signature. Insert a URL at the very start. Then hit enter. Buf: \n inserted BEFORE the URL. Changed the condition to 'StyleOffset != 0' rather than 'TxtIdx != 0' Potentially other test cases could exhibit bugs that need to be added here. */ if (StyleOffset) Txt.AddAt(++TxtIdx, Run); else Txt.AddAt(TxtIdx++, Run); //////////////////////////////////// Pos += StyleOffset; // We are skipping over the run at 'TxtIdx', update pos LOG_FN("TextBlock(%i)::Add(%i,%i,%s)::Insert StyleOffset=%i\n", GetUid(), AtOffset, InChars, Style?Style->Name.Get():NULL, StyleOffset); if (StyleOffset < TxtLen) { // Insert the 2nd part of the string Run = new StyleText(t->At(StyleOffset), TxtLen - StyleOffset, t->GetStyle()); if (!Run) return false; Pos += Chars; Txt.AddAt(++TxtIdx, Run); // Now truncate the existing text.. t->Length(StyleOffset); } Len += Chars; AtOffset += Chars; } Str = NULL; } } if (Str) { // At the end StyleText *Last = Txt.Length() > 0 ? Txt.Last() : NULL; if (Last && Last->GetStyle() == Style && IsEmoji == Last->Emoji) { if (Last->Add((uint32*)Str, Chars)) { Len += Chars; if (AtOffset >= 0) AtOffset += Chars; } } else { StyleText *Run = new StyleText(Str, Chars, Style); if (!Run) return false; Run->Emoji = IsEmoji; Txt.Add(Run); Len += Chars; if (AtOffset >= 0) AtOffset += Chars; } } } // Push existing spelling styles along for (auto &Err : SpellingErrors) { if (Err.Start >= InitialOffset) Err.Start += InChars; } // Update layout and styling LayoutDirty = true; IsValid(); UpdateSpellingAndLinks(Trans, GRange(InitialOffset, InChars)); return true; } bool GRichTextPriv::TextBlock::OnDictionary(Transaction *Trans) { UpdateSpellingAndLinks(Trans, GRange(0, Length())); return true; } #define IsUrlWordChar(t) \ (((t) > ' ') && !strchr("./:", (t))) template bool _ScanWord(Char *&t, Char *e) { if (!IsUrlWordChar(*t)) return false; Char *s = t; while (t < e && IsUrlWordChar(*t)) t++; return t > s; } bool IsBracketed(int s, int e) { if (s == '(' && e == ')') return true; if (s == '[' && e == ']') return true; if (s == '{' && e == '}') return true; if (s == '<' && e == '>') return true; return false; } #define ScanWord() \ if (t >= e || !_ScanWord(t, e)) return false #define ScanChar(ch) \ if (t >= e || *t != ch) \ return false; \ t++ template bool DetectUrl(Char *t, ssize_t &len) { #ifdef _DEBUG GString str(t, len); //char *ss = str; #endif Char *s = t; Char *e = t + len; ScanWord(); // Protocol ScanChar(':'); ScanChar('/'); ScanChar('/'); ScanWord(); // Host name or username.. if (t < e && *t == ':') { t++; _ScanWord(t, e); // Don't return if missing... password optional ScanChar('@'); ScanWord(); // First part of host name... } // Rest of host name while (t < e && *t == '.') { t++; if (t < e && IsUrlWordChar(*t)) ScanWord(); // Second part of host name } if (t < e && *t == ':') // Port number { t++; ScanWord(); } while (t < e && strchr("/.:", *t)) // Path { t++; if (t < e && (IsUrlWordChar(*t) || *t == ':')) ScanWord(); } if (strchr("!.", t[-1])) t--; len = t - s; return true; } int ErrSort(GSpellCheck::SpellingError *a, GSpellCheck::SpellingError *b) { return (int) (a->Start - b->Start); } void GRichTextPriv::TextBlock::SetSpellingErrors(GArray &Errors, GRange r) { // LgiTrace("%s:%i - SetSpellingErrors " LPrintfSSizeT ", " LPrintfSSizeT ":" LPrintfSSizeT "\n", _FL, Errors.Length(), r.Start, r.End()); // Delete any errors overlapping 'r' for (unsigned i=0; i Text; if (!CopyAt(0, Length(), &Text)) return; // Spelling... if (d->SpellCheck && d->SpellDictionaryLoaded) { GRange Rgn = r; while (Rgn.Start > 0 && IsWordChar(Text[Rgn.Start-1])) { Rgn.Start--; Rgn.Len++; } while (Rgn.End() < Len && IsWordChar(Text[Rgn.End()])) { Rgn.Len++; } GString s(Text.AddressOf(Rgn.Start), Rgn.Len); GArray Params; Params[SpellBlockPtr] = (Block*)this; // LgiTrace("%s:%i - Check(%s) " LPrintfSSizeT ":" LPrintfSSizeT "\n", _FL, s.Get(), Rgn.Start, Rgn.End()); d->SpellCheck->Check(d->View->AddDispatch(), s, Rgn.Start, Rgn.Len, &Params); } // Link detection... // Extend the range to include whole words while (r.Start > 0 && !IsWhiteSpace(Text[r.Start])) { r.Start--; r.Len++; } while (r.End() < Text.Length() && !IsWhiteSpace(Text[r.End()])) r.Len++; // Create array of words... GArray Words; bool Ws = true; for (int i = 0; i < r.Len; i++) { bool w = IsWhiteSpace(Text[r.Start + i]); if (w ^ Ws) { Ws = w; if (!w) { GRange &w = Words.New(); w.Start = r.Start + i; // printf("StartWord=%i, %i\n", w.Start, w.Len); } else if (Words.Length() > 0) { GRange &w = Words.Last(); w.Len = r.Start + i - w.Start; // printf("EndWord=%i, %i\n", w.Start, w.Len); } } } if (!Ws && Words.Length() > 0) { GRange &w = Words.Last(); w.Len = r.Start + r.Len - w.Start; // printf("TermWord=%i, %i Words=%i\n", w.Start, w.Len, Words.Length()); } // For each word in the range of text for (unsigned i = 0; iMakeLink(this, w.Start, w.Len, Link); // Also unlink any of the word after the URL if (w.End() < Words[i].End()) { GCss Style; ChangeStyle(Trans, w.End(), Words[i].End() - w.End(), &Style, false); } } } } bool GRichTextPriv::TextBlock::StripLast(Transaction *Trans, const char *Set) { if (Txt.Length() == 0) return false; StyleText *l = Txt.Last(); if (!l || l->Length() <= 0) return false; if (!strchr(Set, l->Last())) return false; PreEdit(Trans); if (!l->PopLast()) return false; LayoutDirty = true; Len--; return true; } bool GRichTextPriv::TextBlock::DoContext(GSubMenu &s, GdcPt2 Doc, ssize_t Offset, bool Spelling) { if (Spelling) { // Is there a spelling error at 'Offset'? for (unsigned i=0; i= e.Start && Offset < e.End()) { ClickErrIdx = i; if (e.Suggestions.Length()) { GSubMenu *Sp = s.AppendSub("Spelling"); if (Sp) { s.AppendSeparator(); for (unsigned n=0; nAppendItem(e.Suggestions[n], SPELLING_BASE+n); } // else printf("%s:%i - No sub menu.\n", _FL); } // else printf("%s:%i - No Suggestion.\n", _FL); break; } // else printf("%s:%i - Outside area, Offset=%i e=%i,%i.\n", _FL, Offset, e.Start, e.End()); } } // else printf("%s:%i - No Spelling.\n", _FL); return true; } bool GRichTextPriv::TextBlock::IsEmptyLine(BlockCursor *Cursor) { if (!Cursor) return false; TextLine *Line = Layout.AddressOf(Cursor->LineHint) ? Layout[Cursor->LineHint] : NULL; if (!Line) return false; int LineLen = Line->Length(); return LineLen == 0; } GRichTextPriv::Block *GRichTextPriv::TextBlock::Clone() { return new TextBlock(this); } -ssize_t GRichTextPriv::TextBlock::CopyAt(ssize_t Offset, ssize_t Chars, GArray *Text) +ssize_t GRichTextPriv::TextBlock::CopyAt(ssize_t Offset, ssize_t Chars, GArray *Text) { if (!Text) return 0; if (Chars < 0) Chars = Length() - Offset; int Pos = 0; for (unsigned i=0; i= Pos && Offset < Pos + (int)t->Length()) { ssize_t Skip = Offset - Pos; ssize_t Remain = t->Length() - Skip; ssize_t Cp = MIN(Chars, Remain); Text->Add(&(*t)[Skip], Cp); Chars -= Cp; Offset += Cp; } Pos += t->Length(); } return Text->Length(); } -ssize_t GRichTextPriv::TextBlock::FindAt(ssize_t StartIdx, const uint32 *Str, GFindReplaceCommon *Params) +ssize_t GRichTextPriv::TextBlock::FindAt(ssize_t StartIdx, const uint32_t *Str, GFindReplaceCommon *Params) { if (!Str || !Params) return -1; size_t InLen = Strlen(Str); bool Match; int CharPos = 0; for (unsigned i=0; iFirst(); uint32 *e = s + t->Length(); if (Params->MatchCase) { for (uint32 *c = s; c < e; c++) { if (*c == *Str) { if (c + InLen <= e) Match = !Strncmp(c, Str, InLen); else { GArray tmp; if (CopyAt(CharPos + (c - s), InLen, &tmp) && tmp.Length() == InLen) Match = !Strncmp(&tmp[0], Str, InLen); else Match = false; } if (Match) return CharPos + (c - s); } } } else { uint32 l = ToLower(*Str); for (uint32 *c = s; c < e; c++) { if (ToLower(*c) == l) { if (c + InLen <= e) Match = !Strnicmp(c, Str, InLen); else { GArray tmp; if (CopyAt(CharPos + (c - s), InLen, &tmp) && tmp.Length() == InLen) Match = !Strnicmp(&tmp[0], Str, InLen); else Match = false; } if (Match) return CharPos + (c - s); } } } CharPos += t->Length(); } return -1; } bool GRichTextPriv::TextBlock::DoCase(Transaction *Trans, ssize_t StartIdx, ssize_t Chars, bool Upper) { GRange Blk(0, Len); GRange Inp(StartIdx, Chars < 0 ? Len - StartIdx : Chars); GRange Change = Blk.Overlap(Inp); PreEdit(Trans); GRange Run(0, 0); bool Changed = false; for (unsigned i=0; iLength(); GRange Edit = Run.Overlap(Change); if (Edit.Len > 0) { uint32 *s = st->At(Edit.Start - Run.Start); for (int n=0; n= 'a' && s[n] <= 'z') s[n] = s[n] - 'a' + 'A'; } else { if (s[n] >= 'A' && s[n] <= 'Z') s[n] = s[n] - 'A' + 'a'; } } Changed = true; } Run.Start += Run.Len; } LayoutDirty |= Changed; return Changed; } GRichTextPriv::Block *GRichTextPriv::TextBlock::Split(Transaction *Trans, ssize_t AtOffset) { if (AtOffset < 0 || AtOffset >= Len) return NULL; GRichTextPriv::TextBlock *After = new GRichTextPriv::TextBlock(d); if (!After) { d->Error(_FL, "Alloc Err"); return NULL; } After->SetStyle(GetStyle()); int Pos = 0; unsigned i; for (i=0; iLength(); if (AtOffset >= Pos && AtOffset < Pos + StLen) { ssize_t StOff = AtOffset - Pos; if (StOff > 0) { // Split the text into 2 blocks... uint32 *t = St->At(StOff); ssize_t remaining = St->Length() - StOff; StyleText *AfterText = new StyleText(t, remaining, St->GetStyle()); if (!AfterText) { d->Error(_FL, "Alloc Err"); return NULL; } St->Length(StOff); i++; Len = Pos + StOff; After->Txt.Add(AfterText); After->Len += AfterText->Length(); } else { Len = Pos; } break; } Pos += StLen; } while (i < Txt.Length()) { StyleText *St = Txt[i]; Txt.DeleteAt(i, true); After->Txt.Add(St); After->Len += St->Length(); } LayoutDirty = true; After->LayoutDirty = true; return After; } void GRichTextPriv::TextBlock::IncAllStyleRefs() { if (Style) Style->RefCount++; for (unsigned i=0; iGetStyle(); if (s) s->RefCount++; } } bool GRichTextPriv::TextBlock::ChangeStyle(Transaction *Trans, ssize_t Offset, ssize_t Chars, GCss *Style, bool Add) { if (!Style) return d->Error(_FL, "No style."); if (Offset < 0 || Offset >= Len) return true; if (Chars < 0) Chars = Len; if (Trans) Trans->Add(new CompleteTextBlockState(d, this)); int CharPos = 0; ssize_t RestyleEnd = Offset + Chars; for (unsigned i=0; iLength(); ssize_t End = CharPos + Len; if (End <= Offset || CharPos > RestyleEnd) ; else { ssize_t Before = Offset >= CharPos ? Offset - CharPos : 0; LgiAssert(Before >= 0); ssize_t After = RestyleEnd < End ? End - RestyleEnd : 0; LgiAssert(After >= 0); ssize_t Inside = Len - Before - After; LgiAssert(Inside >= 0); GAutoPtr TmpStyle(new GCss); if (Add) { if (t->GetStyle()) *TmpStyle = *t->GetStyle(); *TmpStyle += *Style; } else if (Style->Length() != 0) { if (t->GetStyle()) *TmpStyle = *t->GetStyle(); *TmpStyle -= *Style; } GNamedStyle *CacheStyle = TmpStyle && TmpStyle->Length() ? d->AddStyleToCache(TmpStyle) : NULL; if (Before && After) { // Split into 3 parts: // |---before----|###restyled###|---after---| StyleText *st = new StyleText(t->At(Before), Inside, CacheStyle); if (st) Txt.AddAt(++i, st); st = new StyleText(t->At(Before + Inside), After, t->GetStyle()); if (st) Txt.AddAt(++i, st); t->Length(Before); LayoutDirty = true; return true; } else if (Before) { // Split into 2 parts: // |---before----|###restyled###| StyleText *st = new StyleText(t->At(Before), Inside, CacheStyle); if (st) Txt.AddAt(++i, st); t->Length(Before); LayoutDirty = true; } else if (After) { // Split into 2 parts: // |###restyled###|---after---| StyleText *st = new StyleText(t->At(0), Inside, CacheStyle); if (st) Txt.AddAt(i, st); memmove(t->At(0), t->At(Inside), After*sizeof(uint32)); t->Length(After); LayoutDirty = true; } else if (Inside) { // Re-style the whole run t->SetStyle(CacheStyle); LayoutDirty = true; } } CharPos += Len; } // Merge any regions of the same style into contiguous sections for (unsigned i=0; iGetStyle() == b->GetStyle() && a->Emoji == b->Emoji) { // Merge... a->Add(b->AddressOf(0), b->Length()); Txt.DeleteAt(i + 1, true); delete b; i--; } } return true; } bool GRichTextPriv::TextBlock::Seek(SeekType To, BlockCursor &Cur) { int XOffset = Cur.Pos.x1 - Pos.x1; int CharPos = 0; GArray LineOffset; GArray LineLen; int CurLine = -1; int CurLineScore = 0; for (unsigned i=0; iLength(); LineOffset[i] = CharPos; LineLen[i] = Len; if (Cur.Offset >= CharPos && Cur.Offset <= CharPos + Len - Line->NewLine) // Minus 'NewLine' is because the cursor can't be // after the '\n' on a line. It's actually on the // next line. { int Score = 1; if (Cur.LineHint >= 0 && i == Cur.LineHint) Score++; if (Score > CurLineScore) { CurLine = i; CurLineScore = Score; } } CharPos += Len; } if (CurLine < 0) { CharPos = 0; d->Log->Print("TextBlock(%i)::Seek, lines=%i\n", GetUid(), Layout.Length()); for (unsigned i=0; iLog->Print("\tLine[%i] @ %i+%i=%i\n", i, CharPos, Line->Length(), CharPos + Line->Length()); CharPos += Line->Length(); } else { d->Log->Print("\tLine[%i] @ %i, is NULL\n", i, CharPos); break; } } return d->Error(_FL, "Index '%i' not in layout lines.", Cur.Offset); } TextLine *Line = NULL; switch (To) { case SkLineStart: { Cur.Offset = LineOffset[CurLine]; Cur.LineHint = CurLine; return true; } case SkLineEnd: { Cur.Offset = LineOffset[CurLine] + LineLen[CurLine] - Layout[CurLine]->NewLine; Cur.LineHint = CurLine; return true; } case SkUpLine: { // Get previous line... if (CurLine == 0) return false; Line = Layout[--CurLine]; if (!Line) return d->Error(_FL, "No line at %i.", CurLine); break; } case SkDownLine: { // Get next line... if (CurLine >= (int)Layout.Length() - 1) return false; Line = Layout[++CurLine]; if (!Line) return d->Error(_FL, "No line at %i.", CurLine); break; } default: { return false; break; } } if (Line) { // Work out where the cursor should be based on the 'XOffset' if (Line->Strs.Length() > 0) { int FixX = 0; int CharOffset = 0; for (unsigned i=0; iStrs.Length(); i++) { DisplayStr *Ds = Line->Strs[i]; PtrCheckBreak(Ds); if (XOffset >= FixedToInt(FixX) && XOffset <= FixedToInt(FixX + Ds->FX())) { // This is the matching string... int Px = XOffset - FixedToInt(FixX) - Line->PosOff.x1; ssize_t Char = Ds->PosToIndex(Px, true); if (Char >= 0) { Cur.Offset = LineOffset[CurLine] + // Character offset of line CharOffset + // Character offset of current string Char; // Offset into current string for 'XOffset' Cur.LineHint = CurLine; return true; } } FixX += Ds->FX(); CharOffset += Ds->Length(); } // Cursor is nearest the end of the string...? Cur.Offset = LineOffset[CurLine] + Line->Length() - Line->NewLine; Cur.LineHint = CurLine; return true; } else if (Line->NewLine) { Cur.Offset = LineOffset[CurLine]; Cur.LineHint = CurLine; return true; } } return false; } #ifdef _DEBUG void GRichTextPriv::TextBlock::DumpNodes(GTreeItem *Ti) { GString s; s.Printf("TextBlock, style=%s, pos=%s, ptr=%p", Style?Style->Name.Get():NULL, Pos.GetStr(), this); Ti->SetText(s); GTreeItem *TxtRoot = PrintNode(Ti, "Txt(%i)", Txt.Length()); if (TxtRoot) { int Pos = 0; for (unsigned i=0; iLength(); GString u; if (Len) { GStringPipe p(256); uint32 *Str = St->At(0); p.Write("\'", 1); for (int k=0; k= 0x10000) p.Print("&#%i;", Str[k]); else { uint8 utf8[6], *n = utf8; ssize_t utf8len = sizeof(utf8); if (LgiUtf32To8(Str[k], n, utf8len)) p.Write(utf8, sizeof(utf8)-utf8len); } } p.Write("\'", 1); u = p.NewGStr(); } else u = "(Empty)"; PrintNode( TxtRoot, "[%i] range=%i-%i, len=%i, style=%s, %s", i, Pos, Pos + Len - 1, Len, St->GetStyle() ? St->GetStyle()->Name.Get() : NULL, u.Get()); Pos += Len; } } GTreeItem *LayoutRoot = PrintNode(Ti, "Layout(%i)", Layout.Length()); if (LayoutRoot) { int Pos = 0; for (unsigned i=0; iLength() - 1, Tl->Length(), Tl->NewLine, Tl->PosOff.GetStr()); for (unsigned n=0; nStrs.Length(); n++) { DisplayStr *Ds = Tl->Strs[n]; GNamedStyle *Style = Ds->Src ? Ds->Src->GetStyle() : NULL; PrintNode( Elem, "[%i] style=%s len=%i txt='%.20S'", n, Style ? Style->Name.Get() : NULL, Ds->Length(), (const char16*) (*Ds)); } Pos += Tl->Length() + Tl->NewLine; } } } #endif