diff --git a/Ide/Code/LgiIde.cpp b/Ide/Code/LgiIde.cpp --- a/Ide/Code/LgiIde.cpp +++ b/Ide/Code/LgiIde.cpp @@ -1,4101 +1,4101 @@ #include #include #include #include "Lgi.h" #include "LgiIde.h" #include "GMdi.h" #include "GToken.h" #include "GXmlTree.h" #include "GPanel.h" #include "GProcess.h" #include "GButton.h" #include "GTabView.h" #include "FtpThread.h" #include "GClipBoard.h" #include "FindSymbol.h" #include "GBox.h" #include "GTextLog.h" #include "GEdit.h" #include "GTableLayout.h" #include "GTextLabel.h" #include "GCombo.h" #include "GCheckBox.h" #include "GDebugger.h" #include "LgiRes.h" #include "ProjectNode.h" #include "GBox.h" #include "GSubProcess.h" #include "GAbout.h" #define IDM_RECENT_FILE 1000 #define IDM_RECENT_PROJECT 1100 #define IDM_WINDOWS 1200 #define IDM_MAKEFILE_BASE 1300 #define USE_HAIKU_PULSE_HACK 1 #define OPT_ENTIRE_SOLUTION "SearchSolution" #define OPT_SPLIT_PX "SplitPos" #define OPT_OUTPUT_PX "OutputPx" #define OPT_FIX_RENAMED "FixRenamed" #define OPT_RENAMED_SYM "RenamedSym" #define IsSymbolChar(c) ( IsDigit(c) || IsAlpha(c) || strchr("-_", c) ) ////////////////////////////////////////////////////////////////////////////////////////// class FindInProject : public GDialog { AppWnd *App; LList *Lst; public: FindInProject(AppWnd *app) { Lst = NULL; App = app; if (LoadFromResource(IDC_FIND_PROJECT_FILE)) { MoveSameScreen(App); GViewI *v; if (GetViewById(IDC_TEXT, v)) v->Focus(true); if (!GetViewById(IDC_FILES, Lst)) return; RegisterHook(this, GKeyEvents, 0); } } bool OnViewKey(GView *v, GKey &k) { switch (k.vkey) { case VK_UP: case VK_DOWN: case VK_PAGEDOWN: case VK_PAGEUP: { return Lst->OnKey(k); break; } case VK_RETURN: { LListItem *i = Lst->GetSelected(); if (i) { char *Ref = i->GetText(0); App->GotoReference(Ref, 1, false); } EndModal(1); break; } case VK_ESCAPE: { EndModal(0); break; } } return false; } void Search(const char *s) { IdeProject *p = App->RootProject(); if (!p || !s) return; GArray Matches, Nodes; List All; p->GetChildProjects(All); All.Insert(p); for (p=All.First(); p; p=All.Next()) { p->GetAllNodes(Nodes); } FilterFiles(Matches, Nodes, s); Lst->Empty(); for (unsigned i=0; iSetText(Matches[i]->GetFileName()); Lst->Insert(li); } Lst->ResizeColumnsToContent(); } int OnNotify(GViewI *c, int f) { switch (c->GetId()) { case IDC_FILES: if (f == GNotifyItem_DoubleClick) { LListItem *i = Lst->GetSelected(); if (i) { App->GotoReference(i->GetText(0), 1, false); EndModal(1); } } break; case IDC_TEXT: if (f != GNotify_ReturnKey) Search(c->Name()); break; case IDCANCEL: EndModal(0); break; } return 0; } }; ////////////////////////////////////////////////////////////////////////////////////////// char AppName[] = "LgiIde"; char *dirchar(char *s, bool rev = false) { if (rev) { char *last = 0; while (s && *s) { if (*s == '/' || *s == '\\') last = s; s++; } return last; } else { while (s && *s) { if (*s == '/' || *s == '\\') return s; s++; } } return 0; } ////////////////////////////////////////////////////////////////////////////////////////// class Dependency : public GTreeItem { char *File; bool Loaded; GTreeItem *Fake; public: Dependency(const char *file) { File = NewStr(file); char *d = strrchr(File, DIR_CHAR); Loaded = false; Insert(Fake = new GTreeItem); if (FileExists(File)) { SetText(d?d+1:File); } else { char s[256]; sprintf(s, "%s (missing)", d?d+1:File); SetText(s); } } ~Dependency() { DeleteArray(File); } char *GetFile() { return File; } void Copy(GStringPipe &p, int Depth = 0) { { char s[1024]; ZeroObj(s); memset(s, ' ', Depth * 4); sprintf(s+(Depth*4), "[%c] %s\n", Expanded() ? '-' : '+', GetText(0)); p.Push(s); } if (Loaded) { for (GTreeItem *i=GetChild(); i; i=i->GetNext()) { ((Dependency*)i)->Copy(p, Depth+1); } } } char *Find(const char *Paths, char *e) { GToken Path(Paths, LGI_PATH_SEPARATOR); for (int p=0; pSunken(true); Root = new Dependency(File); if (Root) { t->Insert(Root); Root->Expanded(true); } Children.Insert(t); Children.Insert(new GButton(IDC_COPY, 10, t->GView::GetPos().y2 + 10, 60, 20, "Copy")); Children.Insert(new GButton(IDOK, 80, t->GView::GetPos().y2 + 10, 60, 20, "Ok")); } DoModal(); } int OnNotify(GViewI *c, int f) { switch (c->GetId()) { case IDC_COPY: { if (Root) { GStringPipe p; Root->Copy(p); char *s = p.NewStr(); if (s) { GClipBoard c(this); c.Text(s); DeleteArray(s); } break; } break; } case IDOK: { EndModal(0); break; } } return 0; } }; ////////////////////////////////////////////////////////////////////////////////////////// class DebugTextLog : public GTextLog { public: DebugTextLog(int id) : GTextLog(id) { } void PourText(size_t Start, ssize_t Len) override { GTextView3::PourText(Start, Len); for (GTextLine *l=Line.First(); l; l=Line.Next()) { char16 *t = Text + l->Start; if (l->Len > 5 && !StrnicmpW(t, L"(gdb)", 5)) { l->c.Rgb(0, 160, 0); } else if (l->Len > 1 && t[0] == '[') { l->c.Rgb(192, 192, 192); } } } }; WatchItem::WatchItem(IdeOutput *out, const char *Init) { Out = out; Expanded(false); if (Init) SetText(Init); Insert(PlaceHolder = new GTreeItem); } WatchItem::~WatchItem() { } bool WatchItem::SetValue(GVariant &v) { char *Str = v.CastString(); if (ValidStr(Str)) SetText(Str, 2); else GTreeItem::SetText(NULL, 2); return true; } bool WatchItem::SetText(const char *s, int i) { if (ValidStr(s)) { GTreeItem::SetText(s, i); if (i == 0 && Tree && Tree->GetWindow()) { GViewI *Tabs = Tree->GetWindow()->FindControl(IDC_DEBUG_TAB); if (Tabs) Tabs->SendNotify(GNotifyValueChanged); } return true; } if (i == 0) delete this; return false; } void WatchItem::OnExpand(bool b) { if (b && PlaceHolder) { // Do something } } class BuildLog : public GTextLog { public: BuildLog(int id) : GTextLog(id) { } void PourStyle(size_t Start, ssize_t Length) { List::I it = GTextView3::Line.begin(); for (GTextLine *ln = *it; ln; ln = *++it) { if (!ln->c.IsValid()) { char16 *t = Text + ln->Start; char16 *Err = Strnistr(t, L"error", ln->Len); char16 *Undef = Strnistr(t, L"undefined reference", ln->Len); char16 *Warn = Strnistr(t, L"warning", ln->Len); if ( (Err && strchr(":[", Err[5])) || (Undef != NULL) ) ln->c.Rgb(222, 0, 0); else if (Warn && strchr(":[", Warn[7])) ln->c.Rgb(255, 128, 0); else ln->c.Set(LC_TEXT, 24); } } } }; class IdeOutput : public GTabView { public: AppWnd *App; GTabPage *Build; GTabPage *Output; GTabPage *Debug; GTabPage *Find; GTabPage *Ftp; LList *FtpLog; GTextLog *Txt[3]; GArray Buf[3]; GFont Small; GFont Fixed; GTabView *DebugTab; GBox *DebugBox; GBox *DebugLog; LList *Locals, *CallStack, *Threads; GTree *Watch; GTextLog *ObjectDump, *MemoryDump, *Registers; GTableLayout *MemTable; GEdit *DebugEdit; GTextLog *DebuggerLog; IdeOutput(AppWnd *app) { App = app; Build = Output = Debug = Find = Ftp = 0; FtpLog = 0; DebugBox = NULL; Locals = NULL; Watch = NULL; DebugLog = NULL; DebugEdit = NULL; DebuggerLog = NULL; CallStack = NULL; ObjectDump = NULL; MemoryDump = NULL; MemTable = NULL; Threads = NULL; Registers = NULL; Small = *SysFont; Small.PointSize(Small.PointSize()-1); Small.Create(); LgiAssert(Small.Handle()); GFontType Type; if (Type.GetSystemFont("Fixed")) { Type.SetPointSize(SysFont->PointSize()-1); Fixed.Create(&Type); } else { Fixed.PointSize(SysFont->PointSize()-1); Fixed.Face("Courier"); Fixed.Create(); } GetCss(true)->MinHeight("60px"); Build = Append("Build"); Output = Append("Output"); Find = Append("Find"); Ftp = Append("Ftp"); Debug = Append("Debug"); SetFont(&Small); Build->SetFont(&Small); Output->SetFont(&Small); Find->SetFont(&Small); Ftp->SetFont(&Small); Debug->SetFont(&Small); if (Build) Build->Append(Txt[AppWnd::BuildTab] = new BuildLog(IDC_BUILD_LOG)); if (Output) Output->Append(Txt[AppWnd::OutputTab] = new GTextLog(IDC_OUTPUT_LOG)); if (Find) Find->Append(Txt[AppWnd::FindTab] = new GTextLog(IDC_FIND_LOG)); if (Ftp) Ftp->Append(FtpLog = new LList(104, 0, 0, 100, 100)); if (Debug) { Debug->Append(DebugBox = new GBox); if (DebugBox) { DebugBox->SetVertical(false); if ((DebugTab = new GTabView(IDC_DEBUG_TAB))) { DebugTab->GetCss(true)->Padding("0px"); DebugTab->SetFont(&Small); DebugBox->AddView(DebugTab); GTabPage *Page; if ((Page = DebugTab->Append("Locals"))) { Page->SetFont(&Small); if ((Locals = new LList(IDC_LOCALS_LIST, 0, 0, 100, 100, "Locals List"))) { Locals->SetFont(&Small); Locals->AddColumn("", 30); Locals->AddColumn("Type", 50); Locals->AddColumn("Name", 50); Locals->AddColumn("Value", 1000); Locals->SetPourLargest(true); Page->Append(Locals); } } if ((Page = DebugTab->Append("Object"))) { Page->SetFont(&Small); if ((ObjectDump = new GTextLog(IDC_OBJECT_DUMP))) { ObjectDump->SetFont(&Fixed); ObjectDump->SetPourLargest(true); Page->Append(ObjectDump); } } if ((Page = DebugTab->Append("Watch"))) { Page->SetFont(&Small); if ((Watch = new GTree(IDC_WATCH_LIST, 0, 0, 100, 100, "Watch List"))) { Watch->SetFont(&Small); Watch->ShowColumnHeader(true); Watch->AddColumn("Watch", 80); Watch->AddColumn("Type", 100); Watch->AddColumn("Value", 600); Watch->SetPourLargest(true); Page->Append(Watch); GXmlTag *w = App->GetOptions()->LockTag("watches", _FL); if (!w) { App->GetOptions()->CreateTag("watches"); w = App->GetOptions()->LockTag("watches", _FL); } if (w) { for (GXmlTag *c = w->Children.First(); c; c = w->Children.Next()) { if (c->IsTag("watch")) { Watch->Insert(new WatchItem(this, c->GetContent())); } } App->GetOptions()->Unlock(); } } } if ((Page = DebugTab->Append("Memory"))) { Page->SetFont(&Small); if ((MemTable = new GTableLayout(IDC_MEMORY_TABLE))) { GCombo *cbo; GCheckBox *chk; GTextLabel *txt; GEdit *ed; MemTable->SetFont(&Small); int x = 0, y = 0; GLayoutCell *c = MemTable->GetCell(x++, y); if (c) { c->VerticalAlign(GCss::VerticalMiddle); c->Add(txt = new GTextLabel(IDC_STATIC, 0, 0, -1, -1, "Address:")); txt->SetFont(&Small); } c = MemTable->GetCell(x++, y); if (c) { c->PaddingRight(GCss::Len("1em")); c->Add(ed = new GEdit(IDC_MEM_ADDR, 0, 0, 60, 20)); ed->SetFont(&Small); } c = MemTable->GetCell(x++, y); if (c) { c->PaddingRight(GCss::Len("1em")); c->Add(cbo = new GCombo(IDC_MEM_SIZE, 0, 0, 60, 20)); cbo->SetFont(&Small); cbo->Insert("1 byte"); cbo->Insert("2 bytes"); cbo->Insert("4 bytes"); cbo->Insert("8 bytes"); } c = MemTable->GetCell(x++, y); if (c) { c->VerticalAlign(GCss::VerticalMiddle); c->Add(txt = new GTextLabel(IDC_STATIC, 0, 0, -1, -1, "Page width:")); txt->SetFont(&Small); } c = MemTable->GetCell(x++, y); if (c) { c->PaddingRight(GCss::Len("1em")); c->Add(ed = new GEdit(IDC_MEM_ROW_LEN, 0, 0, 60, 20)); ed->SetFont(&Small); } c = MemTable->GetCell(x++, y); if (c) { c->VerticalAlign(GCss::VerticalMiddle); c->Add(chk = new GCheckBox(IDC_MEM_HEX, 0, 0, -1, -1, "Show Hex")); chk->SetFont(&Small); chk->Value(true); } int cols = x; x = 0; c = MemTable->GetCell(x++, ++y, true, cols); if ((MemoryDump = new GTextLog(IDC_MEMORY_DUMP))) { MemoryDump->SetFont(&Fixed); MemoryDump->SetPourLargest(true); c->Add(MemoryDump); } Page->Append(MemTable); } } if ((Page = DebugTab->Append("Threads"))) { Page->SetFont(&Small); if ((Threads = new LList(IDC_THREADS, 0, 0, 100, 100, "Threads"))) { Threads->SetFont(&Small); Threads->AddColumn("", 20); Threads->AddColumn("Thread", 1000); Threads->SetPourLargest(true); Threads->MultiSelect(false); Page->Append(Threads); } } if ((Page = DebugTab->Append("Call Stack"))) { Page->SetFont(&Small); if ((CallStack = new LList(IDC_CALL_STACK, 0, 0, 100, 100, "Call Stack"))) { CallStack->SetFont(&Small); CallStack->AddColumn("", 20); CallStack->AddColumn("Call Stack", 1000); CallStack->SetPourLargest(true); CallStack->MultiSelect(false); Page->Append(CallStack); } } if ((Page = DebugTab->Append("Registers"))) { Page->SetFont(&Small); if ((Registers = new GTextLog(IDC_REGISTERS))) { Registers->SetFont(&Small); Registers->SetPourLargest(true); Page->Append(Registers); } } } if ((DebugLog = new GBox)) { DebugLog->SetVertical(true); DebugBox->AddView(DebugLog); DebugLog->AddView(DebuggerLog = new DebugTextLog(IDC_DEBUGGER_LOG)); DebuggerLog->SetFont(&Small); DebugLog->AddView(DebugEdit = new GEdit(IDC_DEBUG_EDIT, 0, 0, 60, 20)); DebugEdit->GetCss(true)->Height(GCss::Len(GCss::LenPx, (float)(SysFont->GetHeight() + 8))); } } } if (FtpLog) { FtpLog->SetPourLargest(true); FtpLog->Sunken(true); FtpLog->AddColumn("Entry", 1000); FtpLog->ShowColumnHeader(false); } for (int n=0; nSetTabSize(8); Txt[n]->Sunken(true); } } ~IdeOutput() { } const char *GetClass() { return "IdeOutput"; } void Save() { if (Watch) { GXmlTag *w = App->GetOptions()->LockTag("watches", _FL); if (!w) { App->GetOptions()->CreateTag("watches"); w = App->GetOptions()->LockTag("watches", _FL); } if (w) { w->EmptyChildren(); for (GTreeItem *ti = Watch->GetChild(); ti; ti = ti->GetNext()) { GXmlTag *t = new GXmlTag("watch"); if (t) { t->SetContent(ti->GetText(0)); w->InsertTag(t); } } App->GetOptions()->Unlock(); } } } void OnCreate() { #if !USE_HAIKU_PULSE_HACK SetPulse(1000); #endif AttachChildren(); } void RemoveAnsi(GArray &a) { char *s = a.AddressOf(); char *e = s + a.Length(); while (s < e) { if (*s == 0x7) { a.DeleteAt(s - a.AddressOf(), true); s--; } else if ( *s == 0x1b && s[1] >= 0x40 && s[1] <= 0x5f ) { // ANSI seq char *end; if (s[1] == '[' && s[2] == '0' && s[3] == ';') end = s + 4; else { end = s + 2; while (end < e && !IsAlpha(*end)) { end++; } if (*end) end++; } auto len = end - s; memmove(s, end, e - end); a.Length(a.Length() - len); s--; } s++; } } void OnPulse() { int Changed = -1; for (int Channel = 0; Channel w(Utf8ToWide(Utf, (ssize_t)Size)); char16 *OldText = Txt[Channel]->NameW(); size_t OldLen = 0; if (OldText) OldLen = StrlenW(OldText); auto Cur = Txt[Channel]->GetCaret(); Txt[Channel]->Insert(OldLen, w, StrlenW(w)); if (Cur > OldLen - 1) { Txt[Channel]->SetCaret(OldLen + StrlenW(w), false); } Changed = Channel; Buf[Channel].Length(0); Txt[Channel]->Invalidate(); } } if (Changed >= 0) Value(Changed); } }; int DocSorter(IdeDoc *a, IdeDoc *b, NativeInt d) { char *A = a->GetFileName(); char *B = b->GetFileName(); if (A && B) { char *Af = strrchr(A, DIR_CHAR); char *Bf = strrchr(B, DIR_CHAR); return stricmp(Af?Af+1:A, Bf?Bf+1:B); } return 0; } struct FileLoc { GAutoString File; int Line; void Set(const char *f, int l) { File.Reset(NewStr(f)); Line = l; } }; class AppWndPrivate { public: AppWnd *App; GMdiParent *Mdi; GOptionsFile Options; GBox *HBox, *VBox; List Docs; List Projects; GImageList *Icons; GTree *Tree; IdeOutput *Output; bool Debugging; bool Running; bool Building; GSubMenu *WindowsMenu; GSubMenu *CreateMakefileMenu; GAutoPtr FindSym; GArray SystemIncludePaths; GArray BreakPoints; // Debugging GDebugContext *DbgContext; // Cursor history tracking int HistoryLoc; GArray CursorHistory; bool InHistorySeek; void SeekHistory(int Direction) { if (CursorHistory.Length()) { int Loc = HistoryLoc + Direction; if (Loc >= 0 && Loc < CursorHistory.Length()) { HistoryLoc = Loc; FileLoc &Loc = CursorHistory[HistoryLoc]; App->GotoReference(Loc.File, Loc.Line, false, false); App->DumpHistory(); } } } // Find in files GAutoPtr FindParameters; GAutoPtr Finder; int AppHnd; // Mru List RecentFiles; GSubMenu *RecentFilesMenu; List RecentProjects; GSubMenu *RecentProjectsMenu; // Object AppWndPrivate(AppWnd *a) : AppHnd(GEventSinkMap::Dispatch.AddSink(a)), Options(GOptionsFile::DesktopMode, AppName) { FindSym.Reset(new FindSymbolSystem(AppHnd)); HistoryLoc = 0; InHistorySeek = false; WindowsMenu = 0; App = a; HBox = VBox = NULL; Tree = 0; DbgContext = NULL; Output = 0; Debugging = false; Running = false; Building = false; RecentFilesMenu = 0; RecentProjectsMenu = 0; Icons = LgiLoadImageList("icons.png", 16, 16); Options.SerializeFile(false); App->SerializeState(&Options, "WndPos", true); SerializeStringList("RecentFiles", &RecentFiles, false); SerializeStringList("RecentProjects", &RecentProjects, false); } ~AppWndPrivate() { FindSym.Reset(); Finder.Reset(); Output->Save(); App->SerializeState(&Options, "WndPos", false); SerializeStringList("RecentFiles", &RecentFiles, true); SerializeStringList("RecentProjects", &RecentProjects, true); Options.SerializeFile(true); RecentFiles.DeleteArrays(); RecentProjects.DeleteArrays(); Docs.DeleteObjects(); Projects.DeleteObjects(); DeleteObj(Icons); } bool FindSource(GAutoString &Full, char *File, char *Context) { if (!LgiIsRelativePath(File)) { Full.Reset(NewStr(File)); } char *ContextPath = 0; if (Context && !Full) { char *Dir = strrchr(Context, DIR_CHAR); for (IdeProject *p=Projects.First(); p && !ContextPath; p=Projects.Next()) { ContextPath = p->FindFullPath(Dir?Dir+1:Context); } if (ContextPath) { LgiTrimDir(ContextPath); char p[300]; LgiMakePath(p, sizeof(p), ContextPath, File); if (FileExists(p)) { Full.Reset(NewStr(p)); } } else { LgiTrace("%s:%i - Context '%s' not found in project.\n", _FL, Context); } } if (!Full) { List::I Projs = Projects.begin(); for (IdeProject *p=*Projs; p; p=*++Projs) { GAutoString Base = p->GetBasePath(); if (Base) { char Path[MAX_PATH]; LgiMakePath(Path, sizeof(Path), Base, File); if (FileExists(Path)) { Full.Reset(NewStr(Path)); break; } } } } if (!Full) { char *Dir = dirchar(File, true); for (IdeProject *p=Projects.First(); p && !Full; p=Projects.Next()) { Full.Reset(p->FindFullPath(Dir?Dir+1:File)); } if (!Full) { if (FileExists(File)) { Full.Reset(NewStr(File)); } } } return ValidStr(Full); } void ViewMsg(char *File, int Line, char *Context) { GAutoString Full; if (FindSource(Full, File, Context)) { App->GotoReference(Full, Line, false); } } void GetContext(char16 *Txt, ssize_t &i, char16 *&Context) { static char16 NMsg[] = L"In file included "; static char16 FromMsg[] = L"from "; auto NMsgLen = StrlenW(NMsg); if (Txt[i] != '\n') return; if (StrncmpW(Txt + i + 1, NMsg, NMsgLen)) return; i += NMsgLen + 1; while (Txt[i]) { // Skip whitespace while (Txt[i] && strchr(" \t\r\n", Txt[i])) i++; // Check for 'from' if (StrncmpW(FromMsg, Txt + i, 5)) break; i += 5; char16 *Start = Txt + i; // Skip to end of doc or line char16 *Colon = 0; while (Txt[i] && Txt[i] != '\n') { if (Txt[i] == ':' && Txt[i+1] != '\n') { Colon = Txt + i; } i++; } if (Colon) { DeleteArray(Context); Context = NewStrW(Start, Colon-Start); } } } #define PossibleLineSep(ch) \ ( (ch) == ':' || (ch) == '(' ) void SeekMsg(int Direction) { GString Comp; IdeProject *p = App->RootProject(); if (p) p ->GetSettings()->GetStr(ProjCompiler); // bool IsIAR = Comp.Equals("IAR"); if (!Output) return; int64 Current = Output->Value(); GTextView3 *o = Current < CountOf(Output->Txt) ? Output->Txt[Current] : 0; if (!o) return; char16 *Txt = o->NameW(); if (!Txt) return; ssize_t Cur = o->GetCaret(); char16 *Context = NULL; // Scan forward to the end of file for the next filename/line number separator. ssize_t i; for (i=Cur; Txt[i]; i++) { GetContext(Txt, i, Context); if ( PossibleLineSep(Txt[i]) && isdigit(Txt[i+1]) ) { break; } } // If not found then scan from the start of the file for the next filename/line number separator. if (!PossibleLineSep(Txt[i])) { for (i=0; i 0 && !strchr("\n>", Txt[Line-1])) { Line--; } // Store the filename GAutoString File(WideToUtf8(Txt+Line, i-Line)); if (!File) return; // Scan over the line number.. auto NumIndex = ++i; while (isdigit(Txt[NumIndex])) NumIndex++; // Store the line number GAutoString NumStr(WideToUtf8(Txt + i, NumIndex - i)); if (!NumStr) return; // Convert it to an integer int LineNumber = atoi(NumStr); o->SetCaret(Line, false); o->SetCaret(NumIndex + 1, true); GString Context8 = Context; ViewMsg(File, LineNumber, Context8); } void UpdateMenus() { static const char *None = "(none)"; if (!App->GetMenu()) return; // This happens in GTK during window destruction if (RecentFilesMenu) { RecentFilesMenu->Empty(); int n=0; char *f=RecentFiles.First(); if (f) { for (; f; f=RecentFiles.Next()) { RecentFilesMenu->AppendItem(f, IDM_RECENT_FILE+n++, true); } } else { RecentFilesMenu->AppendItem(None, 0, false); } } if (RecentProjectsMenu) { RecentProjectsMenu->Empty(); int n=0; char *f=RecentProjects.First(); if (f) { for (; f; f=RecentProjects.Next()) { RecentProjectsMenu->AppendItem(f, IDM_RECENT_PROJECT+n++, true); } } else { RecentProjectsMenu->AppendItem(None, 0, false); } } if (WindowsMenu) { WindowsMenu->Empty(); Docs.Sort(DocSorter); int n=0; for (IdeDoc *d=Docs.First(); d; d=Docs.Next()) { const char *File = d->GetFileName(); if (!File) File = "(untitled)"; char *Dir = strrchr((char*)File, DIR_CHAR); WindowsMenu->AppendItem(Dir?Dir+1:File, IDM_WINDOWS+n++, true); } if (!Docs.First()) { WindowsMenu->AppendItem(None, 0, false); } } } void OnFile(const char *File, bool IsProject = false) { if (File) { List *Recent = IsProject ? &RecentProjects : &RecentFiles; for (char *f=Recent->First(); f; f=Recent->Next()) { if (stricmp(f, File) == 0) { Recent->Delete(f); Recent->Insert(f, 0); UpdateMenus(); return; } } Recent->Insert(NewStr(File), 0); while (Recent->Length() > 10) { char *f = Recent->Last(); Recent->Delete(f); DeleteArray(f); } UpdateMenus(); } } void RemoveRecent(char *File) { if (File) { List *Recent[3] = { &RecentProjects, &RecentFiles, 0 }; for (List **r = Recent; *r; r++) { for (char *f=(*r)->First(); f; f=(*r)->Next()) { if (stricmp(f, File) == 0) { // LgiTrace("Remove '%s'\n", f); (*r)->Delete(f); DeleteArray(f); break; } } } UpdateMenus(); } } IdeDoc *IsFileOpen(const char *File) { if (!File) { LgiTrace("%s:%i - No input File?\n", _FL); return NULL; } for (IdeDoc *Doc = Docs.First(); Doc; Doc = Docs.Next()) { if (Doc->IsFile(File)) { return Doc; } } // LgiTrace("%s:%i - '%s' not found in %i docs.\n", _FL, File, Docs.Length()); return 0; } IdeProject *IsProjectOpen(char *File) { if (File) { for (IdeProject *p = Projects.First(); p; p = Projects.Next()) { if (p->GetFileName() && stricmp(p->GetFileName(), File) == 0) { return p; } } } return 0; } void SerializeStringList(const char *Opt, List *Lst, bool Write) { GVariant v; if (Write) { GMemQueue p; for (char *s = Lst->First(); s; s = Lst->Next()) { p.Write((uchar*)s, strlen(s)+1); } ssize_t Size = (ssize_t)p.GetSize(); v.SetBinary(Size, p.New(), true); Options.SetValue(Opt, v); } else { Lst->DeleteArrays(); if (Options.GetValue(Opt, v) && v.Type == GV_BINARY) { char *Data = (char*)v.Value.Binary.Data; for (char *s=Data; (NativeInt)s<(NativeInt)Data+v.Value.Binary.Length; s += strlen(s) + 1) { auto ns = NewStr(s); Lst->Insert(ns); } } } } }; #ifdef COCOA #define Chk printf("%s:%i - Cnt=%i\n", LgiGetLeaf(__FILE__), __LINE__, (int)WindowHandle().p.retainCount) #else #define Chk #endif AppWnd::AppWnd() { #ifdef __GTK_H__ LgiGetResObj(true, AppName); #endif Chk; GRect r(0, 0, 1300, 900); #ifdef BEOS r.Offset(GdcD->X() - r.X() - 10, GdcD->Y() - r.Y() - 10); SetPos(r); #else SetPos(r); Chk; MoveToCenter(); #endif Chk; d = new AppWndPrivate(this); Name(AppName); SetQuitOnClose(true); Chk; #if WINNATIVE SetIcon((char*)MAKEINTRESOURCE(IDI_APP)); #else SetIcon("Icon64.png"); #endif Chk; if (Attach(0)) { Chk; Menu = new GMenu; if (Menu) { Menu->Attach(this); bool Loaded = Menu->Load(this, "IDM_MENU"); LgiAssert(Loaded); if (Loaded) { d->RecentFilesMenu = Menu->FindSubMenu(IDM_RECENT_FILES); d->RecentProjectsMenu = Menu->FindSubMenu(IDM_RECENT_PROJECTS); d->WindowsMenu = Menu->FindSubMenu(IDM_WINDOW_LST); d->CreateMakefileMenu = Menu->FindSubMenu(IDM_CREATE_MAKEFILE); if (d->CreateMakefileMenu) { d->CreateMakefileMenu->Empty(); for (int i=0; PlatformNames[i]; i++) { d->CreateMakefileMenu->AppendItem(PlatformNames[i], IDM_MAKEFILE_BASE + i); } } else LgiTrace("%s:%i - FindSubMenu failed.\n", _FL); GMenuItem *Debug = GetMenu()->FindItem(IDM_DEBUG_MODE); if (Debug) { Debug->Checked(true); } else LgiTrace("%s:%i - FindSubMenu failed.\n", _FL); d->UpdateMenus(); } } Chk; GToolBar *Tools; if (GdcD->Y() > 1200) Tools = LgiLoadToolbar(this, "cmds-32px.png", 32, 32); else Tools = LgiLoadToolbar(this, "cmds-16px.png", 16, 16); if (Tools) { Chk; Tools->AppendButton("New", IDM_NEW, TBT_PUSH, true, CMD_NEW); Tools->AppendButton("Open", IDM_OPEN, TBT_PUSH, true, CMD_OPEN); Tools->AppendButton("Save", IDM_SAVE_ALL, TBT_PUSH, true, CMD_SAVE_ALL); Tools->AppendSeparator(); Tools->AppendButton("Cut", IDM_CUT, TBT_PUSH, true, CMD_CUT); Tools->AppendButton("Copy", IDM_COPY, TBT_PUSH, true, CMD_COPY); Tools->AppendButton("Paste", IDM_PASTE, TBT_PUSH, true, CMD_PASTE); Tools->AppendSeparator(); Chk; Tools->AppendButton("Compile", IDM_COMPILE, TBT_PUSH, true, CMD_COMPILE); Tools->AppendButton("Build", IDM_BUILD, TBT_PUSH, true, CMD_BUILD); Tools->AppendButton("Stop", IDM_STOP_BUILD, TBT_PUSH, true, CMD_STOP_BUILD); // Tools->AppendButton("Execute", IDM_EXECUTE, TBT_PUSH, true, CMD_EXECUTE); Tools->AppendSeparator(); Tools->AppendButton("Debug", IDM_START_DEBUG, TBT_PUSH, true, CMD_DEBUG); Tools->AppendButton("Pause", IDM_PAUSE_DEBUG, TBT_PUSH, true, CMD_PAUSE); Tools->AppendButton("Restart", IDM_RESTART_DEBUGGING, TBT_PUSH, true, CMD_RESTART); Tools->AppendButton("Kill", IDM_STOP_DEBUG, TBT_PUSH, true, CMD_KILL); Tools->AppendButton("Step Into", IDM_STEP_INTO, TBT_PUSH, true, CMD_STEP_INTO); Tools->AppendButton("Step Over", IDM_STEP_OVER, TBT_PUSH, true, CMD_STEP_OVER); Tools->AppendButton("Step Out", IDM_STEP_OUT, TBT_PUSH, true, CMD_STEP_OUT); Tools->AppendButton("Run To", IDM_RUN_TO, TBT_PUSH, true, CMD_RUN_TO); Tools->AppendSeparator(); Tools->AppendButton("Find In Files", IDM_FIND_IN_FILES, TBT_PUSH, true, CMD_FIND_IN_FILES); Tools->GetCss(true)->Padding("4px"); Tools->Attach(this); } else LgiTrace("%s:%i - No tools obj?", _FL); Chk; GVariant v = 270, OutPx = 250; d->Options.GetValue(OPT_SPLIT_PX, v); d->Options.GetValue(OPT_OUTPUT_PX, OutPx); AddView(d->VBox = new GBox); d->VBox->SetVertical(true); d->HBox = new GBox; d->VBox->AddView(d->HBox); d->VBox->AddView(d->Output = new IdeOutput(this)); d->HBox->AddView(d->Tree = new IdeTree); if (d->Tree) { d->Tree->SetImageList(d->Icons, false); d->Tree->Sunken(false); } d->HBox->AddView(d->Mdi = new GMdiParent); if (d->Mdi) { d->Mdi->HasButton(true); } Chk; d->HBox->Value(MAX(v.CastInt32(), 20)); GRect c = GetClient(); if (c.Y() > OutPx.CastInt32()) { GCss::Len y(GCss::LenPx, OutPx.CastDouble()); d->Output->GetCss(true)->Height(y); } AttachChildren(); OnPosChange(); Chk; #ifdef LINUX GString f = LgiFindFile("lgiide.png"); if (f) { // Handle()->setIcon(f); } #endif UpdateState(); Chk; Visible(true); DropTarget(true); Chk; SetPulse(1000); } #ifdef LINUX LgiFinishXWindowsStartup(this); #endif #if USE_HAIKU_PULSE_HACK d->Output->SetPulse(1000); #endif Chk; } AppWnd::~AppWnd() { if (d->HBox) { GVariant v = d->HBox->Value(); d->Options.SetValue(OPT_SPLIT_PX, v); } if (d->Output) { GVariant v = d->Output->Y(); d->Options.SetValue(OPT_OUTPUT_PX, v); } ShutdownFtpThread(); CloseAll(); LgiApp->AppWnd = 0; DeleteObj(d); } void AppWnd::OnPulse() { IdeDoc *Top = TopDoc(); if (Top) Top->OnPulse(); } GDebugContext *AppWnd::GetDebugContext() { return d->DbgContext; } struct DumpBinThread : public LThread { GStream *Out; GString InFile; bool IsLib; public: DumpBinThread(GStream *out, GString file) : LThread("DumpBin.Thread") { Out = out; InFile = file; DeleteOnExit = true; auto Ext = LgiGetExtension(InFile); IsLib = Ext && !stricmp(Ext, "lib"); Run(); } bool DumpBin(GString Args, GStream *Str) { char Buf[256]; ssize_t Rd; GSubProcess s("c:\\Program Files (x86)\\Microsoft Visual Studio 12.0\\VC\\bin\\amd64\\dumpbin.exe", Args); if (!s.Start(true, false)) return false; while ((Rd = s.Read(Buf, sizeof(Buf))) > 0) Str->Write(Buf, Rd); return true; } GString::Array Dependencies() { GString Args; GStringPipe p; Args.Printf("/dependents \"%s\"", InFile.Get()); DumpBin(Args, &p); auto Parts = p.NewGStr().Replace("\r", "").Split("\n\n"); auto Files = Parts[4].Strip().Split("\n"); auto Path = LGetPath(); for (auto &f : Files) { f = f.Strip(); bool Found = false; for (auto s : Path) { GFile::Path c(s); c += f.Get(); if (c.IsFile()) { f = c.GetFull(); Found = true; break; } } if (!Found) f += " (not found in path)"; } return Files; } GString GetArch() { GString Args; GStringPipe p; Args.Printf("/headers \"%s\"", InFile.Get()); DumpBin(Args, &p); const char *Key = " machine "; GString Arch; auto Lines = p.NewGStr().SplitDelimit("\r\n"); int64 Machine = 0; for (auto &Ln : Lines) { if (Ln.Find(Key) >= 0) { auto p = Ln.Strip().Split(Key); if (p.Length() == 2) { Arch = p[1].Strip("()"); Machine = p[0].Int(16); } } } if (Machine == 0x14c) Arch += " 32bit"; else if (Machine == 0x200) Arch += " 64bit Itanium"; else if (Machine == 0x8664) Arch += " 64bit"; return Arch; } GString GetExports() { GString Args; GStringPipe p; if (IsLib) Args.Printf("/symbols \"%s\"", InFile.Get()); else Args.Printf("/exports \"%s\"", InFile.Get()); DumpBin(Args, &p); GString Exp; auto Sect = p.NewGStr().Replace("\r", "").Split("\n\n"); if (IsLib) { GString::Array Lines, Funcs; for (auto &s : Sect) { if (s.Find("COFF", 0, 100) == 0) { Lines = s.Split("\n"); break; } } Funcs.SetFixedLength(false); for (auto &l : Lines) { if (l.Length() < 34) continue; const char *Type = l.Get() + 33; if (!Strnicmp(Type, "External", 8)) { auto Nm = l.RSplit("|",1).Last().Strip(); if (!strchr("@$.?", Nm(0))) Funcs.New() = Nm; } } Exp = GString("\n").Join(Funcs); } else { bool Ord = false; for (auto &s : Sect) { if (s.Strip().Find("ordinal") == 0) Ord = true; else if (Ord) { Exp = s; break; } else Ord = false; } } return Exp; } int Main() { if (!IsLib) { auto Deps = Dependencies(); Out->Print("Dependencies:\n\t%s\n\n", GString("\n\t").Join(Deps).Get()); } auto Arch = GetArch(); if (Arch) Out->Print("Arch: %s\n\n", Arch.Get()); auto Exp = GetExports(); if (Arch) Out->Print("Exports:\n%s\n\n", Exp.Get()); return 0; } }; void AppWnd::OnReceiveFiles(GArray &Files) { for (int i=0; iSetFileName(Docs, false); new DumpBinThread(Doc, Files[i]); } } else { OpenFile(f); } } Raise(); } void AppWnd::OnDebugState(bool Debugging, bool Running) { // Make sure this event is processed in the GUI thread. #if DEBUG_SESSION_LOGGING LgiTrace("AppWnd::OnDebugState(%i,%i) InThread=%i\n", Debugging, Running, InThread()); #endif PostEvent(M_DEBUG_ON_STATE, Debugging, Running); } bool IsVarChar(GString &s, int pos) { if (pos < 0) return false; if (pos >= s.Length()) return false; char i = s[pos]; return IsAlpha(i) || IsDigit(i) || i == '_'; } bool ReplaceWholeWord(GString &Ln, GString Word, GString NewWord) { int Pos = 0; bool Status = false; while (Pos >= 0) { Pos = Ln.Find(Word, Pos); if (Pos < 0) return Status; int End = Pos + Word.Length(); if (!IsVarChar(Ln, Pos-1) && !IsVarChar(Ln, End)) { GString NewLn = Ln(0,Pos) + NewWord + Ln(End,-1); Ln = NewLn; Status = true; } Pos++; } return Status; } -struct FileInfo +struct LFileInfo { GString Path; GString::Array Lines; bool Dirty; - FileInfo() + LFileInfo() { Dirty = false; } bool Save() { GFile f; if (!f.Open(Path, O_WRITE)) return false; GString NewFile = GString("\n").Join(Lines); f.SetSize(0); f.Write(NewFile); f.Close(); return true; } }; void AppWnd::OnFixBuildErrors() { LHashTbl, GString> Map; GVariant v; if (GetOptions()->GetValue(OPT_RENAMED_SYM, v)) { auto Lines = GString(v.Str()).Split("\n"); for (auto Ln: Lines) { auto p = Ln.SplitDelimit(); if (p.Length() == 2) Map.Add(p[0], p[1]); } if (Map.Length() == 0) { LgiMsg(this, "No renamed symbols defined.", AppName); return; } } else return; GString Raw = d->Output->Txt[AppWnd::BuildTab]->Name(); GString::Array Lines = Raw.Split("\n"); GProgressDlg Prog(this); Prog.SetDescription("Parsing errors..."); Prog.SetLimits(0, Lines.Length()); Prog.SetYieldTime(300); int i = 0; int Replacements = 0; - GArray Files; + GArray Files; for (auto Ln : Lines) { if (Ln.Find("error") >= 0) { GString::Array p = Ln.SplitDelimit(">()"); if (p.Length() > 2) { int Base = p[0].IsNumeric() ? 1 : 0; GString Fn = p[Base]; if (Fn.Find("Program Files") >= 0) continue; GAutoString Full; if (d->FindSource(Full, p[Base], NULL)) { auto LineNo = p[Base+1].Int(); - FileInfo *Fi = NULL; + LFileInfo *Fi = NULL; for (auto &i: Files) { if (i.Path.Equals(Full)) { Fi = &i; break; } } if (!Fi) { GFile f(Full, O_READ); if (f.IsOpen()) { Fi = &Files.New(); Fi->Path = Full.Get(); auto OldFile = f.Read(); Fi->Lines = OldFile.SplitDelimit("\n", -1, false); } } if (Fi) { if (LineNo <= Fi->Lines.Length()) { GString &s = Fi->Lines[LineNo-1]; for (auto i: Map) { if (ReplaceWholeWord(s, i.key, i.value)) { Fi->Dirty = true; Replacements++; } } } } else { int asd=0; } } } } Prog.Value(++i); if (Prog.IsCancelled()) break; } for (auto &Fi : Files) { if (Fi.Dirty) Fi.Save(); } if (Replacements) LgiMsg(this, "%i replacements made.", AppName, MB_OK, Replacements); } void AppWnd::OnBuildStateChanged(bool NewState) { GVariant v; if (!NewState && GetOptions()->GetValue(OPT_FIX_RENAMED, v) && v.CastInt32()) { OnFixBuildErrors(); } } void AppWnd::UpdateState(int Debugging, int Building) { if (Debugging >= 0) d->Debugging = Debugging; if (Building >= 0) { if (d->Building != (Building != 0)) OnBuildStateChanged(Building); d->Building = Building; } SetCtrlEnabled(IDM_COMPILE, !d->Building); SetCtrlEnabled(IDM_BUILD, !d->Building); SetCtrlEnabled(IDM_STOP_BUILD, d->Building); // SetCtrlEnabled(IDM_RUN, !d->Building); // SetCtrlEnabled(IDM_TOGGLE_BREAKPOINT, !d->Building); SetCtrlEnabled(IDM_START_DEBUG, !d->Debugging && !d->Building); SetCtrlEnabled(IDM_PAUSE_DEBUG, d->Debugging); SetCtrlEnabled(IDM_RESTART_DEBUGGING, d->Debugging); SetCtrlEnabled(IDM_STOP_DEBUG, d->Debugging); SetCtrlEnabled(IDM_STEP_INTO, d->Debugging); SetCtrlEnabled(IDM_STEP_OVER, d->Debugging); SetCtrlEnabled(IDM_STEP_OUT, d->Debugging); SetCtrlEnabled(IDM_RUN_TO, d->Debugging); } void AppWnd::AppendOutput(char *Txt, AppWnd::Channels Channel) { if (!d->Output) { LgiTrace("%s:%i - No output panel.\n", _FL); return; } if (Channel < 0 || Channel >= CountOf(d->Output->Txt)) { LgiTrace("%s:%i - Channel range: %i, %i.\n", _FL, Channel, CountOf(d->Output->Txt)); return; } if (!d->Output->Txt[Channel]) { LgiTrace("%s:%i - No log for channel %i.\n", _FL, Channel); return; } if (Txt) { d->Output->Buf[Channel].Add(Txt, strlen(Txt)); } else { d->Output->Txt[Channel]->Name(""); } } void AppWnd::SaveAll() { List::I Docs = d->Docs.begin(); for (IdeDoc *Doc = *Docs; Doc; Doc = *++Docs) { Doc->SetClean(); d->OnFile(Doc->GetFileName()); } List::I Projs = d->Projects.begin(); for (IdeProject *Proj = *Projs; Proj; Proj = *++Projs) { Proj->SetClean(); d->OnFile(Proj->GetFileName(), true); } } void AppWnd::CloseAll() { SaveAll(); while (d->Docs.First()) delete d->Docs.First(); IdeProject *p = RootProject(); if (p) DeleteObj(p); while (d->Projects.First()) delete d->Projects.First(); DeleteObj(d->DbgContext); } bool AppWnd::OnRequestClose(bool IsClose) { SaveAll(); return GWindow::OnRequestClose(IsClose); } bool AppWnd::OnBreakPoint(GDebugger::BreakPoint &b, bool Add) { List::I it = d->Docs.begin(); for (IdeDoc *doc = *it; doc; doc = *++it) { char *fn = doc->GetFileName(); bool Match = !_stricmp(fn, b.File); if (Match) { doc->AddBreakPoint(b.Line, Add); } } if (d->DbgContext) { d->DbgContext->OnBreakPoint(b, Add); } return true; } bool AppWnd::LoadBreakPoints(IdeDoc *doc) { if (!doc) return false; char *fn = doc->GetFileName(); for (int i=0; iBreakPoints.Length(); i++) { GDebugger::BreakPoint &b = d->BreakPoints[i]; if (!_stricmp(fn, b.File)) { doc->AddBreakPoint(b.Line, true); } } return true; } bool AppWnd::LoadBreakPoints(GDebugger *db) { if (!db) return false; for (int i=0; iBreakPoints.Length(); i++) { GDebugger::BreakPoint &bp = d->BreakPoints[i]; db->SetBreakPoint(&bp); } return true; } bool AppWnd::ToggleBreakpoint(const char *File, ssize_t Line) { bool DeleteBp = false; for (int i=0; iBreakPoints.Length(); i++) { GDebugger::BreakPoint &b = d->BreakPoints[i]; if (!_stricmp(File, b.File) && b.Line == Line) { OnBreakPoint(b, false); d->BreakPoints.DeleteAt(i); DeleteBp = true; break; } } if (!DeleteBp) { GDebugger::BreakPoint &b = d->BreakPoints.New(); b.File = File; b.Line = Line; OnBreakPoint(b, true); } return true; } void AppWnd::DumpHistory() { #if 0 LgiTrace("History %i of %i\n", d->HistoryLoc, d->CursorHistory.Length()); for (int i=0; iCursorHistory.Length(); i++) { FileLoc &p = d->CursorHistory[i]; LgiTrace(" [%i] = %s, %i %s\n", i, p.File.Get(), p.Line, d->HistoryLoc == i ? "<-----":""); } #endif } /* void CheckHistory(GArray &CursorHistory) { if (CursorHistory.Length() > 0) { FileLoc *loc = &CursorHistory[0]; for (unsigned i=CursorHistory.Length(); iInHistorySeek) { if (d->CursorHistory.Length() > 0) { FileLoc &Last = d->CursorHistory.Last(); if (_stricmp(File, Last.File) == 0 && abs(Last.Line - Line) <= 1) { // Previous or next line... just update line number Last.Line = Line; DumpHistory(); return; } // Add new entry d->HistoryLoc++; FileLoc &loc = d->CursorHistory[d->HistoryLoc]; #ifdef WIN64 if ((NativeInt)loc.File.Get() == 0xcdcdcdcdcdcdcdcd) LgiAssert(0); // wtf? else #endif loc.Set(File, Line); } else { // Add new entry d->CursorHistory[0].Set(File, Line); } // Destroy any history after the current... d->CursorHistory.Length(d->HistoryLoc+1); DumpHistory(); } } void AppWnd::OnFile(char *File, bool IsProject) { d->OnFile(File, IsProject); } IdeDoc *AppWnd::NewDocWnd(const char *FileName, NodeSource *Src) { IdeDoc *Doc = new IdeDoc(this, Src, 0); if (Doc) { d->Docs.Insert(Doc); GRect p = d->Mdi->NewPos(); Doc->SetPos(p); Doc->Attach(d->Mdi); Doc->Focus(true); Doc->Raise(); if (FileName) d->OnFile(FileName); } return Doc; } IdeDoc *AppWnd::GetCurrentDoc() { if (d->Mdi) return dynamic_cast(d->Mdi->GetTop()); return NULL; } IdeDoc *AppWnd::GotoReference(const char *File, int Line, bool CurIp, bool WithHistory) { if (!WithHistory) d->InHistorySeek = true; IdeDoc *Doc = File ? OpenFile(File) : GetCurrentDoc(); if (Doc) Doc->SetLine(Line, CurIp); /* else LgiTrace("%s:%i - No file '%s' found.\n", _FL, File); */ if (!WithHistory) d->InHistorySeek = false; return Doc; } IdeDoc *AppWnd::FindOpenFile(char *FileName) { List::I it = d->Docs.begin(); for (IdeDoc *i=*it; i; i=*++it) { char *f = i->GetFileName(); if (f) { IdeProject *p = i->GetProject(); if (p) { GAutoString Base = p->GetBasePath(); if (Base) { char Path[MAX_PATH]; if (*f == '.') LgiMakePath(Path, sizeof(Path), Base, f); else strcpy_s(Path, sizeof(Path), f); if (stricmp(Path, FileName) == 0) return i; } } else { if (stricmp(f, FileName) == 0) return i; } } } return 0; } IdeDoc *AppWnd::OpenFile(const char *FileName, NodeSource *Src) { static bool DoingProjectFind = false; IdeDoc *Doc = 0; const char *File = Src ? Src->GetFileName() : FileName; if (!Src && !ValidStr(File)) { LgiTrace("%s:%i - No source or file?\n", _FL); return NULL; } GString FullPath; if (LgiIsRelativePath(File)) { IdeProject *Proj = Src && Src->GetProject() ? Src->GetProject() : RootProject(); if (Proj) { List Projs; Projs.Insert(Proj); Proj->CollectAllSubProjects(Projs); for (auto Project : Projs) { GAutoString ProjPath = Project->GetBasePath(); char p[MAX_PATH]; LgiMakePath(p, sizeof(p), ProjPath, File); if (FileExists(p)) { FullPath = p; File = FullPath; break; } } } } Doc = d->IsFileOpen(File); if (!Doc) { if (Src) { Doc = NewDocWnd(File, Src); } else if (!DoingProjectFind) { DoingProjectFind = true; List::I Proj = d->Projects.begin(); for (IdeProject *p=*Proj; p && !Doc; p=*++Proj) { p->InProject(LgiIsRelativePath(File), File, true, &Doc); } DoingProjectFind = false; d->OnFile(File); } } if (!Doc && FileExists(File)) { Doc = new IdeDoc(this, 0, File); if (Doc) { GRect p = d->Mdi->NewPos(); Doc->SetPos(p); d->Docs.Insert(Doc); d->OnFile(File); } } if (Doc) { #ifdef BEOS BView *h = Doc->Handle(); BWindow *w = h ? h->Window() : 0; bool att = Doc->IsAttached(); printf("%s:%i - att=%i h=%p w=%p\n", _FL, att, h, w); #endif if (!Doc->IsAttached()) { Doc->Attach(d->Mdi); } Doc->Focus(true); Doc->Raise(); } return Doc; } IdeProject *AppWnd::RootProject() { IdeProject *Root = 0; for (IdeProject *p=d->Projects.First(); p; p=d->Projects.Next()) { if (!p->GetParentProject()) { LgiAssert(Root == 0); Root = p; } } return Root; } IdeProject *AppWnd::OpenProject(char *FileName, IdeProject *ParentProj, bool Create, bool Dep) { if (!FileName) { LgiTrace("%s:%i - Error: No filename.\n", _FL); return NULL; } if (d->IsProjectOpen(FileName)) { LgiTrace("%s:%i - Warning: Project already open.\n", _FL); return NULL; } IdeProject *p = new IdeProject(this); if (!p) { LgiTrace("%s:%i - Error: mem alloc.\n", _FL); return NULL; } GString::Array Inc; p->BuildIncludePaths(Inc, false, false, PlatformCurrent); d->FindSym->SetIncludePaths(Inc); p->SetParentProject(ParentProj); ProjectStatus Status = p->OpenFile(FileName); if (Status == OpenOk) { d->Projects.Insert(p); d->OnFile(FileName, true); if (!Dep) { char *d = strrchr(FileName, DIR_CHAR); if (d++) { char n[256]; sprintf(n, "%s [%s]", AppName, d); Name(n); } } } else { LgiTrace("%s:%i - Failed to open '%s'\n", _FL, FileName); DeleteObj(p); if (Status == OpenError) d->RemoveRecent(FileName); } if (!GetTree()->Selection()) { GetTree()->Select(GetTree()->GetChild()); } GetTree()->Focus(true); return p; } GMessage::Result AppWnd::OnEvent(GMessage *m) { switch (MsgCode(m)) { case M_START_BUILD: { IdeProject *p = RootProject(); if (p) p->Build(true, IsReleaseMode()); else printf("%s:%i - No root project.\n", _FL); break; } case M_BUILD_DONE: { UpdateState(-1, false); IdeProject *p = RootProject(); if (p) p->StopBuild(); break; } case M_BUILD_ERR: { char *Msg = (char*)MsgB(m); if (Msg) { d->Output->Txt[AppWnd::BuildTab]->Print("Build Error: %s\n", Msg); DeleteArray(Msg); } break; } case M_APPEND_TEXT: { char *Text = (char*) MsgA(m); Channels Ch = (Channels) MsgB(m); AppendOutput(Text, Ch); DeleteArray(Text); break; } case M_DEBUG_ON_STATE: { bool Debugging = m->A(); bool Running = m->B(); if (d->Running != Running) { bool RunToNotRun = d->Running && !Running; d->Running = Running; if (RunToNotRun && d->Output && d->Output->DebugTab) { d->Output->DebugTab->SendNotify(GNotifyValueChanged); } } if (d->Debugging != Debugging) { d->Debugging = Debugging; if (!Debugging) { IdeDoc::ClearCurrentIp(); IdeDoc *c = GetCurrentDoc(); if (c) c->UpdateControl(); // Shutdown the debug context and free the memory DeleteObj(d->DbgContext); } } SetCtrlEnabled(IDM_START_DEBUG, !Debugging || !Running); SetCtrlEnabled(IDM_PAUSE_DEBUG, Debugging && Running); SetCtrlEnabled(IDM_RESTART_DEBUGGING, Debugging); SetCtrlEnabled(IDM_STOP_DEBUG, Debugging); SetCtrlEnabled(IDM_STEP_INTO, Debugging && !Running); SetCtrlEnabled(IDM_STEP_OVER, Debugging && !Running); SetCtrlEnabled(IDM_STEP_OUT, Debugging && !Running); SetCtrlEnabled(IDM_RUN_TO, Debugging && !Running); break; } default: { if (d->DbgContext) d->DbgContext->OnEvent(m); break; } } return GWindow::OnEvent(m); } bool AppWnd::OnNode(const char *Path, ProjectNode *Node, FindSymbolSystem::SymAction Action) { // This takes care of adding/removing files from the symbol search engine. if (!Path || !Node) return false; d->FindSym->OnFile(Path, Action, Node->GetPlatforms()); return true; } GOptionsFile *AppWnd::GetOptions() { return &d->Options; } class Options : public GDialog { AppWnd *App; GFontType Font; public: Options(AppWnd *a) { SetParent(App = a); if (LoadFromResource(IDD_OPTIONS)) { SetCtrlEnabled(IDC_FONT, false); MoveToCenter(); if (!Font.Serialize(App->GetOptions(), OPT_EditorFont, false)) { Font.GetSystemFont("Fixed"); } char s[256]; if (Font.GetDescription(s, sizeof(s))) { SetCtrlName(IDC_FONT, s); } GVariant v; if (App->GetOptions()->GetValue(OPT_Jobs, v)) SetCtrlValue(IDC_JOBS, v.CastInt32()); else SetCtrlValue(IDC_JOBS, 2); DoModal(); } } int OnNotify(GViewI *c, int f) { switch (c->GetId()) { case IDOK: { GVariant v; Font.Serialize(App->GetOptions(), OPT_EditorFont, true); App->GetOptions()->SetValue(OPT_Jobs, v = GetCtrlValue(IDC_JOBS)); } case IDCANCEL: { EndModal(c->GetId()); break; } case IDC_SET_FONT: { if (Font.DoUI(this)) { char s[256]; if (Font.GetDescription(s, sizeof(s))) { SetCtrlName(IDC_FONT, s); } } break; } } return 0; } }; void AppWnd::UpdateMemoryDump() { if (d->DbgContext) { char *sWord = GetCtrlName(IDC_MEM_SIZE); int iWord = sWord ? atoi(sWord) : 1; int64 RowLen = GetCtrlValue(IDC_MEM_ROW_LEN); bool InHex = GetCtrlValue(IDC_MEM_HEX) != 0; d->DbgContext->FormatMemoryDump(iWord, (int)RowLen, InHex); } } int AppWnd::OnNotify(GViewI *Ctrl, int Flags) { switch (Ctrl->GetId()) { case IDC_PROJECT_TREE: { if (Flags == GNotify_DeleteKey) { ProjectNode *n = dynamic_cast(d->Tree->Selection()); if (n) n->Delete(); } break; } case IDC_DEBUG_EDIT: { if (Flags == VK_RETURN && d->DbgContext) { char *Cmd = Ctrl->Name(); if (Cmd) { d->DbgContext->OnUserCommand(Cmd); Ctrl->Name(NULL); } } break; } case IDC_MEM_ADDR: { if (Flags == VK_RETURN) { if (d->DbgContext) { char *s = Ctrl->Name(); if (s) { char *sWord = GetCtrlName(IDC_MEM_SIZE); int iWord = sWord ? atoi(sWord) : 1; d->DbgContext->OnMemoryDump(s, iWord, (int)GetCtrlValue(IDC_MEM_ROW_LEN), GetCtrlValue(IDC_MEM_HEX) != 0); } else if (d->DbgContext->MemoryDump) { d->DbgContext->MemoryDump->Print("No address specified."); } else { LgiAssert(!"No MemoryDump."); } } else LgiAssert(!"No debug context."); } break; } case IDC_MEM_ROW_LEN: { if (Flags == VK_RETURN) UpdateMemoryDump(); break; } case IDC_MEM_HEX: case IDC_MEM_SIZE: { UpdateMemoryDump(); break; } case IDC_DEBUG_TAB: { if (d->DbgContext && Flags == GNotifyValueChanged) { switch (Ctrl->Value()) { case AppWnd::LocalsTab: { d->DbgContext->UpdateLocals(); break; } case AppWnd::WatchTab: { d->DbgContext->UpdateWatches(); break; } case AppWnd::RegistersTab: { d->DbgContext->UpdateRegisters(); break; } case AppWnd::CallStackTab: { d->DbgContext->UpdateCallStack(); break; } case AppWnd::ThreadsTab: { d->DbgContext->UpdateThreads(); break; } default: break; } } break; } case IDC_LOCALS_LIST: { if (d->Output->Locals && Flags == GNotifyItem_DoubleClick && d->DbgContext) { LListItem *it = d->Output->Locals->GetSelected(); if (it) { char *Var = it->GetText(2); char *Val = it->GetText(3); if (Var) { if (d->Output->DebugTab) d->Output->DebugTab->Value(AppWnd::ObjectTab); d->DbgContext->DumpObject(Var, Val); } } } break; } case IDC_CALL_STACK: { if (Flags == M_CHANGE) { if (d->Output->DebugTab) d->Output->DebugTab->Value(AppWnd::CallStackTab); } else if (Flags == GNotifyItem_Select) { // This takes the user to a given call stack reference if (d->Output->CallStack && d->DbgContext) { LListItem *item = d->Output->CallStack->GetSelected(); if (item) { GAutoString File; int Line; if (d->DbgContext->ParseFrameReference(item->GetText(1), File, Line)) { GAutoString Full; if (d->FindSource(Full, File, NULL)) { GotoReference(Full, Line, false); char *sFrame = item->GetText(0); if (sFrame && IsDigit(*sFrame)) d->DbgContext->SetFrame(atoi(sFrame)); } } } } } break; } case IDC_WATCH_LIST: { WatchItem *Edit = NULL; switch (Flags) { case GNotify_DeleteKey: { GArray Sel; for (GTreeItem *c = d->Output->Watch->GetChild(); c; c = c->GetNext()) { if (c->Select()) Sel.Add(c); } Sel.DeleteObjects(); break; } case GNotifyItem_Click: { Edit = dynamic_cast(d->Output->Watch->Selection()); break; } case GNotifyContainer_Click: { // Create new watch. Edit = new WatchItem(d->Output); if (Edit) d->Output->Watch->Insert(Edit); break; } } if (Edit) Edit->EditLabel(0); break; } case IDC_THREADS: { if (Flags == GNotifyItem_Select) { // This takes the user to a given thread if (d->Output->Threads && d->DbgContext) { LListItem *item = d->Output->Threads->GetSelected(); if (item) { GString sId = item->GetText(0); int ThreadId = (int)sId.Int(); if (ThreadId > 0) { d->DbgContext->SelectThread(ThreadId); } } } } break; } } return 0; } bool AppWnd::IsReleaseMode() { GMenuItem *Release = GetMenu()->FindItem(IDM_RELEASE_MODE); bool IsRelease = Release ? Release->Checked() : false; return IsRelease; } bool AppWnd::Build() { SaveAll(); IdeDoc *Top; IdeProject *p = RootProject(); if (p) { UpdateState(-1, true); GMenuItem *Release = GetMenu()->FindItem(IDM_RELEASE_MODE); bool IsRelease = Release ? Release->Checked() : false; p->Build(false, IsRelease); return true; } else if ((Top = TopDoc())) { return Top->Build(); } return false; } class RenameDlg : public GDialog { AppWnd *App; public: RenameDlg(AppWnd *a) { SetParent(App = a); MoveSameScreen(a); if (LoadFromResource(IDC_RENAME)) { GVariant v; if (App->GetOptions()->GetValue(OPT_FIX_RENAMED, v)) SetCtrlValue(IDC_FIX_RENAMED, v.CastInt32()); if (App->GetOptions()->GetValue(OPT_RENAMED_SYM, v)) SetCtrlName(IDC_SYM, v.Str()); } } int OnNotify(GViewI *c, int f) { switch (c->GetId()) { case IDOK: { GVariant v; App->GetOptions()->SetValue(OPT_RENAMED_SYM, v = GetCtrlName(IDC_SYM)); App->GetOptions()->SetValue(OPT_FIX_RENAMED, v = GetCtrlValue(IDC_FIX_RENAMED)); } case IDCANCEL: { EndModal(c->GetId() == IDOK); break; } } return 0; } }; bool AppWnd::ShowInProject(const char *Fn) { if (!Fn) return false; for (IdeProject *p=d->Projects.First(); p; p=d->Projects.Next()) { ProjectNode *Node = NULL; if (p->FindFullPath(Fn, &Node)) { for (GTreeItem *i = Node->GetParent(); i; i = i->GetParent()) { i->Expanded(true); } Node->Select(true); Node->ScrollTo(); return true; } } return false; } int AppWnd::OnCommand(int Cmd, int Event, OsView Wnd) { switch (Cmd) { case IDM_EXIT: { LgiCloseApp(); break; } case IDM_OPTIONS: { Options Dlg(this); break; } case IDM_HELP: { LgiExecute(APP_URL); break; } case IDM_ABOUT: { GAbout a(this, AppName, APP_VER, "\nLGI Integrated Development Environment", "icon128.png", APP_URL, "fret@memecode.com"); break; } case IDM_NEW: { IdeDoc *Doc; d->Docs.Insert(Doc = new IdeDoc(this, 0, 0)); if (Doc) { GRect p = d->Mdi->NewPos(); Doc->SetPos(p); Doc->Attach(d->Mdi); Doc->Focus(true); } break; } case IDM_OPEN: { GFileSelect s; s.Parent(this); if (s.Open()) { OpenFile(s.Name()); } break; } case IDM_SAVE_ALL: { SaveAll(); break; } case IDM_SAVE: { IdeDoc *Top = TopDoc(); if (Top) Top->SetClean(); break; } case IDM_SAVEAS: { IdeDoc *Top = TopDoc(); if (Top) { GFileSelect s; s.Parent(this); if (s.Save()) { Top->SetFileName(s.Name(), true); d->OnFile(s.Name()); } } break; } case IDM_CLOSE: { IdeDoc *Top = TopDoc(); if (Top) { if (Top->OnRequestClose(false)) { Top->Quit(); } } DeleteObj(d->DbgContext); break; } case IDM_CLOSE_ALL: { CloseAll(); Name(AppName); break; } // // Editor // case IDM_UNDO: { GTextView3 *Doc = FocusEdit(); if (Doc) { Doc->Undo(); } else LgiTrace("%s:%i - No focus doc.\n", _FL); break; } case IDM_REDO: { GTextView3 *Doc = FocusEdit(); if (Doc) { Doc->Redo(); } else LgiTrace("%s:%i - No focus doc.\n", _FL); break; } case IDM_FIND: { GTextView3 *Doc = FocusEdit(); if (Doc) { Doc->DoFind(); } else LgiTrace("%s:%i - No focus doc.\n", _FL); break; } case IDM_FIND_NEXT: { GTextView3 *Doc = FocusEdit(); if (Doc) { Doc->DoFindNext(); } else LgiTrace("%s:%i - No focus doc.\n", _FL); break; } case IDM_REPLACE: { GTextView3 *Doc = FocusEdit(); if (Doc) { Doc->DoReplace(); } else LgiTrace("%s:%i - No focus doc.\n", _FL); break; } case IDM_GOTO: { GTextView3 *Doc = FocusEdit(); if (Doc) Doc->DoGoto(); else { GInput Inp(this, NULL, LgiLoadString(L_TEXTCTRL_GOTO_LINE, "Goto [file:]line:"), "Goto"); if (Inp.DoModal()) { GString s = Inp.GetStr(); GString::Array p = s.SplitDelimit(":,"); if (p.Length() == 2) { GString file = p[0]; int line = (int)p[1].Int(); GotoReference(file, line, false, true); } else LgiMsg(this, "Error: Needs a file name as well.", AppName); } } break; } case IDM_CUT: { GTextView3 *Doc = FocusEdit(); if (Doc) Doc->PostEvent(M_CUT); break; } case IDM_COPY: { GTextView3 *Doc = FocusEdit(); if (Doc) Doc->PostEvent(M_COPY); break; } case IDM_PASTE: { GTextView3 *Doc = FocusEdit(); if (Doc) Doc->PostEvent(M_PASTE); break; } case IDM_FIND_IN_FILES: { if (!d->Finder) { d->Finder.Reset(new FindInFilesThread(d->AppHnd)); } if (d->Finder) { if (!d->FindParameters && d->FindParameters.Reset(new FindParams)) { GVariant var; if (GetOptions()->GetValue(OPT_ENTIRE_SOLUTION, var)) d->FindParameters->Type = var.CastInt32() ? FifSearchSolution : FifSearchDirectory; } FindInFiles Dlg(this, d->FindParameters); GViewI *Focus = GetFocus(); if (Focus) { GTextView3 *Edit = dynamic_cast(Focus); if (Edit && Edit->HasSelection()) { GAutoString a(Edit->GetSelection()); Dlg.Params->Text = a; } } IdeProject *p = RootProject(); if (p) { GAutoString Base = p->GetBasePath(); if (Base) Dlg.Params->Dir = Base; } if (Dlg.DoModal()) { if (p && Dlg.Params->Type == FifSearchSolution) { Dlg.Params->ProjectFiles.Length(0); List Projects; Projects.Insert(p); p->GetChildProjects(Projects); GArray Nodes; for (IdeProject *p = Projects.First(); p; p = Projects.Next()) p->GetAllNodes(Nodes); for (unsigned i=0; iGetFullPath(); if (s) Dlg.Params->ProjectFiles.Add(s); } } GVariant var = d->FindParameters->Type == FifSearchSolution; GetOptions()->SetValue(OPT_ENTIRE_SOLUTION, var); d->Finder->Stop(); d->Finder->PostEvent(FindInFilesThread::M_START_SEARCH, (GMessage::Param) new FindParams(d->FindParameters)); } } break; } case IDM_FIND_SYMBOL: { IdeDoc *Doc = FocusDoc(); if (Doc) { Doc->GotoSearch(IDC_SYMBOL_SEARCH); } else { FindSymResult r = d->FindSym->OpenSearchDlg(this); if (r.File) { GotoReference(r.File, r.Line, false); } } break; } case IDM_GOTO_SYMBOL: { IdeDoc *Doc = FocusDoc(); if (Doc) { Doc->SearchSymbol(); } break; } case IDM_FIND_PROJECT_FILE: { IdeDoc *Doc = FocusDoc(); if (Doc) { Doc->SearchFile(); } else { FindInProject Dlg(this); Dlg.DoModal(); } break; } case IDM_FIND_REFERENCES: { GViewI *f = LgiApp->GetFocus(); GDocView *doc = dynamic_cast(f); if (!doc) break; ssize_t c = doc->GetCaret(); if (c < 0) break; GString Txt = doc->Name(); char *s = Txt.Get() + c; char *e = s; while ( s > Txt.Get() && IsSymbolChar(s[-1])) s--; while (*e && IsSymbolChar(*e)) e++; if (e <= s) break; GString Word(s, e - s); if (!d->Finder) d->Finder.Reset(new FindInFilesThread(d->AppHnd)); if (!d->Finder) break; IdeProject *p = RootProject(); if (!p) break; List Projects; Projects.Insert(p); p->GetChildProjects(Projects); GArray Nodes; for (p = Projects.First(); p; p = Projects.Next()) p->GetAllNodes(Nodes); GAutoPtr Params(new FindParams); Params->Type = FifSearchSolution; Params->MatchWord = true; Params->Text = Word; for (unsigned i = 0; i < Nodes.Length(); i++) { Params->ProjectFiles.New() = Nodes[i]->GetFullPath(); } d->Finder->Stop(); d->Finder->PostEvent(FindInFilesThread::M_START_SEARCH, (GMessage::Param) Params.Release()); break; } case IDM_PREV_LOCATION: { d->SeekHistory(-1); break; } case IDM_NEXT_LOCATION: { d->SeekHistory(1); break; } // // Project // case IDM_NEW_PROJECT: { CloseAll(); IdeProject *p; d->Projects.Insert(p = new IdeProject(this)); if (p) { p->CreateProject(); } break; } case IDM_OPEN_PROJECT: { GFileSelect s; s.Parent(this); s.Type("Projects", "*.xml"); if (s.Open()) { CloseAll(); OpenProject(s.Name(), NULL, Cmd == IDM_NEW_PROJECT); if (d->Tree) { d->Tree->Focus(true); } } break; } case IDM_IMPORT_DSP: { IdeProject *p = RootProject(); if (p) { GFileSelect s; s.Parent(this); s.Type("Developer Studio Project", "*.dsp"); if (s.Open()) { p->ImportDsp(s.Name()); } } break; } case IDM_RUN: { SaveAll(); IdeProject *p = RootProject(); if (p) { p->Execute(); } break; } case IDM_VALGRIND: { SaveAll(); IdeProject *p = RootProject(); if (p) { p->Execute(ExeValgrind); } break; } case IDM_FIX_MISSING_FILES: { IdeProject *p = RootProject(); if (p) p->FixMissingFiles(); else LgiMsg(this, "No project loaded.", AppName); break; } case IDM_FIND_DUPE_SYM: { IdeProject *p = RootProject(); if (p) p->FindDuplicateSymbols(); else LgiMsg(this, "No project loaded.", AppName); break; } case IDM_RENAME_SYM: { RenameDlg Dlg(this); Dlg.DoModal(); break; } case IDM_START_DEBUG: { SaveAll(); IdeProject *p = RootProject(); if (!p) { LgiMsg(this, "No project loaded.", "Error"); break; } if (d->DbgContext) { d->DbgContext->OnCommand(IDM_CONTINUE); } else if ((d->DbgContext = p->Execute(ExeDebug))) { d->DbgContext->DebuggerLog = d->Output->DebuggerLog; d->DbgContext->Watch = d->Output->Watch; d->DbgContext->Locals = d->Output->Locals; d->DbgContext->CallStack = d->Output->CallStack; d->DbgContext->Threads = d->Output->Threads; d->DbgContext->ObjectDump = d->Output->ObjectDump; d->DbgContext->Registers = d->Output->Registers; d->DbgContext->MemoryDump = d->Output->MemoryDump; d->DbgContext->OnCommand(IDM_START_DEBUG); d->Output->Value(AppWnd::DebugTab); d->Output->DebugEdit->Focus(true); } break; } case IDM_TOGGLE_BREAKPOINT: { IdeDoc *Cur = GetCurrentDoc(); if (Cur) ToggleBreakpoint(Cur->GetFileName(), Cur->GetLine()); break; } case IDM_ATTACH_TO_PROCESS: case IDM_PAUSE_DEBUG: case IDM_RESTART_DEBUGGING: case IDM_RUN_TO: case IDM_STEP_INTO: case IDM_STEP_OVER: case IDM_STEP_OUT: { if (d->DbgContext) d->DbgContext->OnCommand(Cmd); break; } case IDM_STOP_DEBUG: { if (d->DbgContext && d->DbgContext->OnCommand(Cmd)) { DeleteObj(d->DbgContext); } break; } case IDM_BUILD: { Build(); break; } case IDM_STOP_BUILD: { IdeProject *p = RootProject(); if (p) p->StopBuild(); break; } case IDM_CLEAN: { SaveAll(); IdeProject *p = RootProject(); if (p) p->Clean(true, IsReleaseMode()); break; } case IDM_NEXT_MSG: { d->SeekMsg(1); break; } case IDM_PREV_MSG: { d->SeekMsg(-1); break; } case IDM_DEBUG_MODE: { GMenuItem *Debug = GetMenu()->FindItem(IDM_DEBUG_MODE); GMenuItem *Release = GetMenu()->FindItem(IDM_RELEASE_MODE); if (Debug && Release) { Debug->Checked(true); Release->Checked(false); } break; } case IDM_RELEASE_MODE: { GMenuItem *Debug = GetMenu()->FindItem(IDM_DEBUG_MODE); GMenuItem *Release = GetMenu()->FindItem(IDM_RELEASE_MODE); if (Debug && Release) { Debug->Checked(false); Release->Checked(true); } break; } // // Other // case IDM_LOOKUP_SYMBOLS: { IdeDoc *Cur = GetCurrentDoc(); if (Cur) { // LookupSymbols(Cur->Read()); } break; } case IDM_DEPENDS: { IdeProject *p = RootProject(); if (p) { GString Exe = p->GetExecutable(GetCurrentPlatform()); if (FileExists(Exe)) { Depends Dlg(this, Exe); } else { LgiMsg(this, "Couldn't find '%s'\n", AppName, MB_OK, Exe ? Exe.Get() : ""); } } break; } case IDM_SP_TO_TAB: { IdeDoc *Doc = FocusDoc(); if (Doc) Doc->ConvertWhiteSpace(true); break; } case IDM_TAB_TO_SP: { IdeDoc *Doc = FocusDoc(); if (Doc) Doc->ConvertWhiteSpace(false); break; } case IDM_ESCAPE: { IdeDoc *Doc = FocusDoc(); if (Doc) Doc->EscapeSelection(true); break; } case IDM_DESCAPE: { IdeDoc *Doc = FocusDoc(); if (Doc) Doc->EscapeSelection(false); break; } case IDM_EOL_LF: { IdeDoc *Doc = FocusDoc(); if (!Doc) break; Doc->SetCrLf(false); break; } case IDM_EOL_CRLF: { IdeDoc *Doc = FocusDoc(); if (!Doc) break; Doc->SetCrLf(true); break; } case IDM_LOAD_MEMDUMP: { NewMemDumpViewer(this); break; } case IDM_SYS_CHAR_SUPPORT: { new SysCharSupport(this); break; } default: { char *r = d->RecentFiles[Cmd - IDM_RECENT_FILE]; if (r) { IdeDoc *f = d->IsFileOpen(r); if (f) { f->Raise(); } else { OpenFile(r); } } char *p = d->RecentProjects[Cmd - IDM_RECENT_PROJECT]; if (p) { CloseAll(); OpenProject(p, NULL, false); if (d->Tree) { d->Tree->Focus(true); } } IdeDoc *Doc = d->Docs[Cmd - IDM_WINDOWS]; if (Doc) { Doc->Raise(); } IdePlatform PlatIdx = (IdePlatform) (Cmd - IDM_MAKEFILE_BASE); const char *Platform = PlatIdx >= 0 && PlatIdx < PlatformMax ? PlatformNames[Cmd - IDM_MAKEFILE_BASE] : NULL; if (Platform) { IdeProject *p = RootProject(); if (p) { p->CreateMakefile(PlatIdx, false); } } break; } } return 0; } GTree *AppWnd::GetTree() { return d->Tree; } IdeDoc *AppWnd::TopDoc() { return dynamic_cast(d->Mdi->GetTop()); } GTextView3 *AppWnd::FocusEdit() { return dynamic_cast(GetWindow()->GetFocus()); } IdeDoc *AppWnd::FocusDoc() { IdeDoc *Doc = TopDoc(); if (Doc) { if (Doc->HasFocus()) { return Doc; } else { GViewI *f = GetFocus(); LgiTrace("%s:%i - Edit doesn't have focus, f=%p %s doc.edit=%s\n", _FL, f, f ? f->GetClass() : 0, Doc->Name()); } } return 0; } void AppWnd::OnProjectDestroy(IdeProject *Proj) { d->Projects.Delete(Proj); } void AppWnd::OnProjectChange() { GArray Views; if (d->Mdi->GetChildren(Views)) { for (unsigned i=0; i(Views[i]); if (Doc) Doc->OnProjectChange(); } } } void AppWnd::OnDocDestroy(IdeDoc *Doc) { if (d) { d->Docs.Delete(Doc); d->UpdateMenus(); } } int AppWnd::GetBuildMode() { GMenuItem *Release = GetMenu()->FindItem(IDM_RELEASE_MODE); if (Release && Release->Checked()) { return BUILD_TYPE_RELEASE; } return BUILD_TYPE_DEBUG; } LList *AppWnd::GetFtpLog() { return d->Output->FtpLog; } GStream *AppWnd::GetBuildLog() { return d->Output->Txt[AppWnd::BuildTab]; } void AppWnd::FindSymbol(int ResultsSinkHnd, const char *Sym, bool AllPlatforms) { d->FindSym->Search(ResultsSinkHnd, Sym, AllPlatforms); } #include "GSubProcess.h" bool AppWnd::GetSystemIncludePaths(::GArray &Paths) { if (d->SystemIncludePaths.Length() == 0) { #if !defined(WINNATIVE) // echo | gcc -v -x c++ -E - GSubProcess sp1("echo"); GSubProcess sp2("gcc", "-v -x c++ -E -"); sp1.Connect(&sp2); sp1.Start(true, false); char Buf[256]; ssize_t r; GStringPipe p; while ((r = sp1.Read(Buf, sizeof(Buf))) > 0) { p.Write(Buf, r); } bool InIncludeList = false; while (p.Pop(Buf, sizeof(Buf))) { if (stristr(Buf, "#include")) { InIncludeList = true; } else if (stristr(Buf, "End of search")) { InIncludeList = false; } else if (InIncludeList) { GAutoString a(TrimStr(Buf)); d->SystemIncludePaths.New() = a; } } #else char p[MAX_PATH]; LGetSystemPath(LSP_USER_DOCUMENTS, p, sizeof(p)); LgiMakePath(p, sizeof(p), p, "Visual Studio 2008\\Settings\\CurrentSettings.xml"); if (FileExists(p)) { GFile f; if (f.Open(p, O_READ)) { GXmlTree t; GXmlTag r; if (t.Read(&r, &f)) { GXmlTag *Opts = r.GetChildTag("ToolsOptions"); if (Opts) { GXmlTag *Projects = NULL; char *Name; for (GXmlTag *c = Opts->Children.First(); c; c = Opts->Children.Next()) { if (c->IsTag("ToolsOptionsCategory") && (Name = c->GetAttr("Name")) && !stricmp(Name, "Projects")) { Projects = c; break; } } GXmlTag *VCDirectories = NULL; for (GXmlTag *c = Projects ? Projects->Children.First() : NULL; c; c = Projects->Children.Next()) { if (c->IsTag("ToolsOptionsSubCategory") && (Name = c->GetAttr("Name")) && !stricmp(Name, "VCDirectories")) { VCDirectories = c; break; } } for (GXmlTag *prop = VCDirectories ? VCDirectories->Children.First() : NULL; prop; prop = VCDirectories->Children.Next()) { if (prop->IsTag("PropertyValue") && (Name = prop->GetAttr("Name")) && !stricmp(Name, "IncludeDirectories")) { char *Bar = strchr(prop->GetContent(), '|'); GToken t(Bar ? Bar + 1 : prop->GetContent(), ";"); for (int i=0; iSystemIncludePaths.New().Reset(NewStr(s)); } } } } } } } #endif } for (int i=0; iSystemIncludePaths.Length(); i++) { Paths.Add(NewStr(d->SystemIncludePaths[i])); } return true; } /* #include "GSubProcess.h" void Test() { GDirectory d; for (int b = d.First("C:\\Users\\matthew\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\Cache"); b; b = d.Next()) { if (!d.IsDir()) { char p[MAX_PATH]; d.Path(p, sizeof(p)); GFile f; if (f.Open(p, O_READ)) { char Buf[256]; ssize_t Rd = f.Read(Buf, sizeof(Buf)); if (Rd > 3 && !strnicmp(Buf, "Ogg", 3)) { char out[MAX_PATH]; f.Close(); LgiMakePath(out, sizeof(out), "C:\\Users\\matthew\\Desktop\\new day", d.GetName()); strcat(out, ".ogg"); if (!FileDev->Copy(p, out)) { LgiTrace("%s:%i - Failed to copy '%s'\n", _FL, d.GetName()); } } else { LgiTrace("%s:%i - Not an ogg '%s'\n", _FL, d.GetName()); } } else { LgiTrace("%s:%i - Can't open '%s'\n", _FL, d.GetName()); } } } } */ int LgiMain(OsAppArguments &AppArgs) { printf("LgiIde v%s\n", APP_VER); GApp a(AppArgs, "LgiIde"); if (a.IsOk()) { /* GString mt = LGetAppForProtocol("mailto"); GString https = LGetAppForProtocol("https"); printf("%s\n%s\n", mt.Get(), https.Get()); GArray Out; if (GSocket::EnumInterfaces(Out)) { for (auto &i : Out) { printf("%s %s %s\n", i.Name.Get(), i.ToString().Get(), i.ToString(i.Netmask4).Get()); } } */ a.AppWnd = new AppWnd; a.Run(); } return 0; } diff --git a/Lvc/Src/VcCommit.cpp b/Lvc/Src/VcCommit.cpp --- a/Lvc/Src/VcCommit.cpp +++ b/Lvc/Src/VcCommit.cpp @@ -1,310 +1,310 @@ #include "Lvc.h" #include "GClipBoard.h" #include "../Resources/resdefs.h" #include "GPath.h" VcCommit::VcCommit(AppPriv *priv) { d = priv; Current = false; NodeIdx = -1; Parents.SetFixedLength(false); } char *VcCommit::GetRev() { return Rev; } char *VcCommit::GetAuthor() { return Author; } char *VcCommit::GetMsg() { return Msg; } void VcCommit::SetCurrent(bool b) { Current = b; } void VcCommit::OnPaintColumn(GItem::ItemPaintCtx &Ctx, int i, GItemColumn *c) { LListItem::OnPaintColumn(Ctx, i, c); if (i == 0) { double Px = 12; int Ht = Ctx.Y(); double Half = 5.5; #define MAP(col) ((col) * Px + Half) GMemDC Mem(Ctx.X(), Ctx.Y(), System32BitColourSpace); double r = Half - 2; bool Dbg = IsRev("938e8ccfdac365e91ca7ca732aab538d8769a37e"); if (Dbg) { Mem.Colour(GColour::Red); // Mem.Rectangle(); } double x = MAP(NodeIdx); if (NodeIdx >= 0) { double Cx = x; double Cy = Ht / 2; GPath p; p.Circle(Cx, Cy, r); //p.Circle(Cx, Cy, r - 1); GSolidBrush sb(GColour::Black); p.Fill(&Mem, sb); } Mem.Colour(GColour::Black); for (unsigned i=0; iOp(GDC_ALPHA); Ctx.pDC->Blt(Ctx.x1, Ctx.y1, &Mem); } } char *VcCommit::GetText(int Col) { switch (Col) { case 0: // Cache.Printf("%i%s", (int)Parents.Length(), Current ? " ***" : ""); return NULL; case 1: return Rev; case 2: return Author; case 3: Cache = Ts.Get(); return Cache; case 4: if (!Msg) return NULL; Cache = Msg.Split("\n", 1)[0]; return Cache; } return NULL; } bool VcCommit::GitParse(GString s, bool RevList) { GString::Array lines = s.Split("\n"); if (lines.Length() < 3) return false; if (RevList) { auto a = lines[0].SplitDelimit(); if (a.Length() != 2) return false; - Ts.Set(a[0].Int()); + Ts.Set((uint64) a[0].Int()); Rev = a[1]; for (int i=0; i= 0) Author = l.Split(":", 1)[1].Strip(); else if (l.Find("Date:") >= 0) Ts.Parse(l.Split(":", 1)[1].Strip()); else if (l.Strip().Length() > 0) { if (Msg) Msg += "\n"; Msg += l.Strip(); } } } return Author && Rev; } bool VcCommit::CvsParse(LDateTime &Dt, GString Auth, GString Message) { Ts = Dt; Ts.ToLocal(); uint64 i; if (Ts.Get(i)) Rev.Printf(LPrintfInt64, i); Author = Auth; Msg = Message; return true; } bool VcCommit::HgParse(GString s) { GString::Array Lines = s.SplitDelimit("\n"); if (Lines.Length() < 1) return false; for (GString *Ln = NULL; Lines.Iterate(Ln); ) { GString::Array f = Ln->Split(":", 1); if (f.Length() == 2) { if (f[0].Equals("changeset")) Rev = f[1].Strip(); else if (f[0].Equals("user")) Author = f[1].Strip(); else if (f[0].Equals("date")) Ts.Parse(f[1].Strip()); else if (f[0].Equals("summary")) Msg = f[1].Strip(); } } return Rev.Get() != NULL; } bool VcCommit::SvnParse(GString s) { GString::Array lines = s.Split("\n"); if (lines.Length() < 1) return false; for (unsigned ln = 0; ln < lines.Length(); ln++) { GString &l = lines[ln]; if (ln == 0) { GString::Array a = l.Split("|"); if (a.Length() > 3) { Rev = a[0].Strip(" \tr"); Author = a[1].Strip(); Ts.Parse(a[2]); } } else { if (Msg) Msg += "\n"; Msg += l.Strip(); } } Msg = Msg.Strip(); return Author && Rev && Ts.IsValid(); } VcFolder *VcCommit::GetFolder() { for (GTreeItem *i = d->Tree->Selection(); i; i = i->GetParent()) { auto f = dynamic_cast(i); if (f) return f; } return NULL; } void VcCommit::Select(bool b) { LListItem::Select(b); if (Rev && b) { VcFolder *f = GetFolder(); if (f) f->ListCommit(this); if (d->Msg) { d->Msg->Name(Msg); GWindow *w = d->Msg->GetWindow(); if (w) { w->SetCtrlEnabled(IDC_COMMIT, false); w->SetCtrlEnabled(IDC_COMMIT_AND_PUSH, false); } } else LgiAssert(0); } } void VcCommit::OnMouseClick(GMouse &m) { LListItem::OnMouseClick(m); if (m.IsContextMenu()) { GSubMenu s; s.AppendItem("Update", IDM_UPDATE, !Current); s.AppendItem("Copy Revision", IDM_COPY_REV); int Cmd = s.Float(GetList(), m); switch (Cmd) { case IDM_UPDATE: { VcFolder *f = GetFolder(); if (!f) { LgiAssert(!"No folder?"); break; } f->OnUpdate(Rev); break; } case IDM_COPY_REV: { GClipBoard c(GetList()); c.Text(Rev); break; } } } } diff --git a/Lvc/Src/VcFolder.cpp b/Lvc/Src/VcFolder.cpp --- a/Lvc/Src/VcFolder.cpp +++ b/Lvc/Src/VcFolder.cpp @@ -1,2611 +1,2614 @@ #include "Lvc.h" #include "../Resources/resdefs.h" #ifndef CALL_MEMBER_FN #define CALL_MEMBER_FN(object,ptrToMember) ((object).*(ptrToMember)) #endif int Ver2Int(GString v) { auto p = v.Split("."); int i = 0; for (auto s : p) { auto Int = s.Int(); if (Int < 256) { i <<= 8; i |= (uint8_t)Int; } else { LgiAssert(0); return 0; } } return i; } int ToolVersion[VcMax] = {0}; ReaderThread::ReaderThread(GSubProcess *p, GStream *out) : LThread("ReaderThread") { Process = p; Out = out; Run(); } ReaderThread::~ReaderThread() { Out = NULL; while (!IsExited()) LgiSleep(1); } int ReaderThread::Main() { bool b = Process->Start(true, false); if (!b) { GString s("Process->Start failed.\n"); Out->Write(s.Get(), s.Length(), ErrSubProcessFailed); return ErrSubProcessFailed; } while (Process->IsRunning()) { if (Out) { char Buf[1024]; ssize_t r = Process->Read(Buf, sizeof(Buf)); if (r > 0) Out->Write(Buf, r); } else { Process->Kill(); return -1; break; } } if (Out) { char Buf[1024]; ssize_t r = Process->Read(Buf, sizeof(Buf)); if (r > 0) Out->Write(Buf, r); } return (int) Process->GetExitValue(); } ///////////////////////////////////////////////////////////////////////////////////////////// void VcFolder::Init(AppPriv *priv) { d = priv; IsCommit = false; IsLogging = false; IsGetCur = false; IsUpdate = false; IsFilesCmd = false; IsWorkingFld = false; CommitListDirty = false; IsUpdatingCounts = false; Unpushed = Unpulled = -1; Type = VcNone; CmdErrors = 0; Expanded(false); Insert(Tmp = new GTreeItem); Tmp->SetText("Loading..."); LgiAssert(d != NULL); } VcFolder::VcFolder(AppPriv *priv, const char *p) { Init(priv); Path = p; } VcFolder::VcFolder(AppPriv *priv, GXmlTag *t) { Init(priv); Serialize(t, false); } VersionCtrl VcFolder::GetType() { if (Type == VcNone) Type = DetectVcs(Path); return Type; } char *VcFolder::GetText(int Col) { switch (Col) { case 0: { if (Cmds.Length()) { Cache = Path; Cache += " (...)"; return Cache; } return Path; } case 1: { CountCache.Printf("%i/%i", Unpulled, Unpushed); CountCache = CountCache.Replace("-1", "--"); return CountCache; } } return NULL; } bool VcFolder::Serialize(GXmlTag *t, bool Write) { if (Write) t->SetContent(Path); else Path = t->GetContent(); return true; } GXmlTag *VcFolder::Save() { GXmlTag *t = new GXmlTag(OPT_Folder); if (t) Serialize(t, true); return t; } const char *VcFolder::GetVcName() { const char *Def = NULL; switch (GetType()) { case VcGit: Def = "git"; break; case VcSvn: Def = "svn"; break; case VcHg: Def = "hg"; break; case VcCvs: Def = "cvs"; break; default: break; } if (!VcCmd) { GString Opt; Opt.Printf("%s-path", Def); GVariant v; if (d->Opts.GetValue(Opt, v)) VcCmd = v.Str(); } if (!VcCmd) VcCmd = Def; return VcCmd; } bool VcFolder::StartCmd(const char *Args, ParseFn Parser, ParseParams *Params, LoggingType Logging) { const char *Exe = GetVcName(); if (!Exe) return false; if (CmdErrors > 2) return false; if (d->Log && Logging != LogSilo) d->Log->Print("%s %s\n", Exe, Args); GAutoPtr Process(new GSubProcess(Exe, Args)); if (!Process) return false; Process->SetInitFolder(Params && Params->AltInitPath ? Params->AltInitPath : Path); GString::Array Ctx; Ctx.SetFixedLength(false); Ctx.Add(Path); Ctx.Add(Exe); Ctx.Add(Args); GAutoPtr c(new Cmd(Ctx, Logging, d->Log)); if (!c) return false; c->PostOp = Parser; c->Params.Reset(Params); c->Rd.Reset(new ReaderThread(Process.Release(), c)); Cmds.Add(c.Release()); Update(); LgiTrace("Cmd: %s %s\n", Exe, Args); return true; } int LogDateCmp(LListItem *a, LListItem *b, NativeInt Data) { VcCommit *A = dynamic_cast(a); VcCommit *B = dynamic_cast(b); if ((A != NULL) ^ (B != NULL)) { // This handles keeping the "working folder" list item at the top return (A != NULL) - (B != NULL); } // Sort the by date from most recent to least return -A->GetTs().Compare(&B->GetTs()); } bool VcFolder::ParseBranches(int Result, GString s, ParseParams *Params) { switch (GetType()) { case VcGit: { GString::Array a = s.SplitDelimit("\r\n"); for (GString *l = NULL; a.Iterate(l); ) { GString n = l->Strip(); if (n(0) == '*') { GString::Array c = n.SplitDelimit(" \t", 1); if (c.Length() > 1) Branches.New() = CurrentBranch = c[1]; else Branches.New() = n; } else Branches.New() = n; } break; } case VcHg: { Branches = s.SplitDelimit("\r\n"); break; } default: { break; } } OnBranchesChange(); return false; } void VcFolder::OnBranchesChange() { GWindow *w = d->Tree->GetWindow(); if (!w) return; DropDownBtn *dd; if (w->GetViewById(IDC_BRANCH_DROPDOWN, dd)) { dd->SetList(IDC_BRANCH, Branches); } if (Branches.Length() > 0) { GViewI *b; if (w->GetViewById(IDC_BRANCH, b)) { if (!ValidStr(b->Name())) b->Name(Branches.First()); } } } void VcFolder::Select(bool b) { if (!b) { GWindow *w = d->Tree->GetWindow(); w->SetCtrlName(IDC_BRANCH, NULL); } GTreeItem::Select(b); if (b) { if (!DirExists(Path)) return; if ((Log.Length() == 0 || CommitListDirty) && !IsLogging) { switch (GetType()) { case VcGit: { IsLogging = StartCmd("rev-list --all --header --timestamp", &VcFolder::ParseRevList); break; } case VcSvn: { if (CommitListDirty) { IsLogging = StartCmd("up", &VcFolder::ParsePull, new ParseParams("log")); break; } // else fall through } default: { IsLogging = StartCmd("log", &VcFolder::ParseLog); } } CommitListDirty = false; } if (Branches.Length() == 0) { switch (GetType()) { case VcGit: StartCmd("branch -a", &VcFolder::ParseBranches); break; case VcSvn: Branches.New() = "trunk"; break; case VcHg: StartCmd("branch", &VcFolder::ParseBranches); break; case VcCvs: break; default: LgiAssert(!"Impl me."); break; } } OnBranchesChange(); /* if (!IsUpdatingCounts && Unpushed < 0) { switch (GetType()) { case VcGit: IsUpdatingCounts = StartCmd("cherry -v", &VcFolder::ParseCounts); break; case VcSvn: IsUpdatingCounts = StartCmd("status -u", &VcFolder::ParseCounts); break; default: LgiAssert(!"Impl me."); break; } } */ char *Ctrl = d->Lst->GetWindow()->GetCtrlName(IDC_FILTER); GString Filter = ValidStr(Ctrl) ? Ctrl : NULL; if (d->CurFolder != this) { d->CurFolder = this; d->Lst->RemoveAll(); } if (!Uncommit) Uncommit.Reset(new UncommitedItem(d)); d->Lst->Insert(Uncommit, 0); int64 CurRev = Atoi(CurrentCommit.Get()); List Ls; for (unsigned i=0; iGetRev()) { switch (GetType()) { case VcSvn: { int64 LogRev = Atoi(Log[i]->GetRev()); if (CurRev >= 0 && CurRev >= LogRev) { CurRev = -1; Log[i]->SetCurrent(true); } else { Log[i]->SetCurrent(false); } break; } default: Log[i]->SetCurrent(!_stricmp(CurrentCommit, Log[i]->GetRev())); break; } } bool Add = !Filter; if (Filter) { const char *s = Log[i]->GetRev(); if (s && strstr(s, Filter) != NULL) Add = true; s = Log[i]->GetAuthor(); if (s && stristr(s, Filter) != NULL) Add = true; s = Log[i]->GetMsg(); if (s && stristr(s, Filter) != NULL) Add = true; } LList *CurOwner = Log[i]->GetList(); if (Add ^ (CurOwner != NULL)) { if (Add) Ls.Insert(Log[i]); else d->Lst->Remove(Log[i]); } } d->Lst->Insert(Ls); d->Lst->Sort(LogDateCmp); if (GetType() == VcGit) { d->Lst->ColumnAt(0)->Width(40); d->Lst->ColumnAt(1)->Width(270); d->Lst->ColumnAt(2)->Width(240); d->Lst->ColumnAt(3)->Width(130); d->Lst->ColumnAt(4)->Width(400); } else d->Lst->ResizeColumnsToContent(); d->Lst->UpdateAllItems(); if (!CurrentCommit && !IsGetCur) { switch (GetType()) { case VcGit: IsGetCur = StartCmd("rev-parse HEAD", &VcFolder::ParseInfo); break; case VcSvn: IsGetCur = StartCmd("info", &VcFolder::ParseInfo); break; case VcHg: IsGetCur = StartCmd("id -i", &VcFolder::ParseInfo); break; case VcCvs: break; default: LgiAssert(!"Impl me."); break; } } } } int CommitRevCmp(VcCommit **a, VcCommit **b) { int64 arev = Atoi((*a)->GetRev()); int64 brev = Atoi((*b)->GetRev()); int64 diff = (int64)brev - arev; if (diff < 0) return -1; return (diff > 0) ? 1 : 0; } int CommitDateCmp(VcCommit **a, VcCommit **b) { uint64 ats, bts; (*a)->GetTs().Get(ats); (*b)->GetTs().Get(bts); int64 diff = (int64)bts - ats; if (diff < 0) return -1; return (diff > 0) ? 1 : 0; } bool VcFolder::ParseRevList(int Result, GString s, ParseParams *Params) { LHashTbl, VcCommit*> Map; for (VcCommit **pc = NULL; Log.Iterate(pc); ) Map.Add((*pc)->GetRev(), *pc); int Skipped = 0, Errors = 0; switch (GetType()) { case VcGit: { GString::Array Commits; Commits.SetFixedLength(false); // Split on the NULL chars... char *c = s.Get(); char *e = c + s.Length(); while (c < e) { char *nul = c; while (nul < e && *nul) nul++; if (nul <= c) break; Commits.New().Set(c, nul-c); if (nul >= e) break; c = nul + 1; } for (auto Commit: Commits) { GAutoPtr Rev(new VcCommit(d)); if (Rev->GitParse(Commit, true)) { if (!Map.Find(Rev->GetRev())) Log.Add(Rev.Release()); else Skipped++; } else { LgiTrace("%s:%i - Failed:\n%s\n\n", _FL, Commit.Get()); Errors++; } } Log.Sort(CommitDateCmp); LinkParents(); break; } + default: + LgiAssert(!"Impl me."); + break; } return true; } bool VcFolder::ParseLog(int Result, GString s, ParseParams *Params) { LHashTbl, VcCommit*> Map; for (VcCommit **pc = NULL; Log.Iterate(pc); ) Map.Add((*pc)->GetRev(), *pc); int Skipped = 0, Errors = 0; switch (GetType()) { case VcGit: { GString::Array c; c.SetFixedLength(false); char *prev = s.Get(); for (char *i = s.Get(); *i; ) { if (!strnicmp(i, "commit ", 7)) { if (i > prev) { c.New().Set(prev, i - prev); // LgiTrace("commit=%i\n", (int)(i - prev)); } prev = i; } while (*i) { if (*i++ == '\n') break; } } for (unsigned i=0; i Rev(new VcCommit(d)); if (Rev->GitParse(c[i], false)) { if (!Map.Find(Rev->GetRev())) Log.Add(Rev.Release()); else Skipped++; } else { LgiTrace("%s:%i - Failed:\n%s\n\n", _FL, c[i].Get()); Errors++; } } Log.Sort(CommitDateCmp); break; } case VcSvn: { GString::Array c = s.Split("------------------------------------------------------------------------"); for (unsigned i=0; i Rev(new VcCommit(d)); GString Raw = c[i].Strip(); if (Rev->SvnParse(Raw)) { if (!Map.Find(Rev->GetRev())) Log.Add(Rev.Release()); else Skipped++; } else if (Raw) { LgiTrace("%s:%i - Failed:\n%s\n\n", _FL, Raw.Get()); Errors++; } } Log.Sort(CommitRevCmp); break; } case VcHg: { GString::Array c = s.Split("\n\n"); for (GString *Commit = NULL; c.Iterate(Commit); ) { GAutoPtr Rev(new VcCommit(d)); if (Rev->HgParse(*Commit)) { if (!Map.Find(Rev->GetRev())) Log.Add(Rev.Release()); } } break; } case VcCvs: { LHashTbl, VcCommit*> Map; GString::Array c = s.Split("============================================================================="); for (GString *Commit = NULL; c.Iterate(Commit);) { if (Commit->Strip().Length()) { GString Head, File; GString::Array Versions = Commit->Split("----------------------------"); GString::Array Lines = Versions[0].SplitDelimit("\r\n"); for (GString *Line = NULL; Lines.Iterate(Line);) { GString::Array p = Line->Split(":", 1); if (p.Length() == 2) { // LgiTrace("Line: %s\n", Line->Get()); GString Var = p[0].Strip().Lower(); GString Val = p[1].Strip(); if (Var.Equals("branch")) { if (Val.Length()) Branches.Add(Val); } else if (Var.Equals("head")) { Head = Val; } else if (Var.Equals("rcs file")) { GString::Array f = Val.SplitDelimit(","); File = f.First(); } } } // LgiTrace("%s\n", Commit->Get()); for (unsigned i=1; i= 3) { GString Ver = Lines[0].Split(" ").Last(); GString::Array a = Lines[1].SplitDelimit(";"); GString Date = a[0].Split(":", 1).Last().Strip(); GString Author = a[1].Split(":", 1).Last().Strip(); GString Id = a[2].Split(":", 1).Last().Strip(); GString Msg = Lines[2]; LDateTime Dt; if (Dt.Parse(Date)) { uint64 Ts; if (Dt.Get(Ts)) { VcCommit *Cc = Map.Find(Ts); if (!Cc) { Map.Add(Ts, Cc = new VcCommit(d)); Log.Add(Cc); Cc->CvsParse(Dt, Author, Msg); } Cc->Files.Add(File.Get()); } else LgiAssert(!"NO ts for date."); } else LgiAssert(!"Date parsing failed."); } } } } break; } default: LgiAssert(!"Impl me."); break; } // LgiTrace("%s:%i - ParseLog: Skip=%i, Error=%i\n", _FL, Skipped, Errors); IsLogging = false; return true; } void VcFolder::LinkParents() { GString Dbg = "21ccae56b8bc707be6d7af7c055a86cdb697b1f1"; VcCommit *p = NULL; for (auto i = Log.Length()-1; i > 0; i--) { VcCommit *c = Log[i]; if (p) { c->Nodes.Empty(); c->NodeIdx = -1; // Match the parent nodes... auto Par = c->GetParents(); if (c->IsRev("1802edf955ff9037e66b1481121be3a6e21bf0a7")) { int asd=0; } LgiAssert(Par->Length() > 0); for (unsigned n = 0; n < Par->Length(); n++) { bool Found = false; GString &pRev = (*Par)[n]; for (unsigned k = 0; k < p->Nodes.Length(); k++) { auto &PrevNode = p->Nodes[k]; if (pRev.Equals(PrevNode.Rev, false)) { if (c->NodeIdx < 0) c->NodeIdx = k; auto &CurNode = c->Nodes[c->NodeIdx]; CurNode.Prev.Add(k); CurNode.Rev = c->GetRev(); PrevNode.Next.Add(c->NodeIdx); Found = true; break; } } if (!Found) { // Go and find it... size_t end; for (end = i + 1; end < Log.Length(); end++) { if (!_stricmp(Log[end]->GetRev(), pRev)) break; } if (end < Log.Length()) { // Found.. VcCommit *Cur = c; VcCommit *Next = Log[i+1]; if (Cur->NodeIdx < 0) { Cur->NodeIdx = (int)Next->Nodes.Length(); Cur->Nodes[Cur->NodeIdx].Rev = Cur->GetRev(); } int CurIdx = Cur->NodeIdx; for (auto n = i; n < end; n++) { auto *Next = Log[n + 1]; int NextIdx = (int)Next->Nodes.Length(); for (unsigned k=0; kNodes.Length(); k++) { if (Next->Nodes[k].Rev.Equals(pRev.Get(), false)) { NextIdx = k; break; } } if (Cur->IsRev(Dbg) && CurIdx > 0) { int asd=0; } auto &CurNode = Cur->Nodes[CurIdx]; CurNode.Next.Add(NextIdx); if (Next->IsRev(Dbg) && NextIdx > 0) { int asd=0; } auto &NextNode = Next->Nodes[NextIdx]; NextNode.Prev.Add(CurIdx); NextNode.Rev = pRev; CurIdx = NextIdx; Cur = Next; } } else { int asd=0; } } } int CurIdx = 0; for (unsigned PrevIdx = 0; PrevIdx < p->Nodes.Length(); PrevIdx++) { auto &PrevNode = p->Nodes[PrevIdx]; if (PrevNode.Next.Length() == 0) { while (1) { auto &CurNode = c->Nodes[CurIdx]; if (CurNode.Rev) { CurIdx++; } else { CurNode.Rev = PrevNode.Rev; CurNode.Prev.Add(PrevIdx); PrevNode.Next.Add(CurIdx); break; } } } } } else { // First node c->NodeIdx = 0; auto &n = c->Nodes[0]; n.Rev = c->GetRev(); } p = c; } } VcFile *VcFolder::FindFile(const char *Path) { if (!Path) return NULL; GArray Files; if (d->Files->GetAll(Files)) { GString p = Path; p = p.Replace(DIR_STR, "/"); for (auto f : Files) { auto Fn = f->GetFileName(); if (p.Equals(Fn)) { return f; } } } return NULL; } void VcFolder::OnCmdError(GString Output, const char *Msg) { if (!CmdErrors) { d->Log->Write(Output, Output.Length()); GString::Array a = GetProgramsInPath(GetVcName()); d->Log->Print("'%s' executables in the path:\n", GetVcName()); for (auto Bin : a) d->Log->Print(" %s\n", Bin.Get()); } CmdErrors++; d->Tabs->Value(1); Color(GColour::Red); } void VcFolder::ClearError() { Color(GCss::ColorInherit); } bool VcFolder::ParseInfo(int Result, GString s, ParseParams *Params) { switch (GetType()) { case VcGit: case VcHg: { CurrentCommit = s.Strip(); break; } case VcSvn: { if (s.Find("client is too old") >= 0) { OnCmdError(s, "Client too old"); break; } GString::Array c = s.Split("\n"); for (unsigned i=0; iIsWorking = true; ParseStatus(Result, s, Params); } else ParseDiffs(s, NULL, true); IsWorkingFld = false; d->Files->ResizeColumnsToContent(); if (GetType() == VcSvn) { Unpushed = d->Files->Length() > 0 ? 1 : 0; Update(); } return false; } bool VcFolder::ParseDiff(int Result, GString s, ParseParams *Params) { ParseDiffs(s, NULL, true); return false; } void VcFolder::Diff(VcFile *file) { switch (GetType()) { case VcSvn: case VcGit: case VcHg: { GString a; a.Printf("diff \"%s\"", file->GetFileName()); StartCmd(a, &VcFolder::ParseDiff); break; } default: LgiAssert(!"Impl me."); break; } } bool VcFolder::ParseDiffs(GString s, GString Rev, bool IsWorking) { LgiAssert(IsWorking || Rev.Get() != NULL); switch (GetType()) { case VcGit: { GString::Array a = s.Split("\n"); GString Diff; VcFile *f = NULL; for (unsigned i=0; iSetDiff(Diff); Diff.Empty(); auto Bits = a[i].SplitDelimit(); GString Fn, State = "M"; if (Bits[1].Equals("--cc")) { Fn = Bits.Last(); State = "C"; } else Fn = Bits.Last()(2,-1); LgiTrace("%s\n", a[i].Get()); f = FindFile(Fn); if (!f) f = new VcFile(d, this, Rev, IsWorking); f->SetText(State, COL_STATE); f->SetText(Fn.Replace("\\","/"), COL_FILENAME); f->GetStatus(); d->Files->Insert(f); } else if (!_strnicmp(Ln, "new file", 8)) { if (f) f->SetText("A", COL_STATE); } else if (!_strnicmp(Ln, "index", 5) || !_strnicmp(Ln, "commit", 6) || !_strnicmp(Ln, "Author:", 7) || !_strnicmp(Ln, "Date:", 5) || !_strnicmp(Ln, "+++", 3) || !_strnicmp(Ln, "---", 3)) { // Ignore } else { if (Diff) Diff += "\n"; Diff += a[i]; } } if (f && Diff) { f->SetDiff(Diff); Diff.Empty(); } break; } case VcHg: { GString::Array a = s.Split("\n"); GString Diff; VcFile *f = NULL; for (unsigned i=0; iSetDiff(Diff); Diff.Empty(); GString Fn = a[i].Split(" ").Last(); f = new VcFile(d, this, Rev, IsWorking); f->SetText(Fn.Replace("\\","/"), COL_FILENAME); d->Files->Insert(f); } else if (!_strnicmp(Ln, "index", 5) || !_strnicmp(Ln, "commit", 6) || !_strnicmp(Ln, "Author:", 7) || !_strnicmp(Ln, "Date:", 5) || !_strnicmp(Ln, "+++", 3) || !_strnicmp(Ln, "---", 3)) { // Ignore } else { if (Diff) Diff += "\n"; Diff += a[i]; } } if (f && Diff) { f->SetDiff(Diff); Diff.Empty(); } break; } case VcSvn: { GString::Array a = s.Replace("\r").Split("\n"); GString Diff; VcFile *f = NULL; bool InPreamble = false; bool InDiff = false; for (unsigned i=0; iSetDiff(Diff); f->Select(false); } Diff.Empty(); InDiff = false; InPreamble = false; GString Fn = a[i].Split(":", 1).Last().Strip(); f = FindFile(Fn); if (!f) f = new VcFile(d, this, Rev, IsWorking); f->SetText(Fn.Replace("\\","/"), COL_FILENAME); f->SetText("M", COL_STATE); f->GetStatus(); d->Files->Insert(f); } else if (!_strnicmp(Ln, "------", 6)) { InPreamble = !InPreamble; } else if (!_strnicmp(Ln, "======", 6)) { InPreamble = false; InDiff = true; } else if (InDiff) { if (!strncmp(Ln, "--- ", 4) || !strncmp(Ln, "+++ ", 4)) { } else { if (Diff) Diff += "\n"; Diff += a[i]; } } } if (f && Diff) { f->SetDiff(Diff); Diff.Empty(); } break; } case VcCvs: { break; } default: { LgiAssert(!"Impl me."); break; } } return true; } bool VcFolder::ParseFiles(int Result, GString s, ParseParams *Params) { d->ClearFiles(); ParseDiffs(s, Params->Str, false); IsFilesCmd = false; d->Files->ResizeColumnsToContent(); return false; } void VcFolder::OnPulse() { bool Reselect = false, CmdsChanged = false; static bool Processing = false; if (!Processing) { Processing = true; // Lock out processing, if it puts up a dialog or something... // bad things happen if we try and re-process something. for (unsigned i=0; iRd->IsExited()) { GString s = c->GetBuf(); int Result = c->Rd->ExitCode(); if (Result == ErrSubProcessFailed) { if (!CmdErrors) d->Log->Print("Error: Can't run '%s'\n", GetVcName()); CmdErrors++; } if (c->PostOp) Reselect |= CALL_MEMBER_FN(*this, c->PostOp)(Result, s, c->Params); Cmds.DeleteAt(i--, true); delete c; CmdsChanged = true; } } Processing = false; } if (Reselect) { if (GTreeItem::Select()) Select(true); } if (CmdsChanged) Update(); } void VcFolder::OnRemove() { GXmlTag *t = d->Opts.LockTag(NULL, _FL); if (t) { for (GXmlTag *c = t->Children.First(); c; c = t->Children.Next()) { if (c->IsTag(OPT_Folder) && c->GetContent() && !_stricmp(c->GetContent(), Path)) { c->RemoveTag(); delete c; break; } } d->Opts.Unlock(); } } void VcFolder::OnMouseClick(GMouse &m) { if (m.IsContextMenu()) { GSubMenu s; s.AppendItem("Browse To", IDM_BROWSE_FOLDER); s.AppendItem( #ifdef WINDOWS "Command Prompt At", #else "Terminal At", #endif IDM_TERMINAL); s.AppendItem("Clean", IDM_CLEAN); s.AppendSeparator(); s.AppendItem("Remove", IDM_REMOVE); int Cmd = s.Float(GetTree(), m); switch (Cmd) { case IDM_BROWSE_FOLDER: { LgiBrowseToFile(Path); break; } case IDM_TERMINAL: { #if defined(MAC) LgiExecute("/Applications/Utilities/Terminal.app/Contents/MacOS/Terminal", Path); #elif defined(WINDOWS) TCHAR w[MAX_PATH]; auto r = GetWindowsDirectory(w, CountOf(w)); if (r > 0) { GFile::Path p = GString(w); p += "system32\\cmd.exe"; FileDev->SetCurrentFolder(Path); LgiExecute(p); } #elif defined(LINUX) // #error "Impl me." #endif break; } case IDM_CLEAN: { Clean(); break; } case IDM_REMOVE: { OnRemove(); delete this; break; } default: break; } } } void VcFolder::OnUpdate(const char *Rev) { if (!Rev) return; if (!IsUpdate) { GString Args; NewRev = Rev; switch (GetType()) { case VcGit: Args.Printf("checkout %s", Rev); IsUpdate = StartCmd(Args, &VcFolder::ParseUpdate, NULL, LogNormal); break; case VcSvn: Args.Printf("up -r %s", Rev); IsUpdate = StartCmd(Args, &VcFolder::ParseUpdate, NULL, LogNormal); break; default: { LgiAssert(!"Impl me."); break; } } } } /////////////////////////////////////////////////////////////////////////////////////// int FolderCompare(GTreeItem *a, GTreeItem *b, NativeInt UserData) { VcLeaf *A = dynamic_cast(a); VcLeaf *B = dynamic_cast(b); if (!A || !B) return 0; return A->Compare(B); } void VcFolder::ReadDir(GTreeItem *Parent, const char *Path) { // Read child items GDirectory Dir; for (int b = Dir.First(Path); b; b = Dir.Next()) { if (Dir.IsDir()) { if (Dir.GetName()[0] != '.') { new VcLeaf(this, Parent, Path, Dir.GetName(), true); } } else if (!Dir.IsHidden()) { char *Ext = LgiGetExtension(Dir.GetName()); if (!Ext) continue; if (!stricmp(Ext, "c") || !stricmp(Ext, "cpp") || !stricmp(Ext, "h")) { new VcLeaf(this, Parent, Path, Dir.GetName(), false); } } } Parent->SortChildren(FolderCompare); } void VcFolder::OnExpand(bool b) { if (Tmp && b) { Tmp->Remove(); DeleteObj(Tmp); ReadDir(this, Path); } } void VcFolder::OnPaint(ItemPaintCtx &Ctx) { auto c = Color(); if (c.IsValid()) Ctx.Fore = c; c = BackgroundColor(); if (c.IsValid() && !GTreeItem::Select()) Ctx.Back = c; GTreeItem::OnPaint(Ctx); } void VcFolder::ListCommit(VcCommit *c) { if (!IsFilesCmd) { GString Args; switch (GetType()) { case VcGit: // Args.Printf("show --oneline --name-only %s", Rev); Args.Printf("show %s", c->GetRev()); IsFilesCmd = StartCmd(Args, &VcFolder::ParseFiles, new ParseParams(c->GetRev())); break; case VcSvn: Args.Printf("log --verbose --diff -r %s", c->GetRev()); IsFilesCmd = StartCmd(Args, &VcFolder::ParseFiles, new ParseParams(c->GetRev())); break; case VcCvs: { d->ClearFiles(); for (unsigned i=0; iFiles.Length(); i++) { VcFile *f = new VcFile(d, this, c->GetRev(), false); if (f) { f->SetText(c->Files[i], COL_FILENAME); d->Files->Insert(f); } } d->Files->ResizeColumnsToContent(); break; } default: LgiAssert(!"Impl me."); break; } if (IsFilesCmd) d->ClearFiles(); } } GString ConvertUPlus(GString s) { GArray c; GUtf8Ptr p(s); int32 ch; while ((ch = p)) { if (ch == '{') { auto n = p.GetPtr(); if (n[1] == 'U' && n[2] == '+') { // Convert unicode code point p += 3; ch = (int32)htoi(p.GetPtr()); c.Add(ch); while ((ch = p) != '}') p++; } else c.Add(ch); } else c.Add(ch); p++; } c.Add(0); #ifdef LINUX return GString((char16*)c.AddressOf()); #else return GString(c.AddressOf()); #endif } bool VcFolder::ParseStatus(int Result, GString s, ParseParams *Params) { bool ShowUntracked = d->Files->GetWindow()->GetCtrlValue(IDC_UNTRACKED) != 0; bool IsWorking = Params ? Params->IsWorking : false; List Ins; switch (GetType()) { case VcCvs: { GString::Array a = s.Split("==================================================================="); for (auto i : a) { GString::Array Lines = i.SplitDelimit("\r\n"); GString f = Lines[0].Strip(); if (f.Find("File:") == 0) { GString::Array Parts = f.SplitDelimit("\t"); GString File = Parts[0].Split(": ").Last(); GString Status = Parts[1].Split(": ").Last(); GString WorkingRev; for (auto l : Lines) { GString::Array p = l.Strip().Split(":", 1); if (p.Length() > 1 && p[0].Strip().Equals("Working revision")) { WorkingRev = p[1].Strip(); } } VcFile *f = new VcFile(d, this, WorkingRev, IsWorking); f->SetText(Status, COL_STATE); f->SetText(File, COL_FILENAME); Ins.Insert(f); } else if (f(0) == '?' && ShowUntracked) { GString File = f(2, -1); VcFile *f = new VcFile(d, this, NULL, IsWorking); f->SetText("?", COL_STATE); f->SetText(File, COL_FILENAME); Ins.Insert(f); } } break; } case VcGit: { GString::Array Lines = s.SplitDelimit("\r\n"); int Fmt = ToolVersion[VcGit] >= Ver2Int("2.8.0") ? 2 : 1; for (auto Ln : Lines) { char Type = Ln(0); if (Ln.Lower().Find("error:") >= 0) { } else if (Ln.Find("usage: git") >= 0) { // It's probably complaining about the --porcelain=2 parameter OnCmdError(s, "Args error"); } else if (Type != '?') { VcFile *f = NULL; if (Fmt == 2) { GString::Array p = Ln.SplitDelimit(" ", 8); if (p.Length() < 7) d->Log->Print("%s:%i - Error: not enough tokens: '%s'\n", _FL, Ln.Get()); else { f = new VcFile(d, this, p[6], IsWorking); f->SetText(p[1].Strip("."), COL_STATE); f->SetText(p.Last(), COL_FILENAME); } } else if (Fmt == 1) { GString::Array p = Ln.SplitDelimit(" "); f = new VcFile(d, this, NULL, IsWorking); f->SetText(p[0], COL_STATE); f->SetText(p.Last(), COL_FILENAME); } if (f) Ins.Insert(f); } else if (ShowUntracked) { VcFile *f = new VcFile(d, this, NULL, IsWorking); f->SetText("?", COL_STATE); f->SetText(Ln(2,-1), COL_FILENAME); Ins.Insert(f); } } break; } case VcHg: case VcSvn: { GString::Array Lines = s.SplitDelimit("\r\n"); for (auto Ln : Lines) { char Type = Ln(0); if (Ln.Lower().Find("error:") >= 0) { } else if (Ln.Find("client is too old") >= 0) { OnCmdError(s, "Client too old."); return false; } else if (Strchr(" \t", Type) || Ln.Find("Summary of conflicts") >= 0) { // Ignore } else if (Type != '?') { GString::Array p = Ln.SplitDelimit(" ", 1); if (p.Length() == 2) { GString File; if (GetType() == VcSvn) File = ConvertUPlus(p.Last()); else File = p.Last(); VcFile *f = new VcFile(d, this, NULL, IsWorking); f->SetText(p[0], COL_STATE); f->SetText(File.Replace("\\","/"), COL_FILENAME); f->GetStatus(); Ins.Insert(f); } else LgiAssert(!"What happen?"); } else if (ShowUntracked) { VcFile *f = new VcFile(d, this, NULL, IsWorking); f->SetText("?", COL_STATE); f->SetText(Ln(2,-1), COL_FILENAME); Ins.Insert(f); } } break; } default: { LgiAssert(!"Impl me."); break; } } Unpushed = Ins.Length() > 0; Update(); if (GTreeItem::Select()) { d->Files->Insert(Ins); d->Files->ResizeColumnsToContent(); } else { Ins.DeleteObjects(); } return false; // Don't refresh list } void VcFolder::FolderStatus(const char *Path, VcLeaf *Notify) { if (GTreeItem::Select()) d->ClearFiles(); GString Arg; switch (GetType()) { case VcSvn: case VcHg: Arg = "status"; break; case VcCvs: Arg = "status -l"; break; case VcGit: if (!ToolVersion[VcGit]) LgiAssert(!"Where is the version?"); // What version did =2 become available? It's definately not in v2.5.4 // Not in v2.7.4 either... if (ToolVersion[VcGit] >= Ver2Int("2.8.0")) Arg = "status --porcelain=2"; else Arg = "status --porcelain"; break; default: return; } ParseParams *p = new ParseParams; if (Path && Notify) { p->AltInitPath = Path; p->Leaf = Notify; } else { p->IsWorking = true; } StartCmd(Arg, &VcFolder::ParseStatus, p); } void VcFolder::ListWorkingFolder() { if (!IsWorkingFld) { d->ClearFiles(); GString Arg; switch (GetType()) { case VcCvs: Arg = "-q diff --brief"; break; case VcSvn: Arg = "status"; break; case VcGit: StartCmd("diff --staged", &VcFolder::ParseWorking); Arg = "diff --diff-filter=ACDMRTU"; // return FolderStatus(); break; default: Arg ="diff"; break; } IsWorkingFld = StartCmd(Arg, &VcFolder::ParseWorking); } } void VcFolder::GitAdd() { if (!PostAdd) return; GString Args; if (PostAdd->Files.Length() == 0) { GString m(PostAdd->Msg); m = m.Replace("\"", "\\\""); Args.Printf("commit -m \"%s\"", m.Get()); IsCommit = StartCmd(Args, &VcFolder::ParseCommit, PostAdd->Param, LogNormal); PostAdd.Reset(); } else { GString Last = PostAdd->Files.Last(); Args.Printf("add \"%s\"", Last.Replace("\"", "\\\"").Replace("/", DIR_STR).Get()); PostAdd->Files.PopLast(); StartCmd(Args, &VcFolder::ParseGitAdd, NULL, LogNormal); } } bool VcFolder::ParseGitAdd(int Result, GString s, ParseParams *Params) { GitAdd(); return false; } bool VcFolder::ParseCommit(int Result, GString s, ParseParams *Params) { if (GTreeItem::Select()) Select(true); CommitListDirty = Result == 0; CurrentCommit.Empty(); IsCommit = false; if (Result) { switch (GetType()) { case VcGit: { if (s.Find("Please tell me who you are") >= 0) { { GInput i(GetTree(), "", "Git user name:", AppName); if (i.DoModal()) { GString Args; Args.Printf("config --global user.name \"%s\"", i.GetStr().Get()); StartCmd(Args); } } { GInput i(GetTree(), "", "Git user email:", AppName); if (i.DoModal()) { GString Args; Args.Printf("config --global user.email \"%s\"", i.GetStr().Get()); StartCmd(Args); } } } break; - } - default: + } + default: break; } return false; } if (Result == 0 && GTreeItem::Select()) { d->ClearFiles(); GWindow *w = d->Diff ? d->Diff->GetWindow() : NULL; if (w) w->SetCtrlName(IDC_MSG, NULL); } switch (GetType()) { case VcGit: { Unpushed++; Update(); if (Params && Params->Str.Find("Push") >= 0) Push(); break; } case VcSvn: { CurrentCommit.Empty(); CommitListDirty = true; GetTree()->SendNotify(LvcCommandEnd); if (!Result) { Unpushed = 0; Update(); } break; } default: { LgiAssert(!"Impl me."); break; } } return true; } void VcFolder::Commit(const char *Msg, const char *Branch, bool AndPush) { VcFile *f = NULL; GArray Add; bool Partial = false; while (d->Files->Iterate(f)) { int c = f->Checked(); if (c > 0) Add.Add(f); else Partial = true; } if (CurrentBranch && Branch && !CurrentBranch.Equals(Branch)) { int Response = LgiMsg(GetTree(), "Do you want to start a new branch?", AppName, MB_YESNO); if (Response != IDYES) return; } if (!IsCommit) { GString Args; ParseParams *Param = AndPush ? new ParseParams("Push") : NULL; switch (GetType()) { case VcGit: { if (Add.Length() == 0) { break; } else if (Partial) { if (PostAdd.Reset(new GitCommit)) { PostAdd->Files.SetFixedLength(false); for (auto f : Add) PostAdd->Files.Add(f->GetFileName()); PostAdd->Msg = Msg; PostAdd->Branch = Branch; PostAdd->Param = Param; GitAdd(); } } else { GString m(Msg); m = m.Replace("\"", "\\\""); Args.Printf("commit -am \"%s\"", m.Get()); IsCommit = StartCmd(Args, &VcFolder::ParseCommit, Param, LogNormal); } break; } case VcSvn: { GString::Array a; a.New().Printf("commit -m \"%s\"", Msg); for (VcFile **pf = NULL; Add.Iterate(pf); ) { GString s = (*pf)->GetFileName(); if (s.Find(" ") >= 0) a.New().Printf("\"%s\"", s.Get()); else a.New() = s; } Args = GString(" ").Join(a); IsCommit = StartCmd(Args, &VcFolder::ParseCommit, NULL, LogNormal); if (d->Tabs && IsCommit) { d->Tabs->Value(1); GetTree()->SendNotify(LvcCommandStart); } break; } default: { LgiAssert(!"Impl me."); break; } } } } void VcFolder::Push() { GString Args; bool Working = false; switch (GetType()) { case VcGit: Working = StartCmd("push", &VcFolder::ParsePush, NULL, LogNormal); break; case VcSvn: // Nothing to do here.. the commit pushed the data already break; default: LgiAssert(!"Impl me."); break; } if (d->Tabs && Working) { d->Tabs->Value(1); GetTree()->SendNotify(LvcCommandStart); } } bool VcFolder::ParsePush(int Result, GString s, ParseParams *Params) { bool Status = false; if (Result) { OnCmdError(s, "Push failed."); } else { ClearError(); switch (GetType()) { case VcGit: break; case VcSvn: break; default: break; } Unpushed = 0; Update(); Status = true; } GetTree()->SendNotify(LvcCommandEnd); return Status; // no reselect } void VcFolder::Pull(LoggingType Logging) { GString Args; bool Status = false; switch (GetType()) { case VcHg: case VcGit: Status = StartCmd("pull", &VcFolder::ParsePull, NULL, Logging); break; case VcSvn: Status = StartCmd("up", &VcFolder::ParsePull, NULL, Logging); break; default: LgiAssert(!"Impl me."); break; } if (d->Tabs && Status) { d->Tabs->Value(1); GetTree()->SendNotify(LvcCommandStart); } } bool VcFolder::ParsePull(int Result, GString s, ParseParams *Params) { if (Result) { OnCmdError(s, "Pull failed."); return false; } else ClearError(); switch (GetType()) { case VcGit: case VcHg: { // Git does a merge by default, so the current commit changes... CurrentCommit.Empty(); break; } case VcSvn: { // Svn also does a merge by default and can update our current position... CurrentCommit.Empty(); GString::Array a = s.SplitDelimit("\r\n"); for (GString *Ln = NULL; a.Iterate(Ln); ) { if (Ln->Find("At revision") >= 0) { GString::Array p = Ln->SplitDelimit(" ."); CurrentCommit = p.Last(); break; } else if (Ln->Find("svn cleanup") >= 0) { OnCmdError(s, "Needs cleanup"); break; } } if (Params && Params->Str.Equals("log")) { IsLogging = StartCmd("log", &VcFolder::ParseLog); return false; } break; } default: break; } GetTree()->SendNotify(LvcCommandEnd); CommitListDirty = true; return true; // Yes - reselect and update } void VcFolder::Clean() { switch (GetType()) { case VcSvn: StartCmd("cleanup", &VcFolder::ParseClean, NULL, LogNormal); break; default: LgiMsg(GetTree(), "Not implemented.", AppName); break; } } bool VcFolder::ParseClean(int Result, GString s, ParseParams *Params) { switch (GetType()) { case VcSvn: if (Result == 0) Color(ColorInherit); break; default: LgiAssert(!"Impl me."); break; } return false; } void VcFolder::GetVersion() { auto t = GetType(); switch (t) { case VcGit: case VcSvn: case VcHg: case VcCvs: StartCmd("--version", &VcFolder::ParseVersion, NULL, LogNormal); break; default: OnCmdError(NULL, "No version control found."); break; } } bool VcFolder::ParseVersion(int Result, GString s, ParseParams *Params) { auto p = s.SplitDelimit(); switch (GetType()) { case VcGit: { if (p.Length() > 2) { ToolVersion[GetType()] = Ver2Int(p[2]); printf("Git version: %s\n", p[2].Get()); } else LgiAssert(0); break; } case VcSvn: { if (p.Length() > 2) { ToolVersion[GetType()] = Ver2Int(p[2]); printf("Svn version: %s\n", p[2].Get()); } else LgiAssert(0); break; } case VcHg: { if (p.Length() >= 5) { auto Ver = p[4].Strip("()"); ToolVersion[GetType()] = Ver2Int(Ver); printf("Hg version: %s\n", Ver.Get()); } break; } case VcCvs: { #ifdef _DEBUG for (auto i : p) printf("i='%s'\n", i.Get()); #endif LgiAssert(!"Impl me."); break; } default: break; } return false; } bool VcFolder::ParseAddFile(int Result, GString s, ParseParams *Params) { switch (GetType()) { case VcCvs: { break; } default: break; } return false; } bool VcFolder::AddFile(const char *Path, bool AsBinary) { if (!Path) return false; switch (GetType()) { case VcCvs: { GString a; a.Printf("add%s \"%s\"", AsBinary ? " -kb" : "", Path); return StartCmd(a, &VcFolder::ParseAddFile); break; } default: { LgiAssert(!"Impl me."); break; } } return false; } bool VcFolder::ParseRevert(int Result, GString s, ParseParams *Params) { ListWorkingFolder(); return false; } bool VcFolder::Revert(const char *Path, const char *Revision) { if (!Path) return false; switch (GetType()) { case VcGit: { GString a; a.Printf("checkout \"%s\"", Path); return StartCmd(a, &VcFolder::ParseRevert); break; } case VcSvn: { GString a; a.Printf("revert \"%s\"", Path); return StartCmd(a, &VcFolder::ParseRevert); break; } default: { LgiAssert(!"Impl me."); break; } } return false; } bool VcFolder::ParseResolve(int Result, GString s, ParseParams *Params) { switch (GetType()) { case VcGit: { break; } case VcSvn: case VcHg: case VcCvs: default: { LgiAssert(!"Impl me."); break; } } return true; } bool VcFolder::Resolve(const char *Path) { if (!Path) return false; switch (GetType()) { case VcGit: { GString a; a.Printf("add \"%s\"", Path); return StartCmd(a, &VcFolder::ParseResolve, new ParseParams(Path)); } case VcSvn: case VcHg: case VcCvs: default: { LgiAssert(!"Impl me."); break; } } return false; } bool VcFolder::ParseBlame(int Result, GString s, ParseParams *Params) { new BlameUi(d, GetType(), s); return false; } bool VcFolder::Blame(const char *Path) { if (!Path) return false; switch (GetType()) { case VcGit: { GString a; a.Printf("blame \"%s\"", Path); return StartCmd(a, &VcFolder::ParseBlame); break; } case VcHg: { GString a; a.Printf("annotate -un \"%s\"", Path); return StartCmd(a, &VcFolder::ParseBlame); break; } case VcSvn: { GString a; a.Printf("blame \"%s\"", Path); return StartCmd(a, &VcFolder::ParseBlame); break; } default: { LgiAssert(!"Impl me."); break; } } return true; } bool VcFolder::SaveFileAs(const char *Path, const char *Revision) { if (!Path || !Revision) return false; return true; } bool VcFolder::ParseSaveAs(int Result, GString s, ParseParams *Params) { return false; } bool VcFolder::ParseCounts(int Result, GString s, ParseParams *Params) { switch (GetType()) { case VcGit: { Unpushed = (int) s.Strip().Split("\n").Length(); break; } case VcSvn: { int64 ServerRev = 0; bool HasUpdate = false; GString::Array c = s.Split("\n"); for (unsigned i=0; i 1 && a[0].Equals("Status")) ServerRev = a.Last().Int(); else if (a[0].Equals("*")) HasUpdate = true; } if (ServerRev > 0 && HasUpdate) { int64 CurRev = CurrentCommit.Int(); Unpulled = (int) (ServerRev - CurRev); } else Unpulled = 0; Update(); break; } default: { LgiAssert(!"Impl me."); break; } } IsUpdatingCounts = false; Update(); return false; // No re-select } void VcFolder::SetEol(const char *Path, int Type) { if (!Path) return; switch (Type) { case IDM_EOL_LF: { ConvertEol(Path, false); break; } case IDM_EOL_CRLF: { ConvertEol(Path, true); break; } case IDM_EOL_AUTO: { #ifdef WINDOWS ConvertEol(Path, true); #else ConvertEol(Path, false); #endif break; } } } void VcFolder::UncommitedItem::Select(bool b) { LListItem::Select(b); if (b) { GTreeItem *i = d->Tree->Selection(); VcFolder *f = dynamic_cast(i); if (f) f->ListWorkingFolder(); if (d->Msg) { d->Msg->Name(NULL); GWindow *w = d->Msg->GetWindow(); if (w) { w->SetCtrlEnabled(IDC_COMMIT, true); w->SetCtrlEnabled(IDC_COMMIT_AND_PUSH, true); } } } } void VcFolder::UncommitedItem::OnPaint(GItem::ItemPaintCtx &Ctx) { GFont *f = GetList()->GetFont(); f->Transparent(false); f->Colour(Ctx.Fore, Ctx.Back); GDisplayString ds(f, "(working folder)"); ds.Draw(Ctx.pDC, Ctx.x1 + ((Ctx.X() - ds.X()) / 2), Ctx.y1 + ((Ctx.Y() - ds.Y()) / 2), &Ctx); } ////////////////////////////////////////////////////////////////////////////////////////// VcLeaf::VcLeaf(VcFolder *parent, GTreeItem *Item, GString path, GString leaf, bool folder) { Parent = parent; d = Parent->GetPriv(); Path = path; Leaf = leaf; Folder = folder; Tmp = NULL; Item->Insert(this); if (Folder) { Insert(Tmp = new GTreeItem); Tmp->SetText("Loading..."); } } GString VcLeaf::Full() { GFile::Path p(Path); p += Leaf; return p.GetFull(); } void VcLeaf::OnBrowse() { Parent->FolderStatus(Full(), this); } void VcLeaf::AfterBrowse() { LList *Files = d->Files; Files->Empty(); GDirectory Dir; for (int b = Dir.First(Full()); b; b = Dir.Next()) { if (Dir.IsDir()) continue; VcFile *f = new VcFile(d, Parent, NULL); if (f) { // f->SetText(COL_STATE, f->SetText(Dir.GetName(), COL_FILENAME); Files->Insert(f); } } Files->ResizeColumnsToContent(); } void VcLeaf::OnExpand(bool b) { if (Tmp && b) { Tmp->Remove(); DeleteObj(Tmp); GFile::Path p(Path); p += Leaf; Parent->ReadDir(this, p); } } char *VcLeaf::GetText(int Col) { if (Col == 0) return Leaf; return NULL; } int VcLeaf::GetImage(int Flags) { return Folder ? IcoFolder : IcoFile; } int VcLeaf::Compare(VcLeaf *b) { // Sort folders to the top... if (Folder ^ b->Folder) return (int)b->Folder - (int)Folder; // Then alphabetical return Stricmp(Leaf.Get(), b->Leaf.Get()); } bool VcLeaf::Select() { return GTreeItem::Select(); } void VcLeaf::Select(bool b) { GTreeItem::Select(b); if (b) OnBrowse(); } void VcLeaf::OnMouseClick(GMouse &m) { if (m.IsContextMenu()) { GSubMenu s; s.AppendItem("Log", IDM_LOG); s.AppendItem("Blame", IDM_BLAME, !Folder); int Cmd = s.Float(GetTree(), m); switch (Cmd) { case IDM_LOG: { break; } case IDM_BLAME: { Parent->Blame(Full()); break; } } } } 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/GMenu.h b/include/common/GMenu.h --- a/include/common/GMenu.h +++ b/include/common/GMenu.h @@ -1,533 +1,533 @@ /** \file \author Matthew Allen */ #ifndef __GMENU_H #define __GMENU_H // Os specific declarations #if defined __GTK_H__ typedef Gtk::GtkMenuShell *OsSubMenu; typedef Gtk::GtkMenuItem *OsMenuItem; #elif defined WINNATIVE typedef HMENU OsSubMenu; typedef MENUITEMINFO OsMenuItem; #ifndef COLOR_MENUHILIGHT #define COLOR_MENUHILIGHT 29 #endif #ifndef COLOR_MENUBAR #define COLOR_MENUBAR 30 #endif #elif defined BEOS typedef BMenu *OsSubMenu; typedef BMenuItem *OsMenuItem; #elif defined ATHEOS #include typedef os::Menu *OsSubMenu; typedef os::MenuItem *OsMenuItem; #elif defined(MAC) && !defined(LGI_SDL) - #if defined(COCOA) - #ifdef __OBJC__ - #include - #endif - ObjCWrapper(NSMenu, OsSubMenu) - ObjCWrapper(NSMenuItem, OsMenuItem) + #if defined(COCOA) + #ifdef __OBJC__ + #include + #endif + ObjCWrapper(NSMenu, OsSubMenu) + ObjCWrapper(NSMenuItem, OsMenuItem) #else typedef MenuRef OsSubMenu; typedef MenuItemIndex OsMenuItem; #endif #else #include "GMenuImpl.h" typedef class MenuClickImpl *OsSubMenu; typedef class MenuItemImpl *OsMenuItem; #endif #include "GXmlTree.h" #include "Res.h" #include "GImageList.h" /////////////////////////////////////////////////////////////////////////////////////////////// // Menu wrappers class LgiClass GMenuLoader { friend class GMenuItem; friend class GMenu; friend class GSubMenu; friend class MenuImpl; friend class SubMenuImplPrivate; protected: #ifdef WIN32 OsSubMenu Info; #endif List Items; public: GMenuLoader() { #ifdef WIN32 Info = 0; #endif } bool Load( class LgiMenuRes *MenuRes, GXmlTag *Tag, ResFileFormat Format, class TagHash *TagList); virtual GMenuItem *AppendItem(const char *Str, int Id, bool Enabled = true, int Where = -1, const char *Shortcut = 0) = 0; virtual GSubMenu *AppendSub(const char *Str, int Where = -1) = 0; virtual GMenuItem *AppendSeparator(int Where = -1) = 0; }; /// Sub menu. class LgiClass GSubMenu : public GBase, public GMenuLoader, public GImageListOwner, public GDom { friend class GMenuItem; friend class GMenu; friend class SubMenuImpl; friend class MenuItemImpl; friend class MenuImpl; #if !WINNATIVE OsSubMenu Info; #endif #if defined(__GTK_H__) friend void MenuItemCallback(class GMenuItem *Item); friend void GSubMenuDeactivate(Gtk::GtkMenuShell *widget, GSubMenu *Sub); int *_ContextMenuId; bool IsContext(GMenuItem *Item); void OnDeactivate(); #elif defined(WINNATIVE) HWND TrackHandle; #elif defined(BEOS) void _CopyMenu(BMenu *To, GSubMenu *From); GMenuItem *MatchShortcut(GKey &k); #else bool OnKey(GKey &k); #endif protected: /// The parent menu item or NULL if the root menu GMenuItem *Parent; /// The top level window this sub menu belongs to or NULL GMenu *Menu; /// The window that the menu belongs to or NULL. GViewI *Window; void OnAttach(bool Attach); void ClearHandle(); public: GSubMenu(OsSubMenu Hnd); /// Constructor GSubMenu ( /// Name of the menu const char *name = "", /// True if it's popup bool Popup = true ); virtual ~GSubMenu(); /// Returns the OS handle OsSubMenu Handle() { return Info; } /// Detachs the OS handle and returns it OsSubMenu Release() { OsSubMenu Hnd = Info; Info = NULL; return Hnd; } /// Add a new item GMenuItem *AppendItem ( /// The text of the item. /// /// If you put a tab control in the text, anything after the tab is considered /// to be the keyboard shortcut for the menu item. The shortcut can be a combination /// of keys added together with '+'. /// /// e.g. ///
    ///
  • Ctrl+S ///
  • Alt+Del ///
  • F2 ///
const char *Str, /// Command ID to post to the OnCommand() handler int Id, /// True if the item should be enabled bool Enabled = true, /// The index into the list to insert at, or -1 to insert at the end int Where = -1, // Shortcut if not embeded in "Str" const char *Shortcut = 0 ); /// Add a submenu GSubMenu *AppendSub ( /// The text of the item const char *Str, /// The index to insert the item, or -1 to insert on the end int Where = -1 ); /// Add a separator GMenuItem *AppendSeparator(int Where = -1); /// Delete all items void Empty(); /// Detachs an item from the sub menu but doesn't delete it bool RemoveItem ( /// The index of the item to remove int i ); /// Detachs an item from the sub menu but doesn't delete it bool RemoveItem ( /// Pointer of the item to remove GMenuItem *Item ); /// Returns numbers of items in menu size_t Length(); /// Return a pointer to an item GMenuItem *ItemAt ( /// The index of the item to return int i ); /// Returns a pointer to an item GMenuItem *FindItem ( /// The ID of the item to return int Id ); /// Returns a pointer to an sub menu GSubMenu *FindSubMenu ( /// The ID of the sub menu to return int Id ); enum { BtnLeft = 1, BtnMiddle = 2, BtnRight = 3, }; /// Floats the submenu anywhere on the screen int Float ( /// The parent view GView *Parent, /// The x coord of the top-left corner int x, /// The y coord of the top-left corner int y, /// True if the menu is tracking the left button, else it tracks the right button int Button = BtnRight ); int Float(GView *Parent, GMouse m) { int Btn = 0; if (m.Left()) Btn = BtnLeft; else if (m.Middle()) Btn = BtnMiddle; else if (m.Right()) Btn = BtnRight; m.ToScreen(); return Float(Parent, m.x, m.y, Btn); } /// Returns the parent menu item GMenuItem *GetParent() { return Parent; } /// Returns the menu that this belongs to GMenu *GetMenu() { return Menu; } // Dom impl bool GetVariant(const char *Name, GVariant &Value, char *Arr = NULL); bool SetVariant(const char *Name, GVariant &Value, char *Arr = NULL); bool CallMethod(const char *MethodName, GVariant *ReturnValue, GArray &Args); }; /// An item an a menu class LgiClass GMenuItem : public GBase, public GDom { friend class GSubMenu; friend class GMenu; friend class GView; friend class SubMenuImpl; friend class MenuItemImpl; friend class MenuImpl; friend class SubMenuImplPrivate; #ifdef BEOS friend class LgiMenuItem; #endif private: #ifdef WIN32 bool Insert(int Pos); bool Update(); #endif #if defined(__GTK_H__) || defined(BEOS) || defined(LGI_SDL) GString ShortCut; #endif protected: GMenu *Menu; GSubMenu *Parent; GSubMenu *Child; int Position; int _Icon; OsMenuItem Info; class GMenuItemPrivate *d; #if defined BEOS BMessage *Msg; bool UnsupportedShortcut; int ShortcutKey; int ShortcutMod; GMenuItem *MatchShortcut(GKey &k); #else int _Id; int _Flags; #endif #if defined(__GTK_H__) friend void MenuItemCallback(GMenuItem *Item); bool InSetCheck; GAutoPtr IconImg; bool Replace(Gtk::GtkWidget *newWid); #else virtual void _Measure(GdcPt2 &Size); virtual void _Paint(GSurface *pDC, int Flags); virtual void _PaintText(GSurface *pDC, int x, int y, int Width); #endif void OnAttach(bool Attach); void ClearHandle(); public: GMenuItem(); #if defined BEOS GMenuItem(BMenuItem *item); GMenuItem(GSubMenu *p); #endif GMenuItem(GMenu *m, GSubMenu *p, const char *txt, int Pos, const char *Shortcut = 0); virtual ~GMenuItem(); GMenuItem &operator =(const GMenuItem &m) { LgiAssert(!"This shouldn't be used anywhere"); return *this; } /// Creates a sub menu off the item GSubMenu *Create(); /// Removes the item from it's place in the menu but doesn't delete it bool Remove(); /// Returns the parent sub menu GSubMenu *GetParent(); /// Returns the parent sub menu GMenu *GetMenu() { return Menu; } /// Scans the text of the item for a keyboard shortcut bool ScanForAccel(); /// Returns the OS handle for the menuitem OsMenuItem Handle() { return Info; } /// Set the id void Id(int i); /// Turn the item into a separator void Separator(bool s); /// Put a check mark on the item void Checked(bool c); /// \brief Set the text of the item /// \sa GSubMenu::AppendItem() bool Name(const char *n); /// Enable or disable the item void Enabled(bool e); void Visible(bool v); void Focus(bool f); /// Attach a sub menu to the item void Sub(GSubMenu *s); /// Set the icon for the item. The icon is stored in the GMenu's image list. void Icon(int i); /// Get the id int Id(); /// Get the text of the item char *Name(); /// Return whether this item is a separator bool Separator(); /// Return whether this item has a check mark bool Checked(); /// Return whether this item is enabled bool Enabled(); bool Visible(); bool Focus(); /// Return whether this item's submenu GSubMenu *Sub(); /// Return the icon of this this int Icon(); }; /// Encapsulates a keyboard shortcut class LgiClass GAccelerator : public GUiEvent { int Key; int Id; public: GAccelerator(int flags, int key, int id); int GetId() { return Id; } /// See if the accelerator matchs a keyboard event bool Match(GKey &k); }; /** \brief Top level window menu This class contains GMenuItem's and GSubMenu's. A basic menu can be constructed inside a GWindow like this: \code Menu = new GMenu; if (Menu) { Menu->Attach(this); GSubMenu *File = Menu->AppendSub("&File"); if (File) { File->AppendItem("&Open\tCtrl+O", IDM_OPEN, true); File->AppendItem("&Save All\tCtrl+S", IDM_SAVE_ALL, true); File->AppendItem("Save &As", IDM_SAVEAS, true); File->AppendSeparator(); File->AppendItem("&Options", IDM_OPTIONS, true); File->AppendSeparator(); File->AppendItem("E&xit", IDM_EXIT, true); } GSubMenu *Help = Menu->AppendSub("&Help"); if (Help) { Help->AppendItem("&Help", IDM_HELP, true); Help->AppendItem("&About", IDM_ABOUT, true); } } \endcode Or you can load a menu from a resource like this: \code Menu = new GMenu; if (Menu) { Menu->Attach(this); Menu->Load(this, "IDM_MENU"); } \endcode */ class LgiClass GMenu : public GSubMenu { friend class GSubMenu; friend class GMenuItem; friend class GWindow; static GFont *_Font; class GMenuPrivate *d; #if defined WIN32 void OnChange(); #else void OnChange() {} #endif protected: /// List of keyboard shortcuts in the menu items attached List Accel; #ifdef __GTK_H__ Gtk::GtkAccelGroup *AccelGrp; #endif public: /// Constructor GMenu(const char *AppName = NULL); /// Destructor virtual ~GMenu(); /// Returns the font used by the menu items static GFont *GetFont(); /// Returns the top level window that this menu is attached to GViewI *WindowHandle() { return Window; } /// Attach the menu to a window bool Attach(GViewI *p); /// Detact the menu from the window bool Detach(); /// Load the menu from a resource file bool Load ( /// The parent view for any error message boxes GView *p, /// The resource to load. Will probably change to an int sometime. const char *Res, /// Optional list of comma or space separated tags const char *Tags = 0 ); /// \brief See if any of the accelerators match the key event /// \return true if none of the accelerators match bool OnKey ( /// The view that will eventually receive the key event GView *v, /// The keyboard event details GKey &k ); /// This creates copies of the preference and about menu items in the /// application menu. On other platforms it's a NOP. bool SetPrefAndAboutItems(int PrefId, int AboutId); #if defined(WIN32) static int _OnEvent(GMessage *Msg); #elif defined(BEOS) GRect GetPos(); #elif defined(MAC) - int GetIdForCommand(uint32 Cmd); + int GetIdForCommand(uint32_t Cmd); #endif }; #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/GRops.h b/include/common/GRops.h --- a/include/common/GRops.h +++ b/include/common/GRops.h @@ -1,1320 +1,1320 @@ #ifndef _GROPS_H_ #define _GROPS_H_ #include "GPixelRops.h" #define IsOverlapping() ((uint8_t*)dst == (uint8_t*)src) #define OverlapCheck() \ if (IsOverlapping()) \ { \ LgiAssert(!"regions can't overlap."); \ return; \ } ////////////////////////////////////////////////////////////////// // To 15 bits template void GRop15To15(DstPx *dst, SrcPx *src, int px) { REG DstPx *d = dst; REG SrcPx *s = src; REG int Px = px; OverlapCheck() while (Px--) { d->r = s->r; d->g = s->g; d->b = s->b; s++; d++; } } template void GRop16To15(DstPx *dst, SrcPx *src, int px) { REG DstPx *d = dst; REG SrcPx *s = src; REG int Px = px; OverlapCheck() while (Px--) { d->r = s->r; d->g = s->g >> 1; d->b = s->b; s++; d++; } } template void GRop24To15(DstPx *dst, SrcPx *src, int px) { REG DstPx *d = dst; REG SrcPx *s = src; REG int Px = px; OverlapCheck() while (Px--) { d->r = s->r >> 3; d->g = s->g >> 3; d->b = s->b >> 3; s++; d++; } } template void GRop32To15(DstPx *dst, SrcPx *src, int px) { REG DstPx *d = dst; REG SrcPx *s = src; REG int Px = px; OverlapCheck() while (Px--) { d->r = s->r >> 3; d->g = s->g >> 3; d->b = s->b >> 3; s++; d++; } } template void GRop48To15(DstPx *dst, SrcPx *src, int px) { REG DstPx *d = dst; REG SrcPx *s = src; REG int Px = px; OverlapCheck() while (Px--) { d->r = s->r >> 11; d->g = s->g >> 11; d->b = s->b >> 11; s++; d++; } } template void GRop64To15(DstPx *dst, SrcPx *src, int px) { REG DstPx *d = dst; REG SrcPx *s = src; REG int Px = px; OverlapCheck() while (Px--) { d->r = s->r >> 11; d->g = s->g >> 11; d->b = s->b >> 11; s++; d++; } } ////////////////////////////////////////////////////////////////// // To 16 bits template void GRop15To16(DstPx *dst, SrcPx *src, int px) { REG DstPx *d = dst; REG SrcPx *s = src; REG int Px = px; if (IsOverlapping()) { REG uint8_t r, g, b; while (Px--) { r = s->r; g = s->g; b = s->b; d->r = r; d->g = (g << 1) | (g >> 4); d->b = b; s++; d++; } } else { while (Px--) { d->r = s->r; d->g = (uint16)(s->g << 1) | (s->g >> 4); d->b = s->b; s++; d++; } } } template void GRop16To16(DstPx *dst, SrcPx *src, int px) { REG DstPx *d = dst; REG SrcPx *s = src; REG int Px = px; if (IsOverlapping()) { REG uint8_t r, g, b; while (Px--) { r = s->r; g = s->g; b = s->b; d->r = r; d->g = g; d->b = b; s++; d++; } } else { while (Px--) { d->r = s->r; d->g = s->g; d->b = s->b; s++; d++; } } } template void GRop24To16(DstPx *dst, SrcPx *src, int px) { REG DstPx *d = dst; REG SrcPx *s = src; REG int Px = px; OverlapCheck() while (Px--) { d->r = s->r >> 3; d->g = s->g >> 2; d->b = s->b >> 3; s++; d++; } } template void GRop32To16(DstPx *dst, SrcPx *src, int px) { REG DstPx *d = dst; REG SrcPx *s = src; REG int Px = px; OverlapCheck() while (Px--) { d->r = s->r >> 3; d->g = s->g >> 2; d->b = s->b >> 3; s++; d++; } } template void GRop48To16(DstPx *dst, SrcPx *src, int px) { REG DstPx *d = dst; REG SrcPx *s = src; REG int Px = px; OverlapCheck() while (Px--) { d->r = s->r >> 11; d->g = s->g >> 10; d->b = s->b >> 11; s++; d++; } } template void GRop64To16(DstPx *dst, SrcPx *src, int px) { REG DstPx *d = dst; REG SrcPx *s = src; REG int Px = px; OverlapCheck() while (Px--) { d->r = s->r >> 11; d->g = s->g >> 10; d->b = s->b >> 11; s++; d++; } } ////////////////////////////////////////////////////////////////// // To 24 bits template void GRop15To24(DstPx *dst, SrcPx *src, int px) { REG DstPx *d = dst; REG SrcPx *s = src; REG int Px = px; OverlapCheck() while (Px--) { d->r = G5bitTo8bit(s->r); d->g = G5bitTo8bit(s->g); d->b = G5bitTo8bit(s->b); s++; d++; } } template void GRop16To24(DstPx *dst, SrcPx *src, int px) { REG DstPx *d = dst; REG SrcPx *s = src; REG int Px = px; OverlapCheck() while (Px--) { d->r = G5bitTo8bit(s->r); d->g = G6bitTo8bit(s->g); d->b = G5bitTo8bit(s->b); s++; d++; } } template void GRop24To24(DstPx *dst, SrcPx *src, int px) { REG DstPx *d = dst; REG SrcPx *s = src; REG int Px = px; if ((uint8_t*)d == (uint8_t*)s) { REG uint8_t r, g, b; d += px - 1; s += px - 1; while (Px--) { r = s->r; g = s->g; b = s->b; d->r = r; d->g = g; d->b = b; s--; d--; } } else { while (Px--) { d->r = s->r; d->g = s->g; d->b = s->b; s++; d++; } } } template void GRop32To24(DstPx *dst, SrcPx *src, int px) { REG DstPx *d = dst; REG SrcPx *s = src; REG int Px = px; OverlapCheck() while (Px--) { d->r = s->r; d->g = s->g; d->b = s->b; s++; d++; } } template void GRop48To24(DstPx *dst, SrcPx *src, int px) { REG DstPx *d = dst; REG SrcPx *s = src; REG int Px = px; OverlapCheck() while (Px--) { d->r = s->r >> 8; d->g = s->g >> 8; d->b = s->b >> 8; s++; d++; } } template void GRop64To24(DstPx *dst, SrcPx *src, int px) { REG DstPx *d = dst; REG SrcPx *s = src; REG int Px = px; OverlapCheck() while (Px--) { d->r = s->r >> 8; d->g = s->g >> 8; d->b = s->b >> 8; s++; d++; } } ////////////////////////////////////////////////////////////////// // To 32 bits template void GRop15To32(DstPx *dst, SrcPx *src, int px) { REG DstPx *d = dst; REG SrcPx *s = src; REG int Px = px; OverlapCheck() while (Px--) { d->r = G5bitTo8bit(s->r); d->g = G5bitTo8bit(s->g); d->b = G5bitTo8bit(s->b); d->a = 255; s++; d++; } } template void GRop16To32(DstPx *dst, SrcPx *src, int px) { REG DstPx *d = dst; REG SrcPx *s = src; REG int Px = px; OverlapCheck() while (Px--) { d->r = G5bitTo8bit(s->r); d->g = G6bitTo8bit(s->g); d->b = G5bitTo8bit(s->b); d->a = 255; s++; d++; } } template void GRop24To32(DstPx *dst, SrcPx *src, int px) { REG DstPx *d = dst; REG SrcPx *s = src; REG int Px = px; if (IsOverlapping()) { REG uint8_t r, g, b; d += px - 1; s += px - 1; while (Px--) { r = s->r; g = s->g; b = s->b; d->r = r; d->g = g; d->b = b; d->a = 255; s--; d--; } } else { while (Px--) { d->r = s->r; d->g = s->g; d->b = s->b; d->a = 255; s++; d++; } } } template void GRop32To32(DstPx *dst, SrcPx *src, int px) { REG DstPx *d = dst; REG SrcPx *s = src; REG int Px = px; if (IsOverlapping()) { REG uint8_t r, g, b, a; while (Px--) { r = s->r; g = s->g; b = s->b; a = s->a; d->r = r; d->g = g; d->b = b; d->a = a; s++; d++; } } else { while (Px--) { d->r = s->r; d->g = s->g; d->b = s->b; d->a = s->a; s++; d++; } } } template void GRop48To32(DstPx *dst, SrcPx *src, int px) { REG DstPx *d = dst; REG SrcPx *s = src; REG int Px = px; OverlapCheck() while (Px--) { d->r = s->r >> 8; d->g = s->g >> 8; d->b = s->b >> 8; d->a = 255; s++; d++; } } template void GRop64To32(DstPx *dst, SrcPx *src, int px) { REG DstPx *d = dst; REG SrcPx *s = src; REG int Px = px; OverlapCheck() while (Px--) { d->r = s->r >> 8; d->g = s->g >> 8; d->b = s->b >> 8; d->a = s->a >> 8; s++; d++; } } ////////////////////////////////////////////////////////////////// // To 48 bits template void GRop15To48(DstPx *dst, SrcPx *src, int px) { REG DstPx *d = dst; REG SrcPx *s = src; REG int Px = px; OverlapCheck() while (Px--) { d->r = (int)G5bitTo8bit(s->r) << 8; d->g = (int)G5bitTo8bit(s->g) << 8; d->b = (int)G5bitTo8bit(s->b) << 8; s++; d++; } } template void GRop16To48(DstPx *dst, SrcPx *src, int px) { REG DstPx *d = dst; REG SrcPx *s = src; REG int Px = px; OverlapCheck() while (Px--) { d->r = (int)G5bitTo8bit(s->r) << 8; d->g = (int)G6bitTo8bit(s->g) << 8; d->b = (int)G5bitTo8bit(s->b) << 8; s++; d++; } } template void GRop24To48(DstPx *dst, SrcPx *src, int px) { REG DstPx *d = dst; REG SrcPx *s = src; REG int Px = px; OverlapCheck() while (Px--) { d->r = G8bitTo16bit(s->r); d->g = G8bitTo16bit(s->g); d->b = G8bitTo16bit(s->b); s++; d++; } } template void GRop32To48(DstPx *dst, SrcPx *src, int px) { REG DstPx *d = dst; REG SrcPx *s = src; REG int Px = px; OverlapCheck() while (Px--) { d->r = G8bitTo16bit(s->r); d->g = G8bitTo16bit(s->g); d->b = G8bitTo16bit(s->b); s++; d++; } } template void GRop48To48(DstPx *dst, SrcPx *src, int px) { REG DstPx *d = dst; REG SrcPx *s = src; REG int Px = px; OverlapCheck() while (Px--) { d->r = s->r; d->g = s->g; d->b = s->b; s++; d++; } } template void GRop64To48(DstPx *dst, SrcPx *src, int px) { REG DstPx *d = dst; REG SrcPx *s = src; REG int Px = px; OverlapCheck() while (Px--) { d->r = s->r; d->g = s->g; d->b = s->b; s++; d++; } } ////////////////////////////////////////////////////////////////// // To 64 bits template void GRop15To64(DstPx *dst, SrcPx *src, int px) { REG DstPx *d = dst; REG SrcPx *s = src; REG int Px = px; OverlapCheck() while (Px--) { d->r = (int)G5bitTo8bit(s->r) << 8; d->g = (int)G5bitTo8bit(s->g) << 8; d->b = (int)G5bitTo8bit(s->b) << 8; d->a = 0xffff; s++; d++; } } template void GRop16To64(DstPx *dst, SrcPx *src, int px) { REG DstPx *d = dst; REG SrcPx *s = src; REG int Px = px; OverlapCheck() while (Px--) { d->r = (int)G5bitTo8bit(s->r) << 8; d->g = (int)G6bitTo8bit(s->g) << 8; d->b = (int)G5bitTo8bit(s->b) << 8; d->a = 0xffff; s++; d++; } } template void GRop24To64(DstPx *dst, SrcPx *src, int px) { REG DstPx *d = dst; REG SrcPx *s = src; REG int Px = px; OverlapCheck() while (Px--) { d->r = G8bitTo16bit(s->r); d->g = G8bitTo16bit(s->g); d->b = G8bitTo16bit(s->b); d->a = 0xffff; s++; d++; } } template void GRop32To64(DstPx *dst, SrcPx *src, int px) { REG DstPx *d = dst; REG SrcPx *s = src; REG int Px = px; OverlapCheck() while (Px--) { d->r = G8bitTo16bit(s->r); d->g = G8bitTo16bit(s->g); d->b = G8bitTo16bit(s->b); d->a = G8bitTo16bit(s->a); s++; d++; } } template void GRop48To64(DstPx *dst, SrcPx *src, int px) { REG DstPx *d = dst; REG SrcPx *s = src; REG int Px = px; OverlapCheck() while (Px--) { d->r = s->r; d->g = s->g; d->b = s->b; d->a = 0xffff; s++; d++; } } template void GRop64To64(DstPx *dst, SrcPx *src, int px) { REG DstPx *d = dst; REG SrcPx *s = src; REG int Px = px; OverlapCheck() while (Px--) { d->r = s->r; d->g = s->g; d->b = s->b; d->a = s->a; s++; d++; } } template void GComposite15To24(DstPx *dst, SrcPx *src, int px) { REG DstPx *d = dst; REG SrcPx *s = src; REG int Px = px; OverlapCheck() while (Px--) { if (s->a) { d->r = G5bitTo8bit(s->r); d->g = G5bitTo8bit(s->g); d->b = G5bitTo8bit(s->b); } s++; d++; } } template void GComposite15To32(DstPx *dst, SrcPx *src, int px) { REG DstPx *d = dst; REG SrcPx *s = src; REG int Px = px; OverlapCheck() while (Px--) { if (s->a) { d->r = G5bitTo8bit(s->r); d->g = G5bitTo8bit(s->g); d->b = G5bitTo8bit(s->b); d->a = 0xff; } s++; d++; } } template void GComposite15To48(DstPx *dst, SrcPx *src, int px) { REG DstPx *d = dst; REG SrcPx *s = src; REG int Px = px; OverlapCheck() while (Px--) { if (s->a) { d->r = G5bitTo8bit(s->r)<<8; d->g = G5bitTo8bit(s->g)<<8; d->b = G5bitTo8bit(s->b)<<8; } s++; d++; } } template void GComposite15To15(DstPx *dst, SrcPx *src, int px) { REG DstPx *d = dst; REG SrcPx *s = src; REG int Px = px; OverlapCheck() while (Px--) { if (s->a) { d->r = s->r; d->g = s->g; d->b = s->b; } s++; d++; } } template void GComposite15To16(DstPx *dst, SrcPx *src, int px) { REG DstPx *d = dst; REG SrcPx *s = src; REG int Px = px; OverlapCheck() while (Px--) { if (s->a) { d->r = s->r; d->g = s->g << 1; d->b = s->b; } s++; d++; } } template void GComposite15To64(DstPx *dst, SrcPx *src, int px) { REG DstPx *d = dst; REG SrcPx *s = src; REG int Px = px; OverlapCheck() while (Px--) { if (s->a) { d->r = G5bitTo8bit(s->r) << 8; d->g = G5bitTo8bit(s->g) << 8; d->b = G5bitTo8bit(s->b) << 8; } s++; d++; } } template void GComposite32To15(OutPx *d, InPx *s, int Len) { InPx *end = s + Len; uint8_t *DivLut = Div255Lut; REG uint8_t sa; while (s < end) { sa = s->a; if (sa == 255) { // Copy pixel d->r = G8bitTo5bit(s->r); d->g = G8bitTo5bit(s->g); d->b = G8bitTo5bit(s->b); } else if (sa) { // Composite pixel // Dc' = (Sca + Dc.Da.(1 - Sa)) / Da' // Da' = Sa + Da.(1 - Sa) REG uint8_t o = 0xff - sa; REG uint8_t val; #define NonPreMul(c) \ val = DivLut[(s->c * sa) + (G5bitTo8bit(d->c) * o)]; \ d->c = G8bitTo5bit(val) NonPreMul(r); NonPreMul(g); NonPreMul(b); #undef NonPreMul } s++; d++; } } template void GComposite32To16(OutPx *d, InPx *s, int Len) { InPx *end = s + Len; uint8_t *DivLut = Div255Lut; REG uint8_t sa; while (s < end) { sa = s->a; if (sa == 255) { // Copy pixel d->r = G8bitTo5bit(s->r); d->g = G8bitTo6bit(s->g); d->b = G8bitTo5bit(s->b); } else if (sa) { // Composite pixel // Dc' = (Sca + Dc.Da.(1 - Sa)) / Da' // Da' = Sa + Da.(1 - Sa) REG uint8_t o = 0xff - sa; REG uint8_t val; #define NonPreMul(c, up, down) \ val = DivLut[(s->c * sa) + (G##up(d->c) * o)]; \ d->c = G##down(val) NonPreMul(r, 5bitTo8bit, 8bitTo5bit); NonPreMul(g, 6bitTo8bit, 8bitTo6bit); NonPreMul(b, 5bitTo8bit, 8bitTo5bit); #undef NonPreMul } s++; d++; } } template void GComposite32To24(OutPx *d, InPx *s, int Len) { InPx *end = s + Len; uint8_t *DivLut = Div255Lut; REG uint8_t sa; while (s < end) { sa = s->a; if (sa == 255) { // Copy pixel d->r = s->r; d->g = s->g; d->b = s->b; } else if (sa) { // Composite pixel // Dc' = (Sca + Dc.Da.(1 - Sa)) / Da' // Da' = Sa + Da.(1 - Sa) REG uint8_t o = 0xff - sa; #define NonPreMul(c) \ d->c = DivLut[(s->c * sa) + (d->c * o)] NonPreMul(r); NonPreMul(g); NonPreMul(b); #undef NonPreMul } s++; d++; } } template void GComposite32To32(OutPx *d, InPx *s, int Len) { InPx *end = s + Len; uint8_t *DivLut = Div255Lut; REG uint8_t sa; while (s < end) { sa = s->a; if (sa == 255) { // Copy pixel d->r = s->r; d->g = s->g; d->b = s->b; d->a = sa; } else if (sa) { // Composite pixel // Dc' = (Sca + Dc.Da.(1 - Sa)) / Da' // Da' = Sa + Da.(1 - Sa) OverNpm32toNpm32(s, d); } s++; d++; } } template void GComposite32To48(OutPx *d, InPx *s, int Len) { InPx *end = s + Len; REG uint8_t sa; while (s < end) { sa = s->a; if (sa == 255) { // Copy pixel d->r = G8bitTo16bit(s->r); d->g = G8bitTo16bit(s->g); d->b = G8bitTo16bit(s->b); } else if (sa) { // Composite pixel // Da' = Sa + Da.(1 - Sa) // Dc' = (Sc.Sa + Dc.Da.(1 - Sa)) / Da' OverNpm32toNpm48(s, d); } s++; d++; } } template void GComposite32To64(OutPx *d, InPx *s, int Len) { InPx *end = s + Len; REG uint8_t sa; while (s < end) { sa = s->a; if (sa == 255) { // Copy pixel d->r = G8bitTo16bit(s->r); d->g = G8bitTo16bit(s->g); d->b = G8bitTo16bit(s->b); d->a = G8bitTo16bit(sa); } else if (sa) { // Composite pixel // Dc' = (Sca + Dc.Da.(1 - Sa)) / Da' // Da' = Sa + Da.(1 - Sa) OverNpm32toNpm64(s, d); } s++; d++; } } template void GComposite32to24(OutPx *d, InPx *s, int Len) { REG OutPx *dst = d; REG InPx *src = s; REG InPx *end = src + Len; - uint8 *DivLut = Div255Lut; - REG uint8 sa; + uint8_t *DivLut = Div255Lut; + REG uint8_t sa; while (src < end) { sa = src->a; if (sa == 255) { // Copy pixel dst->r = src->r; dst->g = src->g; dst->b = src->b; } else if (sa) { // Composite pixel // Dc' = (Sc.Sa + Dc.Da.(1 - Sa)) / Da' // Da' = Sa + Da.(1 - Sa) - REG uint8 o = 0xff - sa; + REG uint8_t o = 0xff - sa; #define NonPreMul24(c) \ dst->c = DivLut[(src->c * sa) + (dst->c * o)] NonPreMul24(r); NonPreMul24(g); NonPreMul24(b); } src++; dst++; } } template void GComposite64To15(DstPx *dst, SrcPx *src, int px) { REG DstPx *d = dst; REG SrcPx *s = src; REG int Px = px; REG uint8_t *DivLut = Div255Lut; OverlapCheck() while (Px--) { REG GRgb24 dst = { G5bitTo8bit(d->r), G5bitTo8bit(d->g), G5bitTo8bit(d->b) }; OverNpm64toNpm24(s, &dst); d->r = dst.r >> 3; d->g = dst.g >> 3; d->b = dst.b >> 3; s++; d++; } } template void GComposite64To16(DstPx *dst, SrcPx *src, int px) { REG DstPx *d = dst; REG SrcPx *s = src; REG int Px = px; REG uint8_t *DivLut = Div255Lut; OverlapCheck() while (Px--) { REG GRgb24 dst = { G5bitTo8bit(d->r), G6bitTo8bit(d->g), G5bitTo8bit(d->b) }; OverNpm64toNpm24(s, &dst); d->r = dst.r >> 3; d->g = dst.g >> 2; d->b = dst.b >> 3; s++; d++; } } template void GComposite64To24(DstPx *dst, SrcPx *src, int px) { REG DstPx *d = dst; REG SrcPx *s = src; REG int Px = px; REG uint8_t *DivLut = Div255Lut; OverlapCheck() while (Px--) { OverNpm64toNpm24(s, d); s++; d++; } } template void GComposite64To32(DstPx *dst, SrcPx *src, int px) { REG DstPx *d = dst; REG SrcPx *s = src; REG int Px = px; REG uint8_t *DivLut = Div255Lut; OverlapCheck() while (Px--) { OverNpm64toNpm32(s, d); s++; d++; } } template void GComposite64To48(DstPx *dst, SrcPx *src, int px) { REG DstPx *d = dst; REG SrcPx *s = src; REG int Px = px; OverlapCheck() while (Px--) { OverNpm64toNpm48(s, d); s++; d++; } } template void GComposite64To64(DstPx *dst, SrcPx *src, int px) { REG DstPx *d = dst; REG SrcPx *s = src; REG int Px = px; OverlapCheck() while (Px--) { OverNpm64toNpm64(s, d); s++; d++; } } -#endif \ No newline at end of file +#endif diff --git a/include/common/GStringClass.h b/include/common/GStringClass.h --- a/include/common/GStringClass.h +++ b/include/common/GStringClass.h @@ -1,1187 +1,1187 @@ /* * A mis-guided attempt to make a string class look and feel like a python string. * * Author: Matthew Allen * Email: fret@memecode.com * Created: 16 Sept 2014 */ #ifndef _GSTRING_CLASS_H_ #define _GSTRING_CLASS_H_ #include #include #include #ifdef _MSC_VER // This fixes compile errors in VS2008/Gtk #undef _SIGN_DEFINED #undef abs #endif #include #if defined(_MSC_VER) && _MSC_VER < 1800/*_MSC_VER_VS2013*/ #include #define PRId64 "I64i" #else #define __STDC_FORMAT_MACROS 1 #include #include #ifndef PRId64 #warning "PRId64 not defined." #define PRId64 "Ld" #endif #endif #include "GUnicode.h" #include "GArray.h" #include "LgiClass.h" #ifndef IsDigit #define IsDigit(ch) ((ch) >= '0' && (ch) <= '9') #endif LgiExtern int LgiPrintf(class GString &Str, const char *Format, va_list &Arg); /// A pythonic string class. class GString { protected: /// This structure holds the string's data itself and is shared /// between one or more GString instances. struct RefStr { /// A reference count int32 Refs; /// The bytes in 'Str' not including the NULL terminator size_t Len; /// The first byte of the string. Further bytes are allocated /// off the end of the structure using malloc. This must always /// be the last element in the struct. char Str[1]; } *Str; inline void _strip(GString &ret, const char *set, bool left, bool right) { if (!Str) return; char *s = Str->Str; char *e = s + Str->Len; if (!set) set = " \t\r\n"; if (left) { while (s < e && strchr(set, *s)) s++; } if (right) { while (e > s && strchr(set, e[-1])) e--; } if (e > s) ret.Set(s, e - s); } public: #ifdef LGI_UNIT_TESTS static int32 RefStrCount; #endif /// A copyable array of strings class Array : public GArray { public: Array(size_t PreAlloc = 0) : GArray(PreAlloc) {} Array(const Array &a) { *this = (Array&)a; } Array &operator =(const Array &a) { SetFixedLength(false); *((GArray*)this) = a; SetFixedLength(true); return *this; } }; /// Empty constructor GString() { Str = NULL; } // This odd looking constructor allows the object to be used as the value type // in a GHashTable, where the initialiser is '0', an integer. GString(int i) { Str = NULL; } #ifndef _MSC_VER // This odd looking constructor allows the object to be used as the value type // in a GHashTable, where the initialiser is '0', an integer. GString(long int i) { Str = NULL; } #endif /// String constructor GString(const char *str, ptrdiff_t bytes) { Str = NULL; Set(str, bytes); } /// const char* constructor GString(const char *str) { Str = NULL; Set(str); } /// const char16* constructor GString(const wchar_t *str, ptrdiff_t wchars = -1) { Str = NULL; SetW(str, wchars); } #if defined(_WIN32) || defined(MAC) /// const uint32* constructor GString(const uint32_t *str, ptrdiff_t chars = -1) { Str = NULL; if (chars < 0) chars = Strlen(str); ptrdiff_t utf_len = 0; const uint32_t *end = str + chars; const uint32_t *c = str; while (c < end) { uint8_t utf[6], *u = utf; ssize_t len = sizeof(utf); if (!LgiUtf32To8(*c++, u, len)) break; utf_len += u - utf; } if (Length((uint32_t)utf_len)) { c = str; uint8_t *u = (uint8_t*)Str->Str; ssize_t len = Str->Len; while (c < end) { if (!LgiUtf32To8(*c++, u, len)) break; } *u++ = 0; } } #endif /// GString constructor GString(const GString &s) { Str = s.Str; if (Str) Str->Refs++; } ~GString() { Empty(); } /// Removes a reference to the string and deletes if needed void Empty() { if (!Str) return; Str->Refs--; assert(Str->Refs >= 0); if (Str->Refs == 0) { free(Str); #ifdef LGI_UNIT_TESTS RefStrCount--; #endif } Str = NULL; } /// Returns the pointer to the string data char *Get() const { return Str ? Str->Str : NULL; } /// Sets the string to a new value bool Set ( /// Can be a pointer to string data or NULL to create an empty buffer (requires valid length) const char *str, /// Byte length of input string or -1 to copy till the NULL terminator. ptrdiff_t bytes = -1 ) { Empty(); if (bytes < 0) { if (str) bytes = strlen(str); else return false; } Str = (RefStr*)malloc(sizeof(RefStr) + bytes); if (!Str) return false; Str->Refs = 1; Str->Len = (uint32_t)bytes; #ifdef LGI_UNIT_TESTS RefStrCount++; #endif if (str) memcpy(Str->Str, str, bytes); Str->Str[bytes] = 0; return true; } /// Sets the string to a new value bool SetW ( /// Can be a pointer to string data or NULL to create an empty buffer (requires valid length) const wchar_t *str, /// Number of 'char16' values in the input string or -1 to copy till the NULL terminator. ptrdiff_t wchars = -1 ) { size_t Sz = WideToUtf8Len(str, wchars); if (Length(Sz)) { #ifdef _MSC_VER const uint16 *i = (const uint16*) str; ssize_t InLen = wchars >= 0 ? wchars << 1 : 0x7fffffff; assert(sizeof(*i) == sizeof(*str)); uint8_t *o = (uint8_t*)Str->Str; ssize_t OutLen = Str->Len; for (uint32_t ch; ch = LgiUtf16To32(i, InLen); ) { if (!LgiUtf32To8(ch, o, OutLen)) { *o = 0; break; } } #else - uint8 *o = (uint8*)Str->Str; + uint8_t *o = (uint8_t*)Str->Str; ssize_t OutLen = Str->Len; if (wchars >= 0) { const wchar_t *end = str + wchars; for (const wchar_t *ch = str; ch < end; ch++) { if (!LgiUtf32To8(*ch, o, OutLen)) { *o = 0; break; } } } else { for (const wchar_t *ch = str; *ch; ch++) { if (!LgiUtf32To8(*ch, o, OutLen)) { *o = 0; break; } } } #endif *o = 0; } return true; } /// Equality operator (case sensitive) bool operator ==(const GString &s) { const char *a = Get(); const char *b = s.Get(); if (!a && !b) return true; if (!a || !b) return false; return !strcmp(a, b); } bool operator !=(const GString &s) { return !(*this == s); } // Equality function (default: case insensitive, as the operator== is case sensitive) bool Equals(const char *b, bool CaseInsensitive = true) const { const char *a = Get(); if (!a && !b) return true; if (!a || !b) return false; return !(CaseInsensitive ? _stricmp(a, b) : strcmp(a, b)); } // Equality function (default: case insensitive, as the operator== is case sensitive) bool Equals(const GString &s, bool CaseInsensitive = true) const { const char *a = Get(); const char *b = s.Get(); if (!a && !b) return true; if (!a || !b) return false; return !(CaseInsensitive ? _stricmp(a, b) : strcmp(a, b)); } /// Assignment operator to copy one string to another GString &operator =(const GString &s) { if (this != &s) { Empty(); Str = s.Str; if (Str) Str->Refs++; } return *this; } /// Equality with a C string (case sensitive) bool operator ==(const char *b) { const char *a = Get(); if (!a && !b) return true; if (!a || !b) return false; return !strcmp(a, b); } bool operator !=(const char *b) { return !(*this == b); } /// Assignment operators GString &operator =(const char *s) { if (Str == NULL || s < Str->Str || s > Str->Str + Str->Len) { Empty(); Set(s); } else if (s != Str->Str) { // Special case for setting it to part of itself // If you try and set a string to the start, it's a NOP ptrdiff_t Off = s - Str->Str; memmove(Str->Str, s, Str->Len - Off + 1); Str->Len -= (uint32_t)Off; } return *this; } GString &operator =(const wchar_t *s) { SetW(s); return *this; } GString &operator =(int val) { char n[32]; sprintf_s(n, sizeof(n), "%i", val); Set(n); return *this; } GString &operator =(int64 val) { char n[32]; sprintf_s(n, sizeof(n), "%" PRId64, (int64_t)val); Set(n); return *this; } /// Cast to C string operator operator char *() { return Str ? Str->Str : NULL; } operator const char *() const { return Str ? Str->Str : NULL; } /// Concatenation operator GString operator +(const GString &s) { GString Ret; size_t Len = Length() + s.Length(); if (Ret.Set(NULL, Len)) { char *p = Ret.Get(); if (p) { if (Str) { memcpy(p, Str->Str, Str->Len); p += Str->Len; } if (s.Str) { memcpy(p, s.Str->Str, s.Str->Len); p += s.Str->Len; } *p++ = 0; } } return Ret; } /// Concatenation / assignment operator GString &operator +=(const GString &s) { ssize_t Len = Length() + s.Length(); ssize_t Alloc = sizeof(RefStr) + Len; RefStr *rs = (RefStr*)malloc(Alloc); if (rs) { rs->Refs = 1; rs->Len = Len; #ifdef LGI_UNIT_TESTS RefStrCount++; #endif char *p = rs->Str; if (Str) { memcpy(p, Str->Str, Str->Len); p += Str->Len; } if (s.Str) { memcpy(p, s.Str->Str, s.Str->Len); p += s.Str->Len; } *p++ = 0; assert(p - (char*)rs <= Alloc); Empty(); Str = rs; } return *this; } /// Gets the length in bytes size_t Length() const { return Str ? Str->Len : 0; } size_t Length(size_t NewLen) { if (Str) { if (NewLen < Str->Len) { Str->Len = NewLen; Str->Str[NewLen] = 0; } else { RefStr *n = (RefStr*)malloc(sizeof(RefStr) + NewLen); if (n) { n->Len = NewLen; n->Refs = 1; memcpy(n->Str, Str->Str, Str->Len); n->Str[Str->Len] = 0; // NULL terminate... Empty(); // Deref the old string... Str = n; } else return 0; } } else { Str = (RefStr*)malloc(sizeof(RefStr) + NewLen); if (Str) { Str->Len = NewLen; Str->Refs = 1; Str->Str[0] = 0; // NULL terminate... } else return 0; } return Str->Len; } /// Splits the string into parts using a separator Array Split(const char *Sep, int Count = -1, bool CaseSen = false) { Array a; if (Str && Sep) { const char *s = Str->Str, *Prev = s; const char *end = s + Str->Len; size_t SepLen = strlen(Sep); if (s[Str->Len] == 0) { while ((s = CaseSen ? strstr(s, Sep) : Stristr(s, Sep))) { if (s > Prev) a.New().Set(Prev, s - Prev); s += SepLen; Prev = s; if (Count > 0 && a.Length() >= (uint32_t)Count) break; } if (Prev < end) a.New().Set(Prev, end - Prev); a.SetFixedLength(); } else assert(!"String not NULL terminated."); } return a; } /// Splits the string into parts using a separator Array RSplit(const char *Sep, int Count = -1, bool CaseSen = false) { Array a; if (Str && Sep) { const char *s = Get(); size_t SepLen = strlen(Sep); GArray seps; while ((s = CaseSen ? strstr(s, Sep) : Stristr(s, Sep))) { seps.Add(s); s += SepLen; } ssize_t i, Last = seps.Length() - 1; GString p; for (i=Last; i>=0; i--) { const char *part = seps[i] + SepLen; if (i == Last) p.Set(part); else p.Set(part, seps[i+1]-part); a.AddAt(0, p); if (Count > 0 && a.Length() >= (uint32_t)Count) break; } const char *End = seps[i > 0 ? i : 0]; p.Set(Get(), End - Get()); a.AddAt(0, p); } a.SetFixedLength(); return a; } /// Splits the string into parts using delimiter chars Array SplitDelimit(const char *Delimiters = NULL, int Count = -1, bool GroupDelimiters = true) const { Array a; if (Str) { const char *delim = Delimiters ? Delimiters : " \t\r\n"; const char *s = Get(); while (*s) { // Skip over non-delimiters const char *e = s; while (*e && !strchr(delim, *e)) e++; if (e > s || !GroupDelimiters) a.New().Set(s, e - s); s = e; if (*s) s++; if (GroupDelimiters) { // Skip any delimiters while (*s && strchr(delim, *s)) s++; } // Create the string if (Count > 0 && a.Length() >= (uint32_t)Count) break; } if ( *s || ( !GroupDelimiters && s > Get() && strchr(delim, s[-1]) ) ) a.New().Set(s); } a.SetFixedLength(); return a; } /// Joins an array of strings using a separator GString Join(Array &a) { GString ret; if (a.Length() == 0) return ret; char *Sep = Get(); size_t SepLen = Sep ? strlen(Sep) : 0; size_t Bytes = SepLen * (a.Length() - 1); GArray ALen; for (unsigned i=0; iStr; GArray Matches; while ((Match = (CaseSen ? strstr(Match, Old) : Stristr(Match, Old)))) { Matches.Add(Match); if (Count >= 0 && (int)Matches.Length() >= Count) break; Match += OldLen; } size_t NewSize = Str->Len + (Matches.Length() * (NewLen - OldLen)); s.Length((uint32_t)NewSize); char *Out = s.Get(); char *In = Str->Str; // For each match... for (unsigned i=0; iStr + Str->Len; if (In < End) { ptrdiff_t Bytes = End - In; memcpy(Out, In, Bytes); Out += Bytes; } assert(Out - s.Get() == NewSize); // Check we got the size right... *Out = 0; // Null terminate } else { s = *this; } return s; } /// Convert string to double double Float() { return Str ? atof(Str->Str) : NAN; } /// Convert to integer int64 Int(int Base = 10) { if (!Str) return -1; if ( Str->Len > 2 && Str->Str[0] == '0' && ( Str->Str[1] == 'x' || Str->Str[1] == 'X' ) ) { return Atoi(Str->Str+2, 16); } return Atoi(Str->Str, Base); } /// Checks if the string is a number bool IsNumeric() { if (!Str) return false; for (char *s = Str->Str; *s; s++) { if (!IsDigit(*s) && !strchr("e-+.", *s)) return false; } return true; } /// Reverses all the characters in the string GString Reverse() { GString s; if (Length() > 0) { s = Str->Str; for (char *a = s, *b = s.Get() + s.Length() - 1; a < b; a++, b--) { char t = *a; *a = *b; *b = t; } } return s; } /// Find a sub-string ssize_t Find(const char *needle, ssize_t start = 0, ssize_t end = -1) { if (!needle) return -1; char *c = Get(); if (!c) return -1; char *pos = c + start; while (c < pos) { if (!*c) return -1; c++; } char *found = (end > 0) ? Strnstr(c, needle, end - start) : strstr(c, needle); return (found) ? found - Get() : -1; } /// Reverse find a string (starting from the end) ssize_t RFind(const char *needle, int start = 0, ssize_t end = -1) { if (!needle) return -1; char *c = Get(); if (!c) return -1; char *pos = c + start; while (c < pos) { if (!*c) return -1; c++; } char *found, *prev = NULL; size_t str_len = strlen(needle); while (( found = ( (end > 0) ? Strnstr(c, needle, end - start) : strstr(c, needle) ) )) { prev = found; c = found + str_len; } return (prev) ? prev - Get() : -1; } /// Returns a copy of the string with all the characters converted to lower case GString Lower() { GString s; if (Str && s.Set(Str->Str, Str->Len)) Strlwr(s.Get()); return s; } /// Returns a copy of the string with all the characters converted to upper case GString Upper() { GString s; if (Str && s.Set(Str->Str, Str->Len)) Strupr(s.Get()); return s; } void Swap(GString &s) { LSwap(Str, s.Str); } /// Gets the character at 'index' int operator() (ssize_t index) const { if (!Str) return 0; char *c = Str->Str; if (index < 0) { size_t idx = Str->Len + index; return c[idx]; } else if (index < (int)Str->Len) { return c[index]; } return 0; } /// Gets the string between at 'start' and 'end' (not including the end'th character) GString operator() (ptrdiff_t start, ptrdiff_t end) { GString s; if (Str) { ptrdiff_t start_idx = start < 0 ? Str->Len + start + 1 : start; if (start_idx >= 0 && (uint32_t)start_idx < Str->Len) { ptrdiff_t end_idx = end < 0 ? Str->Len + end + 1 : end; if (end_idx >= start_idx && (uint32_t)end_idx <= Str->Len) s.Set(Str->Str + start_idx, end_idx - start_idx); } } return s; } /// Strip off any leading and trailing whitespace GString Strip(const char *set = NULL) { GString ret; _strip(ret, set, true, true); return ret; } /// Strip off any leading whitespace GString LStrip(const char *set = NULL) { GString ret; _strip(ret, set, true, false); return ret; } /// Strip off any trailing whitespace GString RStrip(const char *set = NULL) { GString ret; _strip(ret, set, false, true); return ret; } /// Prints a formatted string to this object int Printf(const char *Fmt, ...) { Empty(); va_list Arg; va_start(Arg, Fmt); int Bytes = Printf(Arg, Fmt); va_end(Arg); return Bytes; } /// Prints a varargs string int Printf(va_list &Arg, const char *Fmt) { Empty(); return LgiPrintf(*this, Fmt, Arg); } static GString Escape(const char *In, ssize_t Len = -1, const char *Chars = "\r\n\b\\\'\"") { GString s; if (In && Chars) { char Buf[256]; int Ch = 0; if (Len < 0) Len = strlen(In); while (Len-- > 0) { if (Ch > sizeof(Buf)-4) { // Buffer full, add substring to 's' Buf[Ch] = 0; s += Buf; Ch = 0; } if (strchr(Chars, *In)) { Buf[Ch++] = '\\'; switch (*In) { #undef EscChar #define EscChar(from, to) \ case from: Buf[Ch++] = to; break EscChar('\n', 'n'); EscChar('\r', 'r'); EscChar('\\', '\\'); EscChar('\b', 'b'); EscChar('\a', 'a'); EscChar('\t', 't'); EscChar('\v', 'v'); EscChar('\'', '\''); EscChar('\"', '\"'); EscChar('?', '?'); #undef EscChar default: Ch += sprintf_s(Buf+Ch, sizeof(Buf)-Ch, "x%02x", *In); break; } } else Buf[Ch++] = *In; In++; } if (Ch > 0) { Buf[Ch] = 0; s += Buf; } } return s; } template static GString UnEscape(const T *In, ssize_t Len = -1) { GString s; if (In) { T Buf[256]; int Ch = 0; const T *End = Len >= 0 ? In + Len : NULL; while ( (!End || In < End) && *In ) { if (Ch > sizeof(Buf)-4) { // Buffer full, add substring to 's' Buf[Ch] = 0; s += Buf; Ch = 0; } if (*In == '\\') { In++; switch (*In) { case 'n': case 'N': Buf[Ch++] = '\n'; break; case 'r': case 'R': Buf[Ch++] = '\r'; break; case 'b': case 'B': Buf[Ch++] = '\b'; break; case 't': case 'T': Buf[Ch++] = '\t'; break; default: Buf[Ch++] = *In; break; case 0: break; } if (*In) In++; else break; } else Buf[Ch++] = *In++; } if (Ch > 0) { Buf[Ch] = 0; s += Buf; } } return s; } static GString UnEscape(GString s) { return UnEscape(s.Get(), s.Length()); } #if defined(MAC) // && __COREFOUNDATION_CFBASE__ GString(const CFStringRef r) { Str = NULL; *this = r; } GString &operator =(CFStringRef r) { CFIndex length = CFStringGetLength(r); CFRange range = CFRangeMake(0, length); CFIndex usedBufLen = 0; CFIndex slen = CFStringGetBytes(r, range, kCFStringEncodingUTF8, '?', false, NULL, 0, &usedBufLen); if (Set(NULL, usedBufLen)) { slen = CFStringGetBytes( r, range, kCFStringEncodingUTF8, '?', false, (UInt8*)Str->Str, Str->Len, &usedBufLen); Str->Str[usedBufLen] = 0; // NULL terminate } return *this; } CFStringRef CreateStringRef() { char *s = Get(); if (!s) return NULL; return CFStringCreateWithCString(kCFAllocatorDefault, s, kCFStringEncodingUTF8); } #ifdef __OBJC__ NSString *NsStr() { if (Str) return [[NSString alloc] initWithBytes:Str->Str length:Str->Len encoding:NSUTF8StringEncoding]; return nil; } #endif #endif }; #endif diff --git a/include/common/GUnicode.h b/include/common/GUnicode.h --- a/include/common/GUnicode.h +++ b/include/common/GUnicode.h @@ -1,870 +1,870 @@ // // GUnicode.h // // Created by Matthew Allen on 1/08/15. // #ifndef _GUnicode_h #define _GUnicode_h #include "LgiInc.h" #ifdef MAC #define REG #else #define REG register #endif typedef unsigned char uint8_t; typedef signed char int8; typedef signed short int16; typedef unsigned short uint16; typedef signed int int32; typedef unsigned int uint32_t; #ifdef _MSC_VER typedef signed __int64 int64; typedef unsigned __int64 uint64; #ifdef _WIN64 typedef signed __int64 ssize_t; #else typedef signed int ssize_t; #endif #else typedef signed long long int64; typedef unsigned long long uint64; #endif // Defines for decoding UTF8 #define IsUtf8_1Byte(c) ( ((uint8_t)(c) & 0x80) == 0x00 ) #define IsUtf8_2Byte(c) ( ((uint8_t)(c) & 0xe0) == 0xc0 ) #define IsUtf8_3Byte(c) ( ((uint8_t)(c) & 0xf0) == 0xe0 ) #define IsUtf8_4Byte(c) ( ((uint8_t)(c) & 0xf8) == 0xf0 ) #define IsUtf8_Lead(c) ( ((uint8_t)(c) & 0xc0) == 0xc0 ) #define IsUtf8_Trail(c) ( ((uint8_t)(c) & 0xc0) == 0x80 ) // Stand-alone functions /// Convert a single utf-8 char to utf-32 or returns -1 on error. inline int32 LgiUtf8To32(uint8_t *&i, ssize_t &Len) { int32 Out = 0; #define InvalidUtf() { Len--; i++; return -1; } if (Len > 0) { if (!*i) { Len = 0; return 0; } if (IsUtf8_1Byte(*i)) { // 1 byte UTF-8 Len--; return *i++; } else if (IsUtf8_2Byte(*i)) { // 2 byte UTF-8 if (Len > 1) { Out = ((int)(*i++ & 0x1f)) << 6; Len--; if (IsUtf8_Trail(*i)) { Out |= *i++ & 0x3f; Len--; } else InvalidUtf() } } else if (IsUtf8_3Byte(*i)) { // 3 byte UTF-8 if (Len > 2) { Out = ((int)(*i++ & 0x0f)) << 12; Len--; if (IsUtf8_Trail(*i)) { Out |= ((int)(*i++ & 0x3f)) << 6; Len--; if (IsUtf8_Trail(*i)) { Out |= *i++ & 0x3f; Len--; } else InvalidUtf() } else InvalidUtf() } } else if (IsUtf8_4Byte(*i)) { // 4 byte UTF-8 if (Len > 3) { Out = ((int)(*i++ & 0x07)) << 18; Len--; if (IsUtf8_Trail(*i)) { Out |= ((int)(*i++ & 0x3f)) << 12; Len--; if (IsUtf8_Trail(*i)) { Out |= ((int)(*i++ & 0x3f)) << 6; Len--; if (IsUtf8_Trail(*i)) { Out |= *i++ & 0x3f; Len--; } else InvalidUtf() } else InvalidUtf() } else InvalidUtf() } } else InvalidUtf() } return Out; } /// Convert a single utf-32 char to utf-8 inline bool LgiUtf32To8(uint32_t c, uint8_t *&i, ssize_t &Len) { if ((c & ~0x7f) == 0) { if (Len > 0) { *i++ = c; Len--; return true; } } else if ((c & ~0x7ff) == 0) { if (Len > 1) { *i++ = 0xc0 | (c >> 6); *i++ = 0x80 | (c & 0x3f); Len -= 2; return true; } } else if ((c & 0xffff0000) == 0) { if (Len > 2) { *i++ = 0xe0 | (c >> 12); *i++ = 0x80 | ((c & 0x0fc0) >> 6); *i++ = 0x80 | (c & 0x3f); Len -= 3; return true; } } else { if (Len > 3) { *i++ = 0xf0 | (c >> 18); *i++ = 0x80 | ((c&0x3f000) >> 12); *i++ = 0x80 | ((c&0xfc0) >> 6); *i++ = 0x80 | (c&0x3f); Len -= 4; return true; } } return false; } // Defined for decoding UTF16 #define IsUtf16_Lead(c) ( ((uint16)(c) & 0xfc00) == 0xD800 ) #define IsUtf16_Trail(c) ( ((uint16)(c) & 0xfc00) == 0xDc00 ) /// Convert a single utf-16 char to utf-32 inline uint32_t LgiUtf16To32(const uint16_t *&i, ssize_t &Len) { if (Len > 1) { if (!*i) { Len = 0; return 0; } int n = *i & 0xfc00; if (n == 0xd800 || n == 0xdc00) { // 2 word UTF if (Len > 3) { Len -= sizeof(uint16)<<1; int w = (*i & 0x3c0) >> 6; int zy = *i++ & 0x3f; return ((w + 1) << 16) | (zy << 10) | (*i++ & 0x3ff); } } // 1 word UTF Len -= sizeof(uint16); return *i++; } return 0; } /// Convert a single utf-32 char to utf-16 inline bool LgiUtf32To16(uint32_t c, uint16_t *&i, ssize_t &Len) { if (c >= 0x10000) { // 2 word UTF if (Len < 4) return false; int w = c - 0x10000; *i++ = 0xd800 + (w >> 10); *i++ = 0xdc00 + (c & 0x3ff); Len -= sizeof(*i) << 1; } else { if (Len < 2) return false; if (c > 0xD7FF && c < 0xE000) return false; // 1 word UTF *i++ = c; Len -= sizeof(*i); return true; } return false; } /// Seeks the pointer 'Ptr' to the next utf-8 character inline bool LgiNextUtf8(char *&p) { char *old = p; if (IsUtf8_Lead(*p)) { p++; while (IsUtf8_Trail(*p)) p++; } else p++; return p > old; } /// Seeks the pointer 'Ptr' to the previous utf-8 character inline void LgiPrevUtf8(char *&p) { p--; while (IsUtf8_Trail(*p)) p--; } /// Pointer to utf-8 string class LgiClass GUtf8Ptr { protected: uint8_t *Ptr; public: GUtf8Ptr(const void *p = 0); /// Assign a new pointer to the string GUtf8Ptr &operator =(char *s) { Ptr = (uint8_t*)s; return *this; } /// Assign a new pointer to the string GUtf8Ptr &operator =(uint8_t *s) { Ptr = s; return *this; } /// \returns the current character in the string or -1 on error. operator int32(); /// Seeks forward GUtf8Ptr &operator ++(); GUtf8Ptr &operator ++(const int i); GUtf8Ptr &operator +=(const ssize_t n); /// Seeks 1 character backward GUtf8Ptr &operator --(); GUtf8Ptr &operator --(const int i); GUtf8Ptr &operator -=(const ssize_t n); // Comparison bool operator <(const GUtf8Ptr &p) { return Ptr < p.Ptr; } bool operator <=(const GUtf8Ptr &p) { return Ptr <= p.Ptr; } bool operator >(const GUtf8Ptr &p) { return Ptr > p.Ptr; } bool operator >=(const GUtf8Ptr &p) { return Ptr >= p.Ptr; } bool operator ==(const GUtf8Ptr &p) { return Ptr == p.Ptr; } bool operator !=(const GUtf8Ptr &p) { return Ptr != p.Ptr; } ptrdiff_t operator -(const GUtf8Ptr &p) { return Ptr - p.Ptr; } /// Gets the bytes between the cur pointer and the end of the buffer or string. int GetBytes(); /// Gets the characters between the cur pointer and the end of the buffer or string. int GetChars(); /// Encodes a utf-8 char at the current location and moves the pointer along void Add(wchar_t c); /// Returns the current pointer. uint8_t *GetPtr() { return Ptr; } }; /// Unicode string class. Allows traversing a utf-8 strings. class LgiClass GUtf8Str : public GUtf8Ptr { // Complete buffer uint8_t *Start; uint8_t *End; GUtf8Ptr Cur; bool Own; void Empty(); public: /// Constructor GUtf8Str ( /// The string pointer to start with char *utf, /// The number of bytes containing characters, or -1 if NULL terminated. int bytes = -1, /// Copy the string first bool Copy = false ); /// Constructor GUtf8Str ( /// The string pointer to start with. A utf-8 copy of the string will be created. wchar_t *wide, /// The number of wide chars, or -1 if NULL terminated. int chars = -1 ); ~GUtf8Str(); /// Assign a new pointer to the string GUtf8Str &operator =(char *s); /// Allocates a block of memory containing the wide representation of the string. wchar_t *ToWide(); /// \returns true if the class seems to be valid. bool Valid(); /// \returns true if at the start bool IsStart(); /// \returns true if at the end bool IsEnd(); }; // Converts character to lower case template T Tolower(T ch) { if (ch >= 'A' && ch <= 'Z') return ch - 'A' + 'a'; return ch; } template T *Strlwr(T *Str) { if (!Str) return NULL; for (T *s = Str; *s; s++) { if (*s >= 'A' && *s <= 'Z') *s = *s - 'A' + 'a'; } return Str; } // Converts character to upper case template T Toupper(T ch) { if (ch >= 'a' && ch <= 'z') return ch - 'a' + 'A'; return ch; } template T *Strupr(T *Str) { if (!Str) return NULL; for (T *s = Str; *s; s++) { if (*s >= 'a' && *s <= 'z') *s = *s - 'a' + 'A'; } return Str; } // Finds the length of the string in characters template ssize_t Strlen(const T *str) { if (!str) return 0; REG const T *s = str; while (*s) s++; return s - str; } // Templated version of NewStr/NewStrW // Duplicates a string in heap memory. template T *Strdup(const T *s, ssize_t len = -1) { if (!s) return NULL; if (len < 0) len = Strlen(s); T *n = new T[len+1]; if (!n) return NULL; memcpy(n, s, sizeof(T) * len); n[len] = 0; return n; } // Compares two strings, case sensitive template int Strcmp(const T *str_a, const T *str_b) { if (!str_a || !str_b) return -1; REG const T *a = str_a; REG const T *b = str_b; while (true) { if (!*a || !*b || *a != *b) return *a - *b; a++; b++; } return 0; } // Compares the first 'len' chars of two strings, case sensitive template int Strncmp(const T *str_a, const T *str_b, ssize_t len) { if (!str_a || !str_b) return -1; REG const T *a = str_a; REG const T *b = str_b; REG const T *end = a + len; while (a < end) { if (!*a || !*b || *a != *b) return *a - *b; a++; b++; } return 0; } // Compares two strings, case insensitive template int Stricmp(const T *str_a, const T *str_b) { if (!str_a || !str_b) return -1; REG const T *a = str_a; REG const T *b = str_b; REG T ach, bch; while (true) { ach = Tolower(*a); bch = Tolower(*b); if (!ach || !bch || ach != bch) return ach - bch; a++; b++; } return 0; } // Compares the first 'len' chars of two strings, case insensitive template int Strnicmp(const T *str_a, const T *str_b, ssize_t len) { if (!str_a || !str_b || len == 0) return -1; REG const T *a = str_a; REG const T *b = str_b; REG const T *end = a + len; REG T ach, bch; while (a < end) { ach = Tolower(*a); bch = Tolower(*b); if (!ach || !bch || ach != bch) return ach - bch; a++; b++; } return 0; } /// Copies a string template T *Strcpy(T *dst, ssize_t dst_len, const I *src) { if (!dst || !src || dst_len == 0) return NULL; REG T *d = dst; REG T *end = d + dst_len - 1; // leave 1 char for NULL terminator REG const I *s = src; while (d < end && *s) { *d++ = *s++; } *d = 0; // NULL terminate return dst; } /// Finds the first instance of a character in the string template T *Strchr(T *str, int ch) { if (!str) return NULL; for (REG T *s = str; *s; s++) { if (*s == ch) return s; } return NULL; } /// Finds the first instance of a character in the string template T *Strnchr(T *str, int ch, size_t len) { if (!str || len == 0) return NULL; REG T *e = str + len; for (REG T *s = str; s < e; s++) { if (*s == ch) return s; } return NULL; } /// Finds the last instance of a character in the string template T *Strrchr(T *str, int ch) { if (!str) return NULL; T *last = NULL; for (REG T *s = str; *s; s++) { if (*s == ch) last = s; } return last; } /// Appends a string to another template T *Strcat(T *dst, int dst_len, const T *postfix) { if (!dst || !postfix || dst_len < 1) return NULL; // Find the end of the string to append to while (*dst) { dst++; dst_len--; } // Reuse string copy at this point Strcpy(dst, dst_len, postfix); // Return the start of the complete string return dst; } /// Searches the string 'Data' for the 'Value' in a case insensitive manner template T *Stristr(const T *Data, const T *Value) { if (!Data || !Value) return NULL; const T v = Tolower(*Value); while (*Data) { if (Tolower(*Data) == v) { int i; for (i=1; Data[i] && Tolower(Data[i]) == Tolower(Value[i]); i++) ; if (Value[i] == 0) return (T*)Data; } Data++; } return NULL; } /// Searches the string 'Data' for the 'Value' in a case insensitive manner template T *Strstr(const T *Data, const T *Value) { if (!Data || !Value) return NULL; const T v = *Value; while (*Data) { if (*Data == v) { int i; for (i=1; Data[i] && Data[i] == Value[i]; i++) ; if (Value[i] == 0) return (T*)Data; } Data++; } return NULL; } /// Searches the string 'Data' for the 'Value' in a case insensitive manner template T *Strnstr(const T *Data, const T *Value, ssize_t DataLen) { if (!Data || !Value) return NULL; const T v = *Value; ptrdiff_t ValLen = Strlen(Value); if (ValLen > DataLen) return NULL; while (*Data && DataLen >= ValLen) { if (*Data == v) { int i; for (i=1; Data[i] && Data[i] == Value[i]; i++) ; if (Value[i] == 0) return (T*)Data; } Data++; DataLen--; } return NULL; } /// Searches the string 'Data' for the 'Value' in a case insensitive manner template T *Strnistr(const T *Data, const T *Value, ptrdiff_t DataLen) { if (!Data || !Value) return NULL; const T v = Tolower(*Value); ptrdiff_t ValLen = Strlen(Value); if (ValLen > DataLen) return NULL; while (*Data && DataLen >= ValLen) { if (Tolower(*Data) == v) { int i; for (i=1; Data[i] && Tolower(Data[i]) == Tolower(Value[i]); i++) ; if (Value[i] == 0) return (T*)Data; } Data++; DataLen--; } return NULL; } /// Converts a string to int64 (base 10) template int64 Atoi(const T *s, int Base = 10, int64 DefaultValue = -1) { if (!s) return DefaultValue; bool Minus = false; if (*s == '-') { Minus = true; s++; } else if (*s == '+') s++; int64 v = 0; const T *Start = s; if (Base <= 10) { while (*s >= '0' && *s <= '9') { int d = *s - '0'; v *= Base; v += d; s++; } } else { if (*s == '0' && Tolower(s[1]) == 'x') s += 2; int ValidChars = Base > 10 ? Base - 10 : 0; while (*s) { int d; if (*s >= '0' && *s <= '9') d = *s - '0'; else if (*s >= 'a' && *s <= 'a' + ValidChars) d = *s - 'a' + 10; else if (*s >= 'A' && *s <= 'A' + ValidChars) d = *s - 'A' + 10; else break; v *= Base; v += d; s++; } } if (s == Start) return DefaultValue; return Minus ? -v : v; } /// Works out the UTF8 length of a wide char string inline size_t WideToUtf8Len(const wchar_t *s, ssize_t wchars = -1) { if (!s) return 0; size_t Out = 0; uint8_t Buf[6]; #ifdef _MSC_VER const uint16 *i = (const uint16*) s; ssize_t Len = wchars >= 0 ? wchars << 1 : 0x7fffffff; for (uint32_t ch; ch = LgiUtf16To32(i, Len); ) { uint8_t *b = Buf; ssize_t len = sizeof(Buf); if (!LgiUtf32To8(ch, b, len)) break; Out += sizeof(Buf) - len; } #else const wchar_t *end = wchars < 0 ? NULL : s + wchars; - for (uint32 ch = 0; + for (uint32_t ch = 0; ( wchars < 0 || s < end ) && (ch = *s); s++) { - uint8 *b = Buf; + uint8_t *b = Buf; ssize_t len = sizeof(Buf); if (!LgiUtf32To8(ch, b, len)) break; Out += sizeof(Buf) - len; } #endif return Out; } /// Converts a utf-8 string into a wide character string /// \ingroup Text LgiFunc wchar_t *Utf8ToWide ( /// Input string const char *In, /// [Optional] Size of 'In' in 'chars' or -1 for NULL terminated ssize_t InLen = -1 ); /// Converts a wide character string into a utf-8 string /// \ingroup Text LgiFunc char *WideToUtf8 ( /// Input string const wchar_t *In, /// [Optional] Number of wchar_t's in the input or -1 for NULL terminated ptrdiff_t InLen = -1 ); #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/include/common/LUnicodeString.h b/include/common/LUnicodeString.h --- a/include/common/LUnicodeString.h +++ b/include/common/LUnicodeString.h @@ -1,235 +1,235 @@ #ifndef _LUNICODE_STR_H_ #define _LUNICODE_STR_H_ #include "GUnicode.h" /// Yet another attempt to abstract away the unicode nightmare. template class LUnicodeString { T *start, *cur, *next; ssize_t words, chars, alloc/*words*/; bool own; bool Alloc(ssize_t words) { if (start && own) { ssize_t c = cur - start, n = next - start; start = (T*) realloc(start, sizeof(T) * words); if (!start) return false; cur = start + c; next = start + n; } else { start = (T*) calloc(sizeof(T), words); cur = start; next = NULL; } alloc = words; own = true; return true; } uint32_t Read(const uint8_t *&s) { if (IsUtf8_1Byte(*s)) return *s++; #define Trailing(sh) \ if (!IsUtf8_Trail(*s)) return 0; \ Out |= (*s++ & 0x3f) << sh; if (IsUtf8_2Byte(*s)) { uint32_t Out = ((int)(*s++ & 0x1f)) << 6; Trailing(0); return Out; } if (IsUtf8_3Byte(*s)) { uint32_t Out = ((int)(*s++ & 0x1f)) << 12; Trailing(6); Trailing(0); return Out; } if (IsUtf8_4Byte(*s)) { uint32_t Out = ((int)(*s++ & 0x1f)) << 18; Trailing(12); Trailing(6); Trailing(0); return Out; } #undef Trailing return 0; } uint32_t Read(uint8_t *&s) { return Read((const uint8_t*&)s); } uint32_t Read(char *&s) { return Read((const uint8_t*&)s); } uint32_t Read(const uint16_t *&s) { int n = *s & 0xfc00; if (n == 0xd800 || n == 0xdc00) { int w = (*s & 0x3c0) >> 6; int zy = *s++ & 0x3f; return ((w + 1) << 16) | (zy << 10) | (*s++ & 0x3ff); } return *s++; } uint32_t Read(uint16_t *&s) { return Read((const uint16_t*&)s); } void Write(uint16_t *&s, uint32_t ch) { if (ch < 0x10000) { if (!(ch > 0xD7FF && ch < 0xE000)) { // 1 word UTF *s++ = ch; return; } LgiAssert(!"Invalid char."); return; } // 2 word UTF int w = ch - 0x10000; *s++ = 0xd800 + (w >> 10); *s++ = 0xdc00 + (ch & 0x3ff); } #ifdef WINDOWS uint32_t Read(char16 *&s) { return Read((const uint16_t*&)s); } void Write(char16 *&s, uint32_t ch) { Write((uint16_t*&)s, ch); } #else - uint32 Read(char16 *&s) { return Read((const uint32*&)s); } - void Write(char16 *&s, uint32 ch) { Write((uint32*&)s, ch); } + uint32_t Read(char16 *&s) { return Read((const uint32_t*&)s); } + void Write(char16 *&s, uint32_t ch) { Write((uint32_t*&)s, ch); } #endif uint32_t Read(const uint32_t *&s) { return *s++; } void Write(uint32_t *&s, uint32_t ch) { *s++ = ch; } void Scan() { if (chars >= 0) return; chars = 0; if (words >= 0) { // Length terminated T *End = start + words; T *s = start; while (s < End) { if (Read(s)) chars++; } } else { // NULL terminated T *s = start; while (Read(s)) chars++; words = s - start; } } void Construct(T *init, ssize_t sz) { own = false; start = init; cur = init; next = NULL; words = sz; chars = -1; alloc = 0; } public: LUnicodeString(T *init = NULL, ssize_t words = -1) { Construct(init, words); } ~LUnicodeString() { Empty(); } ssize_t Bytes() { Scan(); return words * sizeof(T); } ssize_t Words() { Scan(); return words; } ssize_t Chars() { Scan(); return chars; } T *Get() { return cur; } T *End() { Scan(); return start + words; } void Empty() { if (own) { free(start); start = NULL; own = false; } next = cur = NULL; words = alloc = 0; chars = -1; } class Iter { LUnicodeString *u; T *p; public: Iter(LUnicodeString *str, T *cur) : u(str), p(cur) {} operator uint32_t() { return u->Read(p); } Iter &operator =(uint32_t ch) { LgiAssert(u->start != NULL); u->Write(p, ch); return *this; } Iter &operator *() { return *this; } }; Iter operator *() { return Iter(this, cur); } Iter operator ++(int) { if (!start) Alloc(256); Iter old(this, cur); Read(cur); return old; } }; LgiFunc bool LUnicodeString_UnitTests(); #endif diff --git a/include/common/LgiClass.h b/include/common/LgiClass.h --- a/include/common/LgiClass.h +++ b/include/common/LgiClass.h @@ -1,347 +1,347 @@ #ifndef _LGI_CLASS_H_ #define _LGI_CLASS_H_ #include "LgiInc.h" #include "LgiDefs.h" #if defined __OBJC__ #include #endif // Virtual input classes class GKey; class GMouse; // General GUI classes class GTarget; class GComponent; class GEvent; class GId; class GApp; class GWindow; class GWin32Class; class GView; class GLayout; class GFileSelect; class GFindReplace; class GSubMenu; class GMenuItem; class GMenu; class GToolBar; class GToolButton; class GSplitter; class GStatusPane; class GStatusBar; class GToolColour; class GScrollBar; class GImageList; class GDialog; // General objects class LgiClass GBase { char *_Name8; char16 *_Name16; public: GBase(); virtual ~GBase(); virtual char *Name(); virtual bool Name(const char *n); virtual char16 *NameW(); virtual bool NameW(const char16 *n); }; #define AssignFlag(f, bit, to) if (to) f |= bit; else f &= ~(bit) /// Sets the output stream for the LgiTrace statement. By default the stream output /// is to .txt in the executables folder or $LSP_APP_ROOT\.txt if /// that is not writable. If the stream is set to something then normal file output is /// directed to the specified stream instead. LgiFunc void LgiTraceSetStream(class GStreamI *stream); /// Gets the log file path LgiFunc bool LgiTraceGetFilePath(char *LogPath, int BufLen); /// Writes a debug statement to a output stream, or if not defined with LgiTraceSetStream /// then to a log file (see LgiTraceSetStream for details) /// /// Default path is ./.txt relative to the executable. /// Fallback path is LgiGetSystemPath(LSP_APP_ROOT). LgiFunc void LgiTrace(const char *Format, ...); #ifndef LGI_STATIC /// Same as LgiTrace but writes a stack trace as well. LgiFunc void LgiStackTrace(const char *Format, ...); #endif /// General user interface event class LgiClass GUiEvent { public: int Flags; GUiEvent() { Flags = 0; } virtual ~GUiEvent() {} virtual void Trace(const char *Msg) {} /// The key or mouse button was being pressed. false on the up-click. bool Down() { return TestFlag(Flags, LGI_EF_DOWN); } /// The mouse button was double clicked. bool Double() { return TestFlag(Flags, LGI_EF_DOUBLE); } /// A ctrl button was held down during the event bool Ctrl() { return TestFlag(Flags, LGI_EF_CTRL); } /// A alt button was held down during the event bool Alt() { return TestFlag(Flags, LGI_EF_ALT); } /// A shift button was held down during the event bool Shift() { return TestFlag(Flags, LGI_EF_SHIFT); } /// The system key was held down (windows key / apple key etc) bool System() { return TestFlag(Flags, LGI_EF_SYSTEM); } // Set void Down(bool i) { AssignFlag(Flags, LGI_EF_DOWN, i); } void Double(bool i) { AssignFlag(Flags, LGI_EF_DOUBLE, i); } void Ctrl(bool i) { AssignFlag(Flags, LGI_EF_CTRL, i); } void Alt(bool i) { AssignFlag(Flags, LGI_EF_ALT, i); } void Shift(bool i) { AssignFlag(Flags, LGI_EF_SHIFT, i); } void System(bool i) { AssignFlag(Flags, LGI_EF_SYSTEM, i); } bool Modifier() { #if defined(BEOS) return Alt(); #elif defined(MAC) return System(); // "Apple" key #else // win32 and linux return Ctrl(); #endif } #if defined COCOA - void SetModifer(uint32 modifierKeys); + void SetModifer(uint32_t modifierKeys); #else void SetModifer(uint32_t modifierKeys) { #if defined(MAC) #if !defined COCOA System(modifierKeys & cmdKey); Shift(modifierKeys & shiftKey); Alt(modifierKeys & optionKey); Ctrl(modifierKeys & controlKey); #endif #elif defined(__GTK_H__) System(modifierKeys & Gtk::GDK_MOD4_MASK); Shift(modifierKeys & Gtk::GDK_SHIFT_MASK); Alt(modifierKeys & Gtk::GDK_MOD1_MASK); Ctrl(modifierKeys & Gtk::GDK_CONTROL_MASK); #endif } #endif }; /// All the information related to a keyboard event class LgiClass GKey : public GUiEvent { public: /// The virtual code for key char16 vkey; /// The unicode character for the key char16 c16; /// OS Specific uint32_t Data; /// True if this is a standard character (ie not a control key) bool IsChar; GKey() { vkey = 0; c16 = 0; Data = 0; IsChar = 0; } GKey(int vkey, int flags); void Trace(const char *Msg) { LgiTrace("%s GKey vkey=%i(0x%x) c16=%i(%c) IsChar=%i down=%i ctrl=%i alt=%i sh=%i sys=%i\n", Msg ? Msg : (char*)"", vkey, vkey, c16, c16 >= ' ' && c16 < 127 ? c16 : '.', IsChar, Down(), Ctrl(), Alt(), Shift(), System()); } /// Returns the character in the right case... char16 GetChar() { if (Shift() ^ TestFlag(Flags, LGI_EF_CAPS_LOCK)) { return (c16 >= 'a' && c16 <= 'z') ? c16 - 'a' + 'A' : c16; } else { return (c16 >= 'A' && c16 <= 'Z') ? c16 - 'A' + 'a' : c16; } } /// \returns true if this event should show a context menu bool IsContextMenu(); }; /// \brief All the parameters of a mouse click event /// /// The parent class GUiEvent keeps information about whether it was a Down() /// or Double() click. You can also query whether the Alt(), Ctrl() or Shift() /// keys were pressed at the time the event occured. /// /// To get the position of the mouse in screen co-ordinates you can either use /// GView::GetMouse() and pass true in the 'ScreenCoords' parameter. Or you can /// construct a GdcPt2 out of the x,y fields of this class and use GView::PointToScreen() /// to map the point to screen co-ordinates. class LgiClass GMouse : public GUiEvent { public: /// Receiving view class GViewI *Target; /// True if specified in view coordinates, false if in screen coords bool ViewCoords; /// The x co-ordinate of the mouse relitive to the current view int x; /// The y co-ordinate of the mouse relitive to the current view int y; GMouse() { Target = 0; ViewCoords = true; x = y = 0; } void Trace(const char *Msg) { LgiTrace("%s GMouse pos=%i,%i view=%i btns=%i/%i/%i dwn=%i dbl=%i " "ctrl=%i alt=%i sh=%i sys=%i\n", Msg ? Msg : (char*)"", x, y, ViewCoords, Left(), Middle(), Right(), Down(), Double(), Ctrl(), Alt(), Shift(), System()); } /// True if the left mouse button was clicked bool Left() { return TestFlag(Flags, LGI_EF_LEFT); } /// True if the middle mouse button was clicked bool Middle() { return TestFlag(Flags, LGI_EF_MIDDLE); } /// True if the right mouse button was clicked bool Right() { return TestFlag(Flags, LGI_EF_RIGHT); } /// True if the mouse event is a move, false for a click event. bool IsMove() { return TestFlag(Flags, LGI_EF_MOVE); } /// Sets the left button flag void Left(bool i) { AssignFlag(Flags, LGI_EF_LEFT, i); } /// Sets the middle button flag void Middle(bool i) { AssignFlag(Flags, LGI_EF_MIDDLE, i); } /// Sets the right button flag void Right(bool i) { AssignFlag(Flags, LGI_EF_RIGHT, i); } /// Sets the move flag void IsMove(bool i) { AssignFlag(Flags, LGI_EF_MOVE, i); } /// Converts to screen coordinates bool ToScreen(); /// Converts to local coordinates bool ToView(); /// \returns true if this event should show a context menu bool IsContextMenu(); #if defined __OBJC__ void SetFromEvent(NSEvent *ev, NSView *view); #else void SetButton(uint32_t Btn) { #if defined(MAC) && defined(__CARBONEVENTS__) Left(Btn == kEventMouseButtonPrimary); Right(Btn == kEventMouseButtonSecondary); Middle(Btn == kEventMouseButtonTertiary); #endif } #endif }; #include "GAutoPtr.h" /// Holds information pertaining to an application class GAppInfo { public: /// The path to the executable for the app GAutoString Path; /// Plain text name for the app GAutoString Name; /// A path to an icon to display for the app GAutoString Icon; /// The params to call the app with GAutoString Params; }; template void LSwap(T &a, T &b) { T tmp = a; a = b; b = tmp; } template RESULT LHash(const CHAR *v, ssize_t l, bool Case) { RESULT h = 0; if (Case) { // case sensitive if (l > 0) { while (l--) h = (h << 5) - h + *v++; } else { for (; *v; v ++) h = (h << 5) - h + *v; } } else { // case insensitive CHAR c; if (l > 0) { while (l--) { c = tolower(*v); v++; h = (h << 5) - h + c; } } else { for (; *v; v++) { c = tolower(*v); h = (h << 5) - h + c; } } } return h; } #endif diff --git a/include/mac/cocoa/LgiOsDefs.h b/include/mac/cocoa/LgiOsDefs.h --- a/include/mac/cocoa/LgiOsDefs.h +++ b/include/mac/cocoa/LgiOsDefs.h @@ -1,359 +1,359 @@ // // FILE: LgiOsDefs.h (Mac) // AUTHOR: Matthew Allen // DATE: 1/1/2013 // DESCRIPTION: Lgi cocoa header // // Copyright (C) 2013, Matthew Allen // fret@memecode.com // #ifndef __LGI_MAC_OS_DEFS_H #define __LGI_MAC_OS_DEFS_H #include #include #include #include "LgiInc.h" #include "LgiDefs.h" #include "GAutoPtr.h" #include "LgiClass.h" #include "pthread.h" ////////////////////////////////////////////////////////////////// // Includes #include "LgiInc.h" #ifdef __OBJC__ #import #endif #include #include ////////////////////////////////////////////////////////////////// // Typedefs #define ObjCWrap(Type, Name) \ Name(Type *i) { p = i; } \ Name(long i = 0) { p = 0; } \ Name &operator=(Type *i) { p = i; return *this; } \ Name &operator=(long i) { p = 0; return *this; } \ operator bool() { return p != NULL; } \ operator Type*() { return p; } \ bool operator ==(const Name &obj) { return p == obj.p; } \ #ifdef __OBJC__ #define ObjCWrapper(Type, Name) \ class Name \ { \ public: \ Type *p; \ ObjCWrap(Type, Name) \ }; #else #define ObjCWrapper(Type, Name) \ class Name \ { \ void *p; \ public: \ ObjCWrap(void, Name) \ }; #endif ObjCWrapper(NSWindow, OsWindow) ObjCWrapper(NSView, OsView) typedef pthread_t OsThread; typedef uint64_t OsThreadId; typedef pthread_mutex_t OsSemaphore; -typedef uint16 OsChar; +typedef uint16_t OsChar; typedef int OsProcessId; typedef int OsProcess; typedef CGContextRef OsPainter; typedef CGContextRef OsBitmap; typedef CTFontRef OsFont; #include "GMessage.h" class OsAppArguments { struct OsAppArgumentsPriv *d; public: int Args; const char **Arg; OsAppArguments(int args = 0, const char **arg = 0); ~OsAppArguments(); void Set(char *CmdLine); OsAppArguments &operator =(OsAppArguments &a); }; ////////////////////////////////////////////////////////////////// // Defines #define _stricmp strcasecmp #define _strnicmp strncasecmp // Text system #define USE_CORETEXT 1 // System defines #define POSIX 1 #define LGI_COCOA 1 #define LGI_64BIT 1 // Process typedef int OsProcess; typedef int OsProcessId; #define LgiGetCurrentProcess() getpid() // Threads #define LgiGetCurrentThread() pthread_self() LgiFunc OsThreadId GetCurrentThreadId(); // Sockets #define ValidSocket(s) ((s)>=0) #define INVALID_SOCKET -1 typedef int OsSocket; // Sleep the current thread -LgiFunc void LgiSleep(uint32 i); +LgiFunc void LgiSleep(uint32_t i); // Run the message loop to process any pending messages #define LgiYield() GApp::ObjInstance()->Run(false) #define LGI_GViewMagic 0x14412662 #define LGI_FileDropFormat "furl" // typeFileURL #define LGI_StreamDropFormat "" // kPasteboardTypeFileURLPromise #define LGI_LgiDropFormat "lgi " #define LGI_WideCharset "utf-32" #define LPrintfInt64 "%lli" #define LPrintfHex64 "%llx" #define LPrintfSizeT "%zu" #define LPrintfSSizeT "%zi" #define atoi64 atoll #define sprintf_s snprintf #define vsprintf_s vsnprintf #define LGI_IllegalFileNameChars "/" // FIXME: what other characters should be in here? #define LGI_EXECUTABLE_EXT "" // Empty // Window flags #define GWF_VISIBLE 0x00000001 #define GWF_DISABLED 0x00000002 #define GWF_FOCUS 0x00000004 #define GWF_OVER 0x00000008 #define GWF_DROP_TARGET 0x00000010 #define GWF_SUNKEN 0x00000020 #define GWF_FLAT 0x00000040 #define GWF_RAISED 0x00000080 #define GWF_BORDER 0x00000100 #define GWF_DIALOG 0x00000200 #define GWF_DESTRUCTOR 0x00000400 #define GWF_QUIT_WND 0x00000800 // Menu flags #define ODS_SELECTED 0x1 #define ODS_DISABLED 0x2 #define ODS_CHECKED 0x4 /// Edge type: Sunken #define SUNKEN 1 /// Edge type: Raised #define RAISED 2 /// Edge type: Chiseled #define CHISEL 3 /// Edge type: Flat #define FLAT 4 /// The directory separator character on Linux as a char #define DIR_CHAR '/' /// The directory separator character on Linux as a string #define DIR_STR "/" /// The standard end of line string for Linux #define EOL_SEQUENCE "\n" /// Tests a char for being a slash #define IsSlash(c) (((c)=='/')||((c)=='\\')) /// Tests a char for being a quote #define IsQuote(c) (((c)=='\"')||((c)=='\'')) /// The path list separator character for Linux #define LGI_PATH_SEPARATOR ":" /// The pattern that matches all files in Linux #define LGI_ALL_FILES "*" /// The stardard extension for dynamically linked code #define LGI_LIBRARY_EXT "dylib" // Carbon user events #define GViewThisPtr 'gvtp' #define kEventClassUser 'user' #define kEventUser 1 #define kEventParamLgiEvent 'Lgie' #define kEventParamLgiA 'Lgia' #define kEventParamLgiB 'Lgib' /// Base point for system messages. #define M_SYSTEM 0 /// Message that indicates the user is trying to close a top level window. #define M_CLOSE (M_SYSTEM+92) /// Minimum value for application defined message ID's #define M_USER (M_SYSTEM+1000) /// \brief Mouse enter event /// /// a = bool Inside; // is the mouse inside the client area?\n /// b = MAKELONG(x, y); // mouse location #define M_MOUSEENTER (M_USER+100) /// \brief Mouse exit event /// /// a = bool Inside; // is the mouse inside the client area?\n /// b = MAKELONG(x, y); // mouse location #define M_MOUSEEXIT (M_USER+101) /// \brief GView change notification /// /// a = (GView*) Wnd;\n /// b = (int) Flags; // Specific to each GView #define M_CHANGE (M_USER+102) /// \brief Pass a text message up to the UI to descibe whats happening /// /// a = (GView*) Wnd;\n /// b = (char*) Text; // description from window #define M_DESCRIBE (M_USER+103) // return (bool) #define M_WANT_DIALOG_PROC (M_USER+104) #define M_MENU (M_USER+105) #define M_COMMAND (M_USER+106) #define M_DRAG_DROP (M_USER+107) #define M_TRAY_NOTIFY (M_USER+108) #define M_CUT (M_USER+109) #define M_COPY (M_USER+110) #define M_PASTE (M_USER+111) #define M_PULSE (M_USER+112) #define M_DELETE (M_USER+113) #define M_SET_VISIBLE (M_USER+114) #define M_TEXT_UPDATE_NAME (M_USER+115) /// GThreadWork object completed /// /// MsgA = (GThreadOwner*) Owner; /// MsgB = (GThreadWork*) WorkUnit; #define M_GTHREADWORK_COMPELTE (M_USER+114) /// Standard ID for an "Ok" button. /// \sa LgiMsg #define IDOK 1 /// Standard ID for a "Cancel" button. /// \sa LgiMsg #define IDCANCEL 2 /// Standard ID for a "Yes" button. /// \sa LgiMsg #define IDYES 3 /// Standard ID for a "No" button. /// \sa LgiMsg #define IDNO 4 /// Standard message box with an Ok button. /// \sa LgiMsg #define MB_OK 5 /// Standard message box with Ok and Cancel buttons. /// \sa LgiMsg #define MB_OKCANCEL 6 /// Standard message box with Yes and No buttons. /// \sa LgiMsg #define MB_YESNO 7 /// Standard message box with Yes, No and Cancel buttons. /// \sa LgiMsg #define MB_YESNOCANCEL 8 #define MB_SYSTEMMODAL 0x1000 /// The CTRL key is pressed /// \sa GKey #define LGI_VKEY_CTRL 0x001 /// The ALT key is pressed /// \sa GKey #define LGI_VKEY_ALT 0x002 /// The SHIFT key is pressed /// \sa GKey #define LGI_VKEY_SHIFT 0x004 /// The left mouse button is pressed /// \sa GMouse #define LGI_VMOUSE_LEFT 0x008 /// The middle mouse button is pressed /// \sa GMouse #define LGI_VMOUSE_MIDDLE 0x010 /// The right mouse button is pressed /// \sa GMouse #define LGI_VMOUSE_RIGHT 0x020 /// The ctrl key is pressed /// \sa GMouse #define LGI_VMOUSE_CTRL 0x040 /// The alt key is pressed /// \sa GMouse #define LGI_VMOUSE_ALT 0x080 /// The shift key is pressed /// \sa GMouse #define LGI_VMOUSE_SHIFT 0x100 /// The mouse event is a down click /// \sa GMouse #define LGI_VMOUSE_DOWN 0x200 /// The mouse event is a double click /// \sa GMouse #define LGI_VMOUSE_DOUBLE 0x400 // Keys #define VK_F1 1 #define VK_F2 2 #define VK_ENTER 3 #define VK_F3 4 #define VK_F4 5 #define VK_F5 6 #define VK_F6 7 #define VK_BACKSPACE 8 #define VK_TAB 9 #define VK_F7 11 #define VK_F8 12 #define VK_RETURN 13 #define VK_F9 14 #define VK_F10 15 #define VK_F11 16 #define VK_F12 17 #define VK_SHIFT 18 #define VK_PAGEUP 19 #define VK_PAGEDOWN 20 #define VK_HOME 21 #define VK_END 22 #define VK_INSERT 23 #define VK_DELETE 24 #define VK_APPS 25 #define VK_ESCAPE 27 #define VK_LEFT 28 #define VK_RIGHT 29 #define VK_UP 30 #define VK_DOWN 31 ///////////////////////////////////////////////////////////////////////////////////// // Externs LgiFunc GView *GWindowFromHandle(OsView hWnd); LgiFunc int GetMouseWheelLines(); LgiFunc int WinPointToHeight(int Pt); LgiFunc int WinHeightToPoint(int Ht); LgiFunc int stricmp(const char *a, const char *b); LgiFunc char *strlwr(char *a); LgiFunc char *strupr(char *a); LgiFunc char *p2c(unsigned char *s); /// Convert a string d'n'd format to an OS dependant integer. LgiFunc int FormatToInt(char *s); /// Convert a Os dependant integer d'n'd format to a string. LgiFunc char *FormatToStr(int f); #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; \ + uint32_t *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); \ + *Sz = (uint32_t) (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/GDisplayString.cpp b/src/common/Gdc2/Font/GDisplayString.cpp --- a/src/common/Gdc2/Font/GDisplayString.cpp +++ b/src/common/Gdc2/Font/GDisplayString.cpp @@ -1,2184 +1,2184 @@ ////////////////////////////////////////////////////////////////////// // Includes #include #include #include #include #include "Lgi.h" #include "GVariant.h" #include "GFontSelect.h" #include "GdiLeak.h" #include "GDisplayString.h" #include "GPixelRops.h" #include "LUnicodeString.h" #ifdef FontChange #undef FontChange #endif #ifdef LGI_SDL #include "ftsynth.h" #endif #if WINNATIVE static OsChar GDisplayStringDots[] = {'.', '.', '.', 0}; #endif //345678123456781234567812345678 // 2nd #define DEBUG_CHAR_AT 0 #if defined(__GTK_H__) || (defined(MAC) && !defined(LGI_SDL)) #define DISPLAY_STRING_FRACTIONAL_NATIVE 1 #else #define DISPLAY_STRING_FRACTIONAL_NATIVE 0 #endif template bool StringConvert(Out *&out, ssize_t *OutLen, const In *in, ssize_t InLen) { char OutCs[8], InCs[8]; sprintf_s(OutCs, sizeof(OutCs), "utf-%i", (int)sizeof(Out)<<3); sprintf_s(InCs, sizeof(InCs), "utf-%i", (int)sizeof(In)<<3); if (InLen < 0) InLen = StringLen(in); out = (Out*)LgiNewConvertCp ( OutCs, in, InCs, InLen*sizeof(In) ); if (OutLen) *OutLen = out ? StringLen(out) : 0; return out != NULL; } ////////////////////////////////////////////////////////////////////// #define SubtractPtr(a, b) ( (((NativeInt)(a))-((NativeInt)(b))) / sizeof(*a) ) #define VisibleTabChar 0x2192 #define IsTabChar(c) (c == '\t') // || (c == VisibleTabChar && VisibleTab)) // static OsChar GDisplayStringDots[] = {'.', '.', '.', 0}; #if USE_CORETEXT #include void GDisplayString::CreateAttrStr() { if (!StrCache.Get()) return; if (AttrStr) { CFRelease(AttrStr); AttrStr = NULL; } wchar_t *w = StrCache.Get(); - CFStringRef string = CFStringCreateWithBytes(kCFAllocatorDefault, (const uint8*)w, StrlenW(w) * sizeof(*w), kCFStringEncodingUTF32LE, false); + CFStringRef string = CFStringCreateWithBytes(kCFAllocatorDefault, (const uint8_t*)w, StrlenW(w) * sizeof(*w), kCFStringEncodingUTF32LE, false); if (string) { CFDictionaryRef attributes = Font->GetAttributes(); if (attributes) AttrStr = CFAttributedStringCreate(kCFAllocatorDefault, string, attributes); else LgiAssert(0); CFRelease(string); } } #endif GDisplayString::GDisplayString(GFont *f, const char *s, ssize_t l, GSurface *pdc) { pDC = pdc; Font = f; #if LGI_DSP_STR_CACHE StrCache.Reset(Utf8ToWide(s, l)); #endif #if defined(MAC) StringConvert(Str, &len, s, l); #elif defined(WINNATIVE) || defined(LGI_SDL) StringConvert(Str, &len, s, l); #else Str = NewStr(s, l); len = Str ? strlen(Str) : 0; if (!LgiIsUtf8(Str)) printf("%s:%i - Not valid utf\n", _FL); #endif x = y = 0; xf = 0; yf = 0; DrawOffsetF = 0; LaidOut = 0; AppendDots = 0; VisibleTab = 0; #if defined MAC && !defined(LGI_SDL) Hnd = 0; #if USE_CORETEXT AttrStr = NULL; #endif if (Font && Str) { len = l >= 0 ? l : StringLen(Str); if (len > 0) { #if USE_CORETEXT CreateAttrStr(); #else ATSUCreateTextLayout(&Hnd); #endif } } #elif defined __GTK_H__ Hnd = 0; LastTabOffset = -1; if (Font && Str) { len = l >= 0 ? l : strlen(Str); if (len > 0) { Gtk::GtkPrintContext *PrintCtx = pDC ? pDC->GetPrintContext() : NULL; if (PrintCtx) Hnd = Gtk::gtk_print_context_create_pango_layout(PrintCtx); else Hnd = Gtk::pango_layout_new(GFontSystem::Inst()->GetContext()); } } #endif } GDisplayString::GDisplayString(GFont *f, const char16 *s, ssize_t l, GSurface *pdc) { pDC = pdc; Font = f; #if LGI_DSP_STR_CACHE StrCache.Reset(NewStrW(s, l)); #endif #if defined(MAC) || WINNATIVE || defined(LGI_SDL) StringConvert(Str, &len, s, l); #else Str = WideToUtf8(s, l < 0 ? -1 : l); len = Str ? strlen(Str) : 0; #endif x = y = 0; xf = 0; yf = 0; DrawOffsetF = 0; LaidOut = 0; AppendDots = 0; VisibleTab = 0; #if defined MAC && !defined(LGI_SDL) Hnd = NULL; #if USE_CORETEXT AttrStr = NULL; #endif if (Font && Str && len > 0) { #if USE_CORETEXT CreateAttrStr(); #else OSStatus e = ATSUCreateTextLayout(&Hnd); if (e) printf("%s:%i - ATSUCreateTextLayout failed with %i.\n", _FL, (int)e); #endif } #elif defined __GTK_H__ Hnd = 0; if (Font && Str && len > 0) { Gtk::GtkPrintContext *PrintCtx = pDC ? pDC->GetPrintContext() : NULL; if (PrintCtx) Hnd = Gtk::gtk_print_context_create_pango_layout(PrintCtx); else Hnd = Gtk::pango_layout_new(GFontSystem::Inst()->GetContext()); } #endif } #ifdef _MSC_VER GDisplayString::GDisplayString(GFont *f, const uint32_t *s, ssize_t l, GSurface *pdc) { pDC = pdc; Font = f; #if LGI_DSP_STR_CACHE size_t Chars = l < 0 ? Strlen(s) : l; StrCache.Reset((char16*)LgiNewConvertCp(LGI_WideCharset, s, "utf-32", Chars*4)); #endif #if defined(MAC) || defined(LGI_SDL) || defined(_MSC_VER) StringConvert(Str, &len, s, l); #else Str = WideToUtf8(s, l < 0 ? -1 : l); len = Str ? strlen(Str) : 0; #endif x = y = 0; xf = 0; yf = 0; DrawOffsetF = 0; LaidOut = 0; AppendDots = 0; VisibleTab = 0; } #endif GDisplayString::~GDisplayString() { #if defined(LGI_SDL) Img.Reset(); #elif defined MAC #if USE_CORETEXT if (Hnd) { CFRelease(Hnd); Hnd = NULL; } if (AttrStr) { CFRelease(AttrStr); AttrStr = NULL; } #else if (Hnd) ATSUDisposeTextLayout(Hnd); #endif #elif defined __GTK_H__ if (Hnd) g_object_unref(Hnd); #endif DeleteArray(Str); } #if defined __GTK_H__ void GDisplayString::UpdateTabs(int Offset, int Size, bool Debug) { if (Hnd && Font && Font->TabSize()) { int Len = 16; LastTabOffset = Offset; Gtk::PangoTabArray *t = Gtk::pango_tab_array_new(Len, true); if (t) { for (int i=0; i> 1); pDC->Line(r.x1, r.y1+Cy, r.x2, r.y1+Cy); pDC->Line(r.x2, r.y1+Cy, r.x2-Cy, r.y1); pDC->Line(r.x2, r.y1+Cy, r.x2-Cy, r.y2); } else // Space { int x = r.x1 + (r.X()>>1) - 1; int Cy = r.y1 + (int)ceil(Font->Ascent()) - 2; pDC->Rectangle(x, Cy, x+1, Cy+1); } } void GDisplayString::Layout(bool Debug) { if (LaidOut || !Font) return; LaidOut = 1; #if defined(LGI_SDL) FT_Face Fnt = Font->Handle(); FT_Error error; if (!Fnt || !Str) return; // Create an array of glyph indexes GArray Glyphs; for (OsChar *s = Str; *s; s++) { FT_UInt index = FT_Get_Char_Index(Fnt, *s); if (index) Glyphs.Add(index); } // Measure the string... GdcPt2 Sz; int FontHt = Font->GetHeight(); int AscentF = (int) (Font->Ascent() * FScale); int LoadMode = FT_LOAD_FORCE_AUTOHINT; for (unsigned i=0; iglyph->metrics.horiBearingY; Sz.x += Fnt->glyph->metrics.horiAdvance; Sz.y = MAX(Sz.y, PyF + Fnt->glyph->metrics.height); } } // Create the memory context to draw into xf = Sz.x; x = ((Sz.x + FScale - 1) >> FShift) + 1; yf = FontHt << FShift; y = FontHt; // ((Sz.y + FScale - 1) >> FShift) + 1; if (Img.Reset(new GMemDC(x, y, CsIndex8))) { // Clear the context to black Img->Colour(0); Img->Rectangle(); bool IsItalic = Font->Italic(); int CurX = 0; int FBaseline = Fnt->size->metrics.ascender; for (unsigned i=0; iglyph); error = FT_Render_Glyph(Fnt->glyph, FT_RENDER_MODE_NORMAL); if (error == 0) { FT_Bitmap &bmp = Fnt->glyph->bitmap; if (bmp.buffer) { // Copy rendered glyph into our image memory int Px = (CurX + (FScale >> 1)) >> FShift; int PyF = AscentF - Fnt->glyph->metrics.horiBearingY; int Py = PyF >> FShift; int Skip = 0; if (Fnt->glyph->format == FT_GLYPH_FORMAT_BITMAP) { Px += Fnt->glyph->bitmap_left; Skip = Fnt->glyph->bitmap_left < 0 ? -Fnt->glyph->bitmap_left : 0; Py = (AscentF >> FShift) - Fnt->glyph->bitmap_top; } LgiAssert(Px + bmp.width <= Img->X()); for (int y=0; y= 0); out += Px+Skip; memcpy(out, in+Skip, bmp.width-Skip); } /* else { LgiAssert(!"No scanline?"); break; } */ } } if (i < Glyphs.Length() - 1) { FT_Vector kerning; FT_Get_Kerning(Fnt, Glyphs[i], Glyphs[i+1], FT_KERNING_DEFAULT, &kerning); CurX += Fnt->glyph->metrics.horiAdvance + kerning.x; } else { CurX += Fnt->glyph->metrics.horiAdvance; } } } } } else LgiTrace("::Layout Create MemDC failed\n"); #elif defined(__GTK_H__) y = Font->GetHeight(); yf = y * PANGO_SCALE; if (!Hnd || !Font->Handle()) { // LgiTrace("%s:%i - Missing handle: %p,%p\n", _FL, Hnd, Font->Handle()); return; } if (!LgiIsUtf8(Str)) { LgiTrace("%s:%i - Not utf8\n", _FL); return; } GFontSystem *FSys = GFontSystem::Inst(); Gtk::pango_context_set_font_description(FSys->GetContext(), Font->Handle()); int TabSizePx = Font->TabSize(); int TabSizeF = TabSizePx * FScale; int TabOffsetF = DrawOffsetF % TabSizeF; int OffsetF = TabOffsetF ? TabSizeF - TabOffsetF : 0; /* if (Debug) { printf("'%s', TabSizeF=%i, TabOffsetF=%i, DrawOffsetF=%i, OffsetF=%i\n", Str, TabSizeF, TabOffsetF, DrawOffsetF, OffsetF); } */ UpdateTabs(OffsetF / FScale, Font->TabSize()); if (Font->Underline()) { Gtk::PangoAttrList *attrs = Gtk::pango_attr_list_new(); Gtk::PangoAttribute *attr = Gtk::pango_attr_underline_new(Gtk::PANGO_UNDERLINE_SINGLE); Gtk::pango_attr_list_insert(attrs, attr); Gtk::pango_layout_set_attributes(Hnd, attrs); Gtk::pango_attr_list_unref(attrs); } Gtk::pango_layout_set_text(Hnd, Str, len); Gtk::pango_layout_get_size(Hnd, &xf, &yf); x = (xf + PANGO_SCALE - 1) / PANGO_SCALE; #if 1 y = Font->GetHeight(); #else y = (yf + PANGO_SCALE - 1) / PANGO_SCALE; #endif if (y > Font->GetHeight()) { printf("%s:%i - Height error: %i > %i\n", _FL, y, Font->GetHeight()); } #elif defined MAC && !defined(LGI_SDL) #if USE_CORETEXT int height = Font->GetHeight(); y = height; if (AttrStr) { LgiAssert(!Hnd); Hnd = CTLineCreateWithAttributedString(AttrStr); if (Hnd) { CGFloat ascent = 0.0; CGFloat descent = 0.0; CGFloat leading = 0.0; double width = CTLineGetTypographicBounds(Hnd, &ascent, &descent, &leading); x = ceil(width); xf = width * FScale; yf = height * FScale; } } #else if (!Hnd || !Str) return; OSStatus e = ATSUSetTextPointerLocation(Hnd, Str, 0, len, len); if (e) { char *a = 0; StringConvert(a, NULL, Str, len); printf("%s:%i - ATSUSetTextPointerLocation failed with errorcode %i (%s)\n", _FL, (int)e, a); DeleteArray(a); return; } e = ATSUSetRunStyle(Hnd, Font->Handle(), 0, len); if (e) { char *a = 0; StringConvert(a, NULL, Str, len); printf("%s:%i - ATSUSetRunStyle failed with errorcode %i (%s)\n", _FL, (int)e, a); DeleteArray(a); return; } ATSUTextMeasurement fTextBefore; ATSUTextMeasurement fTextAfter; if (pDC) { OsPainter dc = pDC->Handle(); ATSUAttributeTag Tags[1] = {kATSUCGContextTag}; ByteCount Sizes[1] = {sizeof(CGContextRef)}; ATSUAttributeValuePtr Values[1] = {&dc}; e = ATSUSetLayoutControls(Hnd, 1, Tags, Sizes, Values); if (e) printf("%s:%i - ATSUSetLayoutControls failed (e=%i)\n", _FL, (int)e); } ATSUTab Tabs[32]; for (int i=0; iTabSize()) << 16; Tabs[i].tabType = kATSULeftTab; } e = ATSUSetTabArray(Hnd, Tabs, CountOf(Tabs)); if (e) printf("%s:%i - ATSUSetTabArray failed (e=%i)\n", _FL, (int)e); e = ATSUGetUnjustifiedBounds( Hnd, kATSUFromTextBeginning, kATSUToTextEnd, &fTextBefore, &fTextAfter, &fAscent, &fDescent); if (e) { char *a = 0; StringConvert(a, NULL, Str, len); printf("%s:%i - ATSUGetUnjustifiedBounds failed with errorcode %i (%s)\n", _FL, (int)e, a); DeleteArray(a); return; } xf = fTextAfter - fTextBefore; yf = fAscent + fDescent; x = (xf + 0xffff) >> 16; y = (yf + 0xffff) >> 16; ATSUSetTransientFontMatching(Hnd, true); #endif #elif defined WINNATIVE int sx, sy, i = 0; if (!Font) return; if (!Font->Handle()) Font->Create(); y = Font->GetHeight(); GFontSystem *Sys = GFontSystem::Inst(); if (Sys && Str) { GFont *f = Font; bool GlyphSub = Font->SubGlyphs(); Info[i].Str = Str; int TabSize = Font->TabSize() ? Font->TabSize() : 32; bool WasTab = IsTabChar(*Str); f = GlyphSub ? Sys->GetGlyph(*Str, Font) : Font; if (f && f != Font) { f->Size(Font->Size()); f->SetWeight(Font->GetWeight()); if (!f->Handle()) f->Create(); } bool Debug = WasTab; uint32_t u32; for (LUnicodeString u(Str, len); true; u++) { u32 = *u; GFont *n = GlyphSub ? Sys->GetGlyph(u32, Font) : Font; bool Change = n != f || // The font changed (IsTabChar(u32) ^ WasTab) || // Entering/leaving a run of tabs !u32 || // Hit a NULL character (u.Get() - Info[i].Str) >= 1000; // This is to stop very long segments not rendering if (Change) { // End last segment if (n && n != Font) { n->Size(Font->Size()); n->SetWeight(Font->GetWeight()); if (!n->Handle()) n->Create(); } Info[i].Len = (int) (u.Get() - Info[i].Str); if (Info[i].Len) { if (WasTab) { // Handle tab(s) for (int t=0; tGetHeight() > Font->GetHeight())) { Info[i].SizeDelta = -1; f->PointSize(Font->PointSize() + Info[i].SizeDelta); f->Create(); } #endif if (!f) { // no font, so ? out the chars... as they aren't available anyway // printf("Font Cache Miss, Len=%i\n\t", Info[i].Len); m = Font; for (int n=0; n_Measure(sx, sy, Info[i].Str, Info[i].Len); x += Info[i].X = sx > 0xffff ? 0xffff : sx; } auto Ch = Info[i].First(); Info[i].FontId = !f || Font == f ? 0 : Sys->Lut[Ch]; i++; } f = n; // Start next segment WasTab = IsTabChar(u32); Info[i].Str = u.Get(); } if (!u32) break; } if (Info.Length() > 0 && Info.Last().Len == 0) { Info.Length(Info.Length()-1); } } xf = x; yf = y; #elif defined BEOS if (Font && Font->Handle()) { int TabSize = Font->TabSize() ? Font->TabSize() : 32; int TabOrigin = DrawOffsetF / FScale; char *End = Str + len; char *s = Str; x = 0; while (s < End) { char *Start = s; while (s < End && *s && *s != '\t') s++; if (s > Start) { // Normal segment CharInfo &i = Info.New(); i.Str = Start; i.Len = s - Start; GdcPt2 size = Font->StringBounds(i.Str, i.Len); i.X = size.x; i.FontId = -1; i.SizeDelta = 0; x += size.x; } Start = s; while (s < End && *s && *s == '\t') s++; if (s > Start) { // Tabs segment CharInfo &i = Info.New(); i.Str = Start; i.Len = s - Start; i.X = 0; i.FontId = -1; i.SizeDelta = 0; for (int n=0; nGetHeight(); xf = x; #if 0 printf("Layout '%s' = %i,%i\n", Str, x, y); for (int i=0; iStr, ci->Len, ci->X); } #endif } else printf("%s:%i - No font or handle.\n", _FL); #endif } int GDisplayString::GetDrawOffset() { return DrawOffsetF >> FShift; } void GDisplayString::SetDrawOffset(int Px) { if (LaidOut) LgiAssert(!"No point setting TabOrigin after string is laid out.\n"); DrawOffsetF = Px << FShift; } bool GDisplayString::ShowVisibleTab() { return VisibleTab; } void GDisplayString::ShowVisibleTab(bool i) { VisibleTab = i; } bool GDisplayString::IsTruncated() { return AppendDots; } void GDisplayString::TruncateWithDots(int Width) { Layout(); #if defined __GTK_H__ if (Hnd) { Gtk::pango_layout_set_ellipsize(Hnd, Gtk::PANGO_ELLIPSIZE_END); Gtk::pango_layout_set_width(Hnd, Width * PANGO_SCALE); } #elif WINNATIVE if (Width < X() + 8) { ssize_t c = CharAt(Width); if (c >= 0 && c < len) { if (c > 0) c--; // fudge room for dots if (c > 0) c--; AppendDots = 1; if (Info.Length()) { int Width = 0; int Pos = 0; for (int i=0; i= Pos && c < Pos + Info[i].Len) { Info[i].Len = (int) (c - Pos); Info[i].Str[Info[i].Len] = 0; GFont *f = Font; if (Info[i].FontId) { GFontSystem *Sys = GFontSystem::Inst(); f = Sys->Font[Info[i].FontId]; f->PointSize(Font->PointSize() + Info[i].SizeDelta); if (!f->Handle()) { f->Create(); } } else { f = Font; } if (f) { int Sx, Sy; f->_Measure(Sx, Sy, Info[i].Str, Info[i].Len); Info[i].X = Sx; Width += Info[i].X; } Info.Length(i + 1); break; } Pos += Info[i].Len; Width += Info[i].X; } int DotsX, DotsY; Font->_Measure(DotsX, DotsY, GDisplayStringDots, 3); x = Width + DotsX; } } } #elif defined(LGI_SDL) #elif defined(MAC) #if USE_CORETEXT if (Hnd) { CFAttributedStringRef truncationString = CFAttributedStringCreate(NULL, CFSTR("\u2026"), Font->GetAttributes()); if (truncationString) { CTLineRef truncationToken = CTLineCreateWithAttributedString(truncationString); CFRelease(truncationString); if (truncationToken) { CTLineRef TruncatedLine = CTLineCreateTruncatedLine(Hnd, Width, kCTLineTruncationEnd, truncationToken); CFRelease(truncationToken); if (TruncatedLine) { CFRelease(Hnd); Hnd = TruncatedLine; } } } } #endif #endif } ssize_t GDisplayString::CharAt(int Px, LgiPxToIndexType Type) { int Status = -1; Layout(); if (Px < 0) { return 0; } else if (Px >= (int)x) { #if defined __GTK_H__ if (Str) { GUtf8Str u(Str); return u.GetChars(); } return 0; #else return len; #endif } #if defined MAC && !defined(LGI_SDL) if (Hnd && Str) { #if USE_CORETEXT #if 1 CGPoint pos = { (CGFloat)Px, 1.0f }; CFIndex idx = CTLineGetStringIndexForPosition(Hnd, pos); return idx; #else CTTypesetterRef typesetter = CTTypesetterCreateWithAttributedString(AttrStr); if (typesetter) { CFIndex count = CTTypesetterSuggestLineBreak(typesetter, 0, Px); CFRelease(typesetter); // printf("CharAt(%i) = %i\n", Px, (int)count); return count; } Status = Off; #endif #else UniCharArrayOffset Off = 0; // Boolean IsLeading; OSStatus e = ATSUPositionToOffset(Hnd, FloatToFixed(Px), FloatToFixed(y / 2), &Off, &IsLeading, &Off2); if (e) printf("%s:%i - ATSUPositionToOffset failed with %i, CharAt(%i) x=%i len=%i\n", _FL, (int)e, Px, x, len); else Status = Off; #endif } #elif defined __GTK_H__ if (Hnd) { int Index = 0, Trailing = 0; if (Gtk::pango_layout_xy_to_index(Hnd, Px * PANGO_SCALE, 0, &Index, &Trailing)) { // printf("Index = %i, Trailing = %i\n", Index, Trailing); GUtf8Str u(Str); Status = 0; while ((OsChar*)u.GetPtr() < Str + Index) { u++; Status++; } } else if (Trailing) { GUtf8Str u(Str + Index); if (u) u++; Status = (OsChar*)u.GetPtr() - Str; } else Status = 0; } #elif defined(LGI_SDL) #else // This case is for Win32 and Haiku. #if defined(BEOS) if (Font && Font->Handle()) #elif defined(WINNATIVE) GFontSystem *Sys = GFontSystem::Inst(); if (Info.Length() && Font && Sys) #endif { int TabSize = Font->TabSize() ? Font->TabSize() : 32; int Cx = 0; int Char = 0; #if DEBUG_CHAR_AT printf("CharAt(%i) Str='%s'\n", Px, Str); #endif for (int i=0; i= Cx && Px < Cx + Info[i].X) { // The position is in this block of characters if (IsTabChar(Info[i].Str[0])) { // Search through tab block for (int t=0; t= Cx && Px < Cx + TabX) { Status = Char; #if DEBUG_CHAR_AT printf("\tIn tab block %i\n", i); #endif break; } Cx += TabX; Char++; } } else { // Find the pos in this block GFont *f = Font; #if defined(BEOS) BString s(Info[i].Str, Info[i].Len); Font->Handle()->TruncateString(&s, B_TRUNCATE_END, Px - Cx); int Fit = s.CountChars(); #elif defined(WIN32) if (Info[i].FontId) { f = Sys->Font[Info[i].FontId]; f->Colour(Font->Fore(), Font->Back()); f->Size(Font->Size()); if (!f->Handle()) { f->Create(); } } int Fit = f->_CharAt(Px - Cx, Info[i].Str, Info[i].Len, Type); #endif #if DEBUG_CHAR_AT printf("\tNon tab block %i, Fit=%i, Px-Cx=%i-%i=%i, Str='%.5s'\n", i, Fit, Px, Cx, Px-Cx, Info[i].Str); #endif if (Fit >= 0) { Status = Char + Fit; } else { Status = -1; } break; } } else { // Not in this block, skip the whole lot Cx += Info[i].X; Char += Info[i].Len; } } if (Status < 0) { Status = Char; } } #endif return Status; } ssize_t GDisplayString::Length() { return len; } void GDisplayString::Length(int New) { Layout(); #if WINNATIVE if (New < len) { GFontSystem *Sys = GFontSystem::Inst(); int CurX = 0; int CurLen = 0; for (int i=0; i= CurLen && New < CurLen + Info[i].Len ) { // In this block int Offset = New - CurLen; Info[i].Len = Offset; Info[i].Str[Info[i].Len] = 0; // Get the font for this block of characters GFont *f = 0; if (Info[i].FontId) { f = Sys->Font[Info[i].FontId]; f->PointSize(Font->PointSize()); f->Transparent(Font->Transparent()); if (!f->Handle()) { f->Create(); } } else { f = Font; } // Chop the current block and re-measure int ChoppedX, Unused; f->_Measure(ChoppedX, Unused, Info[i].Str, Info[i].Len); Info[i].X = ChoppedX; x = CurX + Info[i].X; Info.Length(i + 1); // Leave the loop break; } CurX += Info[i].X; CurLen += Info[i].Len; } } else { printf("%s:%i - New>=Len (%i>=" LPrintfSSizeT" )\n", _FL, New, len); } #endif } int GDisplayString::X() { Layout(); return x; } int GDisplayString::Y() { Layout(); return y; } GdcPt2 GDisplayString::Size() { Layout(); return GdcPt2(x, y); } #if defined LGI_SDL template bool CompositeText8Alpha(GSurface *Out, GSurface *In, GFont *Font, int px, int py, GBlitRegions &Clip) { OutPx map[256]; if (!Out || !In || !Font) return false; // FIXME, do blt clipping here... // Create colour map of the foreground/background colours uint8 *Div255 = Div255Lut; GColour fore = Font->Fore(); GRgb24 fore_px; fore_px.r = fore.r(); fore_px.g = fore.g(); fore_px.b = fore.b(); if (Font->Transparent()) { for (int a=0; a<256; a++) { map[a].r = Div255[a * fore_px.r]; map[a].g = Div255[a * fore_px.g]; map[a].b = Div255[a * fore_px.b]; map[a].a = a; } } else { GColour back = Font->Back(); GRgb24 back_px; back_px.r = back.r(); back_px.g = back.g(); back_px.b = back.b(); for (int a=0; a<256; a++) { int oma = 255 - a; map[a].r = (oma * back_px.r) + (a * fore_px.r) / 255; map[a].g = (oma * back_px.g) + (a * fore_px.g) / 255; map[a].b = (oma * back_px.b) + (a * fore_px.b) / 255; map[a].a = 255; } } uint8 *StartOfBuffer = (*Out)[0]; uint8 *EndOfBuffer = StartOfBuffer + (Out->GetRowStep() * Out->Y()); for (unsigned y=Clip.SrcClip.y1; y<=Clip.SrcClip.y2; y++) { OutPx *d = ((OutPx*) (*Out)[py + y]) + Clip.DstClip.x1; uint8 *i = (*In)[y]; if (!i) return false; uint8 *e = i + Clip.DstClip.X(); LgiAssert((uint8*)d >= StartOfBuffer); if (Font->Transparent()) { uint8 a, o; OutPx *s; while (i < e) { // Alpha blend map and output pixel a = *i++; switch (a) { case 0: break; case 255: // Copy *d = map[a]; break; default: // Blend o = 255 - a; s = map + a; #define NonPreMulOver32NoAlpha(c) d->c = ((s->c * a) + (Div255[d->c * 255] * o)) / 255 NonPreMulOver32NoAlpha(r); NonPreMulOver32NoAlpha(g); NonPreMulOver32NoAlpha(b); break; } d++; } } else { while (i < e) { // Copy rop *d++ = map[*i++]; } } LgiAssert((uint8*)d <= EndOfBuffer); } return true; } template bool CompositeText8NoAlpha(GSurface *Out, GSurface *In, GFont *Font, int px, int py, GBlitRegions &Clip) { GRgba32 map[256]; if (!Out || !In || !Font) return false; // FIXME, do blt clipping here... // Create colour map of the foreground/background colours uint8 *DivLut = Div255Lut; GColour fore = Font->Fore(); GRgb24 fore_px; fore_px.r = fore.r(); fore_px.g = fore.g(); fore_px.b = fore.b(); if (Font->Transparent()) { for (int a=0; a<256; a++) { map[a].r = DivLut[a * fore_px.r]; map[a].g = DivLut[a * fore_px.g]; map[a].b = DivLut[a * fore_px.b]; map[a].a = a; } } else { GColour back = Font->Back(); GRgb24 back_px; back_px.r = back.r(); back_px.g = back.g(); back_px.b = back.b(); for (int a=0; a<256; a++) { int oma = 255 - a; map[a].r = DivLut[(oma * back_px.r) + (a * fore_px.r)]; map[a].g = DivLut[(oma * back_px.g) + (a * fore_px.g)]; map[a].b = DivLut[(oma * back_px.b) + (a * fore_px.b)]; map[a].a = 255; } } uint8 *StartOfBuffer = (*Out)[0]; uint8 *EndOfBuffer = StartOfBuffer + (Out->GetRowStep() * Out->Y()); for (int y=Clip.SrcClip.y1; y<=Clip.SrcClip.y2; y++) { OutPx *dst = (OutPx*) (*Out)[py + y]; if (!dst) continue; dst += Clip.DstClip.x1; if ((uint8*)dst < StartOfBuffer) continue; uint8 *i = (*In)[y]; if (!i) return false; uint8 *e = i + Clip.DstClip.X(); GRgba32 *src; LgiAssert((uint8*)dst >= StartOfBuffer); if (Font->Transparent()) { uint8 a, oma; while (i < e) { // Alpha blend map and output pixel a = *i++; src = map + a; switch (a) { case 0: break; case 255: // Copy dst->r = src->r; dst->g = src->g; dst->b = src->b; break; default: // Blend OverPm32toPm24(src, dst); break; } dst++; } } else { while (i < e) { // Copy rop src = map + *i++; dst->r = src->r; dst->g = src->g; dst->b = src->b; dst++; } } LgiAssert((uint8*)dst <= EndOfBuffer); } return true; } template bool CompositeText5NoAlpha(GSurface *Out, GSurface *In, GFont *Font, int px, int py, GBlitRegions &Clip) { OutPx map[256]; if (!Out || !In || !Font) return false; // FIXME, do blt clipping here... #define MASK_5BIT 0x1f #define MASK_6BIT 0x3f // Create colour map of the foreground/background colours uint8 *Div255 = Div255Lut; GColour fore = Font->Fore(); GRgb24 fore_px; fore_px.r = fore.r(); fore_px.g = fore.g(); fore_px.b = fore.b(); if (Font->Transparent()) { for (int a=0; a<256; a++) { map[a].r = (int)Div255[a * fore_px.r] >> 3; map[a].g = (int)Div255[a * fore_px.g] >> 2; map[a].b = (int)Div255[a * fore_px.b] >> 3; } } else { GColour back = Font->Back(); GRgb24 back_px; back_px.r = back.r(); back_px.g = back.g(); back_px.b = back.b(); for (int a=0; a<256; a++) { int oma = 255 - a; map[a].r = Div255[(oma * back_px.r) + (a * fore_px.r)] >> 3; map[a].g = Div255[(oma * back_px.g) + (a * fore_px.g)] >> 2; map[a].b = Div255[(oma * back_px.b) + (a * fore_px.b)] >> 3; } } uint8 *StartOfBuffer = (*Out)[0]; uint8 *EndOfBuffer = StartOfBuffer + (Out->GetRowStep() * Out->Y()); for (unsigned y=Clip.SrcClip.y1; y<=Clip.SrcClip.y2; y++) { OutPx *dst = ((OutPx*) (*Out)[py + y]); if (!dst) continue; dst += Clip.DstClip.x1; uint8 *i = (*In)[y]; if (!i) return false; uint8 *e = i + Clip.DstClip.X(); LgiAssert((uint8*)dst >= StartOfBuffer); if (Font->Transparent()) { uint8 a; OutPx *src; while (i < e) { // Alpha blend map and output pixel a = *i++; switch (a) { case 0: break; case 255: // Copy *dst = map[a]; break; default: { // Blend #if 0 uint8 oma = 255 - a; src = map + a; GRgb24 d = { G5bitTo8bit(dst->r), G6bitTo8bit(dst->g), G5bitTo8bit(dst->b)}; GRgb24 s = { G5bitTo8bit(src->r), G6bitTo8bit(src->g), G5bitTo8bit(src->b)}; dst->r = Div255[(oma * d.r) + (a * s.r)] >> 3; dst->g = Div255[(oma * d.g) + (a * s.g)] >> 2; dst->b = Div255[(oma * d.b) + (a * s.b)] >> 3; #else uint8 a5 = a >> 3; uint8 a6 = a >> 2; uint8 oma5 = MASK_5BIT - a5; uint8 oma6 = MASK_6BIT - a6; src = map + a; dst->r = ((oma5 * (uint8)dst->r) + (a5 * (uint8)src->r)) / MASK_5BIT; dst->g = ((oma6 * (uint8)dst->g) + (a6 * (uint8)src->g)) / MASK_6BIT; dst->b = ((oma5 * (uint8)dst->b) + (a5 * (uint8)src->b)) / MASK_5BIT; #endif break; } } dst++; } } else { while (i < e) { // Copy rop *dst++ = map[*i++]; } } LgiAssert((uint8*)dst <= EndOfBuffer); } return true; } #endif void GDisplayString::Draw(GSurface *pDC, int px, int py, GRect *r, bool Debug) { Layout(); #if DISPLAY_STRING_FRACTIONAL_NATIVE // GTK / Mac use fractional pixels, so call the fractional version: GRect rc; if (r) { rc = *r; rc.x1 <<= FShift; rc.y1 <<= FShift; #ifdef MAC rc.x2 <<= FShift; rc.y2 <<= FShift; #else rc.x2 = (rc.x2 + 1) << FShift; rc.y2 = (rc.y2 + 1) << FShift; #endif } FDraw(pDC, px << FShift, py << FShift, r ? &rc : NULL, Debug); #elif defined LGI_SDL if (Img && pDC && pDC->Y() > 0 && (*pDC)[0]) { int Ox = 0, Oy = 0; pDC->GetOrigin(Ox, Oy); GBlitRegions Clip(pDC, px-Ox, py-Oy, Img, r); GColourSpace DstCs = pDC->GetColourSpace(); switch (DstCs) { #define DspStrCase(px_fmt, comp) \ case Cs##px_fmt: \ CompositeText##comp(pDC, Img, Font, px-Ox, py-Oy, Clip); \ break; DspStrCase(Rgb16, 5NoAlpha) DspStrCase(Bgr16, 5NoAlpha) DspStrCase(Rgb24, 8NoAlpha) DspStrCase(Bgr24, 8NoAlpha) DspStrCase(Rgbx32, 8NoAlpha) DspStrCase(Bgrx32, 8NoAlpha) DspStrCase(Xrgb32, 8NoAlpha) DspStrCase(Xbgr32, 8NoAlpha) DspStrCase(Rgba32, 8Alpha) DspStrCase(Bgra32, 8Alpha) DspStrCase(Argb32, 8Alpha) DspStrCase(Abgr32, 8Alpha) default: LgiTrace("%s:%i - GDisplayString::Draw Unsupported colour space.\n", _FL); // LgiAssert(!"Unsupported colour space."); break; #undef DspStrCase } } else LgiTrace("::Draw argument error.\n"); #elif defined WINNATIVE if (Info.Length() && pDC && Font) { GFontSystem *Sys = GFontSystem::Inst(); COLOUR Old = pDC->Colour(); int TabSize = Font->TabSize() ? Font->TabSize() : 32; int Ox = px; GColour cFore = Font->Fore(); GColour cBack = Font->Back(); GColour cWhitespace; if (VisibleTab) { cWhitespace = Font->WhitespaceColour(); LgiAssert(cWhitespace.IsValid()); } for (int i=0; iFont[Info[i].FontId]; f->Colour(cFore, cBack); auto Sz = Font->Size(); Sz.Value += Info[i].SizeDelta; f->Size(Sz); f->Transparent(Font->Transparent()); f->Underline(Font->Underline()); if (!f->Handle()) { f->Create(); } } else { f = Font; } if (f) { GRect b; if (r) { b.x1 = i ? px : r->x1; b.y1 = r->y1; b.x2 = i < Info.Length() - 1 ? px + Info[i].X - 1 : r->x2; b.y2 = r->y2; } else { b.x1 = px; b.y1 = py; b.x2 = px + Info[i].X - 1; b.y2 = py + Y() - 1; } if (b.Valid()) { if (IsTabChar(*Info[i].Str)) { // Invisible tab... draw blank space if (!Font->Transparent()) { pDC->Colour(cBack); pDC->Rectangle(&b); } if (VisibleTab) { int X = px; for (int n=0; nColour(cWhitespace); DrawWhiteSpace(pDC, '\t', r); X += Dx; } } } else { // Draw the character(s) GColour Fg = f->Fore(); LgiAssert(Fg.IsValid()); f->_Draw(pDC, px, py, Info[i].Str, Info[i].Len, &b, Fg); if (VisibleTab) { OsChar *start = Info[i].Str; OsChar *s = start; OsChar *e = s + Info[i].Len; int Sp = -1; while (s < e) { if (*s == ' ') { int Sx, Sy; if (Sp < 0) f->_Measure(Sp, Sy, s, 1); f->_Measure(Sx, Sy, start, (int)(s - start)); GRect r(0, 0, Sp-1, Sy-1); r.Offset(px + Sx, py); pDC->Colour(cWhitespace); DrawWhiteSpace(pDC, ' ', r); } s++; } } } } } // Inc my position px += Info[i].X; } if (AppendDots) { int Sx, Sy; Font->_Measure(Sx, Sy, GDisplayStringDots, 3); GRect b; if (r) { b.x1 = px; b.y1 = r->y1; b.x2 = min(px + Sx - 1, r->x2); b.y2 = r->y2; } else { b.x1 = px; b.y1 = py; b.x2 = px + Sx - 1; b.y2 = py + Y() - 1; } GColour Fg = Font->Fore(); Font->_Draw(pDC, px, py, GDisplayStringDots, 3, &b, Fg); } pDC->Colour(Old); } else if (r && Font && !Font->Transparent()) { pDC->Colour(Font->Back()); pDC->Rectangle(r); } #elif defined(BEOS) if (pDC && Font) { rgb_color Fg = Font->Fore(), Bk = Font->Back(); // Paint text BView *Hnd = pDC->Handle(); if (Hnd) { GLocker Locker(Hnd, _FL); Locker.Lock(); drawing_mode Prev = Hnd->DrawingMode(); Hnd->SetDrawingMode(B_OP_COPY); // Draw background if required. if (!Font->Transparent()) { Hnd->SetHighColor(Bk); if (r) { Hnd->FillRect(*r); } else { GRect b; b.ZOff(x-1, y-1); b.Offset(px, py); Hnd->FillRect(b); } } // Draw foreground text segments Hnd->SetHighColor(Fg); Hnd->SetLowColor(Bk); Hnd->SetFont(Font->Handle()); Hnd->SetDrawingMode(B_OP_OVER); int CurX = 0; for (int i=0; iAscent()); if (ci->Str[0] != '\t') Hnd->DrawString(ci->Str, ci->Len, pos); /* printf("DrawString %i, %i -> '%.*s' in %x,%x,%x\n", px, py, ci->Len, ci->Str, Fg.red, Fg.green, Fg.blue); */ #if 0 // useful to debug where strings are drawn { GRect r; r.ZOff(ci->X-1, Font->GetHeight()-1); r.Offset(px + CurX, py); pDC->Box(&r); } #endif CurX += ci->X; } Hnd->SetDrawingMode(Prev); if (!pDC->IsScreen()) Hnd->Sync(); } else printf("%s:%i - Error: no BView to draw on.\n", _FL); } else printf("%s:%i - Error: no DC or Font.\n", _FL); #endif } int GDisplayString::GetDrawOffsetF() { return DrawOffsetF; } void GDisplayString::SetDrawOffsetF(int Fpx) { if (LaidOut) LgiAssert(!"No point setting TabOrigin after string is laid out.\n"); DrawOffsetF = Fpx; } int GDisplayString::FX() { Layout(); return xf; } int GDisplayString::FY() { Layout(); return y; } GdcPt2 GDisplayString::FSize() { Layout(); return GdcPt2(xf, yf); } void GDisplayString::FDraw(GSurface *pDC, int fx, int fy, GRect *frc, bool Debug) { Layout(Debug); #if !DISPLAY_STRING_FRACTIONAL_NATIVE // BeOS / Windows don't use fractional pixels, so call the integer version: GRect rc; if (frc) { rc = *frc; rc.x1 >>= FShift; rc.y1 >>= FShift; rc.x2 >>= FShift; rc.y2 >>= FShift; } Draw(pDC, fx >> FShift, fy >> FShift, frc ? &rc : NULL, Debug); #elif defined __GTK_H__ Gtk::pango_context_set_font_description(GFontSystem::Inst()->GetContext(), Font->Handle()); Gtk::cairo_t *cr = pDC->Handle(); if (!cr) { LgiAssert(!"Can't get cairo."); return; } int Ox = 0, Oy = 0; pDC->GetOrigin(Ox, Oy); Gtk::cairo_save(cr); GRect Client; if (pDC->GetClient(&Client) && Client.Valid()) { Gtk::cairo_rectangle(cr, Client.x1, Client.y1, Client.X(), Client.Y()); Gtk::cairo_clip(cr); Gtk::cairo_new_path(cr); } else { Client.ZOff(-1, -1); } GColour b = Font->Back(); Gtk::cairo_set_source_rgb(cr, (double)b.r()/255.0, (double)b.g()/255.0, (double)b.b()/255.0); if (!Font->Transparent() && frc) { Gtk::cairo_new_path(cr); Gtk::cairo_rectangle ( cr, ((double)frc->x1 / FScale) - Ox, ((double)frc->y1 / FScale) - Oy, (double)frc->X() / FScale, (double)frc->Y() / FScale ); Gtk::cairo_fill(cr); } double Dx = ((double)fx / FScale) - Ox; double Dy = ((double)fy / FScale) - Oy; cairo_translate(cr, Dx, Dy); if (!Font->Transparent() && !frc) { Gtk::cairo_new_path(cr); Gtk::cairo_rectangle(cr, 0, 0, x, y); Gtk::cairo_fill(cr); } if (Hnd) { GColour f = Font->Fore(); Gtk::cairo_set_source_rgb( cr, (double)f.r()/255.0, (double)f.g()/255.0, (double)f.b()/255.0); Gtk::pango_cairo_show_layout(cr, Hnd); if (VisibleTab && Str) { GUtf8Str Ptr(Str); pDC->Colour(Font->WhitespaceColour()); for (int32 u, Idx = 0; u = Ptr; Idx++) { if (IsTabChar(u) || u == ' ') { Gtk::PangoRectangle pos; Gtk::pango_layout_index_to_pos(Hnd, Idx, &pos); GRect r(0, 0, pos.width / FScale, pos.height / FScale); r.Offset(Dx + (pos.x / FScale), Dy + (pos.y / FScale)); DrawWhiteSpace(pDC, u, r); } Ptr++; } } } Gtk::cairo_restore(cr); #elif defined MAC && !defined(LGI_SDL) int Ox = 0, Oy = 0; int px = fx >> FShift; int py = fy >> FShift; GRect rc; if (frc) rc.Set( frc->x1 >> FShift, frc->y1 >> FShift, frc->x2 >> FShift, frc->y2 >> FShift); if (pDC && !pDC->IsScreen()) pDC->GetOrigin(Ox, Oy); if (pDC && !Font->Transparent()) { GColour Old = pDC->Colour(Font->Back()); if (frc) { pDC->Rectangle(&rc); } else { GRect a(px, py, px + x - 1, py + y - 1); pDC->Rectangle(&a); } pDC->Colour(Old); } if (Hnd && pDC && len > 0) { OsPainter dc = pDC->Handle(); #if USE_CORETEXT int y = (pDC->Y() - py + Oy); CGContextSaveGState(dc); pDC->Colour(Font->Fore()); if (pDC->IsScreen()) { if (frc) { CGRect rect = rc; rect.size.width += 1.0; rect.size.height += 1.0; CGContextClipToRect(dc, rect); } CGContextTranslateCTM(dc, 0, pDC->Y()-1); CGContextScaleCTM(dc, 1.0, -1.0); } else { if (frc) { CGContextSaveGState(dc); CGRect rect = rc; rect.origin.x -= Ox; rect.origin.y = pDC->Y() - rect.origin.y + Oy - rect.size.height; rect.size.width += 1.0; rect.size.height += 1.0; CGContextClipToRect(dc, rect); } } CGFloat Tx = (CGFloat)fx / FScale - Ox; CGFloat Ty = (CGFloat)y - Font->Ascent(); CGContextSetTextPosition(dc, Tx, Ty); CTLineDraw(Hnd, dc); CGContextRestoreGState(dc); #else ATSUAttributeTag Tags[1] = {kATSUCGContextTag}; ByteCount Sizes[1] = {sizeof(CGContextRef)}; ATSUAttributeValuePtr Values[1] = {&dc}; e = ATSUSetLayoutControls(Hnd, 1, Tags, Sizes, Values); if (e) { printf("%s:%i - ATSUSetLayoutControls failed (e=%i)\n", _FL, (int)e); } else { // Set style attr ATSURGBAlphaColor c; GColour Fore = Font->Fore(); c.red = (double) Fore.r() / 255.0; c.green = (double) Fore.g() / 255.0; c.blue = (double) Fore.b() / 255.0; c.alpha = 1.0; ATSUAttributeTag Tags[] = {kATSURGBAlphaColorTag}; ATSUAttributeValuePtr Values[] = {&c}; ByteCount Lengths[] = {sizeof(c)}; e = ATSUSetAttributes( Font->Handle(), CountOf(Tags), Tags, Lengths, Values); if (e) { printf("%s:%i - Error setting font attr (e=%i)\n", _FL, (int)e); } else { int y = (pDC->Y() - py + Oy); if (pDC->IsScreen()) { CGContextSaveGState(dc); if (frc) { CGRect rect = rc; rect.size.width += 1.0; rect.size.height += 1.0; CGContextClipToRect(dc, rect); } CGContextTranslateCTM(dc, 0, pDC->Y()-1); CGContextScaleCTM(dc, 1.0, -1.0); e = ATSUDrawText(Hnd, kATSUFromTextBeginning, kATSUToTextEnd, fx - Long2Fix(Ox), Long2Fix(y) - fAscent); CGContextRestoreGState(dc); } else { if (frc) { CGContextSaveGState(dc); CGRect rect = rc; rect.origin.x -= Ox; rect.origin.y = pDC->Y() - rect.origin.y + Oy - rect.size.height; rect.size.width += 1.0; rect.size.height += 1.0; CGContextClipToRect(dc, rect); } e = ATSUDrawText(Hnd, kATSUFromTextBeginning, kATSUToTextEnd, Long2Fix(px - Ox), Long2Fix(y) - fAscent); if (frc) CGContextRestoreGState(dc); } if (e) { char *a = 0; StringConvert(a, NULL, Str, len); printf("%s:%i - ATSUDrawText failed with %i, len=%i, str=%.20s\n", _FL, (int)e, len, a); DeleteArray(a); } } } #endif } #endif } diff --git a/src/common/Gdc2/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 TNEF_SIGNATURE ((uint32_t) 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 atpTriples ((uint16_t) 0x0000) +#define atpString ((uint16_t) 0x0001) +#define atpText ((uint16_t) 0x0002) +#define atpDate ((uint16_t) 0x0003) +#define atpShort ((uint16_t) 0x0004) +#define atpLong ((uint16_t) 0x0005) +#define atpByte ((uint16_t) 0x0006) +#define atpWord ((uint16_t) 0x0007) +#define atpDword ((uint16_t) 0x0008) +#define atpMax ((uint16_t) 0x0009) -#define LVL_MESSAGE ((uint8) 0x01) -#define LVL_ATTACHMENT ((uint8) 0x02) +#define LVL_MESSAGE ((uint8_t) 0x01) +#define LVL_ATTACHMENT ((uint8_t) 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 ATT_ID(_att) ((uint16_t) ((_att) & 0x0000FFFF)) +#define ATT_TYPE(_att) ((uint16_t) (((_att) >> 16) & 0x0000FFFF)) +#define ATT(_atp, _id) ((((uint32_t) (_atp)) << 16) | ((uint16_t) (_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) { 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]; - int InLen = strlen(Sp); + uint8_t Txt[128]; + auto 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/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; #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++) + for (uint32_t *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)); int len = Strlen(c.Get()); return GMemQueue::Write(c, len * sizeof(uint32)); } #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)); + LgiAssert(sizeof(WChar) == sizeof(uint32_t)); 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); + int IcoIdx = EmojiToIconIndex((uint32_t*)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; + uint32_t 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)); + GAutoPtr Utf32((uint32_t*)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())); + GAutoPtr w((uint32_t*)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; + 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; + 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; + 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())); + Mod->Color(GCss::ColorDef(GCss::ColorRgb, (uint32_t)d->Values[ForegroundColourBtn].CastInt64())); if (d->StyleDirty.HasItem(BackgroundColourBtn)) - Mod->BackgroundColor(GCss::ColorDef(GCss::ColorRgb, (uint32)d->Values[BackgroundColourBtn].CastInt64())); + Mod->BackgroundColor(GCss::ColorDef(GCss::ColorRgb, (uint32_t)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; + uint32_t 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}; + uint32_t 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'}; + const uint32_t 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); + uint32_t 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) +bool EmojiMenu::InsertEmoji(uint32_t 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(); + uint32_t *o = &Out[0]; + uint32_t *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; + uint32_t 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; + 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)); + *Cut = (char16*)LgiNewConvertCp(LGI_WideCharset, &DelTxt->First(), "utf-32", DelTxt->Length()*sizeof(uint32_t)); } 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; + 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; + 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)); + s.Color(GCss::ColorDef(GCss::ColorRgb, (uint32_t) 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)); + s.BackgroundColor(GCss::ColorDef(GCss::ColorRgb, (uint32_t) 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->Colour((uint32_t)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}; + const uint32_t 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; + 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)); + char16 *w = (char16*)LgiNewConvertCp(LGI_WideCharset, &Utf32[0], "utf-32", Utf32.Length() * sizeof(uint32_t)); 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 Pressed : 1; - uint8 MouseOver : 1; + uint8_t IsMenu : 1; + uint8_t IsPress : 1; + uint8_t Pressed : 1; + uint8_t 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)); + c.Reset(new DisplayStr(Src, GetFont(), (uint32_t*)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; #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 +#endif 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()); + Add((uint32_t*)&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); + Add((uint32_t*)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(); + uint32_t *u = (uint32_t*)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) + (uint32_t*)(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(); + uint32_t *sStart = t->At(0); + uint32_t *sEnd = sStart + t->Length(); for (unsigned Off = 0; Off < t->Length(); ) { // How much of 't' is on the same line? - uint32 *s = sStart + Off; + uint32_t *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}; + const uint32_t Empty[] = {0}; CurLine->Strs.Add(new DisplayStr(t, f, Empty, 0, flow.pDC)); } continue; } - uint32 *e = s; + uint32_t *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; + uint32_t 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)); + memmove(&(*t)[TxtOffset], &(*t)[TxtOffset + Remove], Remaining * sizeof(uint32_t)); (*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")); + GAutoPtr u((uint32_t*)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 + uint32_t 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; + const uint32_t *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(); + uint32_t *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)) + if (Last->Add((uint32_t*)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; + GArray 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(); + uint32_t *s = &t->First(); + uint32_t *e = s + t->Length(); if (Params->MatchCase) { - for (uint32 *c = s; c < e; c++) + for (uint32_t *c = s; c < e; c++) { if (*c == *Str) { if (c + InLen <= e) Match = !Strncmp(c, Str, InLen); else { - GArray tmp; + 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++) + uint32_t l = ToLower(*Str); + for (uint32_t *c = s; c < e; c++) { if (ToLower(*c) == l) { if (c + InLen <= e) Match = !Strnicmp(c, Str, InLen); else { - GArray tmp; + 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); + uint32_t *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); + uint32_t *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)); + memmove(t->At(0), t->At(Inside), After*sizeof(uint32_t)); 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); + uint32_t *Str = St->At(0); p.Write("\'", 1); for (int k=0; k= 0x10000) p.Print("&#%i;", Str[k]); else { - uint8 utf8[6], *n = utf8; + uint8_t 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