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,4108 @@ #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); + auto *Log = d->Output->Txt[AppWnd::OutputTab]; + + Log->Name(NULL); + Log->Print("Parsing errors...\n"); int i = 0; int Replacements = 0; - GArray Files; + GArray Files; for (auto Ln : Lines) { - if (Ln.Find("error") >= 0) + auto ErrPos = Ln.Find("error"); + if (ErrPos >= 0) { + Log->Print("Error pos = %i\n", (int)ErrPos); + #ifdef WINDOWS GString::Array p = Ln.SplitDelimit(">()"); + #else + GString::Array p = Ln(0, ErrPos).Strip().SplitDelimit(":"); + #endif + Log->Print("p.Len = %i\n", (int)p.Length()); if (p.Length() > 2) { + #ifdef WINDOWS int Base = p[0].IsNumeric() ? 1 : 0; GString Fn = p[Base]; if (Fn.Find("Program Files") >= 0) continue; + auto LineNo = p[Base+1].Int(); + #else + GString Fn = p[0]; + auto LineNo = p[1].Int(); + #endif GAutoString Full; - if (d->FindSource(Full, p[Base], NULL)) + if (d->FindSource(Full, Fn, 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); + Log->Print("%i replacements made.\n", Replacements); + d->Output->Value(AppWnd::OutputTab); } 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/Resources/Lvc.lr8 b/Lvc/Resources/Lvc.lr8 --- a/Lvc/Resources/Lvc.lr8 +++ b/Lvc/Resources/Lvc.lr8 @@ -1,172 +1,177 @@ - + + + + + + diff --git a/Lvc/Resources/resdefs.h b/Lvc/Resources/resdefs.h --- a/Lvc/Resources/resdefs.h +++ b/Lvc/Resources/resdefs.h @@ -1,34 +1,36 @@ -// This file generated by LgiRes - -#define IDC_COMMIT_AND_PUSH 3 -#define IDC_BRANCH 11 -#define IDC_BRANCH_DROPDOWN 14 -#define IDC_MSG 16 -#define IDD_OPTIONS 22 -#define IDC_23 23 -#define IDC_SVN 25 -#define IDC_SVN_BROWSE 26 -#define IDC_GIT 28 -#define IDC_GIT_BROWSE 29 -#define IDC_HG 32 -#define IDC_CVS 33 -#define IDC_HG_BROWSE 36 -#define IDC_CVS_BROWSE 37 -#define IDC_UNTRACKED 38 -#define IDC_PULL_ALL 39 -#define IDC_STATUS 40 -#define IDC_TABLE 500 -#define IDC_COMMIT 501 -#define IDC_PULL 502 -#define IDC_PUSH 503 -#define IDC_COMMIT_TABLE 504 -#define IDC_FILTER 505 -#define IDC_CLEAR_FILTER 506 -#define IDD_COMMIT 507 -#define IDD_TOOLBAR 508 -#define IDC_OPEN 509 -#define IDM_FILE 510 -#define IDM_OPTIONS 511 -#define IDM_HELP 512 -#define IDM_ABOUT 513 -#define IDC_PUSH_ALL 514 +// This file generated by LgiRes + +#define IDC_COMMIT_AND_PUSH 3 +#define IDC_BRANCH 11 +#define IDC_BRANCH_DROPDOWN 14 +#define IDC_MSG 16 +#define IDD_OPTIONS 22 +#define IDC_23 23 +#define IDC_SVN 25 +#define IDC_SVN_BROWSE 26 +#define IDC_GIT 28 +#define IDC_GIT_BROWSE 29 +#define IDC_HG 32 +#define IDC_CVS 33 +#define IDC_HG_BROWSE 36 +#define IDC_CVS_BROWSE 37 +#define IDC_UNTRACKED 38 +#define IDC_PULL_ALL 39 +#define IDC_STATUS 40 +#define IDC_TABLE 500 +#define IDC_COMMIT 501 +#define IDC_PULL 502 +#define IDC_PUSH 503 +#define IDC_COMMIT_TABLE 504 +#define IDC_FILTER 505 +#define IDC_CLEAR_FILTER 506 +#define IDD_COMMIT 507 +#define IDD_TOOLBAR 508 +#define IDC_OPEN 509 +#define IDM_FILE 510 +#define IDM_OPTIONS 511 +#define IDM_HELP 512 +#define IDM_ABOUT 513 +#define IDC_PUSH_ALL 514 +#define IDM_EDIT_MENU 515 +#define IDM_FIND 516 diff --git a/Lvc/Src/Main.cpp b/Lvc/Src/Main.cpp --- a/Lvc/Src/Main.cpp +++ b/Lvc/Src/Main.cpp @@ -1,635 +1,710 @@ #include "Lgi.h" #include "../Resources/resdefs.h" #include "Lvc.h" #include "GTableLayout.h" #ifdef WINDOWS #include "../Resources/resource.h" #endif #include "GTextLog.h" #include "GButton.h" #include "GXmlTreeUi.h" ////////////////////////////////////////////////////////////////// const char *AppName = "Lvc"; VersionCtrl DetectVcs(const char *Path) { char p[MAX_PATH]; if (!Path) return VcNone; if (LgiMakePath(p, sizeof(p), Path, ".git") && DirExists(p)) return VcGit; if (LgiMakePath(p, sizeof(p), Path, ".svn") && DirExists(p)) return VcSvn; if (LgiMakePath(p, sizeof(p), Path, ".hg") && DirExists(p)) return VcHg; if (LgiMakePath(p, sizeof(p), Path, "CVS") && DirExists(p)) return VcCvs; return VcNone; } class DiffView : public GTextLog { public: DiffView(int id) : GTextLog(id) { } void PourStyle(size_t Start, ssize_t Length) { for (auto ln : GTextView3::Line) { if (!ln->c.IsValid()) { char16 *t = Text + ln->Start; if (*t == '+') { ln->c = GColour::Green; ln->Back.Rgb(245, 255, 245); } else if (*t == '-') { ln->c = GColour::Red; ln->Back.Rgb(255, 245, 245); } else if (*t == '@') { ln->c.Rgb(128, 128, 128); ln->Back.Rgb(235, 235, 235); } else ln->c.Set(LC_TEXT, 24); } } } }; class ToolBar : public GLayout, public GLgiRes { public: ToolBar() { GAutoString Name; GRect Pos; if (LoadFromResource(IDD_TOOLBAR, this, &Pos, &Name)) { GTableLayout *v; if (GetViewById(IDC_TABLE, v)) { GRect r = v->GetPos(); r.Offset(-r.x1, -r.y1); r.x2++; v->SetPos(r); v->OnPosChange(); r = v->GetUsedArea(); if (r.Y() <= 1) r.Set(0, 0, 30, 30); GetCss(true)->Height(GCss::Len(GCss::LenPx, (float)r.Y()+3)); } else LgiAssert(!"Missing table ctrl"); } else LgiAssert(!"Missing toolbar resource"); } void OnCreate() { AttachChildren(); } void OnPaint(GSurface *pDC) { pDC->Colour(LC_MED, 24); pDC->Rectangle(); } }; class CommitCtrls : public GLayout, public GLgiRes { public: CommitCtrls() { GAutoString Name; GRect Pos; if (LoadFromResource(IDD_COMMIT, this, &Pos, &Name)) { GTableLayout *v; if (GetViewById(IDC_COMMIT_TABLE, v)) { v->GetCss(true)->PaddingRight("8px"); GRect r = v->GetPos(); r.Offset(-r.x1, -r.y1); r.x2++; v->SetPos(r); v->OnPosChange(); r = v->GetUsedArea(); if (r.Y() <= 1) r.Set(0, 0, 30, 30); GetCss(true)->Height(GCss::Len(GCss::LenPx, (float)r.Y())); } else LgiAssert(!"Missing table ctrl"); } else LgiAssert(!"Missing toolbar resource"); } void OnPosChange() { GTableLayout *v; if (GetViewById(IDC_COMMIT_TABLE, v)) v->SetPos(GetClient()); } void OnCreate() { AttachChildren(); } }; GString::Array GetProgramsInPath(const char *Program) { GString::Array Bin; GString Prog = Program; #ifdef WINDOWS Prog += LGI_EXECUTABLE_EXT; #endif GString::Array a = LGetPath(); for (auto p : a) { GFile::Path c(p, Prog); if (c.Exists()) Bin.New() = c.GetFull(); } return Bin; } class OptionsDlg : public GDialog, public GXmlTreeUi { GOptionsFile &Opts; public: OptionsDlg(GViewI *Parent, GOptionsFile &opts) : Opts(opts) { SetParent(Parent); Map("svn-path", IDC_SVN, GV_STRING); Map("git-path", IDC_GIT, GV_STRING); Map("hg-path", IDC_HG, GV_STRING); Map("cvs-path", IDC_CVS, GV_STRING); if (LoadFromResource(IDD_OPTIONS)) { MoveSameScreen(Parent); Convert(&Opts, this, true); } } void Browse(int EditId) { GFileSelect s; s.Parent(this); if (s.Open()) { SetCtrlName(EditId, s.Name()); } } void BrowseFiles(GViewI *Ctrl, const char *Bin, int EditId) { GRect Pos = Ctrl->GetPos(); GdcPt2 Pt(Pos.x1, Pos.y2 + 1); PointToScreen(Pt); GSubMenu s; GString::Array Bins = GetProgramsInPath(Bin); for (unsigned i=0; i= 1000) { GString Bin = Bins[Cmd - 1000]; if (Bin) SetCtrlName(EditId, Bin); } break; } } } int OnNotify(GViewI *Ctrl, int Flags) { switch (Ctrl->GetId()) { case IDC_SVN_BROWSE: BrowseFiles(Ctrl, "svn", IDC_SVN); break; case IDC_GIT_BROWSE: BrowseFiles(Ctrl, "git", IDC_GIT); break; case IDC_HG_BROWSE: BrowseFiles(Ctrl, "hg", IDC_HG); break; case IDC_CVS_BROWSE: BrowseFiles(Ctrl, "cvs", IDC_CVS); break; case IDOK: Convert(&Opts, this, false); // fall case IDCANCEL: { EndModal(Ctrl->GetId() == IDOK); break; } } return GDialog::OnNotify(Ctrl, Flags); } }; +class CommitList : public LList +{ +public: + CommitList(int id) : LList(id, 0, 0, 200, 200) + { + } + + void SelectRevisions(GString::Array &Revs) + { + VcCommit *Scroll = NULL; + for (auto it = begin(); it != end(); it++) + { + VcCommit *item = dynamic_cast(*it); + if (item) + { + for (auto r: Revs) + { + if (item->IsRev(r)) + { + if (!Scroll) + Scroll = item; + item->Select(true); + } + } + } + } + if (Scroll) + Scroll->ScrollTo(); + } + + bool OnKey(GKey &k) + { + switch (k.c16) + { + case 'p': + case 'P': + { + if (k.Down()) + { + GArray Sel; + GetSelection(Sel); + if (Sel.Length()) + { + auto p = Sel[0]->GetParents(); + if (p->Length() == 0) + break; + + for (auto c:Sel) + c->Select(false); + + SelectRevisions(*p); + } + } + return true; + } + } + + return LList::OnKey(k); + } +}; + class App : public GWindow, public AppPriv { GAutoPtr ImgLst; public: App() { GString AppRev; AppRev.Printf("%s v%s", AppName, APP_VERSION); Name(AppRev); GRect r(0, 0, 1400, 800); SetPos(r); MoveToCenter(); SetQuitOnClose(true); Opts.SerializeFile(false); SerializeState(&Opts, "WndPos", true); #ifdef WINDOWS SetIcon(MAKEINTRESOURCEA(IDI_ICON1)); #else SetIcon("icon32.png"); #endif ImgLst.Reset(LgiLoadImageList("image-list.png", 16, 16)); if (Attach(0)) { if ((Menu = new GMenu)) { Menu->SetPrefAndAboutItems(IDM_OPTIONS, IDM_ABOUT); Menu->Attach(this); Menu->Load(this, "IDM_MENU"); } GBox *ToolsBox = new GBox(IDC_TOOLS_BOX, true, "ToolsBox"); GBox *FoldersBox = new GBox(IDC_FOLDERS_BOX, false, "FoldersBox"); GBox *CommitsBox = new GBox(IDC_COMMITS_BOX, true, "CommitsBox"); ToolBar *Tools = new ToolBar; ToolsBox->Attach(this); Tools->Attach(ToolsBox); FoldersBox->Attach(ToolsBox); Tree = new GTree(IDC_TREE, 0, 0, 200, 200); Tree->GetCss(true)->Width(GCss::Len("320px")); Tree->ShowColumnHeader(true); Tree->AddColumn("Folder", 250); Tree->AddColumn("Counts", 50); Tree->Attach(FoldersBox); Tree->SetImageList(ImgLst, false); CommitsBox->Attach(FoldersBox); - Lst = new LList(IDC_LIST, 0, 0, 200, 200); + Lst = new CommitList(IDC_LIST); Lst->Attach(CommitsBox); Lst->GetCss(true)->Height("40%"); Lst->AddColumn("---", 40); Lst->AddColumn("Commit", 270); Lst->AddColumn("Author", 240); Lst->AddColumn("Date", 130); Lst->AddColumn("Message", 400); GBox *FilesBox = new GBox(IDC_FILES_BOX, false); FilesBox->Attach(CommitsBox); Files = new LList(IDC_FILES, 0, 0, 200, 200); Files->GetCss(true)->Width("35%"); Files->Attach(FilesBox); Files->AddColumn("[ ]", 30); Files->AddColumn("State", 100); Files->AddColumn("Name", 400); GBox *MsgBox = new GBox(IDC_MSG_BOX, true); MsgBox->Attach(FilesBox); CommitCtrls *Commit = new CommitCtrls; Commit->Attach(MsgBox); Commit->GetCss(true)->Height("25%"); if (Commit->GetViewById(IDC_MSG, Msg)) { GTextView3 *Tv = dynamic_cast(Msg); if (Tv) { Tv->Sunken(true); Tv->SetWrapType(TEXTED_WRAP_NONE); } } else LgiAssert(!"No ctrl?"); Tabs = new GTabView(IDC_TAB_VIEW); Tabs->Attach(MsgBox); const char *Style = "Padding: 0px 8px 8px 0px"; Tabs->GetCss(true)->Parse(Style); GTabPage *p = Tabs->Append("Diff"); p->Append(Diff = new DiffView(IDC_TXT)); // Diff->Sunken(true); Diff->SetWrapType(TEXTED_WRAP_NONE); p = Tabs->Append("Log"); p->Append(Log = new GTextLog(IDC_LOG)); // Log->Sunken(true); Log->SetWrapType(TEXTED_WRAP_NONE); AttachChildren(); Visible(true); } GXmlTag *f = Opts.LockTag(OPT_Folders, _FL); if (!f) { Opts.CreateTag(OPT_Folders); f = Opts.LockTag(OPT_Folders, _FL); } if (f) { bool Req[VcMax] = {0}; for (GXmlTag *c = f->Children.First(); c; c = f->Children.Next()) { if (c->IsTag(OPT_Folder)) { auto f = new VcFolder(this, c); Tree->Insert(f); if (!Req[f->GetType()]) { Req[f->GetType()] = true; f->GetVersion(); } } } Opts.Unlock(); } SetPulse(200); } ~App() { SerializeState(&Opts, "WndPos", false); GXmlTag *f = Opts.LockTag(OPT_Folders, _FL); if (f) { f->EmptyChildren(); VcFolder *vcf = NULL; bool b; while ((b = Tree->Iterate(vcf))) f->InsertTag(vcf->Save()); Opts.Unlock(); } Opts.SerializeFile(true); } int OnCommand(int Cmd, int Event, OsView Wnd) { switch (Cmd) { case IDM_OPTIONS: { OptionsDlg Dlg(this, Opts); Dlg.DoModal(); break; } + case IDM_FIND: + { + GInput i(this, "", "Search string:"); + if (i.DoModal()) + { + GString::Array Revs; + Revs.Add(i.GetStr()); + + CommitList *cl; + if (GetViewById(IDC_LIST, cl)) + cl->SelectRevisions(Revs); + } + break; + } } return 0; } void OnPulse() { VcFolder *vcf = NULL; bool b; while ((b = Tree->Iterate(vcf))) vcf->OnPulse(); } void OpenFolder() { GFileSelect s; s.Parent(this); if (s.OpenFolder()) { if (DetectVcs(s.Name())) Tree->Insert(new VcFolder(this, s.Name())); else LgiMsg(this, "Folder not under version control.", AppName); } } int OnNotify(GViewI *c, int flag) { switch (c->GetId()) { case IDC_FILES: { switch (flag) { case GNotifyItem_ColumnClicked: { int Col = -1; GMouse m; if (Files->GetColumnClickInfo(Col, m)) { if (Col == 0) { // Select / deselect all checkboxes.. List n; if (Files->GetAll(n)) { bool Checked = false; for (VcFile *f = n.First(); f; f = n.Next()) Checked |= f->Checked() > 0; for (VcFile *f = n.First(); f; f = n.Next()) f->Checked(Checked ? 0 : 1); } } } break; } } break; } case IDC_OPEN: { OpenFolder(); break; } case IDC_TREE: { switch (flag) { case GNotifyContainer_Click: { GMouse m; c->GetMouse(m); if (m.Right()) { GSubMenu s; s.AppendItem("Add", IDM_ADD); int Cmd = s.Float(c->GetGView(), m); switch (Cmd) { case IDM_ADD: { OpenFolder(); break; } } } break; } case LvcCommandStart: { SetCtrlEnabled(IDC_PUSH, false); SetCtrlEnabled(IDC_PUSH_ALL, false); SetCtrlEnabled(IDC_PULL, false); SetCtrlEnabled(IDC_PULL_ALL, false); break; } case LvcCommandEnd: { SetCtrlEnabled(IDC_PUSH, true); SetCtrlEnabled(IDC_PUSH_ALL, true); SetCtrlEnabled(IDC_PULL, true); SetCtrlEnabled(IDC_PULL_ALL, true); break; } } break; } case IDC_CLEAR_FILTER: { SetCtrlName(IDC_FILTER, NULL); // Fall through } case IDC_FILTER: { VcFolder *f = dynamic_cast(Tree->Selection()); if (f) f->Select(true); break; } case IDC_COMMIT_AND_PUSH: case IDC_COMMIT: { const char *Msg = GetCtrlName(IDC_MSG); if (ValidStr(Msg)) { VcFolder *f = dynamic_cast(Tree->Selection()); if (f) { char *Branch = GetCtrlName(IDC_BRANCH); bool AndPush = c->GetId() == IDC_COMMIT_AND_PUSH; f->Commit(Msg, ValidStr(Branch) ? Branch : NULL, AndPush); } } else LgiMsg(this, "No message for commit.", AppName); break; } case IDC_PUSH: { VcFolder *f = dynamic_cast(Tree->Selection()); if (f) f->Push(); break; } case IDC_PULL: { VcFolder *f = dynamic_cast(Tree->Selection()); if (f) f->Pull(); break; } case IDC_PULL_ALL: { GArray Folders; Tree->GetAll(Folders); for (auto f : Folders) { f->Pull(LogSilo); } break; } case IDC_STATUS: { GArray Folders; Tree->GetAll(Folders); for (auto f : Folders) { f->FolderStatus(); } break; } } return 0; } }; ////////////////////////////////////////////////////////////////// int LgiMain(OsAppArguments &AppArgs) { GApp a(AppArgs, AppName); if (a.IsOk()) { a.AppWnd = new App; 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,386 @@ #include "Lvc.h" #include "GClipBoard.h" #include "../Resources/resdefs.h" #include "GPath.h" +#include "LHashTable.h" -VcCommit::VcCommit(AppPriv *priv) +VcEdge::~VcEdge() +{ + if (Parent) + Parent->Edges.Delete(this); + if (Child) + Child->Edges.Delete(this); +} + +void VcEdge::Detach(VcCommit *c) +{ + if (Parent == c) + Parent = NULL; + if (Child == c) + Child = NULL; + if (Parent == NULL && Child == NULL) + delete this; +} + +void VcEdge::Set(VcCommit *p, VcCommit *c) +{ + if ((Parent = p)) + Parent->Edges.Add(this); + if ((Child = c)) + Child->Edges.Add(this); +} + +VcCommit::VcCommit(AppPriv *priv, VcFolder *folder) : Pos(32, -1) { d = priv; + Folder = folder; Current = false; NodeIdx = -1; + NodeColour.Rgb(150, 150, 255); Parents.SetFixedLength(false); } +VcCommit::~VcCommit() +{ + for (auto e: Edges) + e->Detach(this); +} + 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; + double r = Half - 1; + + double x = MAP(NodeIdx); + Mem.Colour(GColour::Black); + + VcCommit *Prev = NULL, *Next = NULL; + Prev = Idx > 0 ? Folder->Log[Idx - 1] : NULL; + Next = Idx < Folder->Log.Length() - 1 ? Folder->Log[Idx + 1] : NULL; + + for (auto it: Pos) + { + VcEdge *e = it.key; + int CurIdx = it.value; + if (CurIdx < 0) + { + continue; + } - bool Dbg = IsRev("938e8ccfdac365e91ca7ca732aab538d8769a37e"); - if (Dbg) - { - Mem.Colour(GColour::Red); - // Mem.Rectangle(); + double CurX = MAP(CurIdx); + + #define I(v) ((int)(v)) + if (e->Child != this) + { + // Line to previous commit + int PrevIdx = Prev ? Prev->Pos.Find(e) : -1; + if (PrevIdx >= 0) + { + double PrevX = MAP(PrevIdx); + Mem.Line(I(PrevX), I(-(Ht/2)), I(CurX), I(Ht/2)); + } + else + { + Mem.Colour(GColour::Red); + Mem.Line(I(CurX), I(Ht/2), I(CurX), I(Ht/2-5)); + Mem.Colour(GColour::Black); + } + } + + if (e->Parent != this) + { + int NextIdx = Next ? Next->Pos.Find(e) : -1; + if (NextIdx >= 0) + { + double NextX = MAP(NextIdx); + Mem.Line(I(NextX), I(Ht+(Ht/2)), I(CurX), I(Ht/2)); + } + else + { + Mem.Colour(GColour::Red); + Mem.Line(I(CurX), I(Ht/2), I(CurX), I(Ht/2+5)); + Mem.Colour(GColour::Black); + } + } } - 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()); + + #ifdef WINDOWS + // Unix timestamp to windows ticks + Ts.Set((uint64)a[0].Int() * LDateTime::Second64Bit + 116445168000000000LL); + #else + Ts.Set((uint64) a[0].Int()); + #endif 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/VcCommit.h b/Lvc/Src/VcCommit.h --- a/Lvc/Src/VcCommit.h +++ b/Lvc/Src/VcCommit.h @@ -1,52 +1,67 @@ #ifndef _VcCommit_h_ #define _VcCommit_h_ class VcFolder; +class VcCommit; -struct Node -{ - GColour c; - GString Rev; - GArray Next, Prev; -}; +struct VcEdge +{ + VcCommit *Parent, *Child; + int Idx; + + VcEdge(VcCommit *p, VcCommit *c) + { + Set(p, c); + Idx = -1; + } + + ~VcEdge(); + void Set(VcCommit *p, VcCommit *c); + void Detach(VcCommit *c); +}; class VcCommit : public LListItem { AppPriv *d; + VcFolder *Folder; bool Current; GString Rev; GString::Array Parents; GString Author; LDateTime Ts; GString Cache; GString Msg; public: GString::Array Files; - GArray Nodes; + GArray Edges; int NodeIdx; + int Idx; + GColour NodeColour; + LHashTbl, int> Pos; - VcCommit(AppPriv *priv); + VcCommit(AppPriv *priv, VcFolder *folder); + ~VcCommit(); char *GetRev(); bool IsRev(const char *r) { return !Strcmp(GetRev(), r); } char *GetAuthor(); char *GetMsg(); GString::Array *GetParents() { return &Parents; } LDateTime &GetTs() { return Ts; } void SetCurrent(bool b); void OnPaintColumn(GItem::ItemPaintCtx &Ctx, int i, GItemColumn *c); char *GetText(int Col); bool GitParse(GString s, bool RevList); bool SvnParse(GString s); bool HgParse(GString s); bool CvsParse(LDateTime &Dt, GString Auth, GString Msg); VcFolder *GetFolder(); // Events void OnMouseClick(GMouse &m); void Select(bool b); }; #endif \ No newline at end of file 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,2656 @@ #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); + IsLogging = StartCmd("rev-list --all --header --timestamp --author-date-order", &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); + // 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) { + GFile f("C:\\Users\\matthew\\Code\\Lgi\\trunk\\rev-list.txt", O_WRITE); + if (f.IsOpen()) + { + f.SetSize(0); + f.Write(s); + f.Close(); + } + 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)); + GAutoPtr Rev(new VcCommit(d, this)); 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); + // 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)); + GAutoPtr Rev(new VcCommit(d, this)); 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)); + GAutoPtr Rev(new VcCommit(d, this)); 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)); + GAutoPtr Rev(new VcCommit(d, this)); 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)); + Map.Add(Ts, Cc = new VcCommit(d, this)); 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"; + LHashTbl,VcCommit*> Map; + + // Index all the commits + int i = 0; + for (auto c:Log) + { + c->Idx = i++; + c->NodeIdx = -1; + Map.Add(c->GetRev(), c); + } - VcCommit *p = NULL; - for (auto i = Log.Length()-1; i > 0; i--) + // Create all the edges... + for (auto c:Log) { - VcCommit *c = Log[i]; - if (p) + auto *Par = c->GetParents(); + for (auto &pRev : *Par) { - c->Nodes.Empty(); - c->NodeIdx = -1; + auto *p = Map.Find(pRev); + if (p) + new VcEdge(p, c); + else + LgiAssert(0); + } + } - // Match the parent nodes... - auto Par = c->GetParents(); + // Map the edges to positions + typedef GArray EdgeArr; + GArray Active; + for (auto c:Log) + { + #if 1 + if (c->IsRev("49d6765e37dfffb0ad4e924da5169c076c87c871")) + { + int asd=0; + } + #endif - if (c->IsRev("1802edf955ff9037e66b1481121be3a6e21bf0a7")) + for (unsigned i=0; c->NodeIdx<0 && iParent == c) + { + c->NodeIdx = i; + break; + } } - - - 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) + // Add starting edges to active set + for (auto e:c->Edges) + { + if (e->Child == c) + { + if (c->NodeIdx < 0) + c->NodeIdx = (int)Active.Length(); + + e->Idx = c->NodeIdx; + c->Pos.Add(e, e->Idx); + Active[e->Idx].Add(e); + } + } + + // Now for all active edges... assign positions + for (unsigned i=0; iChild || c == e->Parent) { - // 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()) + LgiAssert(c->NodeIdx >= 0); + c->Pos.Add(e, c->NodeIdx); + } + else + { + // May need to untangle edges with different parents here + bool Diff = false; + for (auto edge: Edges) { - // Found.. - VcCommit *Cur = c; - VcCommit *Next = Log[i+1]; - - if (Cur->NodeIdx < 0) + if (edge != e && + edge->Child != c && + edge->Parent != e->Parent) { - Cur->NodeIdx = (int)Next->Nodes.Length(); - Cur->Nodes[Cur->NodeIdx].Rev = Cur->GetRev(); + Diff = true; + break; } - - int CurIdx = Cur->NodeIdx; - for (auto n = i; n < end; n++) + } + if (Diff) + { + int NewIndex = -1; + + // Look through existing indexes for a parent match + for (unsigned ii=0; iiNodes.Length(); - for (unsigned k=0; kNodes.Length(); k++) + if (ii == i) continue; + // Match e->Parent? + bool Match = true; + for (auto ee:Active[ii]) { - if (Next->Nodes[k].Rev.Equals(pRev.Get(), false)) + if (ee->Parent != e->Parent) { - NextIdx = k; + Match = false; break; } } - - if (Cur->IsRev(Dbg) && CurIdx > 0) - { - int asd=0; - } - auto &CurNode = Cur->Nodes[CurIdx]; - CurNode.Next.Add(NextIdx); + if (Match) + NewIndex = ii; + } - if (Next->IsRev(Dbg) && NextIdx > 0) - { - int asd=0; - } - auto &NextNode = Next->Nodes[NextIdx]; - NextNode.Prev.Add(CurIdx); - NextNode.Rev = pRev; + if (NewIndex < 0) + // Create new index for this parent + NewIndex = (int)Active.Length(); - CurIdx = NextIdx; - Cur = Next; - } + Edges.Delete(e); + Active[NewIndex].Add(e); + e->Idx = NewIndex; + c->Pos.Add(e, NewIndex); + n--; } 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; - } + LgiAssert(e->Idx == i); + c->Pos.Add(e, i); } } } } - else + + // Process terminating edges + for (auto e:c->Edges) { - // First node - c->NodeIdx = 0; - auto &n = c->Nodes[0]; - n.Rev = c->GetRev(); + if (e->Parent == c) + { + if (e->Idx < 0) + { + // This happens with out of order commits..? + continue; + } + + int i = e->Idx; + if (c->NodeIdx < 0) + c->NodeIdx = i; + + LgiAssert(Active[i].HasItem(e)); + Active[i].Delete(e); + } } - p = c; + + // Collapse any empty active columns + for (unsigned i=0; iIdx > 0); + edge->Idx--; + c->Pos.Add(edge, edge->Idx); + } + } + i--; + } + } } } 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/Lvc/Src/VcFolder.h b/Lvc/Src/VcFolder.h --- a/Lvc/Src/VcFolder.h +++ b/Lvc/Src/VcFolder.h @@ -1,245 +1,246 @@ #ifndef _VcFolder_h_ #define _VcFolder_h_ #include "GSubProcess.h" class VcLeaf; enum LoggingType { LogNone, // No output from cmd LogNormal, // Output appears as it's available LogSilo, // Output appears after cmd finished (keeps it non-interleaved with other log msgs) }; enum LvcError { ErrNone, ErrSubProcessFailed = GSUBPROCESS_ERROR, }; class ReaderThread : public LThread { GStream *Out; GSubProcess *Process; public: ReaderThread(GSubProcess *p, GStream *out); ~ReaderThread(); int Main(); }; extern int Ver2Int(GString v); extern int ToolVersion[VcMax]; class VcFolder : public GTreeItem, public GCss { + friend class VcCommit; struct ParseParams { GString Str; GString AltInitPath; VcLeaf *Leaf; bool IsWorking; ParseParams(const char *str = NULL) { Str = str; Leaf = NULL; IsWorking = false; } }; typedef bool (VcFolder::*ParseFn)(int, GString, ParseParams*); class Cmd : public GStream { GString::Array Context; GStringPipe Buf; public: LoggingType Logging; GStream *Log; GAutoPtr Rd; ParseFn PostOp; GAutoPtr Params; LvcError Err; Cmd(GString::Array &context, LoggingType logging, GStream *log) { Context = context; Logging = logging; Log = log; Err = ErrNone; } GString GetBuf() { GString s = Buf.NewGStr(); if (Log && Logging == LogSilo) { GString m; m.Printf("=== %s ===\n\t%s %s\n", Context[0].Get(), Context[1].Get(), Context[2].Get()); Log->Write(m.Get(), m.Length()); auto Lines = s.Split("\n"); for (auto Ln : Lines) Log->Print("\t%s\n", Ln.Get()); } return s; } ssize_t Write(const void *Ptr, ssize_t Size, int Flags = 0) { ssize_t Wr = Buf.Write(Ptr, Size, Flags); if (Log && Logging == LogNormal) Log->Write(Ptr, Size, Flags); if (Flags) Err = (LvcError) Flags; return Wr; } }; class UncommitedItem : public LListItem { AppPriv *d; public: UncommitedItem(AppPriv *priv) { d = priv; } void OnPaint(GItem::ItemPaintCtx &Ctx); void Select(bool b); }; AppPriv *d; VersionCtrl Type; GString Path, CurrentCommit, RepoUrl, VcCmd; GArray Log; GString CurrentBranch; GString::Array Branches; GAutoPtr Uncommit; GString Cache, NewRev; bool CommitListDirty; int Unpushed, Unpulled; GString CountCache; GTreeItem *Tmp; int CmdErrors; struct GitCommit { GString::Array Files; GString Msg, Branch; ParseParams *Param; GitCommit() { Param = NULL; } }; GAutoPtr PostAdd; void GitAdd(); GArray Cmds; bool IsLogging, IsGetCur, IsUpdate, IsFilesCmd, IsWorkingFld, IsCommit, IsUpdatingCounts; void Init(AppPriv *priv); const char *GetVcName(); bool StartCmd(const char *Args, ParseFn Parser = NULL, ParseParams *Params = NULL, LoggingType Logging = LogNone); void OnBranchesChange(); void OnCmdError(GString Output, const char *Msg); void ClearError(); void OnChange(PropType Prop) { Update(); } VcFile *FindFile(const char *Path); void LinkParents(); bool ParseDiffs(GString s, GString Rev, bool IsWorking); bool ParseRevList(int Result, GString s, ParseParams *Params); bool ParseLog(int Result, GString s, ParseParams *Params); bool ParseInfo(int Result, GString s, ParseParams *Params); bool ParseFiles(int Result, GString s, ParseParams *Params); bool ParseWorking(int Result, GString s, ParseParams *Params); bool ParseUpdate(int Result, GString s, ParseParams *Params); bool ParseCommit(int Result, GString s, ParseParams *Params); bool ParseGitAdd(int Result, GString s, ParseParams *Params); bool ParsePush(int Result, GString s, ParseParams *Params); bool ParsePull(int Result, GString s, ParseParams *Params); bool ParseCounts(int Result, GString s, ParseParams *Params); bool ParseRevert(int Result, GString s, ParseParams *Params); bool ParseResolve(int Result, GString s, ParseParams *Params); bool ParseBlame(int Result, GString s, ParseParams *Params); bool ParseSaveAs(int Result, GString s, ParseParams *Params); bool ParseBranches(int Result, GString s, ParseParams *Params); bool ParseStatus(int Result, GString s, ParseParams *Params); bool ParseAddFile(int Result, GString s, ParseParams *Params); bool ParseVersion(int Result, GString s, ParseParams *Params); bool ParseClean(int Result, GString s, ParseParams *Params); bool ParseDiff(int Result, GString s, ParseParams *Params); public: VcFolder(AppPriv *priv, const char *p); VcFolder(AppPriv *priv, GXmlTag *t); VersionCtrl GetType(); AppPriv *GetPriv() { return d; } const char *GetPath() { return Path; } char *GetText(int Col); bool Serialize(GXmlTag *t, bool Write); GXmlTag *Save(); void Select(bool b); void ListCommit(VcCommit *c); void ListWorkingFolder(); void FolderStatus(const char *Path = NULL, VcLeaf *Notify = NULL); void Commit(const char *Msg, const char *Branch, bool AndPush); void Push(); void Pull(LoggingType Logging = LogNormal); void Clean(); bool Revert(const char *Path, const char *Revision = NULL); bool Resolve(const char *Path); bool AddFile(const char *Path, bool AsBinary = true); bool Blame(const char *Path); bool SaveFileAs(const char *Path, const char *Revision); void ReadDir(GTreeItem *Parent, const char *Path); void SetEol(const char *Path, int Type); void GetVersion(); void Diff(VcFile *file); void OnPulse(); void OnUpdate(const char *Rev); void OnMouseClick(GMouse &m); void OnRemove(); void OnExpand(bool b); void OnPaint(ItemPaintCtx &Ctx); }; class VcLeaf : public GTreeItem { AppPriv *d; VcFolder *Parent; bool Folder; GString Path, Leaf; GTreeItem *Tmp; public: VcLeaf(VcFolder *parent, GTreeItem *Item, GString path, GString leaf, bool folder); GString Full(); void OnBrowse(); void AfterBrowse(); void OnExpand(bool b); char *GetText(int Col); int GetImage(int Flags); int Compare(VcLeaf *b); bool Select(); void Select(bool b); void OnMouseClick(GMouse &m); }; #endif \ No newline at end of file diff --git a/Makefile.linux b/Makefile.linux --- a/Makefile.linux +++ b/Makefile.linux @@ -1,1584 +1,1584 @@ #!/usr/bin/make # # This makefile generated by LgiIde # http://www.memecode.com/lgi.php # .SILENT : CC = gcc CPP = g++ Target = lgi ifndef Build - Build = Release + Build = Debug endif BuildDir = $(Build) Flags = -fPIC -w -fno-inline -fpermissive ifeq ($(Build),Debug) Flags += -g -std=c++11 Tag = d Defs = -D_DEBUG -DLINUX -D_REENTRANT -D_FILE_OFFSET_BITS=64 -DPOSIX -DLGI_LIBRARY -DHAS_FILE_CMD=1 Libs = \ -lmagic \ -static-libgcc \ `pkg-config --libs gtk+-2.0` Inc = \ -I./include/common \ -I./include/linux/Gtk \ -I./src/common/Hash/md5 \ -I./src/common/Hash/sha1 \ -I/usr/include/gstreamer-1.0 \ -I/usr/lib/x86_64-linux-gnu/gstreamer-1.0/include \ `pkg-config --cflags gtk+-2.0` \ -Iinclude/common \ -Iinclude/linux \ -Iinclude/linux/Gtk else Flags += -s -Os -std=c++11 Defs = -DLINUX -D_REENTRANT -D_FILE_OFFSET_BITS=64 -DPOSIX -DLGI_LIBRARY -DHAS_FILE_CMD=1 Libs = \ -lmagic \ -static-libgcc \ `pkg-config --libs gtk+-2.0` Inc = \ -I./include/common \ -I./include/linux/Gtk \ -I./src/common/Hash/md5 \ -I./src/common/Hash/sha1 \ -I/usr/include/gstreamer-1.0 \ -I/usr/lib/x86_64-linux-gnu/gstreamer-1.0/include \ `pkg-config --cflags gtk+-2.0` \ -Iinclude/common \ -Iinclude/linux \ -Iinclude/linux/Gtk endif # Dependencies Depends = LDateTime.o \ GDragAndDrop.o \ GFile.o \ ShowFileProp_Linux.o \ GExeCheck.o \ GFileCommon.o \ GApp.o \ GGeneral.o \ GView.o \ GWindow.o \ LgiWidget.o \ GViewCommon.o \ GWindowCommon.o \ GLibrary.o \ GMem.o \ GProperties.o \ GContainers.o \ GMenuGtk.o \ GMenuCommon.o \ GProcess.o \ LgiRes.o \ Res.o \ Gel.o \ GPath.o \ GMemStream.o \ GStream.o \ LThread.o \ LMutex.o \ LThreadCommon.o \ LThreadEvent.o \ GVariant.o \ GGuiUtils.o \ GObject.o \ GToolTip.o \ GTrayIcon.o \ LgiCommon.o \ LgiMsg.o \ GFileSelect.o \ GAlert.o \ GFindReplace.o \ GFontSelect.o \ GInput.o \ Gdc2.o \ GColour.o \ GColourReduce.o \ GdcCommon.o \ GdcTools.o \ GDisplayString.o \ GFont.o \ GFont_W.o \ GFontCodePages.o \ GFontSystem.o \ LStringLayout.o \ 15Bit.o \ 16Bit.o \ 24Bit.o \ 32Bit.o \ 8Bit.o \ Alpha.o \ GFilter.o \ GRect.o \ GMemDC.o \ GPrintDC.o \ GScreenDC.o \ GPrinter.o \ GSurface.o \ GClipBoard.o \ GDataDlg.o \ GPassword.o \ md5.o \ sha1.o \ GMru.o \ Base64.o \ GOptionsFile.o \ LgiRand.o \ GString.o \ GToken.o \ GUnicode.o \ GUtf8.o \ GXmlTree.o \ GBitmap.o \ GButton.o \ GCheckBox.o \ GCombo.o \ GDocView.o \ GEdit.o \ GItemContainer.o \ GPanel.o \ GPopup.o \ GProgress.o \ GProgressDlg.o \ GRadioGroup.o \ GScrollBar.o \ GStatusBar.o \ GTabView.o \ GTextLabel.o \ GTextView3.o \ GToolBar.o \ GTree.o \ LList.o \ GCss.o \ GCssTools.o \ GLayout.o \ GWidgets.o \ GBox.o \ GSplitter.o \ GTableLayout.o \ GUri.o \ INet.o \ INetTools.o \ MDStringToDigest.o # Target TargetFile = lib$(Target)$(Tag).so $(TargetFile) : outputfolder $(Depends) @echo Linking $(TargetFile) [$(Build)]... $(CPP)$s -shared \ \ -o $(BuildDir)/$(TargetFile) \ $(addprefix $(BuildDir)/,$(Depends)) \ $(Libs) @echo Done. # Create the output folder outputfolder : -mkdir -p $(BuildDir) 2> /dev/null # Clean out targets clean : rm -f $(BuildDir)/*.o $(BuildDir)/$(TargetFile) @echo Cleaned $(BuildDir) LDateTime.o : ./src/common/General/LDateTime.cpp ./include/common/Lgi.h \ ./include/common/LDateTime.h \ ./include/common/GToken.h \ ./include/common/GDocView.h @echo $( #include "LgiResEdit.h" #include "LgiRes_Dialog.h" #include "LgiRes_Menu.h" #include "GAbout.h" #include "GTextLabel.h" #include "GEdit.h" #include "GCheckBox.h" #include "GProgressDlg.h" #include "GTextView3.h" #include "resdefs.h" #include "GToken.h" #include "GDataDlg.h" #include "GButton.h" char AppName[] = "Lgi Resource Editor"; char HelpFile[] = "Help.html"; char OptionsFileName[] = "Options.r"; char TranslationStrMagic[] = "LgiRes.String"; #define VIEW_PULSE_RATE 100 #ifndef DIALOG_X #define DIALOG_X 1.56 #define DIALOG_Y 1.85 #define CTRL_X 1.50 #define CTRL_Y 1.64 #endif enum Ctrls { IDC_HBOX = 100, IDC_VBOX, }; const char *TypeNames[] = { "", "Css", "Dialog", "String", "Menu", 0}; ////////////////////////////////////////////////////////////////////////////// ResFileFormat GetFormat(char *File) { ResFileFormat Format = Lr8File; char *Ext = LgiGetExtension(File); if (Ext) { if (stricmp(Ext, "lr") == 0) Format = CodepageFile; else if (stricmp(Ext, "xml") == 0) Format = XmlFile; } return Format; } char *EncodeXml(char *Str, int Len) { char *Ret = 0; if (Str) { GStringPipe p; char *s = Str; for (char *e = Str; e && *e && (Len < 0 || ((e-Str) < Len)); ) { switch (*e) { case '<': { p.Push(s, e-s); p.Push("<"); s = ++e; break; } case '>': { p.Push(s, e-s); p.Push(">"); s = ++e; break; } case '&': { p.Push(s, e-s); p.Push("&"); s = ++e; break; } case '\\': { if (e[1] == 'n') { // Newline p.Push(s, e-s); p.Push("\n"); s = (e += 2); break; } // fall thru } case '\'': case '\"': case '/': { // Convert to entity p.Push(s, e-s); char b[32]; sprintf(b, "&#%i;", *e); p.Push(b); s = ++e; break; } default: { // Regular character e++; break; } } } p.Push(s); Ret = p.NewStr(); } return Ret; } char *DecodeXml(char *Str, int Len) { if (Str) { GStringPipe p; char *s = Str; for (char *e = Str; e && *e && (Len < 0 || ((e-Str) < Len)); ) { switch (*e) { case '&': { // Store string up to here p.Push(s, e-s); e++; if (*e == '#') { // Numerical e++; if (*e == 'x' || *e == 'X') { // Hex e++; char16 c = htoi(e); char *c8 = WideToUtf8(&c, 1); if (c8) { p.Push(c8); DeleteArray(c8); } } else if (isdigit(*e)) { // Decimal char16 c = atoi(e); char *c8 = WideToUtf8(&c, 1); if (c8) { p.Push(c8); DeleteArray(c8); } } else { LgiAssert(0); } while (*e && *e != ';') e++; } else if (isalpha(*e)) { // named entity char *Name = e; while (*e && *e != ';') e++; int Len = e - Name; if (Len == 3 && strnicmp(Name, "amp", Len) == 0) { p.Push("&"); } else if (Len == 2 && strnicmp(Name, "gt", Len) == 0) { p.Push(">"); } else if (Len == 2 && strnicmp(Name, "lt", Len) == 0) { p.Push("<"); } else { // Unsupported entity LgiAssert(0); } } else { LgiAssert(0); while (*e && *e != ';') e++; } s = ++e; break; } case '\n': { p.Push(s, e-s); p.Push("\\n"); s = ++e; break; } default: { e++; break; } } } p.Push(s); return p.NewStr(); } return 0; } ////////////////////////////////////////////////////////////////////////////// Resource::Resource(AppWnd *w, int t, bool enabled) { AppWindow = w; ResType = t; Item = 0; SysObject = false; LgiAssert(AppWindow); } Resource::~Resource() { AppWindow->OnResourceDelete(this); if (Item) { Item->Obj = 0; DeleteObj(Item); } } bool Resource::IsSelected() { return Item?Item->Select():false; } bool Resource::Attach(GViewI *Parent) { GView *w = Wnd(); if (w) { return w->Attach(Parent); } return false; } ////////////////////////////////////////////////////////////////////////////// ResFolder::ResFolder(AppWnd *w, int t, bool enabled) : Resource(w, t, enabled) { Wnd()->Name(""); Wnd()->Enabled(enabled); } ////////////////////////////////////////////////////////////////////////////// ObjTreeItem::ObjTreeItem(Resource *Object) { if ((Obj = Object)) { Obj->Item = this; if (dynamic_cast(Object)) SetImage(ICON_FOLDER); else { int t = Object->Type(); switch (t) { case TYPE_CSS: SetImage(ICON_CSS); break; case TYPE_DIALOG: SetImage(ICON_DIALOG); break; case TYPE_STRING: SetImage(ICON_STRING); break; case TYPE_MENU: SetImage(ICON_MENU); break; } } } } ObjTreeItem::~ObjTreeItem() { if (Obj) { Obj->Item = 0; DeleteObj(Obj); } } char *ObjTreeItem::GetText(int i) { if (Obj) { int Type = Obj->Type(); if (Type > 0) { return Obj->Wnd()->Name(); } else { return (char*)TypeNames[-Type]; } } return (char*)"#NO_OBJ"; } void ObjTreeItem::OnSelect() { if (Obj) { Obj->App()->OnResourceSelect(Obj); } } void ObjTreeItem::OnMouseClick(GMouse &m) { if (!Obj) return; if (m.IsContextMenu()) { Tree->Select(this); GSubMenu RClick; if (Obj->Wnd()->Enabled()) { if (Obj->Type() > 0) { // Resource RClick.AppendItem("Delete", IDM_DELETE, !Obj->SystemObject()); RClick.AppendItem("Rename", IDM_RENAME, !Obj->SystemObject()); } else { // Folder RClick.AppendItem("New", IDM_NEW, true); RClick.AppendSeparator(); GSubMenu *Insert = RClick.AppendSub("Import from..."); if (Insert) { Insert->AppendItem("Lgi File", IDM_IMPORT, true); Insert->AppendItem("Win32 Resource Script", IDM_IMPORT_WIN32, false); } } // Custom entries if (!Obj->SystemObject()) { Obj->OnRightClick(&RClick); } } else { RClick.AppendItem("Not implemented", 0, false); } if (Tree->GetMouse(m, true)) { int Cmd = 0; switch (Cmd = RClick.Float(Tree, m.x, m.y)) { case IDM_NEW: { SerialiseContext Ctx; Obj->App()->NewObject(Ctx, 0, -Obj->Type()); break; } case IDM_DELETE: { Obj->App()->SetDirty(true); Obj->App()->DelObject(Obj); break; } case IDM_RENAME: { GInput Dlg(Tree, GetText(), "Enter the name for the object", "Object Name"); if (Dlg.DoModal()) { Obj->Wnd()->Name(Dlg.GetStr()); Update(); Obj->App()->SetDirty(true); } break; } case IDM_IMPORT: { GFileSelect Select; Select.Parent(Obj->App()); Select.Type("Text", "*.txt"); if (Select.Open()) { GFile F; if (F.Open(Select.Name(), O_READ)) { SerialiseContext Ctx; Resource *Res = Obj->App()->NewObject(Ctx, 0, -Obj->Type()); if (Res) { // TODO // Res->Read(); } } else { LgiMsg(Obj->App(), "Couldn't open file for reading."); } } break; } case IDM_IMPORT_WIN32: { /* List l; if (ImportWin32Dialogs(l, MainWnd)) { for (ResDialog *r = l.First(); r; r = l.Next()) { Obj->App()->InsertObject(TYPE_DIALOG, r); } } */ break; } default: { Obj->OnCommand(Cmd); break; } } } } } ////////////////////////////////////////////////////////////////////////////// FieldView::FieldView(AppWnd *app) : Fields(NextId, true) { NextId = 100; App = app; Source = 0; Ignore = true; SetTabStop(true); Sunken(true); #ifdef WIN32 SetExStyle(GetExStyle() | WS_EX_CONTROLPARENT); #endif } FieldView::~FieldView() { } void FieldView::Serialize(bool Write) { if (!Source) return; Ignore = !Write; Fields.SetMode(Write ? FieldTree::UiToObj : FieldTree::ObjToUi); Fields.SetView(this); Source->Serialize(Fields); /* for (DataDlgField *f=Fields.First(); f; f=Fields.Next()) { GViewI *v; if (GetViewById(f->GetCtrl(), v)) { switch (f->GetType()) { case DATA_STR: { if (Write) // Ctrl -> Options { char *s = v->Name(); Options->Set(f->GetOption(), s); } else // Options -> Ctrl { char *s = 0; Options->Get(f->GetOption(), s); v->Name(s?s:(char*)""); } break; } case DATA_BOOL: case DATA_INT: { if (Write) // Ctrl -> Options { char *s = v->Name(); if (s && (s = strchr(s, '\''))) { s++; char *e = strchr(s, '\''); int i = 0; if (e - s == 4) { memcpy(&i, s, 4); i = LgiSwap32(i); Options->Set(f->GetOption(), i); } } else { int i = v->Value(); Options->Set(f->GetOption(), i); } } else // Options -> Ctrl { int i = 0; Options->Get(f->GetOption(), i); if (i != -1 && (i & 0xff000000) != 0) { char m[8]; i = LgiSwap32(i); sprintf(m, "'%04.4s'", &i); v->Name(m); } else { v->Value(i); } } break; } case DATA_FLOAT: case DATA_PASSWORD: case DATA_STR_SYSTEM: default: { LgiAssert(0); break; } } } else LgiAssert(0); } */ Ignore = false; } class TextViewEdit : public GTextView3 { public: bool Multiline; TextViewEdit( int Id, int x, int y, int cx, int cy, GFontType *FontInfo = 0) : GTextView3(Id, x, y, cx, cy, FontInfo) { Multiline = false; #ifdef WIN32 SetDlgCode(DLGC_WANTARROWS | DLGC_WANTCHARS); #endif } bool OnKey(GKey &k) { if (!Multiline && (k.c16 == '\t' || k.c16 == VK_RETURN)) { return false; } return GTextView3::OnKey(k); } }; class Hr : public GView { public: Hr(int x1, int y, int x2) { GRect r(x1, y, x2, y+1); SetPos(r); } void OnPaint(GSurface *pDC) { GRect c = GetClient(); LgiThinBorder(pDC, c, DefaultSunkenEdge); } bool OnLayout(GViewLayoutInfo &Inf) { if (Inf.Width.Min) Inf.Height.Min = Inf.Height.Max = 2; else Inf.Width.Min = Inf.Width.Max = -1; return true; } }; void FieldView::OnDelete(FieldSource *s) { if (Source != NULL && Source == s) { // Clear fields Source->_FieldView = 0; Fields.Empty(); // remove all children for (GViewI *c = Children.First(); c; c = Children.First()) { c->Detach(); DeleteObj(c); } Source = NULL; } } void FieldView::OnSelect(FieldSource *s) { Ignore = true; OnDelete(Source); if (Source) { // Clear fields Source->_FieldView = 0; Fields.Empty(); // remove all children for (GViewI *c = Children.First(); c; c = Children.First()) { c->Detach(); DeleteObj(c); } Source = 0; } if (s) { // Add new fields Source = s; Source->_FieldView = Handle(); if (Source->GetFields(Fields)) { GFontType Sys; Sys.GetSystemFont("System"); GTableLayout *t = new GTableLayout(IDC_TABLE); int Row = 0; GLayoutCell *Cell; GArray a; Fields.GetAll(a); for (int i=0; iLength(); n++, Row++) { FieldTree::Field *c = (*b)[n]; switch (c->Type) { case DATA_STR: case DATA_FLOAT: case DATA_INT: case DATA_FILENAME: { Cell = t->GetCell(0, Row); Cell->Add(new GTextLabel(-1, 0, 0, -1, -1, c->Label)); TextViewEdit *Tv; Cell = t->GetCell(1, Row, true, c->Type == DATA_FILENAME ? 1 : 2); Cell->Add(Tv = new TextViewEdit(c->Id, 0, 0, 100, 20, &Sys)); if (Tv) { Tv->Multiline = c->Multiline; Tv->GetCss(true)->Height(GCss::Len(GCss::LenPx, c->Multiline ? SysFont->GetHeight() * 8 : SysFont->GetHeight() + 8)); Tv->SetWrapType(TEXTED_WRAP_NONE); Tv->Sunken(true); } if (c->Type == DATA_FILENAME) { Cell = t->GetCell(2, Row); Cell->Add(new GButton(-c->Id, 0, 0, 21, 21, "...")); } break; } case DATA_BOOL: { Cell = t->GetCell(1, Row, true, 2); Cell->Add(new GCheckBox(c->Id, 0, 0, -1, -1, c->Label)); break; } default: LgiAssert(!"Impl me."); break; } } if (i < a.Length() - 1) { Cell = t->GetCell(0, Row++, true, 3); Cell->Add(new Hr(0, 0, X()-1)); } } AddView(t); OnPosChange(); AttachChildren(); } Serialize(false); Ignore = false; } } void FieldView::OnPosChange() { GRect c = GetClient(); c.Size(6, 6); GViewI *v; if (GetViewById(IDC_TABLE, v)) v->SetPos(c); } GMessage::Result FieldView::OnEvent(GMessage *m) { switch (MsgCode(m)) { case M_OBJECT_CHANGED: { FieldSource *Src = (FieldSource*)MsgA(m); if (Src == Source) { Fields.SetMode(FieldTree::ObjToUi); Fields.SetView(this); Serialize(false); } else LgiAssert(0); break; } } return GLayout::OnEvent(m); } int FieldView::OnNotify(GViewI *Ctrl, int Flags) { if (!Ignore) { GTextView3 *Tv = dynamic_cast(Ctrl); if (Tv && Flags == GNotifyCursorChanged) { return 0; } GArray a; Fields.GetAll(a); for (int i=0; iLength(); n++) { FieldTree::Field *c = (*b)[n]; if (c->Id == Ctrl->GetId()) { // Write the value back to the objects Fields.SetMode(FieldTree::UiToObj); Fields.SetView(this); Source->Serialize(Fields); return 0; } else if (c->Id == -Ctrl->GetId()) { GFileSelect s; s.Parent(this); if (s.Open()) { char *File = App->GetCurFile(); if (File) { GFile::Path p = File; p--; GAutoString Rel = LgiMakeRelativePath(p, s.Name()); if (Rel) SetCtrlName(c->Id, Rel); else SetCtrlName(c->Id, s.Name()); } else SetCtrlName(c->Id, s.Name()); Fields.SetMode(FieldTree::UiToObj); Fields.SetView(this); Source->Serialize(Fields); return 0; } } } } } return 0; } void FieldView::OnPaint(GSurface *pDC) { pDC->Colour(LC_MED, 24); pDC->Rectangle(); } ////////////////////////////////////////////////////////////////////////////// ObjContainer::ObjContainer(AppWnd *w) : GTree(100, 0, 0, 100, 100, "LgiResObjTree") { Window = w; Sunken(true); Insert(Style = new ObjTreeItem( new ResFolder(Window, -TYPE_CSS))); Insert(Dialogs = new ObjTreeItem( new ResFolder(Window, -TYPE_DIALOG))); Insert(Strings = new ObjTreeItem( new ResFolder(Window, -TYPE_STRING))); Insert(Menus = new ObjTreeItem( new ResFolder(Window, -TYPE_MENU))); const char *IconFile = "_icons.gif"; GAutoString f(LgiFindFile(IconFile)); if (f) { Images = LgiLoadImageList(f, 16, 16); if (Images) SetImageList(Images, false); else LgiTrace("%s:%i - failed to load '%s'\n", _FL, IconFile); } } ObjContainer::~ObjContainer() { DeleteObj(Images); } bool ObjContainer::AppendChildren(ObjTreeItem *Res, List &Lst) { bool Status = true; if (Res) { GTreeItem *Item = Res->GetChild(); while (Item) { ObjTreeItem *i = dynamic_cast(Item); if (i) Lst.Insert(i->GetObj()); else Status = false; Item = Item->GetNext(); } } return Status; } Resource *ObjContainer::CurrentResource() { ObjTreeItem *Item = dynamic_cast(Selection()); if (!Item) return NULL; return Item->GetObj(); } bool ObjContainer::ListObjects(List &Lst) { bool Status = AppendChildren(Style, Lst); Status &= AppendChildren(Dialogs, Lst); Status &= AppendChildren(Strings, Lst); Status &= AppendChildren(Menus, Lst); return Status; } ////////////////////////////////////////////////////////////////////////////// #ifdef WIN32 const char *Icon = MAKEINTRESOURCE(IDI_ICON1); #else const char *Icon = "icon64.png"; #endif AppWnd::AppWnd() : GDocApp(AppName, Icon) { LastRes = 0; Fields = 0; ViewMenu = 0; ContentView = NULL; VBox = NULL; HBox = NULL; ShortCuts = 0; CurLang = -1; ShowLanguages.Add("en", true); if (_Create()) { GVariant Langs; if (GetOptions()->GetValue(OPT_ShowLanguages, Langs)) { ShowLanguages.Empty(); GToken L(Langs.Str(), ","); for (int i=0; iEmpty(); _Destroy(); } void AppWnd::OnCreate() { if (_LoadMenu("IDM_MENU")) { if (_FileMenu) { int n = 6; _FileMenu->AppendSeparator(n++); _FileMenu->AppendItem("Import Win32 Script", IDM_IMPORT_WIN32, true, n++); _FileMenu->AppendItem("Import LgiRes Language", IDM_IMPORT_LANG, true, n++); _FileMenu->AppendItem("Compare To File...", IDM_COMPARE, true, n++); _FileMenu->AppendSeparator(n++); _FileMenu->AppendItem("Properties", IDM_PROPERTIES, true, n++); } ViewMenu = Menu->FindSubMenu(IDM_VIEW); LgiAssert(ViewMenu); } else LgiTrace("%s:%i - _LoadMenu failed.\n", _FL); Status = 0; StatusInfo[0] = StatusInfo[1] = 0; HBox = new GBox(IDC_HBOX); if (HBox) { HBox->GetCss(true)->Padding("5px"); VBox = new GBox(IDC_VBOX, true); if (VBox) { HBox->AddView(VBox); VBox->AddView(Objs = new ObjContainer(this)); if (Objs) { Objs->AskImage(true); Objs->AskText(true); } VBox->AddView(Fields = new FieldView(this)); VBox->Value(200); } HBox->Value(240); HBox->Attach(this); } DropTarget(true); GString Open; if (LgiApp->GetOption("o", Open)) LoadLgi(Open); } void AppWnd::OnLanguagesChange(GLanguageId Lang, bool Add, bool Update) { bool Change = false; if (Lang) { // Update the list.... bool Has = false; for (int i=0; iId, Lang) == 0) { Has = true; if (!Add) { Languages.DeleteAt(i); Change = true; } break; } } if (Add && !Has) { Change = true; Languages.Add(GFindLang(Lang)); } } // Update the menu... if (ViewMenu && (Change || Update)) { // Remove existing language menu items while (ViewMenu->RemoveItem(2)); // Add new ones int n = 0; for (int i=0; iAppendItem(Lang->Name, IDM_LANG_BASE + n, true); if (Item) { if (CurLang == i) { Item->Checked(true); } } } } } } bool AppWnd::ShowLang(GLanguageId Lang) { return ShowLanguages.Find(Lang) != 0; } void AppWnd::ShowLang(GLanguageId Lang, bool Show) { // Apply change if (Show) { OnLanguagesChange(Lang, true); ShowLanguages.Add(Lang, true); } else { ShowLanguages.Delete(Lang); } // Store the setting for next time GStringPipe p; // const char *L; // for (bool i = ShowLanguages.First(&L); i; i = ShowLanguages.Next(&L)) for (auto i : ShowLanguages) { if (p.GetSize()) p.Push(","); p.Push(i.key); } char *Langs = p.NewStr(); if (Langs) { GVariant v; GetOptions()->SetValue(OPT_ShowLanguages, v = Langs); DeleteArray(Langs); } // Update everything List res; if (ListObjects(res)) { for (Resource *r = res.First(); r; r = res.Next()) { r->OnShowLanguages(); } } } GLanguage *AppWnd::GetCurLang() { if (CurLang >= 0 && CurLang < Languages.Length()) return Languages[CurLang]; return GFindLang("en"); } void AppWnd::SetCurLang(GLanguage *L) { for (int i=0; iId == L->Id) { // Set new current CurLang = i; // Update everything List res; if (ListObjects(res)) { for (Resource *r = res.First(); r; r = res.Next()) { r->OnShowLanguages(); } } break; } } } GArray *AppWnd::GetLanguages() { return &Languages; } class Test : public GView { COLOUR c; public: Test(COLOUR col, int x1, int y1, int x2, int y2) { c = col; GRect r(x1, y1, x2, y2); SetPos(r); _BorderSize = 1; Sunken(true); } void OnPaint(GSurface *pDC) { pDC->Colour(c, 24); pDC->Rectangle(); } }; GMessage::Result AppWnd::OnEvent(GMessage *m) { GMru::OnEvent(m); switch (MsgCode(m)) { case M_CHANGE: { return OnNotify((GViewI*) MsgA(m), MsgB(m)); } case M_DESCRIBE: { char *Text = (char*) MsgA(m); if (Text) { SetStatusText(Text, STATUS_NORMAL); } break; } } return GWindow::OnEvent(m); } #include "GToken.h" void _CountGroup(ResStringGroup *Grp, int &Words, int &Multi) { for (ResString *s = Grp->GetStrs()->First(); s; s=Grp->GetStrs()->Next()) { if (s->Items.Length() > 1) { Multi++; char *e = s->Get("en"); if (e) { GToken t(e, " "); Words += t.Length(); } } } } int AppWnd::OnCommand(int Cmd, int Event, OsView Handle) { SerialiseContext Ctx; switch (Cmd) { case IDM_SHOW_LANG: { ShowLanguagesDlg Dlg(this); break; } case IDM_NEW_CSS: { NewObject(Ctx, 0, TYPE_CSS); break; } case IDM_NEW_DIALOG: { NewObject(Ctx, 0, TYPE_DIALOG); break; } case IDM_NEW_STRING_GRP: { NewObject(Ctx, 0, TYPE_STRING); break; } case IDM_NEW_MENU: { NewObject(Ctx, 0, TYPE_MENU); break; } case IDM_CLOSE: { Empty(); break; } case IDM_IMPORT_WIN32: { LoadWin32(); break; } case IDM_IMPORT_LANG: { ImportLang(); break; } case IDM_COMPARE: { Compare(); break; } case IDM_PROPERTIES: { List l; if (Objs->ListObjects(l)) { int Dialogs = 0; int Strings = 0; int Menus = 0; int Words = 0; int MultiLingual = 0; for (Resource *r = l.First(); r; r = l.Next()) { switch (r->Type()) { case TYPE_DIALOG: { Dialogs++; break; } case TYPE_STRING: { ResStringGroup *Grp = dynamic_cast(r); if (Grp) { Strings += Grp->GetStrs()->Length(); _CountGroup(Grp, Words, MultiLingual); } break; } case TYPE_MENU: { Menus++; ResMenu *Menu = dynamic_cast(r); if (Menu) { if (Menu->Group) { Strings += Menu->Group->GetStrs()->Length(); _CountGroup(Menu->Group, Words, MultiLingual); } } break; } } } LgiMsg( this, "This file contains:\n" "\n" " Dialogs: %i\n" " Menus: %i\n" " Strings: %i\n" " Multi-lingual: %i\n" " Words: %i", AppName, MB_OK, Dialogs, Menus, Strings, MultiLingual, Words); } break; } case IDM_EXIT: { LgiCloseApp(); break; } case IDM_FIND: { Search s(this); if (s.DoModal()) { new Results(this, &s); } break; } case IDM_NEXT: { LgiMsg(this, "Not implemented :(", AppName); break; } case IDM_CUT: { Resource *r = Objs->CurrentResource(); if (r) r->Copy(true); break; } case IDM_COPY: { Resource *r = Objs->CurrentResource(); if (r) r->Copy(false); break; } case IDM_PASTE: { Resource *r = Objs->CurrentResource(); if (r) r->Paste(); break; } case IDM_TABLELAYOUT_TEST: { OpenTableLayoutTest(this); break; } case IDM_HELP: { char ExeName[256]; LgiGetExePath(ExeName, sizeof(ExeName)); while (strchr(ExeName, DIR_CHAR) && strlen(ExeName) > 3) { char p[256]; LgiMakePath(p, sizeof(p), ExeName, "index.html"); if (!FileExists(p)) { LgiMakePath(p, sizeof(p), ExeName, "help"); LgiMakePath(p, sizeof(p), p, "index.html"); } if (FileExists(p)) { LgiExecute(HelpFile, NULL, ExeName); break; } LgiTrimDir(ExeName); } break; } case IDM_SHOW_SHORTCUTS: { if (!ShortCuts) ShortCuts = new ShortCutView(this); break; } case IDM_ABOUT: { GAbout Dlg( this, AppName, APP_VER, "\nLgi Resource Editor (lr8 files).", "icon64.png", "http://www.memecode.com/lgi/res", "fret@memecode.com"); break; } default: { int Idx = Cmd - IDM_LANG_BASE; if (Idx >= 0 && Idx < Languages.Length()) { // Deselect the old lang GMenuItem *Item = ViewMenu ? ViewMenu->ItemAt(CurLang + 2) : 0; if (Item) { Item->Checked(false); } // Set the current CurLang = Idx; // Set the new lang's menu item Item = ViewMenu ? ViewMenu->ItemAt(CurLang + 2) : 0; if (Item) { Item->Checked(true); } // Update everything List res; if (ListObjects(res)) { for (Resource *r = res.First(); r; r = res.Next()) { r->OnShowLanguages(); } } } break; } } return GDocApp::OnCommand(Cmd, Event, Handle); } int AppWnd::OnNotify(GViewI *Ctrl, int Flags) { switch (Ctrl->GetId()) { default: { break; } } return 0; } void AppWnd::FindStrings(List &Strs, char *Define, int *CtrlId) { if (Objs) { List l; if (Objs->ListObjects(l)) { for (Resource *r = l.First(); r; r = l.Next()) { StringList *s = r->GetStrs(); if (s) { for (ResString *Str = s->First(); Str; Str = s->Next()) { if (Define && ValidStr(Str->GetDefine())) { if (strcmp(Define, Str->GetDefine()) == 0) { Strs.Insert(Str); continue; } } if (CtrlId) { if (*CtrlId == Str->GetId()) { Strs.Insert(Str); continue; } } } } } } } } int AppWnd::GetUniqueCtrlId() { int Max = 0; if (Objs) { List l; if (Objs->ListObjects(l)) { LHashTbl, int> t; for (Resource *r = l.First(); r; r = l.Next()) { StringList *sl = r->GetStrs(); if (sl) { for (ResString *s = sl->First(); s; s = sl->Next()) { if (s->GetId() > 0 && !t.Find(s->GetId())) { t.Add(s->GetId(), s->GetId()); } Max = MAX(s->GetId(), Max); } } } int i = 500; while (true) { if (t.Find(i)) { i++; } else { return i; } } } } return Max + 1; } int AppWnd::GetUniqueStrRef(int Start) { if (!Objs) return -1; List l; if (!Objs->ListObjects(l)) return -1; LHashTbl, ResString*> Map; GArray Dupes; for (Resource *r = l.First(); r; r = l.Next()) { ResStringGroup *Grp = r->GetStringGroup(); if (Grp) { List::I it = Grp->GetStrs()->begin(); for (ResString *s = *it; s; s = *++it) { if (s->GetRef()) { ResString *Existing = Map.Find(s->GetRef()); if (Existing) { // These get their ref's reset to a unique value as a side // effect of this function... Dupes.Add(s); } else { Map.Add(s->GetRef(), s); } } else { int Idx = Grp->GetStrs()->IndexOf(s); LgiAssert(!"No string ref?"); } } } } for (int i=Start; true; i++) { if (!Map.Find(i)) { if (Dupes.Length()) { ResString *s = Dupes[0]; Dupes.DeleteAt(0); s->SetRef(i); SetDirty(true); } else { return i; } } } return -1; } ResString *AppWnd::GetStrFromRef(int Ref) { ResString *Str = 0; if (Objs) { List l; if (Objs->ListObjects(l)) { for (Resource *r = l.First(); r && !Str; r = l.Next()) { ResStringGroup *Grp = dynamic_cast(r); if (Grp) { if ((Str = Grp->FindRef(Ref))) break; } } } } return Str; } ResStringGroup *AppWnd::GetDialogSymbols() { if (Objs) { List l; if (Objs->ListObjects(l)) { for (Resource *r = l.First(); r; r = l.Next()) { ResStringGroup *Grp = dynamic_cast(r); if (Grp) { char *ObjName = Grp->Wnd()->Name(); if (ObjName && stricmp(ObjName, StrDialogSymbols) == 0) { return Grp; } } } } } return NULL; } void AppWnd::OnReceiveFiles(GArray &Files) { char *f = Files.Length() ? Files[0] : 0; if (f) { _OpenFile(f, false); } } void AppWnd::SetStatusText(char *Text, int Pane) { if (Pane >= 0 && Pane < STATUS_MAX && StatusInfo[Pane]) { StatusInfo[Pane]->Name(Text); } } Resource *AppWnd::NewObject(SerialiseContext ctx, GXmlTag *load, int Type, bool Select) { Resource *r = 0; ObjTreeItem *Dir = 0; switch (Type) { case TYPE_CSS: { r = new ResCss(this); Dir = Objs->Style; break; } case TYPE_DIALOG: { r = new ResDialog(this); Dir = Objs->Dialogs; break; } case TYPE_STRING: { r = new ResStringGroup(this); Dir = Objs->Strings; break; } case TYPE_MENU: { r = new ResMenu(this); Dir = Objs->Menus; break; } } if (r) { ObjTreeItem *Item = new ObjTreeItem(r); if (Item) { Dir->Insert(Item); Dir->Update(); Dir->Expanded(true); if (Select) { Objs->Select(Item); } } r->Create(load, &ctx); if (Item) { Item->Update(); } SetDirty(true); } return r; } bool AppWnd::InsertObject(int Type, Resource *r, bool Select) { bool Status = false; if (r) { ObjTreeItem *Dir = 0; switch (Type) { case TYPE_CSS: { Dir = Objs->Style; break; } case TYPE_DIALOG: { Dir = Objs->Dialogs; break; } case TYPE_STRING: { Dir = Objs->Strings; break; } case TYPE_MENU: { Dir = Objs->Menus; break; } } if (Dir) { ObjTreeItem *Item = new ObjTreeItem(r); if (Item) { char *Name = Item->GetText(); r->Item = Item; Dir->Insert(Item, (Name && Name[0] == '_') ? 0 : -1); Dir->Update(); Dir->Expanded(true); if (Select) { Objs->Select(Item); } Status = true; } } } return Status; } void AppWnd::DelObject(Resource *r) { OnResourceSelect(0); DeleteObj(r); } ObjTreeItem *GetTreeItem(GTreeItem *ti, Resource *r) { for (GTreeItem *i=ti->GetChild(); i; i=i->GetNext()) { ObjTreeItem *o = dynamic_cast(i); if (o) { if (o->GetObj() == r) return o; } o = GetTreeItem(i, r); if (o) return o; } return 0; } ObjTreeItem *GetTreeItem(GTree *ti, Resource *r) { for (GTreeItem *i=ti->GetChild(); i; i=i->GetNext()) { ObjTreeItem *o = dynamic_cast(i); if (o) { if (o->GetObj() == r) return o; } o = GetTreeItem(i, r); if (o) return o; } return 0; } void AppWnd::GotoObject(ResString *s, ResStringGroup *g, ResDialog *d, ResMenuItem *m, ResDialogCtrl *c) { if (s) { Resource *Res = 0; if (g) { Res = g; } else if (d) { Res = d; } else if (m) { Res = m->GetMenu(); } if (Res) { ObjTreeItem *ti = GetTreeItem(Objs, Res); if (ti) { ti->Select(true); if (g) { s->GetList()->Select(0); s->ScrollTo(); LgiYield(); s->Select(true); } else if (d) { LgiYield(); d->SelectCtrl(c); } else if (m) { for (GTreeItem *i=m; i; i=i->GetParent()) { i->Expanded(true); } m->Select(true); m->ScrollTo(); } } else { printf("%s:%i - couldn't find resources tree item\n", _FL); } } } } bool AppWnd::ListObjects(List &Lst) { if (Objs) { return Objs->ListObjects(Lst); } return false; } void AppWnd::OnObjChange(FieldSource *r) { if (Fields) { Fields->Serialize(false); SetDirty(true); } } void AppWnd::OnObjSelect(FieldSource *r) { if (Fields) Fields->OnSelect(r); } void AppWnd::OnObjDelete(FieldSource *r) { if (Fields) { Fields->OnDelete(r); } } void AppWnd::OnResourceDelete(Resource *r) { } void AppWnd::OnResourceSelect(Resource *r) { if (LastRes) { OnObjSelect(NULL); if (ContentView) { ContentView->Detach(); DeleteObj(ContentView); } LastRes = NULL; } if (r) { ContentView = r->CreateUI(); if (ContentView) { if (HBox) ContentView->Attach(HBox); LastRes = r; } } } char *TagName(GXmlTag *t) { static char Buf[1024]; GArray Tags; for (; t; t = t->Parent) { Tags.AddAt(0, t); } Buf[0] = 0; for (int i=0; iGetTag()); } return Buf; } class ResCompare : public GWindow, public GLgiRes { LList *Lst; public: ResCompare(char *File1, char *File2) { Lst = 0; GRect p; GAutoString n; if (LoadFromResource(IDD_COMPARE, this, &p, &n)) { SetPos(p); Name(n); MoveToCenter(); GetViewById(IDC_DIFFS, Lst); if (Attach(0)) { Visible(true); AttachChildren(); GXmlTag *t1 = new GXmlTag; GXmlTag *t2 = new GXmlTag; if (t1 && File1) { GFile f; if (f.Open(File1, O_READ)) { GXmlTree x(GXT_NO_ENTITIES); if (!x.Read(t1, &f, 0)) { DeleteObj(t1); } } else { DeleteObj(t1); } } if (t2 && File2) { GFile f; if (f.Open(File2, O_READ)) { GXmlTree x(GXT_NO_ENTITIES); if (!x.Read(t2, &f, 0)) { DeleteObj(t2); } } else { DeleteObj(t2); } } if (Lst && t1 && t2) { Lst->Enabled(false); Compare(t1, t2); Lst->Enabled(true); } DeleteObj(t1); DeleteObj(t2); } } } void Compare(GXmlTag *t1, GXmlTag *t2) { char s[1024]; if (stricmp(t1->GetTag(), t2->GetTag()) != 0) { sprintf(s, "Different Tag: '%s' <-> '%s'", t1->GetTag(), t2->GetTag()); LListItem *i = new LListItem; if (i) { i->SetText(s); i->SetText(TagName(t1), 1); Lst->Insert(i); } } LHashTbl,GXmlAttr*> a; for (int i=0; iAttr.Length(); i++) { GXmlAttr *a1 = &t1->Attr[i]; a.Add(a1->GetName(), a1); } for (int n=0; nAttr.Length(); n++) { GXmlAttr *a2 = &t2->Attr[n]; GXmlAttr *a1 = (GXmlAttr*) a.Find(a2->GetName()); if (a1) { if (strcmp(a1->GetValue(), a2->GetValue()) != 0) { sprintf(s, "Different Attr Value: '%s' <-> '%s'", a1->GetValue(), a2->GetValue()); LListItem *i = new LListItem; if (i) { i->SetText(s); sprintf(s, "%s.%s", TagName(t1), a1->GetName()); i->SetText(s, 1); Lst->Insert(i); } } a.Delete(a2->GetName()); } else { sprintf(s, "[Right] Missing Attr: '%s' = '%s'", a2->GetName(), a2->GetValue()); LListItem *i = new LListItem; if (i) { i->SetText(s); i->SetText(TagName(t1), 1); Lst->Insert(i); } } } // char *Key; // for (void *v = a.First(&Key); v; v = a.Next(&Key)) for (auto v : a) { GXmlAttr *a1 = v.value; sprintf(s, "[Left] Missing Attr: '%s' = '%s'", a1->GetName(), a1->GetValue()); LListItem *i = new LListItem; if (i) { i->SetText(s); i->SetText(TagName(t1), 1); Lst->Insert(i); } } if (t1->IsTag("string-group")) { GXmlTag *t; GArray r1, r2; for (t = t1->Children.First(); t; t = t1->Children.Next()) { char *Ref; if ((Ref = t->GetAttr("ref"))) { int r = atoi(Ref); if (r) { r1[r] = t; } } } for (t = t2->Children.First(); t; t = t2->Children.Next()) { char *Ref; if ((Ref = t->GetAttr("ref"))) { int r = atoi(Ref); if (r) { r2[r] = t; } } } int Max = MAX(r1.Length(), r2.Length()); for (int i = 0; iGetAttr("ref"), r1[i]->GetAttr("Define")); LListItem *n = new LListItem; if (n) { n->SetText(s); n->SetText(TagName(r1[i]), 1); Lst->Insert(n); } } else if (r2[i]) { sprintf(s, "[Left] Missing String: Ref=%s, Def=%s", r2[i]->GetAttr("ref"), r2[i]->GetAttr("Define")); LListItem *n = new LListItem; if (n) { n->SetText(s); n->SetText(TagName(r2[i]), 1); Lst->Insert(n); } } } } else { GXmlTag *c1 = t1->Children.First(); GXmlTag *c2 = t2->Children.First(); while (c1 && c2) { Compare(c1, c2); c1 = t1->Children.Next(); c2 = t2->Children.Next(); } } LgiYield(); } void OnPosChange() { GRect c = GetClient(); if (Lst) { c.Size(7, 7); Lst->SetPos(c); } } }; void AppWnd::Compare() { GFileSelect s; s.Parent(this); s.Type("Lgi Resource", "*.lr8"); if (s.Open()) { new ResCompare(GetCurFile(), s.Name()); } } void AppWnd::ImportLang() { // open dialog GFileSelect Select; Select.Parent(this); Select.Type("Lgi Resources", "*.lr8;*.xml"); if (Select.Open()) { GFile F; if (F.Open(Select.Name(), O_READ)) { SerialiseContext Ctx; Ctx.Format = GetFormat(Select.Name()); // convert file to Xml objects GXmlTag *Root = new GXmlTag; if (Root) { GXmlTree Tree(GXT_NO_ENTITIES); if (Tree.Read(Root, &F, 0)) { List Menus; List Groups; GXmlTag *t; for (t = Root->Children.First(); t; t = Root->Children.Next()) { if (t->IsTag("menu")) { ResMenu *Menu = new ResMenu(this); if (Menu && Menu->Read(t, Ctx)) { Menus.Insert(Menu); } else break; } else if (t->IsTag("string-group")) { ResStringGroup *g = new ResStringGroup(this); if (g && g->Read(t, Ctx)) { Groups.Insert(g); } else break; } } Ctx.PostLoad(this); bool HasData = false; for (ResStringGroup *g=Groups.First(); g; g=Groups.Next()) { g->SetLanguages(); if (g->GetStrs()->Length() > 0 && g->GetLanguages() > 0) { HasData = true; } } if (HasData) { List Langs; for (ResStringGroup *g=Groups.First(); g; g=Groups.Next()) { for (int i=0; iGetLanguages(); i++) { GLanguage *Lang = g->GetLanguage(i); if (Lang) { bool Has = false; for (GLanguage *l=Langs.First(); l; l=Langs.Next()) { if (stricmp((char*)l, (char*)Lang) == 0) { Has = true; break; } } if (!Has) { Langs.Insert(Lang); } } } } LangDlg Dlg(this, Langs); if (Dlg.DoModal() == IDOK && Dlg.Lang) { GStringPipe Errors; int Matches = 0; int NotFound = 0; int Imported = 0; int Different = 0; for (ResStringGroup *g=Groups.First(); g; g=Groups.Next()) { List::I Strings = g->GetStrs()->begin(); for (ResString *s=*Strings; s; s=*++Strings) { ResString *d = GetStrFromRef(s->GetRef()); if (d) { Matches++; char *Str = s->Get(Dlg.Lang->Id); char *Dst = d->Get(Dlg.Lang->Id); if ( ( Str && Dst && strcmp(Dst, Str) != 0 ) || ( (Str != 0) ^ (Dst != 0) ) ) { Different++; d->Set(Str, Dlg.Lang->Id); Imported++; } } else { NotFound++; char e[256]; sprintf(e, "String ref=%i (%s)\n", s->GetRef(), s->GetDefine()); Errors.Push(e); } } } List Lst; if (ListObjects(Lst)) { for (ResMenu *m=Menus.First(); m; m=Menus.Next()) { // find matching menu in our list ResMenu *Match = 0; for (Resource *r=Lst.First(); r; r=Lst.Next()) { ResMenu *n = dynamic_cast(r); if (n && stricmp(n->Name(), m->Name()) == 0) { Match = n; break; } } if (Match) { // match strings List *Src = m->GetStrs(); List *Dst = Match->GetStrs(); for (ResString *s=Src->First(); s; s = Src->Next()) { bool FoundRef = false; for (ResString *d=Dst->First(); d; d=Dst->Next()) { if (s->GetRef() == d->GetRef()) { FoundRef = true; char *Str = s->Get(Dlg.Lang->Id); if (Str) { char *Dst = d->Get(Dlg.Lang->Id); if (!Dst || strcmp(Dst, Str)) { Different++; } d->Set(Str, Dlg.Lang->Id); Imported++; } break; } } if (!FoundRef) { NotFound++; char e[256]; sprintf(e, "MenuString ref=%i (%s)\n", s->GetRef(), s->GetDefine()); Errors.Push(e); } } Match->SetLanguages(); } } for (Resource *r=Lst.First(); r; r=Lst.Next()) { ResStringGroup *StrRes = dynamic_cast(r); if (StrRes) { StrRes->SetLanguages(); } } } char *ErrorStr = Errors.NewStr(); LgiMsg( this, "Imported: %i\n" "Matched: %i\n" "Not matched: %i\n" "Different: %i\n" "Total: %i\n" "\n" "Import complete.\n" "\n%s", AppName, MB_OK, Imported, Matches, NotFound, Different, Matches + NotFound, ErrorStr?ErrorStr:(char*)""); } } else { LgiMsg(this, "No language information to import", AppName, MB_OK); } // Groups.DeleteObjects(); // Menus.DeleteObjects(); } else { LgiMsg(this, "Failed to parse XML from file.\nError: %s", AppName, MB_OK, Tree.GetErrorMsg()); } DeleteObj(Root); } } } } void AppWnd::Empty() { // Delete any existing objects List l; if (ListObjects(l)) { Resource *r; for (r = l.First(); r; ) { if (r->SystemObject()) { l.Delete(r); r = l.Current(); } else { r=l.Next(); } } for (r = l.First(); r; r=l.Next()) { DelObject(r); } } } bool AppWnd::OpenFile(char *FileName, bool Ro) { if (stristr(FileName, ".lr8") || stristr(FileName, ".xml")) { return LoadLgi(FileName); } else if (stristr(FileName, ".rc")) { return LoadWin32(FileName); } return false; } bool AppWnd::SaveFile(char *FileName) { if (stristr(FileName, ".lr8") || stristr(FileName, ".xml")) { return SaveLgi(FileName); } else if (stristr(FileName, ".rc")) { } return false; } void AppWnd::GetFileTypes(GFileSelect *Dlg, bool Write) { Dlg->Type("Lgi Resources", "*.lr8;*.xml"); if (!Write) { Dlg->Type("All Files", LGI_ALL_FILES); } } // Lgi load/save bool AppWnd::TestLgi(bool Quite) { bool Status = true; List l; if (ListObjects(l)) { ErrorCollection Errors; for (Resource *r = l.First(); r; r = l.Next()) { Status &= r->Test(&Errors); } if (Errors.StrErr.Length() > 0) { GStringPipe Sample; for (int i=0; iGetRef(), s->GetDefine(), Errors.StrErr[i].Msg.Get()); } char *Sam = Sample.NewStr(); LgiMsg(this, "%i strings have errors.\n\n%s", AppName, MB_OK, Errors.StrErr.Length(), Sam); DeleteArray(Sam); } else if (!Quite) { LgiMsg(this, "Object are all ok.", AppName); } } return Status; } bool AppWnd::LoadLgi(char *FileName) { bool Status = false; Empty(); if (FileName) { ResFileFormat Format = GetFormat(FileName); GFile f; if (f.Open(FileName, O_READ)) { GProgressDlg Progress(this); Progress.SetDescription("Initializing..."); Progress.SetType("Tags"); GXmlTag *Root = new GXmlTag; if (Root) { // convert file to Xml objects GXmlTree Xml(0); Progress.SetDescription("Lexing..."); if (Xml.Read(Root, &f, 0)) { Progress.SetLimits(0, Root->Children.Length()-1); // convert Xml list into objects int i=0; DoEvery Timer(500); SerialiseContext Ctx; for (GXmlTag *t = Root->Children.First(); t; t = Root->Children.Next(), i++) { if (Timer.DoNow()) { Progress.Value(Root->Children.IndexOf(t)); LgiYield(); } int RType = 0; if (t->IsTag("dialog")) { RType = TYPE_DIALOG; } else if (t->IsTag("string-group")) { RType = TYPE_STRING; } else if (t->IsTag("menu")) { RType = TYPE_MENU; } else if (t->IsTag("style")) { RType = TYPE_CSS; } else { LgiAssert(!"Unexpected tag"); } if (RType > 0) { NewObject(Ctx, t, RType, false); } } Ctx.PostLoad(this); SortDialogs(); TestLgi(); // Scan for languages and update the view lang menu Languages.Length(0); LHashTbl, GLanguage*> Langs; if (ViewMenu) { // Remove existing language menu items while (ViewMenu->RemoveItem(1)); ViewMenu->AppendSeparator(); // Enumerate all languages List res; if (ListObjects(res)) { for (Resource *r = res.First(); r; r = res.Next()) { ResStringGroup *Sg = r->IsStringGroup(); if (Sg) { for (int i=0; iGetLanguages(); i++) { GLanguage *Lang = Sg->GetLanguage(i); if (Lang) { Langs.Add(Lang->Id, Lang); } } } } } // Update languages array int n = 0; for (auto i : Langs) { Languages.Add(i.value); GMenuItem *Item = ViewMenu->AppendItem(i.value->Name, IDM_LANG_BASE + n, true); if (Item && i.value->IsEnglish()) { Item->Checked(true); CurLang = n; } n++; } if (Languages.Length() == 0) { ViewMenu->AppendItem("(none)", -1, false); } } Status = true; } else { LgiMsg(this, "Xml read failed: %s", AppName, MB_OK, Xml.GetErrorMsg()); } DeleteObj(Root); } } } return Status; } void SerialiseContext::PostLoad(AppWnd *App) { for (int i=0; iGetUniqueCtrlId(); s->SetId(Id); Log.Print("Repaired CtrlId of string ref %i to %i\n", s->GetRef(), Id); } GAutoString a(Log.NewStr()); if (ValidStr(a)) { LgiMsg(App, "%s", "Load Warnings", MB_OK, a.Get()); } } int DialogNameCompare(ResDialog *a, ResDialog *b, NativeInt Data) { char *A = (a)?a->Name():0; char *B = (b)?b->Name():0; if (A && B) return stricmp(A, B); return -1; } void AppWnd::SortDialogs() { List Lst; if (ListObjects(Lst)) { List Dlgs; for (Resource *r = Lst.First(); r; r = Lst.Next()) { ResDialog *Dlg = dynamic_cast(r); if (Dlg) { Dlgs.Insert(Dlg); Dlg->Item->Remove(); } } Dlgs.Sort(DialogNameCompare); for (ResDialog *d = Dlgs.First(); d; d = Dlgs.Next()) { Objs->Dialogs->Insert(d->Item); } } } class ResTreeNode { public: char *Str; ResTreeNode *a, *b; ResTreeNode(char *s) { a = b = 0; Str = s; } ~ResTreeNode() { DeleteArray(Str); DeleteObj(a); DeleteObj(b); } void Enum(List &l) { if (a) { a->Enum(l); } if (Str) { l.Insert(Str); } if (b) { b->Enum(l); } } bool Add(char *s) { int Comp = (Str && s) ? stricmp(Str, s) : -1; if (Comp == 0) { return false; } if (Comp < 0) { if (a) { return a->Add(s); } else { a = new ResTreeNode(s); } } if (Comp > 0) { if (b) { return b->Add(s); } else { b = new ResTreeNode(s); } } return true; } }; class ResTree { ResTreeNode *Root; public: ResTree() { Root = 0; } ~ResTree() { DeleteObj(Root); } bool Add(char *s) { if (s) { if (!Root) { Root = new ResTreeNode(NewStr(s)); return true; } else { return Root->Add(NewStr(s)); } } return false; } void Enum(List &l) { if (Root) { Root->Enum(l); } } }; const char *HeaderStr = "// This file generated by LgiRes\r\n\r\n"; struct DefinePair { char *Name; int Value; }; int PairCmp(DefinePair *a, DefinePair *b) { return a->Value - b->Value; } bool AppWnd::WriteDefines(GFile &Defs) { bool Status = false; ResTree Tree; // Empty file Defs.Seek(0, SEEK_SET); Defs.SetSize(0); Defs.Write(HeaderStr, strlen(HeaderStr)); // make a unique list of #define's List Lst; if (ListObjects(Lst)) { LHashTbl,int> Def; LHashTbl,char*> Ident; for (Resource *r = Lst.First(); r; r = Lst.Next()) { List *StrList = r->GetStrs(); if (StrList) { Status = true; List::I sl = StrList->begin(); for (ResString *s = *sl; s; s = *++sl) { if (ValidStr(s->GetDefine())) { if (stricmp(s->GetDefine(), "IDOK") == 0) { s->SetId(IDOK); } else if (stricmp(s->GetDefine(), "IDCANCEL") == 0) { s->SetId(IDCANCEL); } else if (stricmp(s->GetDefine(), "IDC_STATIC") == 0) { s->SetId(-1); } else if (stricmp(s->GetDefine(), "-1") == 0) { s->SetDefine(0); } else { // Remove dupe ID's char IdStr[32]; sprintf(IdStr, "%i", s->GetId()); char *Define; if ((Define = Ident.Find(IdStr))) { if (strcmp(Define, s->GetDefine())) { List n; FindStrings(n, s->GetDefine()); int NewId = GetUniqueCtrlId(); for (ResString *Ns = n.First(); Ns; Ns = n.Next()) { Ns->SetId(NewId); } } } else { Ident.Add(IdStr, s->GetDefine()); } // Make all define's the same int CtrlId; if ((CtrlId = Def.Find(s->GetDefine()))) { // Already there... s->SetId(CtrlId); } else { // Add... LgiAssert(s->GetId()); if (s->GetId()) Def.Add(s->GetDefine(), s->GetId()); } } } } } } // write the list out GArray Pairs; // char *s = 0; // for (int i = Def.First(&s); i; i = Def.Next(&s)) for (auto i : Def) { if (ValidStr(i.key) && stricmp(i.key, "IDOK") != 0 && stricmp(i.key, "IDCANCEL") != 0 && stricmp(i.key, "IDC_STATIC") != 0 && stricmp(i.key, "-1") != 0) { DefinePair &p = Pairs.New(); p.Name = i.key; p.Value = i.value; } } Pairs.Sort(PairCmp); for (int n=0; n=' ' && (uint8)(c) <= 127) + #define IsPrintable(c) ((uint8_t)(c)>=' ' && (uint8_t)(c) <= 127) if (IsPrintable(s[0]) && IsPrintable(s[1]) && IsPrintable(s[2]) && IsPrintable(s[3])) { #ifndef __BIG_ENDIAN__ int32 i = LgiSwap32(p.Value); memcpy(s, &i, 4); #endif Defs.Print("#define %s%s'%04.4s'\r\n", p.Name, Tab, s); } else Defs.Print("#define %s%s%i\r\n", p.Name, Tab, p.Value); } } return Status; } bool AppWnd::SaveLgi(char *FileName) { bool Status = false; if (!TestLgi()) { if (LgiMsg(this, "Do you want to save the file with errors?", AppName, MB_YESNO) == IDNO) { return false; } } // Rename the existing file to 'xxxxxx.bak' if (FileExists(FileName)) { char Bak[MAX_PATH]; strcpy_s(Bak, sizeof(Bak), FileName); char *e = LgiGetExtension(Bak); if (e) { strcpy(e, "bak"); if (FileExists(Bak)) FileDev->Delete(Bak, false); FileDev->Move(FileName, Bak); } } // Save the file to xml if (FileName) { GFile f; GFile Defs; ResFileFormat Format = GetFormat(FileName); char DefsName[256]; strcpy(DefsName, FileName); LgiTrimDir(DefsName); strcat(DefsName, DIR_STR); strcat(DefsName, "resdefs.h"); if (f.Open(FileName, O_WRITE) && Defs.Open(DefsName, O_WRITE)) { SerialiseContext Ctx; f.SetSize(0); Defs.SetSize(0); Defs.Print("// Generated by LgiRes\r\n\r\n"); List l; if (ListObjects(l)) { // Remove all duplicate symbol Id's from the dialogs Resource *r; for (r = l.First(); r; r = l.Next()) { ResDialog *Dlg = dynamic_cast(r); if (Dlg) { Dlg->CleanSymbols(); } } // write defines WriteDefines(Defs); GXmlTag Root("resources"); // Write all string lists out first so that when we load objects // back in again the strings will already be loaded and can // be referenced for (r = l.First(); r; r = l.Next()) { if (r->Type() == TYPE_STRING) { GXmlTag *c = new GXmlTag; if (c && r->Write(c, Ctx)) { Root.InsertTag(c); } else { LgiAssert(0); DeleteObj(c); } } } // now write the rest of the objects out for (r = l.First(); r; r = l.Next()) { if (r->Type() != TYPE_STRING) { GXmlTag *c = new GXmlTag; if (c && r->Write(c, Ctx)) { Root.InsertTag(c); } else { r->Write(c, Ctx); LgiAssert(0); DeleteObj(c); } } } // Set the offset type. // // Older versions of LgiRes stored the dialog's controls at a fixed // offset (3,17) from where they shouldv'e been. That was fixed, but // to differentiate between the 2 systems, we store a tag at the // root element. Root.SetAttr("Offset", 1); GXmlTree Tree(GXT_NO_ENTITIES); Status = Tree.Write(&Root, &f); } } else { LgiMsg(this, "Couldn't open these files for output:\n" "\t%s\n" "\t%s\n" "\n" "Maybe they are read only or locked by another application.", AppName, MB_OK, FileName, DefsName); } } return Status; } // Win32 load/save #define ADJUST_CTRLS_X 2 #define ADJUST_CTRLS_Y 12 #define IMP_MODE_SEARCH 0 #define IMP_MODE_DIALOG 1 #define IMP_MODE_DLG_CTRLS 2 #define IMP_MODE_STRINGS 3 #define IMP_MODE_MENU 4 #include "GToken.h" class ImportDefine { public: char *Name; char *Value; ImportDefine() { Name = Value = 0; } ImportDefine(char *Line) { Name = Value = 0; if (Line && *Line == '#') { Line++; if (strnicmp(Line, "define", 6) == 0) { Line += 6; Line = LgiSkipDelim(Line); char *Start = Line; const char *WhiteSpace = " \r\n\t"; while (*Line && !strchr(WhiteSpace, *Line)) { Line++; } Name = NewStr(Start, Line-Start); Line = LgiSkipDelim(Line); Start = Line; while (*Line && !strchr(WhiteSpace, *Line)) { Line++; } if (Start != Line) { Value = NewStr(Start, Line-Start); } } } } ~ImportDefine() { DeleteArray(Name); DeleteArray(Value); } }; class DefineList : public List { int NestLevel; public: bool Defined; List IncludeDirs; DefineList() { Defined = true; NestLevel = 0; } ~DefineList() { for (ImportDefine *i = First(); i; i = Next()) { DeleteObj(i); } for (char *c = IncludeDirs.First(); c; c = IncludeDirs.Next()) { DeleteArray(c); } } void DefineSymbol(const char *Name, const char *Value = 0) { ImportDefine *Def = new ImportDefine; if (Def) { Def->Name = NewStr(Name); if (Value) Def->Value = NewStr(Value); Insert(Def); } } ImportDefine *GetDefine(char *Name) { if (Name) { for (ImportDefine *i = First(); i; i = Next()) { if (i->Name && stricmp(i->Name, Name) == 0) { return i; } } } return NULL; } void ProcessLine(char *Line) { if (NestLevel > 16) { return; } if (Line && *Line == '#') { Line++; GToken T(Line); if (T.Length() > 0) { if (stricmp(T[0], "define") == 0) // #define { ImportDefine *Def = new ImportDefine(Line-1); if (Def) { if (Def->Name) { Insert(Def); } else { DeleteObj(Def); } } } else if (stricmp(T[0], "include") == 0) // #include { NestLevel++; GFile F; if (T.Length() > 1) { for (char *IncPath = IncludeDirs.First(); IncPath; IncPath = IncludeDirs.Next()) { char FullPath[256]; strcpy(FullPath, IncPath); if (FullPath[strlen(FullPath)-1] != DIR_CHAR) { strcat(FullPath, DIR_STR); } strcat(FullPath, T[1]); if (F.Open(FullPath, O_READ)) { char Line[1024]; while (!F.Eof()) { F.ReadStr(Line, sizeof(Line)); char *p = LgiSkipDelim(Line); if (*p == '#') { ProcessLine(p); } } break; } } } NestLevel--; } else if (stricmp(T[0], "if") == 0) // #if { } else if (stricmp(T[0], "ifdef") == 0) // #if { if (T.Length() > 1) { Defined = GetDefine(T[1]) != 0; } } else if (stricmp(T[0], "endif") == 0) // #endif { Defined = true; } else if (stricmp(T[0], "pragma") == 0) { ImportDefine *Def = new ImportDefine; if (Def) { char *Str = Line + 7; char *First = strchr(Str, '('); char *Second = (First) ? strchr(First+1, ')') : 0; if (First && Second) { Insert(Def); Def->Name = NewStr(Str, First-Str); First++; Def->Value = NewStr(First, Second-First); } else { DeleteObj(Def); } } } else if (stricmp(T[0], "undef") == 0) { } } } // else it's not for us anyway } }; void TokLine(GArray &T, char *Line) { if (Line) { // Exclude comments for (int k=0; Line[k]; k++) { if (Line[k] == '/' && Line[k+1] == '/') { Line[k] = 0; break; } } // Break into tokens for (const char *s = Line; s && *s; ) { while (*s && strchr(" \t", *s)) s++; char *t = LgiTokStr(s); if (t) T.Add(t); else break; } } } bool AppWnd::LoadWin32(char *FileName) { bool Status = false; GFileSelect Select; LHashTbl,bool> CtrlNames; CtrlNames.Add("LTEXT", true); CtrlNames.Add("EDITTEXT", true); CtrlNames.Add("COMBOBOX", true); CtrlNames.Add("SCROLLBAR", true); CtrlNames.Add("GROUPBOX", true); CtrlNames.Add("PUSHBUTTON", true); CtrlNames.Add("DEFPUSHBUTTON", true); CtrlNames.Add("CONTROL", true); CtrlNames.Add("ICON", true); CtrlNames.Add("LISTBOX", true); Empty(); if (!FileName) { Select.Parent(this); Select.Type("Win32 Resource Script", "*.rc"); if (Select.Open()) { FileName = Select.Name(); } } if (FileName) { GProgressDlg Progress(this); Progress.SetDescription("Initializing..."); Progress.SetType("K"); Progress.SetScale(1.0/1024.0); char *FileTxt = ReadTextFile(Select.Name()); if (FileTxt) { GToken Lines(FileTxt, "\r\n"); DeleteArray(FileTxt); DefineList Defines; ResStringGroup *String = new ResStringGroup(this); int Mode = IMP_MODE_SEARCH; // Language char *Language = 0; GLanguageId LanguageId = 0; // Dialogs List DlLList; CtrlDlg *Dlg = 0; // Menus ResDialog *Dialog = 0; ResMenu *Menu = 0; List Menus; ResMenuItem *MenuItem[32]; int MenuLevel = 0; bool MenuNewLang = false; int MenuNextItem = 0; // Include defines char IncPath[256]; strcpy(IncPath, Select.Name()); LgiTrimDir(IncPath); Defines.IncludeDirs.Insert(NewStr(IncPath)); Defines.DefineSymbol("_WIN32"); Defines.DefineSymbol("IDC_STATIC", "-1"); DoEvery Ticker(200); Progress.SetDescription("Reading resources..."); Progress.SetLimits(0, Lines.Length()-1); if (String) { InsertObject(TYPE_STRING, String, false); } for (int CurLine = 0; CurLine < Lines.Length(); CurLine++) { if (Ticker.DoNow()) { Progress.Value(CurLine); LgiYield(); } // Skip white space char *Line = Lines[CurLine]; char *p = LgiSkipDelim(Line); Defines.ProcessLine(Line); // Tokenize GArray T; TokLine(T, Line); // Process line if (Defines.Defined) { switch (Mode) { case IMP_MODE_SEARCH: { DeleteObj(Dialog); Dlg = 0; if (*p != '#') { if (T.Length() > 1 && (stricmp(T[1], "DIALOG") == 0 || stricmp(T[1], "DIALOGEX") == 0)) { Mode = IMP_MODE_DIALOG; Dialog = new ResDialog(this); if (Dialog) { Dialog->Create(NULL, NULL); Dialog->Name(T[0]); GAutoPtr It(Dialog->IterateViews()); Dlg = dynamic_cast(It->First()); if (Dlg) { int Pos[4] = {0, 0, 0, 0}; int i = 0; for (; iResDialogCtrl::SetPos(r); Dlg->Str->SetDefine(T[0]); ImportDefine *Def = Defines.GetDefine(T[0]); if (Def) { Dlg->Str->SetId(atoi(Def->Value)); } } } break; } if (T.Length() > 1 && stricmp(T[1], "MENU") == 0) { ZeroObj(MenuItem); Mode = IMP_MODE_MENU; Menu = 0; // Check for preexisting menu in another language MenuNewLang = false; for (ResMenu *m = Menus.First(); m; m = Menus.Next()) { if (stricmp(m->Name(), T[0]) == 0) { MenuNewLang = true; Menu = m; break; } } // If it doesn't preexist then create it if (!Menu) { Menu = new ResMenu(this); if (Menu) { Menus.Insert(Menu); Menu->Create(NULL, NULL); Menu->Name(T[0]); } } break; } if (T.Length() > 0 && stricmp(T[0], "STRINGTABLE") == 0) { Mode = IMP_MODE_STRINGS; if (String) { String->Name("_Win32 Imports_"); } break; } if (T.Length() > 2 && stricmp(T[0], "LANGUAGE") == 0) { LanguageId = 0; DeleteArray(Language); char *Language = NewStr(T[1]); if (Language) { GLanguage *Info = GFindLang(0, Language); if (Info) { LanguageId = Info->Id; ResDialog::AddLanguage(Info->Id); } } break; } } break; } case IMP_MODE_DIALOG: { if (T.Length() > 0 && Dlg) { if (stricmp(T[0], "CAPTION") == 0) { char *Caption = T[1]; if (Caption) { Dlg->Str->Set(Caption, LanguageId); } } else if (stricmp(T[0], "BEGIN") == 0) { Mode = IMP_MODE_DLG_CTRLS; } } break; } case IMP_MODE_DLG_CTRLS: { char *Type = T[0]; if (!Type) break; if (stricmp(Type, "end") != 0) { // Add wrapped content to the token array. char *Next = Lines[CurLine+1]; if (Next) { Next = LgiSkipDelim(Next); char *NextTok = LgiTokStr((const char*&)Next); if (NextTok) { if (stricmp(NextTok, "END") != 0 && !CtrlNames.Find(NextTok)) { TokLine(T, Lines[++CurLine]); } DeleteArray(NextTok); } } } // Process controls if (stricmp(Type, "LTEXT") == 0) { if (T.Length() >= 7) { CtrlText *Ctrl = new CtrlText(Dialog, 0); if (Ctrl) { Ctrl->Str->Set(T[1], LanguageId); Ctrl->Str->SetDefine(T[2]); ImportDefine *Def = Defines.GetDefine(T[2]); if (Def) { Ctrl->Str->SetId(atoi(Def->Value)); } GRect r; r.ZOff(atoi(T[5])*CTRL_X, atoi(T[6])*CTRL_Y); r.Offset(atoi(T[3])*CTRL_X+ADJUST_CTRLS_X, atoi(T[4])*CTRL_Y+ADJUST_CTRLS_Y); Ctrl->ResDialogCtrl::SetPos(r); Dlg->AddView(Ctrl); } } } else if (stricmp(Type, "EDITTEXT") == 0) { if (T.Length() >= 7) { CtrlEditbox *Ctrl = new CtrlEditbox(Dialog, 0); if (Ctrl) { Ctrl->Str->SetDefine(T[1]); ImportDefine *Def = Defines.GetDefine(T[1]); if (Def) { Ctrl->Str->SetId(atoi(Def->Value)); } GRect r; r.ZOff(atoi(T[4])*CTRL_X, atoi(T[5])*CTRL_Y); r.Offset(atoi(T[2])*CTRL_X+ADJUST_CTRLS_X, atoi(T[3])*CTRL_Y+ADJUST_CTRLS_Y); Ctrl->ResDialogCtrl::SetPos(r); Dlg->AddView(Ctrl); } } } else if (stricmp(Type, "COMBOBOX") == 0) { if (T.Length() >= 6) { CtrlComboBox *Ctrl = new CtrlComboBox(Dialog, 0); if (Ctrl) { Ctrl->Str->SetDefine(T[1]); ImportDefine *Def = Defines.GetDefine(T[1]); if (Def) { Ctrl->Str->SetId(atoi(Def->Value)); } GRect r; r.ZOff(atoi(T[4])*CTRL_X, atoi(T[5])*CTRL_Y); r.Offset(atoi(T[2])*CTRL_X+ADJUST_CTRLS_X, atoi(T[3])*CTRL_Y+ADJUST_CTRLS_Y); Ctrl->ResDialogCtrl::SetPos(r); Dlg->AddView(Ctrl); } } } else if (stricmp(Type, "SCROLLBAR") == 0) { if (T.Length() == 6) { CtrlScrollBar *Ctrl = new CtrlScrollBar(Dialog, 0); if (Ctrl) { Ctrl->Str->SetDefine(T[1]); ImportDefine *Def = Defines.GetDefine(T[1]); if (Def) { Ctrl->Str->SetId(atoi(Def->Value)); } GRect r; r.ZOff(atoi(T[4])*CTRL_X, atoi(T[5])*CTRL_Y); r.Offset(atoi(T[2])*CTRL_X+ADJUST_CTRLS_X, atoi(T[3])*CTRL_Y+ADJUST_CTRLS_Y); Ctrl->ResDialogCtrl::SetPos(r); Dlg->AddView(Ctrl); } } } else if (stricmp(Type, "GROUPBOX") == 0) { if (T.Length() >= 7) { CtrlGroup *Ctrl = new CtrlGroup(Dialog, 0); if (Ctrl) { Ctrl->Str->Set(T[1], LanguageId); Ctrl->Str->SetDefine(T[2]); ImportDefine *Def = Defines.GetDefine(T[2]); if (Def) { Ctrl->Str->SetId(atoi(Def->Value)); } GRect r; r.ZOff(atoi(T[5])*CTRL_X, atoi(T[6])*CTRL_Y); r.Offset(atoi(T[3])*CTRL_X+ADJUST_CTRLS_X, atoi(T[4])*CTRL_Y+ADJUST_CTRLS_Y); Ctrl->ResDialogCtrl::SetPos(r); Dlg->AddView(Ctrl); } } } else if (stricmp(Type, "PUSHBUTTON") == 0 || stricmp(Type, "DEFPUSHBUTTON") == 0) { if (T.Length() >= 7) { CtrlButton *Ctrl = new CtrlButton(Dialog, 0); if (Ctrl) { Ctrl->Str->Set(T[1], LanguageId); Ctrl->Str->SetDefine(T[2]); ImportDefine *Def = Defines.GetDefine(T[2]); if (Def) { Ctrl->Str->SetId(atoi(Def->Value)); } GRect r; r.ZOff(atoi(T[5])*CTRL_X, atoi(T[6])*CTRL_Y); r.Offset(atoi(T[3])*CTRL_X+ADJUST_CTRLS_X, atoi(T[4])*CTRL_Y+ADJUST_CTRLS_Y); Ctrl->ResDialogCtrl::SetPos(r); Dlg->AddView(Ctrl); } } } else if (stricmp(Type, "CONTROL") == 0) { if (T.Length() >= 7) { char *Caption = T[1]; char *Id = T[2]; char *Type = T[3]; bool Checkbox = false; bool Radio = false; bool Done = false; // loop through styles int i; for (i=4; !Done && iSetPos(r); if (Caption) Ctrl->Str->Set(Caption, LanguageId); if (Id) Ctrl->Str->SetDefine(Id); ImportDefine *Def = Defines.GetDefine(Id); if (Def) { Ctrl->Str->SetId(atoi(Def->Value)); } Dlg->AddView(Ctrl->View()); } } } } else if (stricmp(Type, "ICON") == 0) { if (T.Length() >= 7) { CtrlBitmap *Ctrl = new CtrlBitmap(Dialog, 0); if (Ctrl) { Ctrl->Str->SetDefine(T[1]); ImportDefine *Def = Defines.GetDefine(T[1]); if (Def) { Ctrl->Str->SetId(atoi(Def->Value)); } GRect r; r.ZOff(atoi(T[5])*CTRL_X, atoi(T[6])*CTRL_Y); r.Offset(atoi(T[3])*CTRL_X+ADJUST_CTRLS_X, atoi(T[4])*CTRL_Y+ADJUST_CTRLS_Y); Ctrl->ResDialogCtrl::SetPos(r); Dlg->AddView(Ctrl->View()); } } } else if (stricmp(Type, "LISTBOX") == 0) { if (T.Length() >= 7) { CtrlList *Ctrl = new CtrlList(Dialog, 0); if (Ctrl) { Ctrl->Str->SetDefine(T[1]); ImportDefine *Def = Defines.GetDefine(T[1]); if (Def) { Ctrl->Str->SetId(atoi(Def->Value)); } GRect r; r.ZOff(atoi(T[4])*CTRL_X, atoi(T[5])*CTRL_Y); r.Offset(atoi(T[2])*CTRL_X+ADJUST_CTRLS_X, atoi(T[3])*CTRL_Y+ADJUST_CTRLS_Y); Ctrl->ResDialogCtrl::SetPos(r); Dlg->AddView(Ctrl); } } } else if (stricmp(Type, "END") == 0) { // search for an existing dialog resource in // another language ResDialog *Match = 0; CtrlDlg *MatchObj = 0; for (ResDialog *d = DlLList.First(); d; d = DlLList.Next()) { GAutoPtr It(d->IterateViews()); GViewI *Wnd = It->First(); if (Wnd) { CtrlDlg *Obj = dynamic_cast(Wnd); if (Obj) { if (Obj->Str->GetId() == Dlg->Str->GetId()) { MatchObj = Obj; Match = d; break; } } } } if (Match) { // Merge the controls from "Dlg" to "MatchObj" List Old; List New; Dlg->ListChildren(New); MatchObj->ListChildren(Old); // add the language strings for the caption // without clobbering the languages already // present for (StrLang *s = Dlg->Str->Items.First(); s; s = Dlg->Str->Items.Next()) { if (!MatchObj->Str->Get(s->GetLang())) { MatchObj->Str->Set(s->GetStr(), s->GetLang()); } } for (ResDialogCtrl *c = New.First(); c; c = New.Next()) { ResDialogCtrl *MatchCtrl = 0; // try matching by Id { for (ResDialogCtrl *Mc = Old.First(); Mc; Mc = Old.Next()) { if (Mc->Str->GetId() == c->Str->GetId() && Mc->Str->GetId() > 0) { MatchCtrl = Mc; break; } } } // ok no Id match, match by location and type if (!MatchCtrl) { List Overlapping; for (ResDialogCtrl *Mc = Old.First(); Mc; Mc = Old.Next()) { GRect a = Mc->View()->GetPos(); GRect b = c->View()->GetPos(); GRect c = a; c.Bound(&b); if (c.Valid()) { int Sa = a.X() * a.Y(); int Sb = b.X() * b.Y(); int Sc = c.X() * c.Y(); int Total = Sa + Sb - Sc; double Amount = (double) Sc / (double) Total; if (Amount > 0.5) { // mostly similar in size Overlapping.Insert(Mc); } } } if (Overlapping.Length() == 1) { MatchCtrl = Overlapping.First(); } } if (MatchCtrl) { // woohoo we are cool for (StrLang *s = c->Str->Items.First(); s; s = c->Str->Items.Next()) { MatchCtrl->Str->Set(s->GetStr(), s->GetLang()); } } } // Delete the duplicate OnObjSelect(0); DeleteObj(Dialog); } else { // Insert the dialog InsertObject(TYPE_DIALOG, Dialog, false); DlLList.Insert(Dialog); } Dialog = 0; Dlg = 0; Status = true; Mode = IMP_MODE_SEARCH; } break; } case IMP_MODE_STRINGS: { if (stricmp(T[0], "BEGIN") == 0) { } else if (stricmp(T[0], "END") == 0) { Status = true; Mode = IMP_MODE_SEARCH; } else { if (T.Length() > 1) { ResString *Str = String->FindName(T[0]); if (!Str) { Str = String->CreateStr(); if (Str) { ImportDefine *Def = Defines.GetDefine(T[0]); if (Def) { Str->SetId(atoi(Def->Value)); } Str->SetDefine(T[0]); Str->UnDuplicate(); } } if (Str) { // get the language GLanguage *Lang = GFindLang(Language); GLanguageId SLang = (Lang) ? Lang->Id : (char*)"en"; StrLang *s = 0; // look for language present in string object for (s = Str->Items.First(); s && *s != SLang; s = Str->Items.Next()); // if not present then add it if (!s) { s = new StrLang; if (s) { Str->Items.Insert(s); s->SetLang(SLang); } } // set the value if (s) { s->SetStr(T[1]); } } } } break; } case IMP_MODE_MENU: { if (T.Length() >= 1) { if (stricmp(T[0], "BEGIN") == 0) { MenuLevel++; } else if (stricmp(T[0], "END") == 0) { MenuLevel--; if (MenuLevel == 0) { Status = true; Mode = IMP_MODE_SEARCH; Menu->SetLanguages(); if (!MenuNewLang) { InsertObject(TYPE_MENU, Menu, false); } Menu = 0; } } else { ResMenuItem *i = 0; char *Text = T[1]; if (Text) { if (stricmp(T[0], "POPUP") == 0) { if (MenuNewLang) { GTreeItem *Ri = 0; if (MenuItem[MenuLevel]) { Ri = MenuItem[MenuLevel]->GetNext(); } else { if (MenuLevel == 1) { Ri = Menu->ItemAt(0); } else if (MenuItem[MenuLevel-1]) { Ri = MenuItem[MenuLevel-1]->GetChild(); } } if (Ri) { // Seek up to the next submenu while (!Ri->GetChild()) { Ri = Ri->GetNext(); } i = dynamic_cast(Ri); // char *si = i->Str.Get("en"); if (i) { MenuItem[MenuLevel] = i; } } } else { MenuItem[MenuLevel] = i = new ResMenuItem(Menu); } if (i) { GLanguage *Lang = GFindLang(Language); i->GetStr()->Set(Text, (Lang) ? Lang->Id : (char*)"en"); } MenuNextItem = 0; } else if (stricmp(T[0], "MENUITEM") == 0) { if (MenuNewLang) { if (MenuItem[MenuLevel-1] && T.Length() > 2) { ImportDefine *id = Defines.GetDefine(T[2]); if (id) { int Id = atoi(id->Value); int n = 0; for (GTreeItem *o = MenuItem[MenuLevel-1]->GetChild(); o; o = o->GetNext(), n++) { ResMenuItem *Res = dynamic_cast(o); if (Res && Res->GetStr()->GetId() == Id) { i = Res; break; } } } } MenuNextItem++; } else { i = new ResMenuItem(Menu); } if (i) { if (stricmp(Text, "SEPARATOR") == 0) { // Set separator i->Separator(true); } else { if (!MenuNewLang) { // Set Id i->GetStr()->SetDefine(T[2]); if (i->GetStr()->GetDefine()) { ImportDefine *id = Defines.GetDefine(i->GetStr()->GetDefine()); if (id) { i->GetStr()->SetId(atoi(id->Value)); i->GetStr()->UnDuplicate(); } } } // Set Text GLanguage *Lang = GFindLang(Language); i->GetStr()->Set(Text, (Lang) ? Lang->Id : (char*)"en"); } } } } if (i && !MenuNewLang) { if (MenuLevel == 1) { Menu->Insert(i); } else if (MenuItem[MenuLevel-1]) { MenuItem[MenuLevel-1]->Insert(i); } } } } break; } } } T.DeleteArrays(); } DeleteObj(Dialog); if (String->Length() > 0) { String->SetLanguages(); } } Invalidate(); } return Status; } bool AppWnd::SaveWin32() { return false; } ///////////////////////////////////////////////////////////////////////// ResFrame::ResFrame(Resource *child) { Child = child; Name("ResFrame"); } ResFrame::~ResFrame() { if (Child) { Child->App()->OnObjSelect(NULL); Child->Wnd()->Detach(); } } void ResFrame::OnFocus(bool b) { Child->Wnd()->Invalidate(); } bool ResFrame::Attach(GViewI *p) { bool Status = GLayout::Attach(p); if (Status && Child) { Child->Attach(this); Child->Wnd()->Visible(true); } return Status; } bool ResFrame::Pour(GRegion &r) { GRect *Best = FindLargest(r); if (Best) { SetPos(*Best); return true; } return false; } bool ResFrame::OnKey(GKey &k) { bool Status = false; if (k.Down() && Child) { switch (k.c16) { case VK_DELETE: { if (k.Shift()) { Child->Copy(true); } else { Child->Delete(); } Status = true; break; } case 'x': case 'X': { if (k.Ctrl()) { Child->Copy(true); Status = true; } break; } case 'c': case 'C': { if (k.Ctrl()) { Child->Copy(); Status = true; } break; } case VK_INSERT: { if (k.Ctrl()) { Child->Copy(); } else if (k.Shift()) { Child->Paste(); } Status = true; break; } case 'v': case 'V': { if (k.Ctrl()) { Child->Paste(); Status = true; } break; } } } return Child->Wnd()->OnKey(k) || Status; } void ResFrame::OnPaint(GSurface *pDC) { // Draw nice frame GRect r(0, 0, X()-1, Y()-1); LgiThinBorder(pDC, r, DefaultRaisedEdge); pDC->Colour(LC_MED, 24); LgiFlatBorder(pDC, r, 4); LgiWideBorder(pDC, r, DefaultSunkenEdge); // Set the child to the client area Child->Wnd()->SetPos(r); // Draw the dialog & controls GView::OnPaint(pDC); } //////////////////////////////////////////////////////////////////// LgiFunc char *_LgiGenLangLookup(); #include "GAutoPtr.h" #include "GVariant.h" #include "GCss.h" #include "GTableLayout.h" class Foo : public GLayoutCell { public: Foo() { TextAlign(AlignLeft); } bool Add(GView *v) { return false; } bool Remove(GView *v) { return false; } }; ////////////////////////////////////////////////////////////////////// ShortCutView::ShortCutView(AppWnd *app) { App = app; GRect r(0, 0, 300, 600); SetPos(r); MoveSameScreen(App); Name("Dialog Shortcuts"); if (Attach(0)) { Lst = new LList(100, 0, 0, 100, 100); Lst->Attach(this); Lst->SetPourLargest(true); Lst->AddColumn("Key", 50); Lst->AddColumn("Ref", 80); Lst->AddColumn("Control", 150); Visible(true); } } ShortCutView::~ShortCutView() { App->OnCloseView(this); } void FindShortCuts(LList *Out, GViewI *In) { GAutoPtr it(In->IterateViews()); for (GViewI *c = it->First(); c; c = it->Next()) { ResDialogCtrl *rdc = dynamic_cast(c); if (!rdc || !rdc->Str) continue; char *n = rdc->Str->Get(); if (n) { char *a = strchr(n, '&'); if (a && a[1] != '&') { LListItem *li = new LListItem; GString s(++a, 1); GString id; id.Printf("%i", rdc->Str->GetRef()); li->SetText(s.Upper(), 0); li->SetText(id, 1); li->SetText(rdc->GetClass(), 2); li->_UserPtr = rdc; Out->Insert(li); } } FindShortCuts(Out, c); } } int ShortCutView::OnNotify(GViewI *Ctrl, int Flags) { if (Ctrl->GetId() == Lst->GetId()) { switch (Flags) { case GNotifyItem_Click: { LListItem *li = Lst->GetSelected(); if (li) { GString s = li->GetText(1); ResDialogCtrl *c = (ResDialogCtrl*) li->_UserPtr; if (c) App->GotoObject(c->Str, NULL, c->GetDlg(), NULL, c); } break; } } } return GWindow::OnNotify(Ctrl, Flags); } void ShortCutView::OnDialogChange(ResDialog *Dlg) { Lst->Empty(); if (!Dlg) return; FindShortCuts(Lst, Dlg); Lst->Sort(NULL); } ShortCutView *AppWnd::GetShortCutView() { return ShortCuts; } void AppWnd::OnCloseView(ShortCutView *v) { if (v == ShortCuts) ShortCuts = NULL; } ////////////////////////////////////////////////////////////////////// void TestFunc() { /* Foo c; uint32 *p = (uint32*)&c; p++; p = (uint32*)(*p); void *method = (void*)p[2]; for (int i=0; i<16; i++) { printf("[%i]=%p\n", i, p[i]); } c.OnChange(GCss::PropBackground); */ } int LgiMain(OsAppArguments &AppArgs) { GApp a(AppArgs, "LgiRes"); if (a.IsOk()) { if ((a.AppWnd = new AppWnd)) { TestFunc(); a.AppWnd->Visible(true); a.Run(); } } return 0; } diff --git a/ResourceEditor/Code/LgiRes_Dialog.cpp b/ResourceEditor/Code/LgiRes_Dialog.cpp --- a/ResourceEditor/Code/LgiRes_Dialog.cpp +++ b/ResourceEditor/Code/LgiRes_Dialog.cpp @@ -1,4315 +1,4315 @@ /* ** FILE: LgiRes_Dialog.cpp ** AUTHOR: Matthew Allen ** DATE: 5/8/1999 ** DESCRIPTION: Dialog Resource Editor ** ** ** Copyright (C) 1999, Matthew Allen ** fret@memecode.com */ // Old control offset 3,17 //////////////////////////////////////////////////////////////////// #include #include "LgiResEdit.h" #include "LgiRes_Dialog.h" #include "GButton.h" #include "GVariant.h" #include "GToken.h" #include "GDisplayString.h" #include "GClipBoard.h" #include "resdefs.h" //////////////////////////////////////////////////////////////////// #define IDC_UP 101 #define IDC_DOWN 102 // Name mapping table class LgiObjectName { public: int Type; const char *ObjectName; char *ResourceName; bool ToolbarBtn; } NameMap[] = { // ID Lgi's name Resource editor name {UI_DIALOG, "GDialog", Res_Dialog, false}, {UI_TABLE, "GTableLayout", Res_Table, true}, {UI_TEXT, "GText", Res_StaticText, true}, {UI_EDITBOX, "GEdit", Res_EditBox, true}, {UI_CHECKBOX, "GCheckBox", Res_CheckBox, true}, {UI_BUTTON, "GButton", Res_Button, true}, {UI_GROUP, "GRadioGroup", Res_Group, true}, {UI_RADIO, "GRadioButton", Res_RadioBox, true}, {UI_TABS, "GTabView", Res_TabView, true}, {UI_TAB, "GTabPage", Res_Tab, false}, {UI_LIST, "LList", Res_ListView, true}, {UI_COLUMN, "LListColumn", Res_Column, false}, {UI_COMBO, "GCombo", Res_ComboBox, true}, {UI_TREE, "GTree", Res_TreeView, true}, {UI_BITMAP, "GBitmap", Res_Bitmap, true}, {UI_PROGRESS, "GProgress", Res_Progress, true}, {UI_SCROLL_BAR, "GScrollBar", Res_ScrollBar, true}, {UI_CUSTOM, "GCustom", Res_Custom, true}, {UI_CONTROL_TREE, "GControlTree", Res_ControlTree, true}, // If you add a new control here update ResDialog::CreateCtrl(int Tool) as well {0, 0, 0, 0} }; class CtrlItem : public LListItem { friend class TabOrder; ResDialogCtrl *Ctrl; public: CtrlItem(ResDialogCtrl *ctrl) { Ctrl = ctrl; } char *GetText(int Col) { switch (Col) { case 0: { if (Ctrl && Ctrl->Str && Ctrl->Str->GetDefine()) { return Ctrl->Str->GetDefine(); } break; } case 1: { if (Ctrl && Ctrl->Str && Ctrl->Str->Get()) { return Ctrl->Str->Get(); } break; } } return (char*)""; } }; class TabOrder : public GDialog { ResDialogCtrl *Top; LList *Lst; GButton *Ok; GButton *Cancel; GButton *Up; GButton *Down; public: TabOrder(GView *Parent, ResDialogCtrl *top) { Top = top; SetParent(Parent); Children.Insert(Lst = new LList(IDC_LIST, 10, 10, 350, 300)); Children.Insert(Ok = new GButton(IDOK, Lst->GetPos().x2 + 10, 10, 60, 20, "Ok")); Children.Insert(Cancel = new GButton(IDCANCEL, Lst->GetPos().x2 + 10, Ok->GetPos().y2 + 5, 60, 20, "Cancel")); Children.Insert(Up = new GButton(IDC_UP, Lst->GetPos().x2 + 10, Cancel->GetPos().y2 + 15, 60, 20, "Up")); Children.Insert(Down = new GButton(IDC_DOWN, Lst->GetPos().x2 + 10, Up->GetPos().y2 + 5, 60, 20, "Down")); GRect r(0, 0, Ok->GetPos().x2 + 17, Lst->GetPos().y2 + 40); SetPos(r); MoveToCenter(); Lst->AddColumn("#define", 150); Lst->AddColumn("Text", 150); Lst->MultiSelect(false); if (Top) { GAutoPtr It(Top->View()->IterateViews()); for (ResDialogCtrl *Ctrl=dynamic_cast(It->First()); Ctrl; Ctrl=dynamic_cast(It->Next())) { if (Ctrl->GetType() != UI_TEXT) { Lst->Insert(new CtrlItem(Ctrl)); } } char s[256]; sprintf(s, "Set Tab Order: %s", Top->Str->GetDefine()); Name(s); DoModal(); } } int OnNotify(GViewI *Ctrl, int Flags) { int MoveDir = 1; switch (Ctrl->GetId()) { case IDC_UP: { MoveDir = -1; // fall through } case IDC_DOWN: { if (Lst) { CtrlItem *Sel = dynamic_cast(Lst->GetSelected()); if (Sel) { int Index = Lst->IndexOf(Sel); CtrlItem *Up = dynamic_cast(Lst->ItemAt(Index+MoveDir)); if (Up) { Lst->Remove(Sel); Lst->Insert(Sel, Index+MoveDir); Sel->Select(true); Sel->ScrollTo(); } } } break; } case IDOK: { if (Lst) { int i=0; List All; Lst->GetAll(All); for (CtrlItem *n=All.First(); n; n=All.Next()) { Top->View()->DelView(n->Ctrl->View()); Top->View()->AddView(n->Ctrl->View(), i++); } } // fall through } case IDCANCEL: { EndModal(0); break; } } return 0; } }; //////////////////////////////////////////////////////////////////// void DrawGoobers(GSurface *pDC, GRect &r, GRect *Goobers, COLOUR c) { int Mx = (r.x2 + r.x1) / 2 - (GOOBER_SIZE / 2); int My = (r.y2 + r.y1) / 2 - (GOOBER_SIZE / 2); pDC->Colour(c, 24); Goobers[0].x1 = r.x1 - GOOBER_BORDER; Goobers[0].y1 = r.y1 - GOOBER_BORDER; Goobers[0].x2 = r.x1 - (GOOBER_BORDER - GOOBER_SIZE); Goobers[0].y2 = r.y1 - (GOOBER_BORDER - GOOBER_SIZE); Goobers[1].x1 = Mx; Goobers[1].y1 = r.y1 - GOOBER_BORDER; Goobers[1].x2 = Mx + GOOBER_SIZE; Goobers[1].y2 = r.y1 - (GOOBER_BORDER - GOOBER_SIZE); Goobers[2].x1 = r.x2 + (GOOBER_BORDER - GOOBER_SIZE); Goobers[2].y1 = r.y1 - GOOBER_BORDER; Goobers[2].x2 = r.x2 + GOOBER_BORDER; Goobers[2].y2 = r.y1 - (GOOBER_BORDER - GOOBER_SIZE); Goobers[3].x1 = r.x2 + (GOOBER_BORDER - GOOBER_SIZE); Goobers[3].y1 = My; Goobers[3].x2 = r.x2 + GOOBER_BORDER; Goobers[3].y2 = My + GOOBER_SIZE; Goobers[4].x1 = r.x2 + (GOOBER_BORDER - GOOBER_SIZE); Goobers[4].y1 = r.y2 + (GOOBER_BORDER - GOOBER_SIZE); Goobers[4].x2 = r.x2 + GOOBER_BORDER; Goobers[4].y2 = r.y2 + GOOBER_BORDER; Goobers[5].x1 = Mx; Goobers[5].y1 = r.y2 + (GOOBER_BORDER - GOOBER_SIZE); Goobers[5].x2 = Mx + GOOBER_SIZE; Goobers[5].y2 = r.y2 + GOOBER_BORDER; Goobers[6].x1 = r.x1 - GOOBER_BORDER; Goobers[6].y1 = r.y2 + (GOOBER_BORDER - GOOBER_SIZE); Goobers[6].x2 = r.x1 - (GOOBER_BORDER - GOOBER_SIZE); Goobers[6].y2 = r.y2 + GOOBER_BORDER; Goobers[7].x1 = r.x1 - GOOBER_BORDER; Goobers[7].y1 = My; Goobers[7].x2 = r.x1 - (GOOBER_BORDER - GOOBER_SIZE); Goobers[7].y2 = My + GOOBER_SIZE; for (int i=0; i<8; i++) { pDC->Box(Goobers+i); } } //////////////////////////////////////////////////////////////////// int ResDialogCtrl::TabDepth = 0; ResDialogCtrl::ResDialogCtrl(ResDialog *dlg, char *CtrlTypeName, GXmlTag *load) : ResObject(CtrlTypeName) { Dlg = dlg; DragCtrl = -1; AcceptChildren = false; Movable = true; MoveCtrl = false; Vis = true; Client.ZOff(-1, -1); SelectMode = SelNone; SelectStart.ZOff(-1, -1); if (load) { // Base a string off the xml int r = load->GetAsInt("ref"); if (Dlg) { Str = Dlg->Symbols->FindRef(r); LgiAssert(Str); if (!Str) // oh well we should have one anyway... fix things up so to speak. Dlg->Symbols->CreateStr(); } LgiAssert(Str); } else if (Dlg->CreateSymbols) { // We create a symbol for this resource Str = (Dlg && Dlg->Symbols) ? Dlg->Symbols->CreateStr() : 0; if (Str) { char Def[256]; sprintf(Def, "IDC_%i", Str->GetRef()); Str->SetDefine(Def); } } else { // Someone else is going to create the symbol Str = 0; } LgiAssert(Str); } ResDialogCtrl::~ResDialogCtrl() { if (ResDialog::Symbols) { ResDialog::Symbols->DeleteStr(Str); } if (Dlg) { Dlg->App()->OnObjDelete(this); Dlg->OnDeselect(this); } } char *ResDialogCtrl::GetRefText() { static char Buf[64]; if (Str) { sprintf(Buf, "Ref=%i", Str->GetRef()); } else { Buf[0] = 0; } return Buf; } void ResDialogCtrl::ListChildren(List &l, bool Deep) { GAutoPtr it(View()->IterateViews()); for (GViewI *w = it->First(); w; w = it->Next()) { ResDialogCtrl *c = dynamic_cast(w); LgiAssert(c); if (c) { l.Insert(c); if (Deep) { c->ListChildren(l); } } } } GRect ResDialogCtrl::GetMinSize() { GRect m(0, 0, GRID_X-1, GRID_Y-1); if (IsContainer()) { GRect cli = View()->GetClient(false); GAutoPtr it(View()->IterateViews()); for (GViewI *c=it->First(); c; c=it->Next()) { GRect cpos = c->GetPos(); cpos.Offset(cli.x1, cli.y1); m.Union(&cpos); } } return m; } bool ResDialogCtrl::SetPos(GRect &p, bool Repaint) { GRect m = GetMinSize(); if (m.X() > p.X()) p.x2 = p.x1 + m.X() - 1; if (m.Y() > p.Y()) p.y2 = p.y1 + m.Y() - 1; if (p != View()->GetPos()) { /* if (ParentCtrl) { if (p.x1 < ParentCtrl->Client.x1) { p.Offset(ParentCtrl->Client.x1 - p.x1, 0); } if (p.y1 < ParentCtrl->Client.y1) { p.Offset(0, ParentCtrl->Client.y1 - p.y1); } } */ // set our size bool Status = View()->SetPos(p, Repaint); // tell everyone else about the change OnFieldChange(); GRect r(0, 0, p.X()-1, p.Y()-1); r.Size(-GOOBER_BORDER, -GOOBER_BORDER); View()->Invalidate(&r, false, true); // check our parents are big enough to show us... ResDialogCtrl *Par = ParentCtrl(); if (Par) { GRect t = Par->View()->GetPos(); Par->ResDialogCtrl::SetPos(t, true); } return Status; } return true; } void ResDialogCtrl::TabString(char *Str) { if (Str) { for (int i=0; iGetPos(); r.Offset(-r.x1, -r.y1); for (; w && w != Dlg; w = w->GetParent()) { GRect pos = w->GetPos(); if (w->GetParent()) { // GView *Ctrl = w->GetParent()->GetGView(); GRect client = w->GetParent()->GetClient(false); r.Offset(pos.x1 + client.x1, pos.y1 + client.y1); } else { r.Offset(pos.x1, pos.y1); } } return r; } void ResDialogCtrl::StrFromRef(int Ref) { // get the string object Str = Dlg->App()->GetStrFromRef(Ref); if (!Str) { LgiTrace("%s:%i - String with ref '%i' missing.\n", _FL, Ref); LgiAssert(0); if ((Str = Dlg->App()->GetDialogSymbols()->CreateStr())) { Str->SetRef(Ref); } } // if this assert fails then the Ref doesn't exist // and the string can't be found // if this assert fails then the Str is already // associated with a control, and thus we would // duplicate the pointer to the string if we let // it go by // LgiAssert(Str->UpdateWnd == 0); // set the string's control to us Str->UpdateWnd = View(); // make the strings refid unique Str->UnDuplicate(); View()->Name(Str->Get()); } bool ResDialogCtrl::GetFields(FieldTree &Fields) { if (Str) { Str->GetFields(Fields); } int Id = 101; Fields.Insert(this, DATA_STR, Id++, VAL_Pos, "Pos"); Fields.Insert(this, DATA_BOOL, Id++, VAL_Visible, "Visible"); Fields.Insert(this, DATA_BOOL, Id++, VAL_Enabled, "Enabled"); Fields.Insert(this, DATA_STR, Id++, VAL_Class, "Class", -1); Fields.Insert(this, DATA_STR, Id++, VAL_Style, "Style", -1, true); return true; } bool ResDialogCtrl::Serialize(FieldTree &Fields) { if ((Fields.GetMode() == FieldTree::ObjToUi || Fields.GetMode() == FieldTree::UiToObj) && Str) { Str->Serialize(Fields); } GRect r = View()->GetPos(), Old = View()->GetPos(); bool e = true; if (Fields.GetMode() == FieldTree::ObjToUi || Fields.GetMode() == FieldTree::ObjToStore) { r = View()->GetPos(); e = View()->Enabled(); } Fields.Serialize(this, VAL_Pos, r); Fields.Serialize(this, VAL_Visible, Vis, true); Fields.Serialize(this, VAL_Enabled, e, true); Fields.Serialize(this, VAL_Class, CssClass); Fields.Serialize(this, VAL_Style, CssStyle); if (Fields.GetMode() == FieldTree::UiToObj || Fields.GetMode() == FieldTree::StoreToObj) { View()->Enabled(e); SetPos(r); r.Union(&Old); r.Size(-GOOBER_BORDER, -GOOBER_BORDER); if (View()->GetParent()) { View()->GetParent()->Invalidate(&r); } } if (Dlg && Dlg->Item) Dlg->Item->Update(); return true; } void ResDialogCtrl::CopyText() { if (Str) { Str->CopyText(); } } void ResDialogCtrl::PasteText() { if (Str) { Str->PasteText(); View()->Invalidate(); } } bool ResDialogCtrl::AttachCtrl(ResDialogCtrl *Ctrl, GRect *r) { bool Status = false; if (Ctrl) { Ctrl->View()->Visible(true); View()->AddView(Ctrl->View()); Ctrl->View()->SetParent(View()); if (r) { if (!dynamic_cast(Ctrl->View()->GetParent())) { Dlg->SnapRect(r, this); } Ctrl->SetPos(*r); } if (Dlg->Resource::IsSelected()) { Dlg->OnSelect(Ctrl); } Status = true; } return Status; } void ResDialogCtrl::OnPaint(GSurface *pDC) { if (DragCtrl >= 0) { GRect r = DragRgn; r.Normal(); pDC->Colour(LC_FOCUS_SEL_BACK, 24); pDC->Box(&r); } } void ResDialogCtrl::OnMouseClick(GMouse &m) { if (m.Down()) { if (m.Left()) { if (Dlg) { bool Processed = false; GRect c = View()->GetClient(); bool ClickedThis = c.Overlap(m.x, m.y); GRect Cli = View()->GetClient(false); GMouse Ms = m; GdcPt2 Off; View()->WindowVirtualOffset(&Off); Ms.x += Off.x + Cli.x1; Ms.y += Off.y + Cli.y1; Dlg->OnMouseClick(Ms); if (ClickedThis && !Dlg->IsDraging()) { DragCtrl = Dlg->CurrentTool(); if ((DragCtrl > 0 && AcceptChildren) || ((DragCtrl == 0) && !Movable)) { GdcPt2 p(m.x, m.y); Dlg->SnapPoint(&p, ParentCtrl()); DragStart.x = DragRgn.x1 = DragRgn.x2 = p.x; DragStart.y = DragRgn.y1 = DragRgn.y2 = p.y; View()->Capture(true); Processed = true; } else { DragCtrl = -1; if (Movable) { DragRgn.x1 = m.x; DragRgn.y1 = m.y; MoveCtrl = true; Processed = true; View()->Capture(true); } } SelectMode = (m.Shift()) ? SelAdd : SelSet; SelectStart = View()->GetPos(); } } } else if (m.IsContextMenu()) { GSubMenu RClick; bool PasteData = false; bool PasteTranslations = false; { GClipBoard c(Dlg); char *Clip = c.Text(); if (Clip) { PasteTranslations = strstr(Clip, TranslationStrMagic); char *p = Clip; while (*p && strchr(" \r\n\t", *p)) p++; PasteData = *p == '<'; } } RClick.AppendItem("Cu&t", IDM_CUT, Dlg->Selection.Length()>0); RClick.AppendItem("&Copy Control", IDM_COPY, Dlg->Selection.Length()>0); RClick.AppendItem("&Paste", IDM_PASTE, PasteData); RClick.AppendSeparator(); RClick.AppendItem("Copy Text", IDM_COPY_TEXT, Dlg->Selection.Length()==1); RClick.AppendItem("Paste Text", IDM_PASTE_TEXT, PasteTranslations); RClick.AppendSeparator(); RClick.AppendItem("&Delete", IDM_DELETE, Dlg->Selection.Length()>0); if (Dlg->GetMouse(m, true)) { int Cmd = 0; switch (Cmd = RClick.Float(Dlg, m.x, m.y)) { case IDM_DELETE: { Dlg->Delete(); break; } case IDM_CUT: { Dlg->Copy(true); break; } case IDM_COPY: { Dlg->Copy(); break; } case IDM_PASTE: { Dlg->Paste(); break; } case IDM_COPY_TEXT: { ResDialogCtrl *Ctrl = Dlg->Selection.First(); if (Ctrl) { Ctrl->CopyText(); } break; } case IDM_PASTE_TEXT: { PasteText(); break; } } } } } else { if (DragCtrl >= 0) { bool Exit = false; if (Dlg && (DragRgn.X() > 1 || DragRgn.Y() > 1)) { if (DragCtrl == 0) { Dlg->SelectRect(this, &DragRgn, SelectMode != SelAdd); } else { ResDialogCtrl *Ctrl = Dlg->CreateCtrl(DragCtrl, 0); if (Ctrl) { AttachCtrl(Ctrl, &DragRgn); } } Exit = true; } DragCtrl = -1; View()->Invalidate(); View()->Capture(false); if (Exit) { return; } } if (MoveCtrl) { View()->Capture(false); MoveCtrl = false; } if (SelectMode > SelNone) { GRect r = View()->GetPos(); if (SelectStart == r) { Dlg->OnSelect(this, SelectMode != SelAdd); } SelectMode = SelNone; } } } void ResDialogCtrl::OnMouseMove(GMouse &m) { // Drag a rubber band... if (DragCtrl >= 0) { GRect Old = DragRgn; DragRgn.x1 = DragStart.x; DragRgn.y1 = DragStart.y; DragRgn.x2 = m.x; DragRgn.y2 = m.y; DragRgn.Normal(); Dlg->SnapRect(&DragRgn, this); Old.Union(&DragRgn); Old.Size(-1, -1); View()->Invalidate(&Old); } // Move some ctrl(s) if (MoveCtrl && !m.Shift()) { int Dx = m.x - DragRgn.x1; int Dy = m.y - DragRgn.y1; if (Dx != 0 || Dy != 0) { if (!Dlg->IsSelected(this)) { Dlg->OnSelect(this); } GRect Old = View()->GetPos(); GRect New = Old; New.Offset( m.x - DragRgn.x1, m.y - DragRgn.y1); GdcPt2 p(New.x1, New.y1); Dlg->SnapPoint(&p, ParentCtrl()); New.Set(p.x, p.y, p.x + New.X() - 1, p.y + New.Y() - 1); if (New != Old) { Dlg->MoveSelection(New.x1 - Old.x1, New.y1 - Old.y1); } } } } char *ReadInt(char *s, int &Value) { char *c = strchr(s, ','); if (c) { *c = 0; Value = atoi(s); return c+1; } Value = atoi(s); return 0; } void ResDialogCtrl::ReadPos(char *Str) { if (Str) { char *s = NewStr(Str); if (s) { GRect r = View()->GetPos(); char *p = ReadInt(s, r.x1); if (p) p = ReadInt(p, r.y1); if (p) p = ReadInt(p, r.x2); if (p) p = ReadInt(p, r.y2); DeleteArray(s); } } } //////////////////////////////////////////////////////////////////// CtrlDlg::CtrlDlg(ResDialog *dlg, GXmlTag *load) : ResDialogCtrl(dlg, Res_Dialog, load) { Movable = false; AcceptChildren = true; Str->UpdateWnd = View(); View()->Name("CtrlDlg"); } IMPL_DIALOG_CTRL(CtrlDlg) void CtrlDlg::OnPaint(GSurface *pDC) { // Draw any rubber band ResDialogCtrl::OnPaint(pDC); } GRect &CtrlDlg::GetClient(bool InClientSpace) { static GRect r; Client.Set(0, 0, View()->X()-1, View()->Y()-1); Client.Size(3, 3); Client.y1 += LgiApp->GetMetric(LGI_MET_DECOR_CAPTION); if (Client.y1 > Client.y2) Client.y1 = Client.y2; r = Client; if (InClientSpace) r.Offset(-r.x1, -r.y1); return r; } void CtrlDlg::_Paint(GSurface *pDC, GdcPt2 *Offset, GRegion *Update) { Client = GetClient(false); // Draw the border GRect r(0, 0, View()->X()-1, View()->Y()-1); LgiWideBorder(pDC, r, DefaultRaisedEdge); pDC->Colour(LC_MED, 24); LgiFlatBorder(pDC, r, 1); // Draw the title bar Title = r; Title.y2 = Client.y1 - 1; pDC->Colour(LC_ACTIVE_TITLE, 24); pDC->Rectangle(&Title); if (Str) { GDisplayString ds(SysFont, Str->Get()); SysFont->Fore(LC_ACTIVE_TITLE_TEXT); SysFont->Transparent(true); ds.Draw(pDC, Title.x1 + 10, Title.y1 + ((Title.Y()-ds.Y())/2)); } // Draw the client area GRect c = Client; GdcPt2 o; if (Offset) o = *Offset; c.Offset(o.x, o.y); pDC->SetClient(&c); // Draw the grid pDC->Colour(LC_MED, 24); pDC->Rectangle(0, 0, Client.X()-1, Client.Y()-1); pDC->Colour(Rgb24(0x80, 0x80, 0x80), 24); for (int y=0; ySet(x, y); } } // Paint children GdcPt2 co(c.x1, c.y1); GView::_Paint(pDC, &co); pDC->SetOrigin(o.x, o.y); } ///////////////////////////////////////////////////////////////////// // Text box CtrlText::CtrlText(ResDialog *dlg, GXmlTag *load) : ResDialogCtrl(dlg, Res_StaticText, load) { if (Str && !load) { Str->SetDefine("IDC_STATIC"); } } IMPL_DIALOG_CTRL(CtrlText) void CtrlText::OnPaint(GSurface *pDC) { Client.ZOff(X()-1, Y()-1); char *Text = Str->Get(); SysFont->Fore(0); SysFont->Transparent(true); if (Text) { GRect Client = GetClient(); int y = 0; char *Start = Text; for (char *s = Text; 1; s++) { if ((*s == '\\' && *(s+1) == 'n') || (*s == 0)) { GDisplayString ds(SysFont, Start, s - Start); ds.Draw(pDC, 0, y, &Client); y += 15; Start = s + 2; if (*s == '\\') s++; } if (*s == 0) break; } } } // Editbox CtrlEditbox::CtrlEditbox(ResDialog *dlg, GXmlTag *load) : ResDialogCtrl(dlg, Res_EditBox, load) { Password = false; MultiLine = false; } IMPL_DIALOG_CTRL(CtrlEditbox) void CtrlEditbox::OnPaint(GSurface *pDC) { GRect r(0, 0, X()-1, Y()-1); Client = r; // Draw the ctrl LgiWideBorder(pDC, r, DefaultSunkenEdge); pDC->Colour(Enabled() ? LC_WORKSPACE : LC_MED, 24); pDC->Rectangle(&r); char *Text = Str->Get(); SysFont->Fore(Enabled() ? 0 : LC_LOW); SysFont->Transparent(true); if (Text) { if (Password) { char *t = NewStr(Text); if (t) { for (char *p = t; *p; p++) *p = '*'; GDisplayString ds(SysFont, t); ds.Draw(pDC, 4, 4); DeleteArray(t); } } else { GDisplayString ds(SysFont, Text); ds.Draw(pDC, 4, 4); } } // Draw any rubber band ResDialogCtrl::OnPaint(pDC); } #define VAL_Password "Pw" #define VAL_MultiLine "MultiLine" bool CtrlEditbox::GetFields(FieldTree &Fields) { bool Status = ResDialogCtrl::GetFields(Fields); if (Status) { Fields.Insert(this, DATA_BOOL, 300, VAL_Password, "Password"); Fields.Insert(this, DATA_BOOL, 301, VAL_MultiLine, "MultiLine"); } return Status; } bool CtrlEditbox::Serialize(FieldTree &Fields) { bool Status = ResDialogCtrl::Serialize(Fields); if (Status) { Fields.Serialize(this, VAL_Password, Password, false); Fields.Serialize(this, VAL_MultiLine, MultiLine, false); } return Status; } // Check box CtrlCheckbox::CtrlCheckbox(ResDialog *dlg, GXmlTag *load) : ResDialogCtrl(dlg, Res_CheckBox, load) { } IMPL_DIALOG_CTRL(CtrlCheckbox) void CtrlCheckbox::OnPaint(GSurface *pDC) { Client.ZOff(X()-1, Y()-1); GRect r(0, 0, 12, 12); // Draw the ctrl LgiWideBorder(pDC, r, DefaultSunkenEdge); pDC->Colour(LC_WORKSPACE, 24); pDC->Rectangle(&r); GdcPt2 Pt[6] = { GdcPt2(3, 4), GdcPt2(3, 7), GdcPt2(5, 10), GdcPt2(10, 5), GdcPt2(10, 2), GdcPt2(5, 7)}; pDC->Colour(0); pDC->Polygon(6, Pt); pDC->Set(3, 5); char *Text = Str->Get(); if (Text) { SysFont->Fore(LC_TEXT); SysFont->Transparent(true); GDisplayString ds(SysFont, Text); ds.Draw(pDC, r.x2 + 10, r.y1-2); } // Draw any rubber band ResDialogCtrl::OnPaint(pDC); } // Button CtrlButton::CtrlButton(ResDialog *dlg, GXmlTag *load) : ResDialogCtrl(dlg, Res_Button, load) { } IMPL_DIALOG_CTRL(CtrlButton) bool CtrlButton::GetFields(FieldTree &Fields) { bool Status = ResDialogCtrl::GetFields(Fields); int Id = 160; Fields.Insert(this, DATA_FILENAME, Id++, VAL_Image, "Image"); return Status; } bool CtrlButton::Serialize(FieldTree &Fields) { bool Status = ResDialogCtrl::Serialize(Fields); if (Status) Fields.Serialize(this, VAL_Image, Image); return Status; } void CtrlButton::OnPaint(GSurface *pDC) { Client.ZOff(X()-1, Y()-1); GRect r = Client; char *Text = Str->Get(); // Draw the ctrl LgiWideBorder(pDC, r, DefaultRaisedEdge); SysFont->Fore(LC_TEXT); if (ValidStr(Text)) { SysFont->Back(LC_MED); SysFont->Transparent(false); GDisplayString ds(SysFont, Text); ds.Draw(pDC, r.x1 + ((r.X()-ds.X())/2), r.y1 + ((r.Y()-ds.Y())/2), &r); } else { pDC->Colour(LC_MED, 24); pDC->Rectangle(&r); } // Draw any rubber band ResDialogCtrl::OnPaint(pDC); } // Group CtrlGroup::CtrlGroup(ResDialog *dlg, GXmlTag *load) : ResDialogCtrl(dlg, Res_Group, load) { AcceptChildren = true; if (Str && !load) { Str->SetDefine("IDC_STATIC"); } } IMPL_DIALOG_CTRL(CtrlGroup) void CtrlGroup::OnPaint(GSurface *pDC) { Client.ZOff(X()-1, Y()-1); GRect r = Client; // Draw the ctrl r.y1 += 5; LgiWideBorder(pDC, r, EdgeXpChisel); r.y1 -= 5; SysFont->Fore(LC_TEXT); SysFont->Back(LC_MED); SysFont->Transparent(false); char *Text = Str->Get(); GDisplayString ds(SysFont, Text); ds.Draw(pDC, r.x1 + 8, r.y1 - 2); // Draw children //GWindow::OnPaint(pDC); // Draw any rubber band ResDialogCtrl::OnPaint(pDC); } //Radio button -uint32 RadioBmp[] = { +uint32_t RadioBmp[] = { 0xC0C0C0C0, 0xC0C0C0C0, 0xC0C0C0C0, 0x80808080, 0x80808080, 0x80808080, 0xC0C0C0C0, 0xC0C0C0C0, 0xC0C0C0C0, 0xC0C0C0C0, 0x8080C0C0, 0x80808080, 0x00000000, 0x00000000, 0x00000000, 0x80808080, 0xC0C08080, 0xC0C0C0C0, 0x80C0C0C0, 0x00008080, 0x00000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000000, 0xFFFF0000, 0xC0C0C0FF, 0x80C0C0C0, 0x00008080, 0xFFFFFF00, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xDFFFFFFF, 0xFFFFDFDF, 0xC0C0C0FF, 0x00808080, 0xFFFF0000, 0xFFFFFFFF, 0x00FFFFFF, 0x00000000, 0xFFFFFF00, 0xFFFFFFFF, 0xDFDFFFFF, 0xFFFFFFDF, 0x00808080, 0xFFFF0000, 0xFFFFFFFF, 0x00000000, 0x00000000, 0x00000000, 0xFFFFFFFF, 0xDFDFFFFF, 0xFFFFFFDF, 0x00808080, 0xFFFF0000, 0xFFFFFFFF, 0x00000000, 0x00000000, 0x00000000, 0xFFFFFFFF, 0xDFDFFFFF, 0xFFFFFFDF, 0x00808080, 0xFFFF0000, 0xFFFFFFFF, 0x00FFFFFF, 0x00000000, 0xFFFFFF00, 0xFFFFFFFF, 0xDFDFFFFF, 0xFFFFFFDF, 0x80C0C0C0, 0x00008080, 0xFFFFFF00, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xDFFFFFFF, 0xFFFFDFDF, 0xC0C0C0FF, 0x80C0C0C0, 0xDFDF8080, 0xDFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xDFDFDFDF, 0xFFFFDFDF, 0xC0C0C0FF, 0xC0C0C0C0, 0xFFFFC0C0, 0xFFFFFFFF, 0xDFDFDFDF, 0xDFDFDFDF, 0xDFDFDFDF, 0xFFFFFFFF, 0xC0C0FFFF, 0xC0C0C0C0, 0xC0C0C0C0, 0xC0C0C0C0, 0xC0C0C0C0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xC0C0C0C0, 0xC0C0C0C0, 0xC0C0C0C0}; CtrlRadio::CtrlRadio(ResDialog *dlg, GXmlTag *load) : ResDialogCtrl(dlg, Res_RadioBox, load) { Bmp = new GMemDC; if (Bmp && Bmp->Create(12, 12, GdcD->GetColourSpace())) { int Len = ((Bmp->X()*24)+31)/32*4; for (int y=0; yY(); y++) { for (int x=0; xX(); x++) { uchar *s = ((uchar*) RadioBmp) + (y * Len) + (x * 3); Bmp->Colour(Rgb24(s[0], s[1], s[2]), 24); Bmp->Set(x, y); } } } } CtrlRadio::~CtrlRadio() { DeleteObj(Bmp); } IMPL_DIALOG_CTRL(CtrlRadio) void CtrlRadio::OnPaint(GSurface *pDC) { Client.ZOff(X()-1, Y()-1); GRect r(0, 0, 12, 12); // Draw the ctrl if (RadioBmp) { pDC->Blt(r.x1, r.y1, Bmp); } char *Text = Str->Get(); if (Text) { SysFont->Fore(LC_TEXT); SysFont->Transparent(true); GDisplayString ds(SysFont, Text); ds.Draw(pDC, r.x2 + 10, r.y1-2); } // Draw any rubber band ResDialogCtrl::OnPaint(pDC); } // Tab CtrlTab::CtrlTab(ResDialog *dlg, GXmlTag *load) : ResDialogCtrl(dlg, Res_Tab, load) { Str->UpdateWnd = this; } IMPL_DIALOG_CTRL(CtrlTab) void CtrlTab::OnPaint(GSurface *pDC) { } void CtrlTab::ListChildren(List &l, bool Deep) { ResDialogCtrl *Ctrl = dynamic_cast(GetParent()); CtrlTabs *Par = dynamic_cast(Ctrl); LgiAssert(Par); int MyIndex = Par->Tabs.IndexOf(this); LgiAssert(MyIndex >= 0); List *CList = (Par->Current == MyIndex) ? &Par->Children : &Children; for (GViewI *w = CList->First(); w; w = CList->Next()) { ResDialogCtrl *c = dynamic_cast(w); if (c) { l.Insert(c); if (Deep) { c->ListChildren(l, Deep); } } } } // Tab control CtrlTabs::CtrlTabs(ResDialog *dlg, GXmlTag *load) : ResDialogCtrl(dlg, Res_TabView, load) { AcceptChildren = true; Current = 0; if (!load) { for (int i=0; i<3; i++) { CtrlTab *t = new CtrlTab(dlg, load); if (t) { char Text[256]; sprintf(Text, "Tab %i", i+1); if (t->Str) { t->Str->Set(Text); t->SetParent(this); Tabs.Insert(t); } else { DeleteObj(t); } } } } } CtrlTabs::~CtrlTabs() { Empty(); } void CtrlTabs::OnMouseMove(GMouse &m) { ResDialogCtrl::OnMouseMove(m); } void CtrlTabs::ShowMe(ResDialogCtrl *Child) { CtrlTab *t = dynamic_cast(Child); if (t) { int Idx = Tabs.IndexOf(t); if (Idx >= 0) { ToTab(); Current = Idx; FromTab(); } } } void CtrlTabs::EnumCtrls(List &Ctrls) { List::I it = Tabs.begin(); for (CtrlTab *t = *it; t; t = *++it) { t->EnumCtrls(Ctrls); } ResDialogCtrl::EnumCtrls(Ctrls); } GRect CtrlTabs::GetMinSize() { List l; ListChildren(l, false); GRect r(0, 0, GRID_X-1, GRID_Y-1); /* // don't resize smaller than the tabs for (CtrlTab *t = Tabs.First(); t; t = Tabs.Next()) { r.x2 = max(r.x2, t->r.x2 + 10); r.y2 = max(r.y2, t->r.y2 + 10); } */ // don't resize smaller than any of the children // on any of the tabs GRect cli = GetClient(false); for (ResDialogCtrl *c=l.First(); c; c=l.Next()) { GRect cpos = c->View()->GetPos(); cpos.Offset(cli.x1, cli.y1); r.Union(&cpos); } return r; } void CtrlTabs::ListChildren(List &l, bool Deep) { int n=0; for (CtrlTab *t = Tabs.First(); t; t = Tabs.Next(), n++) { l.Add(t); GAutoPtr It((Current == n ? (GViewI*)this : (GViewI*)t)->IterateViews()); for (GViewI *w = It->First(); w; w = It->Next()) { ResDialogCtrl *c = dynamic_cast(w); if (c) { l.Insert(c); c->ListChildren(l, Deep); } } } } void CtrlTabs::Empty() { ToTab(); Tabs.DeleteObjects(); } void CtrlTabs::OnPaint(GSurface *pDC) { // Draw the ctrl Title.ZOff(X()-1, 17); Client.ZOff(X()-1, Y()-1); Client.y1 = Title.y2; GRect r = Client; LgiWideBorder(pDC, r, DefaultRaisedEdge); // Draw the tabs int i = 0; int x = 2; for (CtrlTab *Tab = Tabs.First(); Tab; Tab = Tabs.Next(), i++) { char *Str = Tab->Str ? Tab->Str->Get() : 0; GDisplayString ds(SysFont, Str); int Width = 12 + ds.X(); GRect t(x, Title.y1 + 2, x + Width - 1, Title.y2 - 1); if (Current == i) { t.Size(-2, -2); GAutoPtr It(Tab->IterateViews()); if (It->Length() > 0) { FromTab(); } } Tab->View()->SetPos(t); pDC->Colour(LC_LIGHT, 24); pDC->Line(t.x1, t.y1+2, t.x1, t.y2); pDC->Set(t.x1+1, t.y1+1); pDC->Line(t.x1+2, t.y1, t.x2-2, t.y1); pDC->Colour(LC_MED, 24); pDC->Line(t.x1+1, t.y1+2, t.x1+1, t.y2); pDC->Line(t.x1+2, t.y1+1, t.x2-2, t.y1+1); pDC->Colour(LC_LOW, 24); pDC->Line(t.x2-1, t.y1, t.x2-1, t.y2); pDC->Colour(0, 24); pDC->Line(t.x2, t.y1+2, t.x2, t.y2); t.Size(2, 2); t.y2 += 2; SysFont->Fore(LC_TEXT); SysFont->Back(LC_MED); SysFont->Transparent(false); ds.Draw(pDC, t.x1 + ((t.X()-ds.X())/2), t.y1 + ((t.Y()-ds.Y())/2), &t); x += Width + ((Current == i) ? 2 : 1); } // Draw children //GWindow::OnPaint(pDC); // Draw any rubber band ResDialogCtrl::OnPaint(pDC); } void CtrlTabs::ToTab() { CtrlTab *Cur = Tabs.ItemAt(Current); if (Cur) { // move all our children into the tab losing focus GAutoPtr CurIt(Cur->IterateViews()); GAutoPtr ThisIt(IterateViews()); GViewI *v; while ((v = CurIt->First())) { Cur->DelView(v); } while ((v = ThisIt->First())) { DelView(v); Cur->AddView(v); } } } void CtrlTabs::FromTab() { CtrlTab *Cur = Tabs.ItemAt(Current); if (Cur) { // load all our children from the new tab GAutoPtr CurIt(Cur->IterateViews()); GAutoPtr ThisIt(IterateViews()); GViewI *v; while ((v = ThisIt->First())) { DelView(v); } while ((v = CurIt->First())) { Cur->DelView(v); AddView(v); } } } void CtrlTabs::OnMouseClick(GMouse &m) { if (m.Down()) { if (Title.Overlap(m.x, m.y)) { // select current tab int i = 0; for (CtrlTab *Tab = Tabs.First(); Tab; Tab = Tabs.Next(), i++) { if (Tab->View()->GetPos().Overlap(m.x, m.y) /* && i != Current*/) { ToTab(); Current = i; FromTab(); Dlg->OnSelect(Tab); Invalidate(); break; } } } if (m.IsContextMenu() && Title.Overlap(m.x, m.y)) { GSubMenu *RClick = new GSubMenu; if (RClick) { bool HasTab = Tabs.ItemAt(Current); RClick->AppendItem("New tab", IDM_NEW, true); RClick->AppendItem("Delete tab", IDM_DELETE, HasTab); RClick->AppendItem("Rename tab", IDM_RENAME, HasTab); RClick->AppendItem("Move tab left", IDM_MOVE_LEFT, HasTab); RClick->AppendItem("Move tab right", IDM_MOVE_RIGHT, HasTab); RClick->AppendSeparator(); RClick->AppendItem("Copy Text", IDM_COPY_TEXT, Dlg->Selection.Length()==1); RClick->AppendItem("Paste Text", IDM_PASTE_TEXT, true); if (GetMouse(m, true)) { switch (RClick->Float(this, m.x, m.y, false)) { case IDM_NEW: { CtrlTab *t = new CtrlTab(Dlg, 0); if (t) { char Text[256]; sprintf(Text, "Tab " LPrintfSizeT, Tabs.Length()+1); t->Str->Set(Text); t->SetParent(this); Tabs.Insert(t); } break; } case IDM_DELETE: { CtrlTab *t = Tabs.ItemAt(Current); if (t) { ToTab(); Tabs.Delete(t); DeleteObj(t); FromTab(); } break; } case IDM_RENAME: { CtrlTab *t = Tabs.ItemAt(Current); if (t) { if (!t->Str) t->Str = Dlg->CreateSymbol(); GInput Input(this, t->Str->Get(), "Enter tab name:", "Rename"); Input.SetParent(Dlg); if (Input.DoModal() && Input.GetStr()) { t->Str->Set(Input.GetStr()); } } break; } case IDM_MOVE_LEFT: { CtrlTab *t = Tabs.ItemAt(Current); if (t && Current > 0) { Tabs.Delete(t); Tabs.Insert(t, --Current); } break; } case IDM_MOVE_RIGHT: { CtrlTab *t = Tabs.ItemAt(Current); if (t && Current < Tabs.Length()-1) { Tabs.Delete(t); Tabs.Insert(t, ++Current); } break; } case IDM_COPY_TEXT: { CtrlTab *t = Tabs.ItemAt(Current); if (t) { t->CopyText(); } break; } case IDM_PASTE_TEXT: { CtrlTab *t = Tabs.ItemAt(Current); if (t) { t->PasteText(); } break; } } Invalidate(); } } return; } } else { int asd=0; } ResDialogCtrl::OnMouseClick(m); } // List column ListCol::ListCol(ResDialog *dlg, GXmlTag *load, char *s, int Width) : ResDialogCtrl(dlg, Res_Column, load) { if (s && Str) { Str->Set(s); } GRect r(0, 0, Width-1, 18); ResDialogCtrl::SetPos(r); } IMPL_DIALOG_CTRL(ListCol) void ListCol::OnPaint(GSurface *pDC) { } // List control CtrlList::CtrlList(ResDialog *dlg, GXmlTag *load) : ResDialogCtrl(dlg, Res_ListView, load) { DragCol = -1; } CtrlList::~CtrlList() { Empty(); } void CtrlList::ListChildren(List &l, bool Deep) { if (Deep) { for (GView *w = Cols.First(); w; w = Cols.Next()) { ResDialogCtrl *c = dynamic_cast(w); if (c) { l.Insert(c); c->ListChildren(l); } } } } void CtrlList::Empty() { for (ListCol *c = Cols.First(); c; c = Cols.Next()) { DeleteObj(c); } Cols.Empty(); } void CtrlList::OnMouseClick(GMouse &m) { if (m.Down()) { if (Title.Overlap(m.x, m.y)) { int x=0; ListCol *c = 0; int DragOver = -1; for (ListCol *Col = Cols.First(); Col; Col = Cols.Next()) { if (m.x >= Col->r().x1 && m.x <= Col->r().x2) { if (m.x > Col->r().x2 - 6) { DragOver = Cols.IndexOf(Col); } if (m.x < Col->r().x1 + 6) { DragOver = Cols.IndexOf(Col) - 1; } c = Col; break; } x += Col->r().X(); } if (m.Left()) { if (DragOver >= 0) { DragCol = DragOver; Capture(true); } } else if (m.IsContextMenu()) { GSubMenu *RClick = new GSubMenu; if (RClick) { bool HasCol = c != 0; RClick->AppendItem("New column", IDM_NEW, true); RClick->AppendItem("Delete column", IDM_DELETE, HasCol); RClick->AppendItem("Rename column", IDM_RENAME, HasCol); RClick->AppendItem("Move column left", IDM_MOVE_LEFT, HasCol); RClick->AppendItem("Move column right", IDM_MOVE_RIGHT, HasCol); RClick->AppendSeparator(); RClick->AppendItem("Copy Text", IDM_COPY_TEXT, HasCol); RClick->AppendItem("Paste Text", IDM_PASTE_TEXT, HasCol); if (GetMouse(m, true)) { switch (RClick->Float(this, m.x, m.y, false)) { case IDM_COPY_TEXT: { if (c && c->Str) { c->Str->CopyText(); } break; } case IDM_PASTE_TEXT: { if (c && c->Str) { c->Str->PasteText(); } break; } case IDM_NEW: { ListCol *c = dynamic_cast(Dlg->CreateCtrl(UI_COLUMN,0)); if (c) { char Text[256]; sprintf(Text, "Col " LPrintfSizeT, Cols.Length()+1); c->Str->Set(Text); Cols.Insert(c); } break; } case IDM_DELETE: { Cols.Delete(c); DeleteObj(c); break; } case IDM_RENAME: { if (c) { GInput Input(this, c->Str->Get(), "Enter column name:", "Rename"); Input.SetParent(Dlg); if (Input.DoModal()) { c->Str->Set(Input.GetStr()); } } break; } case IDM_MOVE_LEFT: { int Current = Cols.IndexOf(c); if (c && Current > 0) { Cols.Delete(c); Cols.Insert(c, --Current); } break; } case IDM_MOVE_RIGHT: { int Current = Cols.IndexOf(c); if (c && Current < Cols.Length()-1) { Cols.Delete(c); Cols.Insert(c, ++Current); } break; } } Invalidate(); } DeleteObj(RClick); return; } } return; } } else { if (DragCol >= 0) { Capture(false); DragCol = -1; } } ResDialogCtrl::OnMouseClick(m); } void CtrlList::OnMouseMove(GMouse &m) { if (DragCol >= 0) { int i=0, x=0;; for (ListCol *Col = Cols.First(); Col; Col = Cols.Next(), i++) { if (i == DragCol) { int Dx = (m.x - x - Title.x1); GRect r = Col->GetPos(); r.x2 = r.x1 + Dx; Col->ResDialogCtrl::SetPos(r); break; } x += Col->r().X(); } Invalidate(); } ResDialogCtrl::OnMouseMove(m); } void CtrlList::OnPaint(GSurface *pDC) { GRect r(0, 0, X()-1, Y()-1); // Draw the ctrl LgiWideBorder(pDC, r, DefaultSunkenEdge); Title = r; Title.y2 = Title.y1 + 15; Client = r; Client.y1 = Title.y2 + 1; pDC->Colour(LC_WORKSPACE, 24); pDC->Rectangle(&Client); int x = Title.x1; for (ListCol *c = Cols.First(); c; c = Cols.Next()) { int Width = c->r().X(); c->r().Set(x, Title.y1, x + Width - 1, Title.y2); GRect r = c->r(); r.x2 = MIN(r.x2, Title.x2); x = r.x2 + 1; if (r.Valid()) { LgiWideBorder(pDC, r, DefaultRaisedEdge); SysFont->Fore(LC_TEXT); SysFont->Back(LC_MED); SysFont->Transparent(false); const char *Str = c->Str->Get(); if (!Str) Str = ""; GDisplayString ds(SysFont, Str); ds.Draw(pDC, r.x1 + 2, r.y1 + ((r.Y()-ds.Y())/2) - 1, &r); } } GRect Client(x, Title.y1, Title.x2, Title.y2); if (Client.Valid()) { LgiWideBorder(pDC, Client, DefaultRaisedEdge); pDC->Colour(LC_MED, 24);; pDC->Rectangle(&Client); } // Draw any rubber band ResDialogCtrl::OnPaint(pDC); } // Combo box CtrlComboBox::CtrlComboBox(ResDialog *dlg, GXmlTag *load) : ResDialogCtrl(dlg, Res_ComboBox, load) { } IMPL_DIALOG_CTRL(CtrlComboBox) void CtrlComboBox::OnPaint(GSurface *pDC) { GRect r(0, 0, X()-1, Y()-1); Client = r; // Draw the ctrl LgiWideBorder(pDC, r, DefaultSunkenEdge); // Allocate space GRect e = r; e.x2 -= 15; GRect d = r; d.x1 = e.x2 + 1; // Draw edit pDC->Colour(LC_WORKSPACE, 24); pDC->Rectangle(&e); // Draw drap down LgiWideBorder(pDC, d, DefaultRaisedEdge); pDC->Colour(LC_MED, 24); pDC->Rectangle(&d); pDC->Colour(0, 24); int Size = 4; int cx = (d.X()/2) + d.x1 - 4; int cy = (d.Y()/2) + d.y1 - 3; for (int i=1; i<=Size; i++) { pDC->Line(cx+i, cy+i, cx+(Size*2)-i, cy+i); } // Text char *Text = Str->Get(); SysFont->Fore(0); SysFont->Transparent(true); if (Text) { GDisplayString ds(SysFont, Text); ds.Draw(pDC, 4, 4); } // Draw any rubber band ResDialogCtrl::OnPaint(pDC); } //////////////////////////////////////////////////////////////////// CtrlScrollBar::CtrlScrollBar(ResDialog *dlg, GXmlTag *load) : ResDialogCtrl(dlg, Res_ScrollBar, load) { } IMPL_DIALOG_CTRL(CtrlScrollBar) void CtrlScrollBar::OnPaint(GSurface *pDC) { GRect r(0, 0, X()-1, Y()-1); Client = r; // Draw the ctrl bool Vertical = r.Y() > r.X(); int ButSize = Vertical ? r.X() : r.Y(); GRect a, b, c; if (Vertical) { a.Set(r.x1, r.y1, r.x2, r.y1 + ButSize); c.Set(r.x1, r.y2 - ButSize, r.x2, r.y2); b.Set(r.x1, a.y2 + 1, r.x2, c.y1 - 1); } else { a.Set(r.x1, r.y1, r.x1 + ButSize, r.y2); c.Set(r.x2 - ButSize, r.y1, r.x2, r.y2); b.Set(a.x2 + 1, r.y1, c.x1 - 1, r.y2); } // Buttons LgiWideBorder(pDC, a, DefaultRaisedEdge); LgiWideBorder(pDC, c, DefaultRaisedEdge); pDC->Colour(LC_MED, 24); pDC->Rectangle(&a); pDC->Rectangle(&c); // Arrows pDC->Colour(0); int x = a.x1 + (a.X()/2) - 2; int y = a.y1 + (a.Y()/2); int i; for (i=0; i<5; i++) { pDC->Line(x+i, y-i, x+i, y+i); } x = c.x1 + (c.X()/2) - 2; y = c.y1 + (c.Y()/2); for (i=0; i<5; i++) { pDC->Line(x+i, y-(4-i), x+i, y+(4-i)); } // Slider pDC->Colour(0xd0d0d0, 24); pDC->Rectangle(&b); // Draw any rubber band ResDialogCtrl::OnPaint(pDC); } //////////////////////////////////////////////////////////////////// CtrlTree::CtrlTree(ResDialog *dlg, GXmlTag *load) : ResDialogCtrl(dlg, Res_TreeView, load) { } IMPL_DIALOG_CTRL(CtrlTree) void CtrlTree::OnPaint(GSurface *pDC) { GRect r(0, 0, X()-1, Y()-1); Client = r; LgiWideBorder(pDC, r, DefaultSunkenEdge); pDC->Colour(Rgb24(255, 255, 255), 24); pDC->Rectangle(&r); SysFont->Colour(0, 0xffffff); SysFont->Transparent(true); GDisplayString ds(SysFont, "Tree"); ds.Draw(pDC, r.x1 + 3, r.y1 + 3, &r); // Draw any rubber band ResDialogCtrl::OnPaint(pDC); } //////////////////////////////////////////////////////////////////// CtrlBitmap::CtrlBitmap(ResDialog *dlg, GXmlTag *load) : ResDialogCtrl(dlg, Res_Bitmap, load) { } IMPL_DIALOG_CTRL(CtrlBitmap) void CtrlBitmap::OnPaint(GSurface *pDC) { GRect r(0, 0, X()-1, Y()-1); Client = r; LgiWideBorder(pDC, r, DefaultSunkenEdge); pDC->Colour(Rgb24(255, 255, 255), 24); pDC->Rectangle(&r); pDC->Colour(0, 24); pDC->Line(r.x1, r.y1, r.x2, r.y2); pDC->Line(r.x2, r.y1, r.x1, r.y2); // Draw any rubber band ResDialogCtrl::OnPaint(pDC); } //////////////////////////////////////////////////////////////////// CtrlProgress::CtrlProgress(ResDialog *dlg, GXmlTag *load) : ResDialogCtrl(dlg, Res_Progress, load) { } IMPL_DIALOG_CTRL(CtrlProgress) void CtrlProgress::OnPaint(GSurface *pDC) { GRect r(0, 0, X()-1, Y()-1); Client = r; LgiWideBorder(pDC, r, DefaultSunkenEdge); COLOUR Flat = Rgb24(0x7f, 0x7f, 0x7f); COLOUR White = Rgb24(0xff, 0xff, 0xff); int Pos = 60; int x = Pos * r.X() / 100; pDC->Colour(Flat, 24); pDC->Rectangle(r.x1, r.y1, r.x1 + x, r.y2); pDC->Colour(White, 24); pDC->Rectangle(r.x1 + x + 1, r.y1, r.x2, r.y2); // Draw any rubber band ResDialogCtrl::OnPaint(pDC); } //////////////////////////////////////////////////////////////////// CtrlCustom::CtrlCustom(ResDialog *dlg, GXmlTag *load) : ResDialogCtrl(dlg, Res_Custom, load) { Control = 0; } IMPL_DIALOG_CTRL(CtrlCustom) CtrlCustom::~CtrlCustom() { DeleteArray(Control); } void CtrlCustom::OnPaint(GSurface *pDC) { GRect r(0, 0, X()-1, Y()-1); Client = r; LgiWideBorder(pDC, r, DefaultSunkenEdge); COLOUR White = Rgb24(0xff, 0xff, 0xff); pDC->Colour(White, 24); pDC->Rectangle(&r); char s[256] = "Custom: "; if (Control) { strcat(s, Control); } SysFont->Colour(LC_TEXT, LC_WORKSPACE); GDisplayString ds(SysFont, s); ds.Draw(pDC, r.x1+2, r.y1+1, &r); // Draw any rubber band ResDialogCtrl::OnPaint(pDC); } #define VAL_Control "Ctrl" bool CtrlCustom::GetFields(FieldTree &Fields) { bool Status = ResDialogCtrl::GetFields(Fields); if (Status) { Fields.Insert(this, DATA_STR, 320, VAL_Control, "Control", 1); } return Status; } bool CtrlCustom::Serialize(FieldTree &Fields) { bool Status = ResDialogCtrl::Serialize(Fields); if (Status) { Fields.Serialize(this, VAL_Control, Control); } return Status; } //////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////// ResStringGroup *ResDialog::Symbols = 0; int ResDialog::SymbolRefs = 0; bool ResDialog::CreateSymbols = true; void ResDialog::AddLanguage(const char *Id) { if (Symbols) { Symbols->AppendLanguage(Id); } } void ResDialogCtrl::EnumCtrls(List &Ctrls) { Ctrls.Insert(this); ChildIterator It(View()->IterateViews()); for (GViewI *c = It->First(); c; c = It->Next()) { ResDialogCtrl *dc = dynamic_cast(c); LgiAssert(dc); dc->EnumCtrls(Ctrls); } } void ResDialog::EnumCtrls(List &Ctrls) { for (ResDialogCtrl *c = dynamic_cast(Children.First()); c; c = dynamic_cast(Children.Next())) { c->EnumCtrls(Ctrls); } } int GooberCaps[] = { RESIZE_X1 | RESIZE_Y1, RESIZE_Y1, RESIZE_X2 | RESIZE_Y1, RESIZE_X2, RESIZE_X2 | RESIZE_Y2, RESIZE_Y2, RESIZE_X1 | RESIZE_Y2, RESIZE_X1}; ResDialog::ResDialog(AppWnd *w, int type) : Resource(w, type) { // Maintain a string group just for our dialog // defines if (!Symbols && w) { // First check to see of the symbols have been loaded from a file // and we don't have a pointer to it yet Symbols = App()->GetDialogSymbols(); if (!Symbols) { // else we need to create the symbols object Symbols = new ResStringGroup(w); if (Symbols) { Symbols->Name(StrDialogSymbols); w->InsertObject(TYPE_STRING, Symbols); } } if (Symbols) { Symbols->SystemObject(true); } } SymbolRefs++; // Init dialog resource Ui = 0; DlgPos.ZOff(500-1, 400-1); DragGoober = -1; DragX = 0; DragY = 0; DragCtrl = 0; } ResDialog::~ResDialog() { // Decrement and delete the shared string group SymbolRefs--; if (SymbolRefs < 1) { App()->DelObject(Symbols); Symbols = 0; } // Delete our Ui DeleteObj(Ui); } void ResDialog::OnShowLanguages() { // Current language changed. OnSelect(Selection.First()); Invalidate(); OnLanguageChange(); } char *ResDialog::Name() { GViewI *v = Children.First(); ResDialogCtrl *Ctrl = dynamic_cast(v); if (Ctrl && Ctrl->Str && Ctrl->Str->GetDefine()) { return Ctrl->Str->GetDefine(); } return (char*)""; } bool ResDialog::Name(const char *n) { ResDialogCtrl *Ctrl = dynamic_cast(Children.First()); if (Ctrl && Ctrl->Str) { Ctrl->Str->SetDefine((n)?n:""); return Ctrl->Str->GetDefine() != 0; } return false; } char *ResDialog::StringFromRef(int Ref) { if (Symbols) { ResString *Str = Symbols->FindRef(Ref); if (Str) { return Str->Get(); } } return 0; } bool ResDialog::Res_GetProperties(ResObject *Obj, GDom *Props) { ResDialogCtrl *Ctrl = dynamic_cast(Obj); if (Ctrl && Props) { int Next = -1000; FieldTree t(Next, false); t.SetStore(Props); t.SetMode(FieldTree::ObjToStore); Ctrl->GetFields(t); Ctrl->Serialize(t); return true; } return false; } GDom *ResDialog::Res_GetDom(ResObject *Obj) { return dynamic_cast(Obj); } bool ResDialog::Res_SetProperties(ResObject *Obj, GDom *Props) { ResDialogCtrl *Ctrl = dynamic_cast(Obj); if (Ctrl && Props) { int Next = -1000; FieldTree t(Next, false); t.SetStore(Props); t.SetMode(FieldTree::StoreToObj); Ctrl->GetFields(t); Ctrl->Serialize(t); return true; } return false; } ResObject *ResDialog::CreateObject(GXmlTag *Tag, ResObject *Parent) { return dynamic_cast(CreateCtrl(Tag)); } void ResDialog::Res_SetPos(ResObject *Obj, int x1, int y1, int x2, int y2) { if (Obj) { ResDialogCtrl *Ctrl = dynamic_cast((ResDialogCtrl*)Obj); if (Ctrl) { GRect r(x1, y1, x2, y2); Ctrl->SetPos(r); } } } void ResDialog::Res_SetPos(ResObject *Obj, char *s) { if (Obj) { ResDialogCtrl *Ctrl = dynamic_cast((ResDialogCtrl*)Obj); if (Ctrl) { Ctrl->ReadPos(s); } } } GRect ResDialog::Res_GetPos(ResObject *Obj) { if (Obj) { ResDialogCtrl *Ctrl = dynamic_cast((ResDialogCtrl*)Obj); LgiAssert(Ctrl); if (Ctrl) { return Ctrl->View()->GetPos(); } } return GRect(0, 0, 0, 0); } int ResDialog::Res_GetStrRef(ResObject *Obj) { if (Obj) { ResDialogCtrl *Ctrl = dynamic_cast((ResDialogCtrl*)Obj); if (Ctrl) { return Ctrl->Str->GetRef(); } } return -1; } bool ResDialog::Res_SetStrRef(ResObject *Obj, int Ref, ResReadCtx *Ctx) { ResDialogCtrl *Ctrl = 0; if (!Obj || !Symbols) return false; Ctrl = dynamic_cast((ResDialogCtrl*)Obj); if (!Ctrl) return false; Ctrl->StrFromRef(Ref); LgiAssert(Ctrl && Ctrl->Str); return Ctrl->Str != 0; } void ResDialog::Res_Attach(ResObject *Obj, ResObject *Parent) { if (Obj && Parent) { ResDialogCtrl *Ctrl = dynamic_cast((ResDialogCtrl*)Obj); ResDialogCtrl *Par = dynamic_cast((ResDialogCtrl*)Parent); if (Ctrl && Par) { Par->AttachCtrl(Ctrl); } } } bool ResDialog::Res_GetChildren(ResObject *Obj, List *l, bool Deep) { bool Status = false; if (Obj) { ResDialogCtrl *Ctrl = dynamic_cast((ResDialogCtrl*)Obj); if (Ctrl) { Status = true; List Child; Ctrl->ListChildren(Child, Deep); for (ResDialogCtrl *o = Child.First(); o; o = Child.Next()) { l->Insert(o); } } } return Status; } void ResDialog::Res_Append(ResObject *Obj, ResObject *Parent) { if (Obj && Parent) { CtrlTabs *Tabs = dynamic_cast(Obj); CtrlTab *Tab = dynamic_cast(Parent); if (Tabs && Tab) { Tab->SetParent(Tabs); Tabs->Tabs.Insert(Tab); if (Tabs->Tabs.Length() == 1) { Tabs->FromTab(); } return; } CtrlList *Lst = dynamic_cast(Obj); ListCol *Col = dynamic_cast(Parent); if (Lst && Col) { Lst->Cols.Insert(Col); return; } } } bool ResDialog::Res_GetItems(ResObject *Obj, List *l) { if (Obj && l) { CtrlTabs *Tabs = dynamic_cast(Obj); if (Tabs) { for ( CtrlTab *Tab = Tabs->Tabs.First(); Tab; Tab = Tabs->Tabs.Next()) { l->Insert(Tab); } return true; } CtrlList *Lst = dynamic_cast(Obj); if (Lst) { for ( ListCol *Col = Lst->Cols.First(); Col; Col = Lst->Cols.Next()) { l->Insert(Col); } return true; } } return false; } void ResDialog::Create(GXmlTag *load, SerialiseContext *Ctx) { CtrlDlg *Dlg = new CtrlDlg(this, load); if (Dlg) { GRect r = DlgPos; r.Offset(GOOBER_BORDER, GOOBER_BORDER); Children.Insert(Dlg); Dlg->SetParent(this); if (load) { if (Ctx) Read(load, *Ctx); else LgiAssert(0); } else { Dlg->ResDialogCtrl::SetPos(r); if (Dlg->Str) Dlg->Str->Set("Dialog"); } } } void ResDialog::Delete() { // Deselect the dialog ctrl OnDeselect(dynamic_cast(Children.First())); // Delete selected controls for (ResDialogCtrl *c = Selection.First(); c; c = Selection.First()) { c->View()->Detach(); DeleteObj(c); } // Repaint GView::Invalidate(); } bool IsChild(ResDialogCtrl *Parent, ResDialogCtrl *Child) { if (Parent && Child) { for ( ResDialogCtrl *c = Child->ParentCtrl(); c; c = c->ParentCtrl()) { if (c == Parent) { return true; } } } return false; } void GetTopCtrls(List &Top, List &Selection) { // all children will automatically be cut as well for (ResDialogCtrl *c = Selection.First(); c; c = Selection.Next()) { // is c a child of an item already in Top? bool Ignore = false; for (ResDialogCtrl *p = Top.First(); p; p = Top.Next()) { if (IsChild(p, c)) { Ignore = true; break; } } if (!Ignore) { // is not a child Top.Insert(c); } } } void ResDialog::Copy(bool Delete) { bool Status = false; // Deselect the dialog... can't cut that OnDeselect(dynamic_cast(Children.First())); // Get top level list List Top; GetTopCtrls(Top, Selection); // ok we have a top level list of ctrls // write them to the file List All; ResDialogCtrl *c; GXmlTag *Root = new GXmlTag("Resources"); if (Root) { if (Delete) { // remove selection from UI AppWindow->OnObjSelect(0); } // write the string resources first for (c = Top.First(); c; c = Top.Next()) { All.Insert(c); c->ListChildren(All, true); } // write the strings out at the top of the block // so that we can reference them from the objects // below. SerialiseContext Ctx; for (c = All.First(); c; c = All.Next()) { // Write the string out GXmlTag *t = new GXmlTag; if (t && c->Str->Write(t, Ctx)) { Root->InsertTag(t); } else { DeleteObj(t); } } // write the objects themselves for (c = Top.First(); c; c = Top.Next()) { GXmlTag *t = new GXmlTag; if (t && Res_Write(c, t)) { Root->InsertTag(t); } else { DeleteObj(t); } } // Read the file in and copy to the clipboard GStringPipe Xml; GXmlTree Tree; if (Tree.Write(Root, &Xml)) { char *s = Xml.NewStr(); { GClipBoard Clip(Ui); char16 *w = Utf8ToWide(s); Clip.TextW(w); Status = Clip.Text(s, false); DeleteObj(w); } DeleteArray(s); } DeleteObj(Root); if (Delete && Status) { // delete them for (c = Selection.First(); c; c = Selection.First()) { c->View()->Detach(); Selection.Delete(c); DeleteObj(c); } } // Repaint GView::Invalidate(); } } class StringId { public: ResString *Str; int OldRef; int NewRef; }; void RemapAllRefs(GXmlTag *t, List &Strs) { char *RefStr; if ((RefStr = t->GetAttr("Ref"))) { int r = atoi(RefStr); for (StringId *i=Strs.First(); i; i=Strs.Next()) { if (i->OldRef == r) { // this string ref is to be remapped char Buf[32]; sprintf(Buf, "%i", i->NewRef); t->SetAttr("Ref", Buf); // delete the string ref map // it's not needed anymore Strs.Delete(i); DeleteObj(i); // leave the loop RefStr = 0; break; } } if (RefStr) { // if this assert failes then no map for this // string was found. every incomming string needs // to be remapped LgiAssert(0); } } for (GXmlTag *c = t->Children.First(); c; c = t->Children.Next()) { RemapAllRefs(c, Strs); } } void ResDialog::Paste() { // Get the clipboard data char *Mem = 0; char *Data = 0; { GClipBoard Clip(Ui); char16 *w = Clip.TextW(); if (w) Data = Mem = WideToUtf8(w); else Data = Clip.Text(); } if (Data) { ResDialogCtrl *Container = 0; // Find a container to plonk the controls in ResDialogCtrl *c = Selection.First(); if (c && c->IsContainer()) { Container = c; } if (!Container) { // Otherwise just use the dialog as the container Container = dynamic_cast(Children.First()); } if (Container) { // remap list List Strings; int NextRef = 0; // Deselect everything OnSelect(NULL); // Parse the data List NewStrs; GXmlTree Tree; GStringPipe p; p.Push(Data); // Create the new controls, strings first // that way we can setup the remapping properly to avoid // string ref duplicates GXmlTag Root; if (Tree.Read(&Root, &p, 0)) { GXmlTag *t; for (t = Root.Children.First(); t; t = Root.Children.Next()) { if (t->IsTag("string")) { // string tag LgiAssert(Symbols); ResString *Str = Symbols->CreateStr(); SerialiseContext Ctx; if (Str && Str->Read(t, Ctx)) { // setup remap object, so that we can make all the strings // unique StringId *Id = new StringId; LgiAssert(Id); Id->Str = Str; Id->OldRef = Str->GetRef(); NextRef = Str->SetRef(Id->NewRef = AppWindow->GetUniqueStrRef(NextRef + 1)); Strings.Insert(Id); // insert out new string NewStrs.Insert(Str); } else { break; } } else { // object tag // all strings should have been processed by the time // we get in here. We remap the incomming string refs to // unique values so that we don't get conflicts later. RemapAllRefs(t, Strings); } } // load all the objects now List NewCtrls; for (t = Root.Children.First(); t; t = Root.Children.Next()) { if (!t->IsTag("string")) { // object (not string) CreateSymbols = false; ResDialogCtrl *Ctrl = dynamic_cast(CreateCtrl(t)); CreateSymbols = true; ResReadCtx Ctx; if (Ctrl && Res_Read(Ctrl, t, Ctx)) { NewCtrls.Insert(Ctrl); } else { break; } } } // calculate the new control set's dimensions so we // can paste them in at the top of the container ResDialogCtrl *c = NewCtrls.First(); if (c) { GRect All = c->View()->GetPos(); while ((c = NewCtrls.Next())) { All.Union(&c->View()->GetPos()); } // now paste in the controls for (c = NewCtrls.First(); c; c = NewCtrls.Next()) { GRect *Preference = Container->GetPasteArea(); GRect p = c->View()->GetPos(); p.Offset(-All.x1, -All.y1); p.Offset(Preference ? Preference->x1 : Container->Client.x1 + GRID_X, Preference ? Preference->y1 : Container->Client.y1 + GRID_Y); c->SetPos(p); Container->AttachCtrl(c, &c->View()->GetPos()); OnSelect(c, false); } // reset parent size to fit GRect cp = Container->View()->GetPos(); Container->SetPos(cp, true); } // Deduplicate all these new strings for (ResString *s = NewStrs.First(); s; s = NewStrs.Next()) { s->UnDuplicate(); } } // Repaint GView::Invalidate(); } } DeleteArray(Mem); } void ResDialog::SnapPoint(GdcPt2 *p, ResDialogCtrl *From) { ResDialogCtrl *Ctrl = dynamic_cast(Children.First()); if (p && Ctrl) { int Ox = 0; // -Ctrl->Client.x1; int Oy = 0; // -Ctrl->Client.y1; GView *Parent = dynamic_cast(Ctrl); if (From) { for (GViewI *w = From->View(); w && w != Parent; w = w->GetParent()) { Ox += w->GetPos().x1; Oy += w->GetPos().y1; } } p->x += Ox; p->y += Oy; p->x = ((p->x + (GRID_X / 2)) / GRID_X * GRID_X); p->y = ((p->y + (GRID_X / 2)) / GRID_X * GRID_X); p->x -= Ox; p->y -= Oy; } } void ResDialog::SnapRect(GRect *r, ResDialogCtrl *From) { ResDialogCtrl *Ctrl = dynamic_cast(Children.First()); if (r && Ctrl) { int Ox = 0; // -Ctrl->Client.x1; int Oy = 0; // -Ctrl->Client.y1; GView *Parent = dynamic_cast(Ctrl); for (GViewI *w = From->View(); w && w != Parent; w = w->GetParent()) { Ox += w->GetPos().x1; Oy += w->GetPos().y1; } r->Normal(); r->Offset(Ox, Oy); r->x1 = ((r->x1 + (GRID_X / 2)) / GRID_X * GRID_X); r->y1 = ((r->y1 + (GRID_X / 2)) / GRID_X * GRID_X); r->x2 = ((r->x2 + (GRID_X / 2)) / GRID_X * GRID_X) - 1; r->y2 = ((r->y2 + (GRID_X / 2)) / GRID_X * GRID_X) - 1; r->Offset(-Ox, -Oy); } } bool ResDialog::IsDraging() { return DragGoober >= 0; } int ResDialog::CurrentTool() { return (Ui) ? Ui->CurrentTool() : 0; } void ResDialog::MoveSelection(int Dx, int Dy) { ResDialogCtrl *w = Selection.First(); if (w) { ResDialogCtrl *Parent = w->ParentCtrl(); if (Parent) { // find dimensions of group GRect All = w->View()->GetPos(); for (; w; w = Selection.Next()) { All.Union(&w->View()->GetPos()); } // limit the move to the top-left corner of the parent's client GRect ParentClient = Parent->Client; if (dynamic_cast(Parent)) ParentClient.ZOff(-ParentClient.x1, -ParentClient.y1); if (All.x1 + Dx < ParentClient.x1) { Dx = ParentClient.x1 - All.x1; } if (All.y1 + Dy < ParentClient.y1) { Dy = ParentClient.y1 - All.y1; } // move the ctrls GRegion Update; for (w = Selection.First(); w; w = Selection.Next()) { GRect Old = w->View()->GetPos(); GRect New = Old; New.Offset(Dx, Dy); // optionally limit the move to the containers bounds GRect *b = w->ParentCtrl()->GetChildArea(w); if (b) { if (New.x1 < b->x1) { New.Offset(b->x1 - New.x1, 0); } else if (New.x2 > b->x2) { New.Offset(b->x2 - New.x2, 0); } if (New.y1 < b->y1) { New.Offset(0, b->y1 - New.y1); } else if (New.y2 > b->y2) { New.Offset(0, b->y2 - New.y2); } } GRect Up = w->AbsPos(); Up.Size(-GOOBER_BORDER, -GOOBER_BORDER); Update.Union(&Up); w->SetPos(New, false); Up = w->AbsPos(); Up.Size(-GOOBER_BORDER, -GOOBER_BORDER); Update.Union(&Up); } Invalidate(&Update); } } } void ResDialog::SelectCtrl(ResDialogCtrl *c) { Selection.Empty(); if (c) { bool IsMine = false; for (GViewI *p = c->View(); p; p = p->GetParent()) { if ((GViewI*)this == p) { IsMine = true; break; } } if (IsMine) { Selection.Insert(c); // int TabIdx = -1; ResDialogCtrl *Prev = 0; for (GViewI *p = c->View()->GetParent(); p; p = p->GetParent()) { ResDialogCtrl *c = dynamic_cast(p); if (c) { c->ShowMe(Prev); Prev = c; } else break; } } else { printf("%s:%i - Ctrl doesn't belong to me.\n", __FILE__, __LINE__); } } else { printf("Selecting '0'\n"); } Invalidate(); if (AppWindow) { AppWindow->OnObjSelect(c); } } void ResDialog::SelectNone() { Selection.Empty(); Invalidate(); if (AppWindow) { AppWindow->OnObjSelect(0); } } void ResDialog::SelectRect(ResDialogCtrl *Parent, GRect *r, bool ClearPrev) { if (ClearPrev) { Selection.Empty(); } if (Parent && r) { GAutoPtrIt(Parent->View()->IterateViews()); for (GViewI *c = It->First(); c; c = It->Next()) { if (c->GetPos().Overlap(r)) { ResDialogCtrl *Ctrl = dynamic_cast(c); if (Ctrl) { if (Selection.IndexOf(Ctrl) >= 0) { Selection.Delete(Ctrl); } else { Selection.Insert(Ctrl); } } } } } Invalidate(); if (AppWindow) { AppWindow->OnObjSelect((Selection.Length() == 1) ? Selection.First() : 0); } } bool ResDialog::IsSelected(ResDialogCtrl *Ctrl) { return Selection.IndexOf(Ctrl) >= 0; } void ResDialog::OnSelect(ResDialogCtrl *Wnd, bool ClearPrev) { if (ClearPrev) { Selection.Empty(); } if (Wnd) { ResDialogCtrl *Ctrl = dynamic_cast(Wnd); if (Ctrl) { if (!Selection.HasItem(Ctrl)) { Selection.Insert(Ctrl); } if (Ui) { Ui->SelectTool(UI_CURSOR); } Invalidate(); } if (AppWindow) { AppWindow->OnObjSelect((Selection.Length() == 1) ? Selection.First() : 0); } } } void ResDialog::OnDeselect(ResDialogCtrl *Wnd) { if (Selection.HasItem(Wnd)) { Selection.Delete(Wnd); AppWindow->OnObjSelect(0); } } ResDialogCtrl *ResDialog::CreateCtrl(GXmlTag *t) { if (t) { for (LgiObjectName *o = NameMap; o->Type; o++) { if (t->IsTag(o->ResourceName)) { return CreateCtrl(o->Type, t); } } } return 0; } ResDialogCtrl *ResDialog::CreateCtrl(int Tool, GXmlTag *load) { ResDialogCtrl *Ctrl = 0; switch (Tool) { case UI_DIALOG: { Ctrl = new CtrlDlg(this, load); break; } case UI_TABLE: { Ctrl = new CtrlTable(this, load); break; } case UI_TEXT: { Ctrl = new CtrlText(this, load); if (Ctrl && Ctrl->Str && !load) { Ctrl->Str->Set("Some text"); } break; } case UI_EDITBOX: { Ctrl = new CtrlEditbox(this, load); break; } case UI_CHECKBOX: { Ctrl = new CtrlCheckbox(this, load); if (Ctrl && Ctrl->Str && !load) { Ctrl->Str->Set("Checkbox"); } break; } case UI_BUTTON: { Ctrl = new CtrlButton(this, load); if (Ctrl && Ctrl->Str && !load) { static int i = 1; char Text[256]; sprintf(Text, "Button %i", i++); Ctrl->Str->Set(Text); } break; } case UI_GROUP: { Ctrl = new CtrlGroup(this, load); if (Ctrl && Ctrl->Str && !load) { Ctrl->Str->Set("Text"); } break; } case UI_RADIO: { Ctrl = new CtrlRadio(this, load); if (Ctrl && Ctrl->Str && !load) { Ctrl->Str->Set("Radio"); } break; } case UI_TAB: { Ctrl = new CtrlTab(this, load); break; } case UI_TABS: { Ctrl = new CtrlTabs(this, load); break; } case UI_LIST: { Ctrl = new CtrlList(this, load); break; } case UI_COLUMN: { Ctrl = new ListCol(this, load); break; } case UI_COMBO: { Ctrl = new CtrlComboBox(this, load); break; } case UI_TREE: { Ctrl = new CtrlTree(this, load); break; } case UI_BITMAP: { Ctrl = new CtrlBitmap(this, load); break; } case UI_SCROLL_BAR: { Ctrl = new CtrlScrollBar(this, load); break; } case UI_PROGRESS: { Ctrl = new CtrlProgress(this, load); break; } case UI_CUSTOM: { Ctrl = new CtrlCustom(this, load); break; } case UI_CONTROL_TREE: { Ctrl = new CtrlControlTree(this, load); break; } default: { LgiAssert(!"No control factory handler."); break; } } if (Ctrl && Ctrl->Str) { Ctrl->Str->UpdateWnd = Ctrl->View(); } return Ctrl; } GView *ResDialog::CreateUI() { return Ui = new ResDialogUi(this); } void ResDialog::DrawSelection(GSurface *pDC) { if (Selection.Length() == 0) return; // Draw selection for (ResDialogCtrl *Ctrl = Selection.First(); Ctrl; Ctrl = Selection.Next()) { GRect r = Ctrl->AbsPos(); COLOUR s = Rgb24(255, 0, 0); COLOUR c = GetParent()->Focus() ? s : GdcMixColour(s, LC_MED, 0.4); DrawGoobers(pDC, r, Ctrl->Goobers, c); } } void ResDialog::_Paint(GSurface *pDC, GdcPt2 *Offset, GRegion *Update) { #ifndef MAC GScreenDC DC(this); pDC = &DC; #endif ResDialogCtrl *Ctrl = dynamic_cast(Children.First()); if (Ctrl) { GRect c = Ctrl->View()->GetPos(); c.Size(-GOOBER_BORDER, -GOOBER_BORDER); #ifdef MAC GSurface *pMemDC = pDC; if (pMemDC) #else GAutoPtr pMemDC(new GMemDC); if (pMemDC && pMemDC->Create(c.X(), c.Y(), GdcD->GetColourSpace())) #endif { #ifdef MAC GScreenDC *Scr = dynamic_cast(pDC); if (Scr) Scr->PushState(); #endif // Draw client pMemDC->Colour(LC_WORKSPACE, 24); // pMemDC->Colour(Rgb24(0, 128, 0), 24); pMemDC->Rectangle(); // Paint children GRect Pos = Ctrl->View()->GetPos(); pMemDC->SetClient(&Pos); GView::_Paint(pMemDC); pMemDC->SetClient(0); if (GetParent()) { #ifdef MAC if (Scr) Scr->PopState(); #endif // Draw selection DrawSelection(pMemDC); } #ifndef MAC // Put on screen pMemDC->SetOrigin(0, 0); pDC->Blt(0, 0, pMemDC); // Draw other non Mem-DC regions pDC->Colour(LC_WORKSPACE, 24); if (X() > c.X()) { pDC->Rectangle(c.x2 + 1, 0, X()-1, c.y2); } if (Y() > c.Y()) { pDC->Rectangle(0, c.y2 + 1, X()-1, Y()-1); } #endif } else { // error SysFont->Colour(LC_TEXT, LC_WORKSPACE); SysFont->Transparent(false); GDisplayString Ds(SysFont, "Can't create memory bitmap."); Ds.Draw(pDC, 2, 0, &GetClient()); } } else { pDC->Colour(LC_WORKSPACE, 24); pDC->Rectangle(); } } void ResDialog::OnLanguageChange() { if (Ui && Ui->StatusInfo) { GLanguage *l = Symbols->GetLanguage(App()->GetCurLang()->Id); if (l) { char Str[256]; sprintf(Str, "Current Language: %s (Id: %s)", l->Name, l->Id); Ui->StatusInfo->Name(Str); } } } bool ResDialog::OnKey(GKey &k) { if (k.Ctrl()) { switch (k.c16) { case VK_UP: { if (k.Down()) { int Idx = Symbols->GetLangIdx(App()->GetCurLang()->Id); if (Idx > 0) { GLanguage *l = Symbols->GetLanguage(Idx - 1); if (l) { App()->SetCurLang(l); } } Invalidate(); OnLanguageChange(); } return true; break; } case VK_DOWN: { if (k.Down()) { int Idx = Symbols->GetLangIdx(App()->GetCurLang()->Id); if (Idx < Symbols->GetLanguages() - 1) { GLanguage *l = Symbols->GetLanguage(Idx + 1); if (l) { App()->SetCurLang(l); } } Invalidate(); OnLanguageChange(); } return true; break; } } } return false; } void ResDialog::OnMouseClick(GMouse &m) { if (m.Down()) { if (GetParent()) GetParent()->Focus(true); if (m.Left()) { DragGoober = -1; for (ResDialogCtrl *c = Selection.First(); c; c = Selection.Next()) { for (int i=0; i<8; i++) { if (c->Goobers[i].Overlap(m.x, m.y)) { DragGoober = i; DragCtrl = c; break; } } } if (DragGoober >= 0) { DragRgn = DragCtrl->View()->GetPos(); DragX = DragY = 0; int Cap = GooberCaps[DragGoober]; // Lock the dialog to the top left corner if (dynamic_cast(DragCtrl)) { Cap &= ~(RESIZE_X1|RESIZE_Y1); } if (!Cap) { // Can't move the object anyway so abort the drag DragGoober = -1; } else { // Allow movement along the appropriate edge if (Cap & RESIZE_X1) { DragX = &DragRgn.x1; } if (Cap & RESIZE_Y1) { DragY = &DragRgn.y1; } if (Cap & RESIZE_X2) { DragX = &DragRgn.x2; } if (Cap & RESIZE_Y2) { DragY = &DragRgn.y2; } // Remember the offset to the mouse from the egdes if (DragX) DragOx = m.x - *DragX; if (DragY) DragOy = m.y - *DragY; Capture(true); } } } } else { if (DragGoober >= 0) { Capture(false); DragGoober = -1; DragX = 0; DragY = 0; } } } void ResDialog::OnMouseMove(GMouse &m) { if (DragGoober >= 0) { if (DragX) { *DragX = m.x - DragOx; } if (DragY) { *DragY = m.y - DragOy; } // DragRgn in real coords GRect Old = DragCtrl->View()->GetPos(); GRect New = DragRgn; GAutoPtr It(IterateViews()); if (DragCtrl->View() != It->First()) { SnapRect(&New, DragCtrl->ParentCtrl()); } // New now in snapped coords // If that means the dragging control changes then if (New != Old) { GRegion Update; // change everyone else by the same amount for (ResDialogCtrl *c = Selection.First(); c; c = Selection.Next()) { GRect OldPos = c->View()->GetPos(); GRect NewPos = OldPos; NewPos.x1 += New.x1 - Old.x1; NewPos.y1 += New.y1 - Old.y1; NewPos.x2 += New.x2 - Old.x2; NewPos.y2 += New.y2 - Old.y2; GRect Up = c->AbsPos(); Up.Size(-GOOBER_BORDER, -GOOBER_BORDER); Update.Union(&Up); c->SetPos(NewPos); Up = c->AbsPos(); Up.Size(-GOOBER_BORDER, -GOOBER_BORDER); Update.Union(&Up); } Invalidate(&Update); } } } bool ResDialog::Test(ErrorCollection *e) { return true; } bool ResDialog::Read(GXmlTag *t, SerialiseContext &Ctx) { bool Status = false; // turn symbol creation off so that ctrls find their // symbol via Id matching instead of creating their own CreateSymbols = false; ResDialogCtrl *Dlg = dynamic_cast(Children.First()); if (Dlg) { // Load the resource ResReadCtx Ctx; Status = Res_Read(Dlg, t, Ctx); Item->Update(); } CreateSymbols = true; return Status; } void ResDialog::CleanSymbols() { // removed unreferenced dialog strings if (Symbols) { Symbols->RemoveUnReferenced(); } // re-ID duplicate entries ResDialogCtrl *Ctrl = dynamic_cast(Children.First()); if (Ctrl) { // list all the entries List l; Ctrl->ListChildren(l); l.Insert(Ctrl); // insert the dialog too // remove duplicate string entries for (ResDialogCtrl *c = l.First(); c; c = l.Next()) { LgiAssert(c->Str); c->Str->UnDuplicate(); } } // sort list (cause I need to read the file myself) if (Symbols) { Symbols->Sort(); } } bool ResDialog::Write(GXmlTag *t, SerialiseContext &Ctx) { bool Status = false; ResDialogCtrl *Ctrl = dynamic_cast(Children.First()); if (Ctrl) { // duplicates symbols should have been removed before the // strings were written out // so we don't have to do it here. // write the resources out if (Ctrl) { Status = Res_Write(Ctrl, t); } } else LgiTrace("%s:%i - Not a ResDialogCtrl.\n", _FL); return Status; } void ResDialog::OnRightClick(GSubMenu *RClick) { if (RClick) { if (Enabled()) { RClick->AppendSeparator(); if (Type() > 0) { RClick->AppendItem("Dump to C++", IDM_DUMP, true); GSubMenu *Export = RClick->AppendSub("Export to..."); if (Export) { Export->AppendItem("Lgi File", IDM_EXPORT, true); Export->AppendItem("Win32 Resource Script", IDM_EXPORT_WIN32, false); } } } } } const char *TypeOfRes(ResDialogCtrl *Ctrl) { // the default return "GWindow"; } const char *TextOfCtrl(ResDialogCtrl *Ctrl) { static char Buf[256]; switch (Ctrl->GetType()) { // Has text case UI_TEXT: case UI_EDITBOX: case UI_CHECKBOX: case UI_BUTTON: case UI_GROUP: case UI_RADIO: case UI_COMBO: { char *s = Ctrl->Str->Get(); sprintf(Buf, ", \"%s\"", s?s:""); return Buf; } // Not processed case UI_COLUMN: case UI_TAB: { LgiAssert(0); break; } // No text... case UI_BITMAP: case UI_PROGRESS: case UI_TABS: case UI_LIST: case UI_TREE: case UI_SCROLL_BAR: { break; } } return ""; } void OutputCtrl(GStringPipe &Def, GStringPipe &Var, GStringPipe &Inst, ResDialogCtrl *Ctrl, ResDialogCtrl *Parent, int &Index) { char Str[256]; const char *Type = "GView"; for (LgiObjectName *on=NameMap; on->Type; on++) { if (Ctrl->GetType() == on->Type) { Type = on->ObjectName; break; } } if (stricmp(Type, "GDialog")) { if (ValidStr(Ctrl->Str->GetDefine()) && stricmp(Ctrl->Str->GetDefine(), "IDOK") && stricmp(Ctrl->Str->GetDefine(), "IDCANCEL") && stricmp(Ctrl->Str->GetDefine(), "IDC_STATIC")) { char Tab[8]; int Tabs = (32 - strlen(Ctrl->Str->GetDefine()) - 1) / 4; memset(Tab, '\t', Tabs); Tab[Tabs] = 0; sprintf(Str, "#define %s%s%i\n", Ctrl->Str->GetDefine(), Tab, 1000 + Index); Def.Push(Str); } sprintf(Str, "\t%s *Ctrl%i;\n", Type, Index); Var.Push(Str); sprintf(Str, "\tChildren.Insert(Ctrl%i = new %s(%s, %i, %i, %i, %i%s));\n", Index, Type, Ctrl->Str->GetDefine(), Ctrl->View()->GetPos().x1 - 3, Ctrl->View()->GetPos().y1 - 17, Ctrl->View()->X(), Ctrl->View()->Y(), TextOfCtrl(Ctrl)); Inst.Push(Str); CtrlList *List = dynamic_cast(Ctrl); if (List) { // output columns for (ListCol *c=List->Cols.First(); c; c=List->Cols.Next()) { sprintf(Str, "\tCtrl%i->AddColumn(\"%s\", %i);\n", Index, c->Str->Get(), c->X()); Inst.Push(Str); } } Index++; } GAutoPtr It(Ctrl->View()->IterateViews()); for ( ResDialogCtrl *c=dynamic_cast(It->First()); c; c=dynamic_cast(It->Next())) { OutputCtrl(Def, Var, Inst, c, Ctrl, Index); } } void ResDialog::OnCommand(int Cmd) { switch (Cmd) { case IDM_DUMP: { GStringPipe Def, Var, Inst; GStringPipe Buf; char Str[256]; ResDialogCtrl *Dlg = dynamic_cast(Children.First()); if (Dlg) { // List controls int i=0; OutputCtrl(Def, Var, Inst, Dlg, 0, i); // #define's char *Defs = Def.NewStr(); if (Defs) { Buf.Push(Defs); Def.Empty(); } // Class def Buf.Push( "\nclass Dlg : public GDialog\n" "{\n"); // Variables char *Vars = Var.NewStr(); if (Vars) { Buf.Push(Vars); Var.Empty(); } // Member functions Buf.Push( "\n" "public:\n" "\tDlg(GView *Parent);\n" "\t~Dlg();\n" "\n" "\tint OnNotify(GViewI *Ctrl, int Flags);\n" "};\n" "\n"); // Class impl Buf.Push( "Dlg::Dlg(GView *Parent)\n" "{\n" "\tSetParent(Parent);\n"); sprintf(Str, "\tName(\"%s\");\n" "\tGRegion r(0, 0, %i, %i);\n" "\tSetPos(r);\n", Dlg->Str->Get(), Dlg->View()->X(), Dlg->View()->Y()); Buf.Push(Str); Buf.Push("\tMoveToCenter();\n\n"); // Ctrl instancing char *NewCtrls = Inst.NewStr(); if (NewCtrls) { Buf.Push(NewCtrls); Inst.Empty(); } Buf.Push( "}\n" "\n"); // Destructor Buf.Push( "Dlg::~Dlg()\n" "{\n" "}\n" "\n"); // ::OnNotify Buf.Push( "int Dlg::OnNotify(GViewI *Ctrl, int Flags)\n" "{\n" "\tswitch (Ctrl->GetId())\n" "\t{\n" "\t\tcase IDOK:\n" "\t\t{\n" "\t\t\t// Do something here\n" "\t\t\t// fall thru\n" "\t\t}\n" "\t\tcase IDCANCEL:\n" "\t\t{\n" "\t\t\tEndModal(Ctrl->GetId());\n" "\t\t\tbreak;\n" "\t\t}\n" "\t}\n" "\n" "\treturn 0;\n" "}\n"); // Output to clipboard char *Text = Buf.NewStr(); if (Text) { GClipBoard Clip(Ui); Clip.Text(Text); DeleteArray(Text); } } break; } case IDM_EXPORT: { GFileSelect Select; Select.Parent(AppWindow); Select.Type("Text", "*.txt"); if (Select.Save()) { GFile F; if (F.Open(Select.Name(), O_WRITE)) { F.SetSize(0); // Serialize(F, true); } else { LgiMsg(AppWindow, "Couldn't open file for writing."); } } break; } case IDM_EXPORT_WIN32: { break; } } } int ResDialog::OnCommand(int Cmd, int Event, OsView hWnd) { switch (Cmd) { /* case IDM_SET_LANG: { Symbols->SetCurrent(); OnSelect(Selection.First()); Invalidate(); OnLanguageChange(); break; } */ case IDM_TAB_ORDER: { ResDialogCtrl *Top = 0; if (Selection.Length() == 1 && Selection.First()->IsContainer()) { Top = Selection.First(); } if (!Top) { Top = dynamic_cast(Children.First()); } if (Top) { TabOrder Dlg(this, Top); } break; } } return 0; } ResString *ResDialog::CreateSymbol() { return (Symbols) ? Symbols->CreateStr() : 0; } //////////////////////////////////////////////////////////////////// ResDialogUi::ResDialogUi(ResDialog *Res) { Dialog = Res; Tools = 0; Status = 0; StatusInfo = 0; Name("ResDialogUi"); if (Res) { Res->OnSelect(Res->Selection.First()); ShortCutView *scv = Res->App()->GetShortCutView(); if (scv) scv->OnDialogChange(Res); } } ResDialogUi::~ResDialogUi() { if (Dialog) { ShortCutView *scv = Dialog->App()->GetShortCutView(); if (scv) scv->OnDialogChange(NULL); Dialog->Ui = 0; } } void ResDialogUi::OnPaint(GSurface *pDC) { GRegion Client(0, 0, X()-1, Y()-1); for (GViewI *w = Children.First(); w; w = Children.Next()) { GRect r = w->GetPos(); Client.Subtract(&r); } pDC->Colour(LC_MED, 24); for (GRect *r = Client.First(); r; r = Client.Next()) { pDC->Rectangle(r); } } void ResDialogUi::PourAll() { GRegion Client(GetClient()); GRegion Update; for (GViewI *v = Children.First(); v; v = Children.Next()) { GRect OldPos = v->GetPos(); Update.Union(&OldPos); if (v->Pour(Client)) { if (!v->Visible()) { v->Visible(true); } if (OldPos != v->GetPos()) { // position has changed update... v->Invalidate(); } Client.Subtract(&v->GetPos()); Update.Subtract(&v->GetPos()); } else { // make the view not visible v->Visible(false); } } for (int i=0; iSetBitmap(FileName, 16, 16)) { Tools->Attach(this); Tools->AppendButton("Cursor", 0, TBT_RADIO); for (LgiObjectName *o=NameMap; o->Type; o++) { if (o->ToolbarBtn) { Tools->AppendButton(o->ObjectName, o->Type, TBT_RADIO); } } Tools->AppendSeparator(); // Tools->AppendButton("Change language", IDM_SET_LANG, TBT_PUSH); Tools->AppendButton("Tab Order", IDM_TAB_ORDER, TBT_PUSH, true, 17); } else { DeleteObj(Tools); } DeleteArray(FileName); } Status = new GStatusBar; if (Status) { Status->Attach(this); StatusInfo = Status->AppendPane("", 2); } ResFrame *Frame = new ResFrame(Dialog); if (Frame) { Frame->Attach(this); } PourAll(); } int ResDialogUi::CurrentTool() { if (Tools) { int i=0; GAutoPtr It(Tools->IterateViews()); for (GViewI *w = It->First(); w; w = It->Next(), i++) { GToolButton *But = dynamic_cast(w); if (But) { if (But->Value()) { return But->GetId(); } } } } return -1; } void ResDialogUi::SelectTool(int i) { if (Tools) { GAutoPtr It(Tools->IterateViews()); GViewI *w = (*It)[i]; if (w) { GToolButton *But = dynamic_cast(w); if (But) { But->Value(true); } } } } GMessage::Result ResDialogUi::OnEvent(GMessage *Msg) { switch (MsgCode(Msg)) { case M_COMMAND: { Dialog->OnCommand(MsgA(Msg)&0xffff, MsgA(Msg)>>16, (OsView) MsgB(Msg)); break; } case M_DESCRIBE: { char *Text = (char*) MsgB(Msg); if (Text) { StatusInfo->Name(Text); } break; } } return GView::OnEvent(Msg); } diff --git a/include/common/GFontCache.h b/include/common/GFontCache.h --- a/include/common/GFontCache.h +++ b/include/common/GFontCache.h @@ -1,140 +1,152 @@ #ifndef _GFONTCACHE_H_ #define _GFONTCACHE_H_ class GFontCache { GFont *DefaultFont; GArray Fonts; LHashTbl, GString> FontName; public: /// Constructor for font cache GFontCache ( /// This is an externally owned default font... or optionally /// NULL if there is no default. GFont *DefFnt = NULL ) { DefaultFont = DefFnt; } ~GFontCache() { Fonts.DeleteObjects(); } GFont *GetDefaultFont() { return DefaultFont; } void SetDefaultFont(GFont *Def) { DefaultFont = Def; } /// This defines a text label that links to a font-face. On multi-platform /// software sometimes you need to have one CSS font Label that links to /// different available fonts. void DefineFontName(const char *Label, const char *FontFace) { GString s = FontName.Find(Label); if (!s.Get()) FontName.Add(Label, GString(FontFace)); } GFont *AddFont( const char *Face, GCss::Len Size, GCss::FontWeightType Weight, GCss::FontStyleType Style, GCss::TextDecorType Decor) { // Matching existing fonts... for (unsigned i=0; iFace() && Face && !_stricmp(f->Face(), Face) && f->Size() == Size && f->Bold() == (Weight == GCss::FontWeightBold) && f->Italic() == (Style == GCss::FontStyleItalic) && f->Underline() == (Decor == GCss::TextDecorUnderline) ) return f; } // No matching font... create a new one GFont *f = new GFont; if (f) { f->Bold(Weight == GCss::FontWeightBold); f->Italic(Style == GCss::FontStyleItalic); f->Underline(Decor == GCss::TextDecorUnderline); - if (!f->Create(Face, Size)) + auto Sz = Size; + if (Sz.Type == GCss::SizeLarger) + { + Sz = SysFont->Size(); + Sz.Value++; + } + else if (Sz.Type == GCss::SizeSmaller) + { + Sz = SysFont->Size(); + Sz.Value--; + } + + if (!f->Create(Face, Sz)) { LgiAssert(0); DeleteObj(f); return NULL; } Fonts.Add(f); } return f; } GFont *GetFont(GCss *Style) { if (!Style || !DefaultFont) return DefaultFont; GCss::StringsDef Fam = Style->FontFamily(); bool FamHasDefFace = false; for (unsigned i=0; iFace(), Fam[i]); } } if (!FamHasDefFace) Fam.Add(NewStr(DefaultFont->Face())); GCss::Len Sz = Style->FontSize(); if (!Sz.IsValid()) Sz = DefaultFont->Size(); GCss::FontWeightType Weight = Style->FontWeight(); GCss::FontWeightType DefaultWeight = DefaultFont && DefaultFont->Bold() ? GCss::FontWeightBold : GCss::FontWeightNormal; GCss::FontStyleType FontStyle = Style->FontStyle(); GCss::TextDecorType Decor = Style->TextDecoration(); GFont *f = NULL; for (unsigned i = 0; !f && i 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/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/Gdc2.h b/include/common/Gdc2.h --- a/include/common/Gdc2.h +++ b/include/common/Gdc2.h @@ -1,1399 +1,1410 @@ /** \file \author Matthew Allen \date 20/2/1997 \brief GDC v2.xx header */ #ifndef __GDC2_H_ #define __GDC2_H_ #include // sub-system headers #include "LgiOsDefs.h" // Platform specific #include "LgiInc.h" #include "LgiClass.h" #include "Progress.h" // Xp #include "GFile.h" // Platform specific #include "GMem.h" // Platform specific #include "Core.h" // Platform specific #include "GContainers.h" #include "GCapabilities.h" #include "GRefCount.h" #include "GPalette.h" // Alpha Blting #ifdef WIN32 #include "wingdi.h" #endif #ifndef AC_SRC_OVER #define AC_SRC_OVER 0 #endif #ifndef AC_SRC_ALPHA #define AC_SRC_ALPHA 1 #endif #include "GLibrary.h" // Defines /// The default gamma curve (none) used by the gamma LUT GdcDevice::GetGamma #define LGI_DEFAULT_GAMMA 1.0 #ifndef LGI_PI /// The value of PI #define LGI_PI 3.141592654 #endif /// Converts degrees to radians #define LGI_DegToRad(i) ((i)*LGI_PI/180) /// Converts radians to degrees #define LGI_RadToDeg(i) ((i)*180/LGI_PI) #if defined(WIN32) && !defined(_WIN64) /// Use intel assembly instructions, comment out for porting #define GDC_USE_ASM #endif /// Blending mode: overwrite #define GDC_SET 0 /// Blending mode: bitwise AND with background #define GDC_AND 1 /// Blending mode: bitwise OR with background #define GDC_OR 2 /// Blending mode: bitwise XOR with background #define GDC_XOR 3 /// Blending mode: alpha blend with background #define GDC_ALPHA 4 #define GDC_REMAP 5 #define GDC_MAXOP 6 #define GDC_CACHE_SIZE 4 // Channel types #define GDCC_MONO 0 #define GDCC_GREY 1 #define GDCC_INDEX 2 #define GDCC_R 3 #define GDCC_G 4 #define GDCC_B 5 #define GDCC_ALPHA 6 // Native data formats #define GDC_8BIT 0 #define GDC_16BIT 1 #define GDC_24BIT 2 #define GDC_32BIT 3 #define GDC_MAXFMT 4 // Colour spaces #define GDC_8I 0 // 8 bit paletted #define GDC_5R6G5B 1 // 16 bit #define GDC_8B8G8B 2 // 24 bit #define GDC_8A8R8G8B 3 // 32 bit #define GDC_MAXSPACE 4 // Update types #define GDC_PAL_CHANGE 0x1 #define GDC_BITS_CHANGE 0x2 // Flood fill types /// GSurface::FloodFill to a different colour #define GDC_FILL_TO_DIFFERENT 0 /// GSurface::FloodFill to a certain colour #define GDC_FILL_TO_BORDER 1 /// GSurface::FloodFill while colour is near to the seed colour #define GDC_FILL_NEAR 2 // Gdc options /// Used in GdcApp8Set::Blt when doing colour depth reduction to 8 bit. #define GDC_REDUCE_TYPE 0 /// No conversion #define REDUCE_NONE 0 /// Nearest colour pixels #define REDUCE_NEAREST 1 /// Halftone the pixels #define REDUCE_HALFTONE 2 /// Error diffuse the pixels #define REDUCE_ERROR_DIFFUSION 3 /// Not used. #define REDUCE_DL1 4 /// Not used. #define GDC_HALFTONE_BASE_INDEX 1 /// When in 8-bit defined the behaviour of GdcDevice::GetColour #define GDC_PALETTE_TYPE 2 /// Allocate colours from the palette #define PALTYPE_ALLOC 0 /// Use an RGB cube #define PALTYPE_RGB_CUBE 1 /// Use a HSL palette #define PALTYPE_HSL 2 /// Converts images to the specified bit-depth on load, does nothing if 0 #define GDC_PROMOTE_ON_LOAD 3 #define GDC_MAX_OPTION 4 // GSurface Flags #define GDC_ON_SCREEN 0x0002 #define GDC_ALPHA_CHANNEL 0x0004 #define GDC_UPDATED_PALETTE 0x0008 #define GDC_CAPTURE_CURSOR 0x0010 #define GDC_OWN_APPLICATOR 0x0020 #define GDC_CACHED_APPLICATOR 0x0040 #define GDC_OWN_PALETTE 0x0080 #define GDC_DRAW_ON_ALPHA 0x0100 // Region types #define GDC_RGN_NONE 0 // No clipping #define GDC_RGN_SIMPLE 1 // Single rectangle #define GDC_RGN_COMPLEX 2 // Many rectangles // Error codes #define GDCERR_NONE 0 #define GDCERR_ERROR 1 #define GDCERR_CANT_SET_SCAN_WIDTH 2 #define GDC_INVALIDMODE -1 // Font display flags #define GDCFNT_TRANSPARENT 0x0001 // not set - SOLID #define GDCFNT_UNDERLINE 0x0002 // Palette file types #define GDCPAL_JASC 1 #define GDCPAL_MICROSOFT 2 // Misc #define BMPWIDTH(bits) ((((bits)+31)/32)<<2) #include "GColourSpace.h" // Look up tables #define Div255Lut (GdcDevice::GetInst()->GetDiv255()) // Classes class GFilter; class GSurface; #include "GRect.h" // #include "GFont.h" #include "GPoint.h" #include "GColour.h" class LgiClass GBmpMem { public: enum GdcMemFlags { BmpOwnMemory = 0x1, BmpPreMulAlpha = 0x2, }; uchar *Base; int x, y; ssize_t Line; GColourSpace Cs; int Flags; GBmpMem(); ~GBmpMem(); bool PreMul() { return (Flags & BmpPreMulAlpha) != 0; } bool PreMul(bool set) { if (set) Flags |= BmpPreMulAlpha; else Flags &= ~BmpPreMulAlpha; return PreMul(); } bool OwnMem() { return (Flags & BmpOwnMemory) != 0; } bool OwnMem(bool set) { if (set) Flags |= BmpOwnMemory; else Flags &= ~BmpOwnMemory; return OwnMem(); } int GetBits() { return GColourSpaceToBits(Cs); } void GetMemoryExtents(uchar *&Start, uchar *&End) { if (Line < 0) { Start = Base + (y * Line); End = Base + Line; } else { Start = Base; End = Base + (y * Line); } } bool Overlap(GBmpMem *Mem) { uchar *ThisStart, *ThisEnd; GetMemoryExtents(ThisStart, ThisEnd); uchar *MemStart, *MemEnd; GetMemoryExtents(MemStart, MemEnd); if (ThisEnd < MemStart) return false; if (ThisStart > MemEnd) return false; return true; } }; #define GAPP_ALPHA_A 1 #define GAPP_ALPHA_PAL 2 #define GAPP_BACKGROUND 3 #define GAPP_ANGLE 4 #define GAPP_BOUNDS 5 /// \brief Class to draw onto a memory bitmap /// /// This class assumes that all clipping is done by the layer above. /// It can then implement very simple loops to do the work of filling /// pixels class LgiClass GApplicator { protected: GBmpMem *Dest; GBmpMem *Alpha; GPalette *Pal; int Op; public: union { COLOUR c; // main colour System24BitPixel p24; System32BitPixel p32; }; GApplicator() { c = 0; Dest = NULL; Alpha = NULL; Pal = NULL; } GApplicator(COLOUR Colour) { c = Colour; } virtual ~GApplicator() { } virtual const char *GetClass() { return "GApplicator"; } /// Get a parameter virtual int GetVar(int Var) { return 0; } /// Set a parameter virtual int SetVar(int Var, NativeInt Value) { return 0; } GColourSpace GetColourSpace() { return Dest ? Dest->Cs : CsNone; } /// Sets the operator void SetOp(int o, int Param = -1) { Op = o; } /// Gets the operator int GetOp() { return Op; } /// Gets the bit depth int GetBits() { return (Dest) ? GColourSpaceToBits(Dest->Cs) : 0; } /// Gets the flags in operation int GetFlags() { return (Dest) ? Dest->Flags : 0; } /// Gets the palette GPalette *GetPal() { return Pal; } /// Sets the bitmap to write onto virtual bool SetSurface(GBmpMem *d, GPalette *p = 0, GBmpMem *a = 0) = 0; // sets Dest, returns FALSE on error /// Sets the current position to an x,y virtual void SetPtr(int x, int y) = 0; // calculates Ptr from x, y /// Moves the current position one pixel left virtual void IncX() = 0; /// Moves the current position one scanline down virtual void IncY() = 0; /// Offset the current position virtual void IncPtr(int X, int Y) = 0; /// Sets the pixel at the current location with the current colour virtual void Set() = 0; /// Gets the colour of the pixel at the current location virtual COLOUR Get() = 0; /// Draws a vertical line from the current position down 'height' scanlines virtual void VLine(int height) = 0; /// Draws a rectangle starting from the current position, 'x' pixels across and 'y' pixels down virtual void Rectangle(int x, int y) = 0; /// Copies bitmap data to the current position virtual bool Blt(GBmpMem *Src, GPalette *SPal, GBmpMem *SrcAlpha = 0) = 0; }; /// Creates applications from parameters. class LgiClass GApplicatorFactory { public: GApplicatorFactory(); virtual ~GApplicatorFactory(); /// Find the application factory and create the appropriate object. static GApplicator *NewApp(GColourSpace Cs, int Op); virtual GApplicator *Create(GColourSpace Cs, int Op) = 0; }; class LgiClass GApp15 : public GApplicatorFactory { public: GApplicator *Create(GColourSpace Cs, int Op); }; class LgiClass GApp16 : public GApplicatorFactory { public: GApplicator *Create(GColourSpace Cs, int Op); }; class LgiClass GApp24 : public GApplicatorFactory { public: GApplicator *Create(GColourSpace Cs, int Op); }; class LgiClass GApp32 : public GApplicatorFactory { public: GApplicator *Create(GColourSpace Cs, int Op); }; class LgiClass GApp8 : public GApplicatorFactory { public: GApplicator *Create(GColourSpace Cs, int Op); }; class GAlphaFactory : public GApplicatorFactory { public: GApplicator *Create(GColourSpace Cs, int Op); }; #define OrgX(x) x -= OriginX #define OrgY(y) y -= OriginY #define OrgXy(x, y) x -= OriginX; y -= OriginY #define OrgPt(p) p.x -= OriginX; p.y -= OriginY #define OrgRgn(r) r.Offset(-OriginX, -OriginY) /// Base class API for graphics operations class LgiClass GSurface : public GRefCount, public GDom { friend class GFilter; friend class GView; friend class GWindow; friend class GVariant; friend class GRegionClipDC; void Init(); protected: int Flags; int PrevOp; GRect Clip; GColourSpace ColourSpace; GBmpMem *pMem; GSurface *pAlphaDC; GPalette *pPalette; GApplicator *pApp; GApplicator *pAppCache[GDC_CACHE_SIZE]; int OriginX, OriginY; // Protected functions GApplicator *CreateApplicator(int Op, GColourSpace Cs = CsNone); uint32_t LineBits; uint32_t LineMask; uint32_t LineReset; #if WINNATIVE OsPainter hDC; OsBitmap hBmp; #elif defined __GTK_H__ OsPainter Cairo; #endif public: GSurface(); GSurface(GSurface *pDC); virtual ~GSurface(); // Win32 #if defined(__GTK_H__) /// Gets the drawable size, regardless of clipping or client rect virtual GdcPt2 GetSize() { GdcPt2 p; return p; } virtual Gtk::GtkPrintContext *GetPrintContext() { return NULL; } #elif defined(WINNATIVE) virtual HDC StartDC() { return hDC; } virtual void EndDC() {} #elif defined MAC #ifdef COCOA #else virtual CGColorSpaceRef GetColourSpaceRef() { return 0; } #endif #endif virtual OsBitmap GetBitmap(); virtual OsPainter Handle(); virtual void SetClient(GRect *c) {} virtual bool GetClient(GRect *c) { return false; } // Creation enum SurfaceCreateFlags { SurfaceCreateNone, SurfaceRequireNative, SurfaceRequireExactCs, }; virtual bool Create(int x, int y, GColourSpace Cs, int Flags = SurfaceCreateNone) { return false; } virtual void Update(int Flags) {} // Alpha channel /// Returns true if this Surface has an alpha channel virtual bool HasAlpha() { return pAlphaDC != 0; } /// Creates or destroys the alpha channel for this surface virtual bool HasAlpha(bool b); /// Returns true if we are drawing on the alpha channel bool DrawOnAlpha() { return ((Flags & GDC_DRAW_ON_ALPHA) != 0); } /// True if you want to edit the alpha channel rather than the colour bits bool DrawOnAlpha(bool Draw); /// Returns the surface of the alpha channel. GSurface *AlphaDC() { return pAlphaDC; } /// Lowers the alpha of the whole image to Alpha/255.0. /// Only works on bitmaps with an alpha channel (i.e. CsRgba32 or it's variants) bool SetConstantAlpha(uint8_t Alpha); // Create sub-images (that reference the memory of this object) GSurface *SubImage(GRect r); GSurface *SubImage(int x1, int y1, int x2, int y2) { GRect r(x1, y1, x2, y2); return SubImage(r); } // Applicator virtual bool Applicator(GApplicator *pApp); virtual GApplicator *Applicator(); // Palette virtual GPalette *Palette(); virtual void Palette(GPalette *pPal, bool bOwnIt = true); // Clip region virtual GRect ClipRgn(GRect *Rgn); virtual GRect ClipRgn(); /// Gets the current colour virtual COLOUR Colour() { return pApp->c; } /// Sets the current colour virtual COLOUR Colour ( /// The new colour COLOUR c, /// The bit depth of the new colour or 0 to indicate the depth is the same as the current Surface int Bits = 0 ); /// Sets the current colour virtual GColour Colour ( /// The new colour GColour c ); /// Gets the current blending mode in operation virtual int Op() { return (pApp) ? pApp->GetOp() : GDC_SET; } /// Sets the current blending mode in operation /// \sa GDC_SET, GDC_AND, GDC_OR, GDC_XOR and GDC_ALPHA virtual int Op(int Op, NativeInt Param = -1); /// Gets the width in pixels virtual int X() { return (pMem) ? pMem->x : 0; } /// Gets the height in pixels virtual int Y() { return (pMem) ? pMem->y : 0; } /// Gets the bounds of the image as a GRect GRect Bounds() { return GRect(0, 0, X()-1, Y()-1); } /// Gets the length of a scanline in bytes virtual ssize_t GetRowStep() { return (pMem) ? pMem->Line : 0; } /// Returns the horizontal resolution of the device virtual int DpiX() { return 100; } /// Returns the vertical resolution of the device virtual int DpiY() { return 100; } /// Gets the bits per pixel virtual int GetBits() { return (pMem) ? GColourSpaceToBits(pMem->Cs) : 0; } /// Gets the colour space of the pixels virtual GColourSpace GetColourSpace() { return ColourSpace; } /// Gets any flags associated with the surface virtual int GetFlags() { return Flags; } /// Returns true if the surface is on the screen virtual class GScreenDC *IsScreen() { return 0; } /// Returns true if the surface is for printing virtual bool IsPrint() { return false; } /// Returns a pointer to the start of a scanline, or NULL if not available virtual uchar *operator[](int y); /// Returns true if this surface supports alpha compositing when using Blt virtual bool SupportsAlphaCompositing() { return false; } /// \returns whether if pixel data is pre-multiplied alpha virtual bool IsPreMultipliedAlpha(); /// Converts the pixel data between pre-mul alpha or non-pre-mul alpha virtual bool ConvertPreMulAlpha(bool ToPreMul); /// Makes the alpha channel opaque virtual bool MakeOpaque(); /// Gets the surface origin virtual void GetOrigin(int &x, int &y) { x = OriginX; y = OriginY; } /// Sets the surface origin virtual void SetOrigin(int x, int y) { OriginX = x; OriginY = y; } /// Sets a pixel with the current colour virtual void Set(int x, int y); /// Gets a pixel (doesn't work on some types of image, i.e. GScreenDC) virtual COLOUR Get(int x, int y); // Line /// Draw a horizontal line in the current colour virtual void HLine(int x1, int x2, int y); /// Draw a vertical line in the current colour virtual void VLine(int x, int y1, int y2); /// Draw a line in the current colour virtual void Line(int x1, int y1, int x2, int y2); /// Some surfaces only support specific line styles (e.g. GDI/Win) enum LineStyles { LineNone = 0x0, LineSolid = 0xffffffff, LineAlternate = 0xaaaaaaaa, LineDash = 0xf0f0f0f0, LineDot = 0xcccccccc, LineDashDot = 0xF33CCF30, LineDashDotDot = 0xf0ccf0cc, }; virtual uint LineStyle(uint32_t Bits, uint32_t Reset = 0x80000000) { uint32_t B = LineBits; LineBits = Bits; LineMask = LineReset = Reset; return B; } virtual uint LineStyle() { return LineBits; } // Curve /// Stroke a circle in the current colour virtual void Circle(double cx, double cy, double radius); /// Fill a circle in the current colour virtual void FilledCircle(double cx, double cy, double radius); /// Stroke an arc in the current colour virtual void Arc(double cx, double cy, double radius, double start, double end); /// Fill an arc in the current colour virtual void FilledArc(double cx, double cy, double radius, double start, double end); /// Stroke an ellipse in the current colour virtual void Ellipse(double cx, double cy, double x, double y); /// Fill an ellipse in the current colour virtual void FilledEllipse(double cx, double cy, double x, double y); // Rectangular /// Stroke a rectangle in the current colour virtual void Box(int x1, int y1, int x2, int y2); /// Stroke a rectangle in the current colour virtual void Box ( /// The rectangle, or NULL to stroke the edge of the entire surface GRect *a = NULL ); /// Fill a rectangle in the current colour virtual void Rectangle(int x1, int y1, int x2, int y2); /// Fill a rectangle in the current colour virtual void Rectangle ( /// The rectangle, or NULL to fill the entire surface GRect *a = NULL ); /// Copy an image onto the surface virtual void Blt ( /// The destination x coord int x, /// The destination y coord int y, /// The source surface GSurface *Src, /// The optional area of the source to use, if not specified the whole source is used GRect *a = NULL ); void Blt(int x, int y, GSurface *Src, GRect a) { Blt(x, y, Src, &a); } /// Not implemented virtual void StretchBlt(GRect *d, GSurface *Src, GRect *s); // Other /// Fill a polygon in the current colour virtual void Polygon(int Points, GdcPt2 *Data); /// Stroke a bezier in the current colour virtual void Bezier(int Threshold, GdcPt2 *Pt); /// Flood fill in the current colour (doesn't work on a GScreenDC) virtual void FloodFill ( /// Start x coordinate int x, /// Start y coordinate int y, /// Use #GDC_FILL_TO_DIFFERENT, #GDC_FILL_TO_BORDER or #GDC_FILL_NEAR int Mode, /// Fill colour COLOUR Border = 0, /// The bounds of the filled area or NULL if you don't care GRect *Bounds = NULL ); // GDom interface bool GetVariant(const char *Name, GVariant &Value, char *Array = NULL); bool SetVariant(const char *Name, GVariant &Value, char *Array = NULL); bool CallMethod(const char *Name, GVariant *ReturnValue, GArray &Args); }; #ifdef MAC struct GPrintDcParams { #ifdef COCOA #else PMRect Page; CGContextRef Ctx; PMResolution Dpi; #endif }; #endif /// \brief An implemenation of GSurface to draw onto the screen. /// /// This is the class given to GView::OnPaint() most of the time. Which most of /// the time doesn't matter unless your doing something unusual. class LgiClass GScreenDC : public GSurface { class GScreenPrivate *d; public: GScreenDC(); virtual ~GScreenDC(); // OS Sepcific #if WINNATIVE GScreenDC(GViewI *view); GScreenDC(HWND hwnd); GScreenDC(HDC hdc, HWND hwnd, bool Release = false); GScreenDC(HBITMAP hBmp, int Sx, int Sy); bool CreateFromHandle(HDC hdc); void SetSize(int x, int y); #else /// Construct a wrapper to draw on a window GScreenDC(GView *view, void *Param = 0); #if defined(LGI_SDL) #elif defined(MAC) GScreenDC(GWindow *wnd, void *Param = 0); GScreenDC(GPrintDcParams *Params); // Used by GPrintDC GRect GetPos(); void PushState(); void PopState(); #elif defined(__GTK_H__) /// Constructs a server size pixmap GScreenDC(int x, int y, int bits); /// Constructs a wrapper around a drawable GScreenDC(Gtk::GdkDrawable *Drawable); /// Constructs a DC for drawing on a window ///GScreenDC(OsView View); // Gtk::cairo_surface_t *GetSurface(bool Render); GdcPt2 GetSize(); #elif defined(BEOS) GScreenDC(BView *view); #endif OsPainter Handle(); GView *GetView(); int GetFlags(); GRect *GetClient(); #endif // Properties bool GetClient(GRect *c); void SetClient(GRect *c); int X(); int Y(); GPalette *Palette(); void Palette(GPalette *pPal, bool bOwnIt = true); uint LineStyle(); uint LineStyle(uint Bits, uint32_t Reset = 0x80000000); int GetBits(); GScreenDC *IsScreen() { return this; } bool SupportsAlphaCompositing(); #ifndef LGI_SDL uchar *operator[](int y) { return NULL; } void GetOrigin(int &x, int &y); void SetOrigin(int x, int y); GRect ClipRgn(); GRect ClipRgn(GRect *Rgn); COLOUR Colour(); COLOUR Colour(COLOUR c, int Bits = 0); GColour Colour(GColour c); int Op(); int Op(int Op, NativeInt Param = -1); // Primitives void Set(int x, int y); COLOUR Get(int x, int y); void HLine(int x1, int x2, int y); void VLine(int x, int y1, int y2); void Line(int x1, int y1, int x2, int y2); void Circle(double cx, double cy, double radius); void FilledCircle(double cx, double cy, double radius); void Arc(double cx, double cy, double radius, double start, double end); void FilledArc(double cx, double cy, double radius, double start, double end); void Ellipse(double cx, double cy, double x, double y); void FilledEllipse(double cx, double cy, double x, double y); void Box(int x1, int y1, int x2, int y2); void Box(GRect *a); 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 = NULL); 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); #endif }; /// \brief Blitting region helper class, can calculate the right source and dest rectangles /// for a blt operation including propagating clipping back to the source rect. class GBlitRegions { // Raw image bounds GRect SrcBounds; GRect DstBounds; // Unclipped blit regions GRect SrcBlt; GRect DstBlt; public: /// Clipped blit region in destination co-ords GRect SrcClip; /// Clipped blit region in source co-ords GRect DstClip; /// Calculate the rectangles. GBlitRegions ( /// Destination surface GSurface *Dst, /// Destination blt x offset int x1, /// Destination blt y offset int y1, /// Source surface GSurface *Src, /// [Optional] Crop the source surface first, else whole surface is blt GRect *SrcRc = 0 ) { // Calc full image bounds if (Src) SrcBounds.Set(0, 0, Src->X()-1, Src->Y()-1); else SrcBounds.ZOff(-1, -1); if (Dst) DstBounds.Set(0, 0, Dst->X()-1, Dst->Y()-1); else DstBounds.ZOff(-1, -1); // Calc full sized blt regions if (SrcRc) { SrcBlt = *SrcRc; SrcBlt.Bound(&SrcBounds); } else SrcBlt = SrcBounds; DstBlt = SrcBlt; DstBlt.Offset(x1-DstBlt.x1, y1-DstBlt.y1); // Dest clipped to dest bounds DstClip = DstBlt; DstClip.Bound(&DstBounds); // Now map the dest clipping back to the source SrcClip = SrcBlt; SrcClip.x1 += DstClip.x1 - DstBlt.x1; SrcClip.y1 += DstClip.y1 - DstBlt.y1; SrcClip.x2 -= DstBlt.x2 - DstClip.x2; SrcClip.y2 -= DstBlt.y2 - DstClip.y2; } /// Returns non-zero if both clipped rectangles are valid. bool Valid() { return DstClip.Valid() && SrcClip.Valid(); } void Dump() { printf("SrcBounds: %s\n", SrcBounds.GetStr()); printf("DstBounds: %s\n", DstBounds.GetStr()); printf("SrcBlt: %s\n", SrcBlt.GetStr()); printf("DstBlt: %s\n", DstBlt.GetStr()); printf("SrcClip: %s\n", SrcClip.GetStr()); printf("DstClip: %s\n", DstClip.GetStr()); } }; #if defined(MAC) class CGImg { class CGImgPriv *d; void Create(int x, int y, int Bits, ssize_t Line, uchar *data, uchar *palette, GRect *r); public: CGImg(int x, int y, int Bits, ssize_t Line, uchar *data, uchar *palette, GRect *r); CGImg(GSurface *pDC); ~CGImg(); operator CGImageRef(); }; #endif /// \brief An implemenation of GSurface to draw into a memory bitmap. /// /// This class uses a block of memory to represent an image. You have direct /// pixel access as well as higher level functions to manipulate the bits. class LgiClass GMemDC : public GSurface { protected: class GMemDCPrivate *d; #if defined WINNATIVE PBITMAPINFO GetInfo(); #endif // This is called between capturing the screen and overlaying the // cursor in GMemDC::Blt(x, y, ScreenDC, Src). It can be used to // overlay effects between the screen and cursor layers. virtual void OnCaptureScreen() {} public: /// Creates a memory bitmap GMemDC ( /// The width int x = 0, /// The height int y = 0, /// The colour space to use. CsNone will default to the /// current screen colour space. GColourSpace cs = CsNone, /// Optional creation flags int Flags = SurfaceCreateNone ); GMemDC(GSurface *pDC); virtual ~GMemDC(); #if WINNATIVE HDC StartDC(); void EndDC(); void Update(int Flags); void UpsideDown(bool upsidedown); #else GRect ClipRgn() { return Clip; } #if defined MAC OsBitmap GetBitmap(); #if !defined(LGI_SDL) CGColorSpaceRef GetColourSpaceRef(); CGImg *GetImg(GRect *Sub = 0); #endif #elif defined(__GTK_H__) Gtk::GdkImage *GetImage(); GdcPt2 GetSize(); Gtk::cairo_surface_t *GetSurface(GRect &r); GColourSpace GetCreateCs(); #elif defined(BEOS) || defined(LGI_SDL) OsBitmap GetBitmap(); #endif OsPainter Handle(); #endif // Set new clipping region GRect ClipRgn(GRect *Rgn); void SetClient(GRect *c); /// Locks the bits for access. GMemDC's start in the locked state. bool Lock(); /// Unlocks the bits to optimize for display. While the bitmap is unlocked you /// can't access the data for read or write. On linux this converts the XImage /// to pixmap. On other systems it doesn't do much. As a general rule if you /// don't need access to a bitmap after creating / loading it then unlock it. bool Unlock(); void SetOrigin(int x, int y); void Empty(); bool SupportsAlphaCompositing(); bool Create(int x, int y, GColourSpace Cs, int Flags = SurfaceCreateNone); void Blt(int x, int y, GSurface *Src, GRect *a = NULL); void StretchBlt(GRect *d, GSurface *Src, GRect *s = NULL); void HorzLine(int x1, int x2, int y, COLOUR a, COLOUR b); void VertLine(int x, int y1, int y2, COLOUR a, COLOUR b); }; /// \brief An implemenation of GSurface to print to a printer. /// /// This class redirects standard graphics calls to print a page. /// /// \sa GPrinter class LgiClass GPrintDC #if defined(WIN32) || defined(MAC) : public GScreenDC #else : public GSurface #endif { class GPrintDCPrivate *d; public: GPrintDC(void *Handle, const char *PrintJobName, const char *PrinterName = NULL); ~GPrintDC(); bool IsPrint() { return true; } const char *GetOutputFileName(); int X(); int Y(); int GetBits(); /// Returns the horizontal DPI of the printer or 0 on error int DpiX(); /// Returns the vertical DPI of the printer or 0 on error int DpiY(); #if defined __GTK_H__ Gtk::GtkPrintContext *GetPrintContext(); int Op() { return GDC_SET; } int Op(int Op, NativeInt Param = -1) { return GDC_SET; } GRect ClipRgn(GRect *Rgn); GRect ClipRgn(); COLOUR Colour(); COLOUR Colour(COLOUR c, int Bits = 0); GColour Colour(GColour c); void Set(int x, int y); void HLine(int x1, int x2, int y); void VLine(int x, int y1, int y2); void Line(int x1, int y1, int x2, int y2); void Circle(double cx, double cy, double radius); void FilledCircle(double cx, double cy, double radius); void Arc(double cx, double cy, double radius, double start, double end); void FilledArc(double cx, double cy, double radius, double start, double end); void Ellipse(double cx, double cy, double x, double y); void FilledEllipse(double cx, double cy, double x, double y); void Box(int x1, int y1, int x2, int y2); void Box(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); #endif }; ////////////////////////////////////////////////////////////////////////////// class LgiClass GGlobalColour { class GGlobalColourPrivate *d; public: GGlobalColour(); ~GGlobalColour(); // Add all the colours first COLOUR AddColour(COLOUR c24); bool AddBitmap(GSurface *pDC); bool AddBitmap(GImageList *il); // Then call this bool MakeGlobalPalette(); // Which will give you a palette that // includes everything GPalette *GetPalette(); // Convert a bitmap to the global palette COLOUR GetColour(COLOUR c24); bool RemapBitmap(GSurface *pDC); }; /// This class is useful for double buffering in an OnPaint handler... class GDoubleBuffer { GSurface **In; GSurface *Screen; GMemDC Mem; GRect Rgn; bool Valid; public: GDoubleBuffer(GSurface *&pDC, GRect *Sub = NULL) : In(&pDC) { Rgn = Sub ? *Sub : pDC->Bounds(); Screen = pDC; Valid = pDC && Mem.Create(Rgn.X(), Rgn.Y(), pDC->GetColourSpace()); if (Valid) { *In = &Mem; if (Sub) pDC->SetOrigin(Sub->x1, Sub->y1); } } ~GDoubleBuffer() { if (Valid) { Mem.SetOrigin(0, 0); Screen->Blt(Rgn.x1, Rgn.y1, &Mem); } // Restore state *In = Screen; } }; #ifdef WIN32 typedef int (__stdcall *MsImg32_AlphaBlend)(HDC,int,int,int,int,HDC,int,int,int,int,BLENDFUNCTION); #endif /// Main singleton graphics device class. Holds all global data for graphics rendering. class LgiClass GdcDevice : public GCapabilityClient { friend class GScreenDC; friend class GMemDC; friend class GImageList; static GdcDevice *pInstance; class GdcDevicePrivate *d; #ifdef WIN32 MsImg32_AlphaBlend AlphaBlend; #endif public: GdcDevice(); ~GdcDevice(); static GdcDevice *GetInst() { return pInstance; } /// Returns the colour space of the screen GColourSpace GetColourSpace(); /// Returns the current screen bit depth int GetBits(); /// Returns the current screen width int X(); /// Returns the current screen height int Y(); /// Returns the size of the screen as a rectangle. GRect Bounds() { return GRect(0, 0, X()-1, Y()-1); } GGlobalColour *GetGlobalColour(); /// Set a global graphics option int GetOption(int Opt); /// Get a global graphics option int SetOption(int Opt, int Value); /// 256 lut for squares ulong *GetCharSquares(); /// Divide by 255 lut, 64k entries long. uchar *GetDiv255(); // Palette/Colour void SetGamma(double Gamma); double GetGamma(); // Palette void SetSystemPalette(int Start, int Size, GPalette *Pal); GPalette *GetSystemPalette(); void SetColourPaletteType(int Type); // Type = PALTYPE_xxx define COLOUR GetColour(COLOUR Rgb24, GSurface *pDC = NULL); // File I/O /// \brief Loads a image from a file /// /// This function uses the compiled in codecs, some of which require external /// shared libraries / DLL's to function. Other just need the right source to /// be compiled in. /// /// Lgi comes with the following image codecs: ///
    ///
  • Windows or OS/2 Bitmap: GdcBmp (GFilter.cpp) ///
  • PCX: GdcPcx (Pcx.cpp) ///
  • GIF: GdcGif (Gif.cpp and Lzw.cpp) ///
  • JPEG: GdcJpeg (Jpeg.cpp + libjpeg library) ///
  • PNG: GdcPng (Png.cpp + libpng library) ///
/// GSurface *Load ( /// The full path of the file const char *FileName, /// [Optional] Enable OS based loaders bool UseOSLoader = true ); /// The stream version of the file loader... GSurface *Load ( /// The full path of the file GStream *In, /// [Optional] File name hint for selecting a filter const char *Name = NULL, /// [Optional] Enable OS based loaders bool UseOSLoader = true ); + /// Save an image to a stream. + bool Save + ( + /// The file to write to + GStream *Out, + /// The pixels to store + GSurface *In, + /// Dummy file name to determine the file type, eg: "img.jpg" + const char *FileType + ); + /// Save an image to a file. bool Save ( /// The file to write to const char *Name, /// The pixels to store GSurface *pDC ); #if LGI_SDL SDL_Surface *Handle(); #endif }; /// \brief Defines a bitmap inline in C++ code. /// /// The easiest way I know of create the raw data for an GInlineBmp /// is to use i.Mage to /// load a file or create a image and then use the Edit->Copy As Code /// menu. Then paste into your C++ and put a uint32 array declaration /// around it. Then point the Data member to the uint32 array. Just be /// sure to get the dimensions right. /// /// I use this for embeding resource images directly into the code so /// that a) they load instantly and b) they can't get lost as a separate file. class LgiClass GInlineBmp { public: /// The width of the image. int X; /// The height of the image. int Y; /// The bitdepth of the image (8, 15, 16, 24, 32). int Bits; /// Pointer to the raw data. uint32_t *Data; /// Creates a memory DC of the image. GSurface *Create(uint32_t TransparentPx = 0xffffffff); }; // file filter support #include "GFilter.h" // globals #define GdcD GdcDevice::GetInst() /// Converts a context to a different bit depth LgiFunc GSurface *ConvertDC ( /// The source image GSurface *pDC, /// The destination bit depth int Bits ); /// Wrapper around GdcDevice::Load /// \deprecated Use GdcDevice::Load directly in new code. LgiFunc GSurface *LoadDC(const char *Name, bool UseOSLoader = true) DEPRECATED_POST; /// Wrapper around GdcDevice::Save /// \deprecated Use GdcDevice::Save directly in new code. LgiFunc bool WriteDC(const char *Name, GSurface *pDC) DEPRECATED_POST; /// Converts a colour to a different bit depth LgiFunc COLOUR CBit(int DstBits, COLOUR c, int SrcBits = 24, GPalette *Pal = 0); #ifdef __cplusplus /// blends 2 colours by the amount specified LgiClass GColour GdcMixColour(GColour a, GColour b, float HowMuchA = 0.5); #endif /// blends 2 24bit colours by the amount specified LgiFunc COLOUR GdcMixColour(COLOUR a, COLOUR b, float HowMuchA = 0.5); /// Turns a colour into an 8 bit grey scale representation LgiFunc COLOUR GdcGreyScale(COLOUR c, int Bits = 24); /// Colour reduction option to define what palette to go to enum GColourReducePalette { CR_PAL_NONE = -1, CR_PAL_CUBE = 0, CR_PAL_OPT, CR_PAL_FILE }; /// Colour reduction option to define how to deal with reduction error enum GColourReduceMatch { CR_MATCH_NONE = -1, CR_MATCH_NEAR = 0, CR_MATCH_HALFTONE, CR_MATCH_ERROR }; /// Colour reduction options class GReduceOptions { public: /// Type of palette GColourReducePalette PalType; /// Reduction error handling GColourReduceMatch MatchType; /// 1-256 int Colours; /// Specific palette to reduce to GPalette *Palette; GReduceOptions() { Palette = 0; Colours = 256; PalType = CR_PAL_NONE; MatchType = CR_MATCH_NONE; } }; /// Reduces a images colour depth LgiFunc bool GReduceBitDepth(GSurface *pDC, int Bits, GPalette *Pal = 0, GReduceOptions *Reduce = 0); struct GColourStop { COLOUR Colour; float Pos; }; /// Draws a horizontal or vertical gradient LgiFunc void LgiFillGradient(GSurface *pDC, GRect &r, bool Vert, GArray &Stops); #ifdef WIN32 /// Draws a windows HICON onto a surface at Dx, Dy LgiFunc void LgiDrawIcon(GSurface *pDC, int Dx, int Dy, HICON ico); #endif /// Row copy operator for full RGB (8 bit components) LgiFunc bool LgiRopRgb ( // Pointer to destination pixel buffer uint8_t *Dst, // Destination colour space (must be 8bit components) GColourSpace DstCs, // Pointer to source pixel buffer (if this overlaps 'Dst', set 'Overlap' to true) uint8_t *Src, // Source colour space (must be 8bit components) GColourSpace SrcCs, // Number of pixels to convert int Px, // Whether to composite using alpha or copy blt bool Composite ); /// Universal bit blt method LgiFunc bool LgiRopUniversal(GBmpMem *Dst, GBmpMem *Src, bool Composite); /// Gets the screens DPI LgiFunc int LgiScreenDpi(); /// Find the bounds of an image. /// \return true if there is some non-transparent image in 'rc' LgiFunc bool LgiFindBounds ( /// [in] The image GSurface *pDC, /// [in/out] Starts off as the initial bounds to search. /// Returns the non-background area. GRect *rc ); #if defined(LGI_SDL) LgiFunc GColourSpace PixelFormat2ColourSpace(SDL_PixelFormat *pf); #elif defined(BEOS) LgiFunc GColourSpace BeosColourSpaceToLgi(color_space cs); #endif #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/linux/Gtk/LgiOsDefs.h b/include/linux/Gtk/LgiOsDefs.h --- a/include/linux/Gtk/LgiOsDefs.h +++ b/include/linux/Gtk/LgiOsDefs.h @@ -1,554 +1,554 @@ /** \file \author Matthew Allen \brief GTK specific types and defines. */ #ifndef __LGI_OS_DEFS_H #define __LGI_OS_DEFS_H #include #include "assert.h" #include "LgiDefs.h" #include #include #include #include #include #include #ifdef WIN32 #include "winsock2.h" #define WIN32GTK 1 #define WINNATIVE 0 #ifdef _WIN64 #define LGI_64BIT 1 #else #define LGI_32BIT 1 #endif #else #define _MULTI_THREADED #include #define LINUX 1 #define XP_CTRLS 1 #define POSIX 1 #ifdef __arm__ #define LGI_RPI 1 #define LGI_32BIT 1 #elif __GNUC__ #if __x86_64__ || __ppc64__ #define LGI_64BIT 1 #else #define LGI_32BIT 1 #endif #endif #endif #undef stricmp #include "LgiInc.h" namespace Gtk { #include #ifdef WIN32 #include #endif } // System #undef Status #undef Success #undef None #undef Above #undef Below #define XStatus int #define XSuccess 0 #define XAbove 0 #define XBelow 1 #define XNone 0L class LgiClass OsAppArguments { struct OsAppArgumentsPriv *d; public: int Args; char **Arg; OsAppArguments(int args, const char **arg); ~OsAppArguments(); void Set(char *CmdLine); OsAppArguments &operator =(OsAppArguments &a); }; // Process #ifdef WIN32 typedef HANDLE OsProcess; #else typedef int OsProcess; #endif typedef int OsProcessId; typedef Gtk::GtkWidget *OsView; typedef Gtk::GtkWindow *OsWindow; typedef char OsChar; typedef Gtk::cairo_t *OsPainter; typedef Gtk::PangoFontDescription *OsFont; typedef void *OsBitmap; #define LgiGetCurrentProcess() getpid() // Because of namespace issues you can't use the built in GTK casting macros. // So this is basically the same thing: // e.g. // Gtk::GtkContainer *c = GtkCast(widget, gtk_container, GtkContainer); // #define GtkCast(obj, gtype, ctype) ((Gtk::ctype*)g_type_check_instance_cast( (Gtk::GTypeInstance*)obj, Gtk::gtype##_get_type() )) #define GtkVer(major, minor) ( (GTK_MAJOR_VERSION > major) || (GTK_MAJOR_VERSION == major && GTK_MINOR_VERSION >= minor) ) class OsApplication { class OsApplicationPriv *d; protected: static OsApplication *Inst; public: OsApplication(int Args, char **Arg); ~OsApplication(); static OsApplication *GetInst() { LgiAssert(Inst); return Inst; } }; #define XcbConn() (OsApplication::GetInst()->GetConn()) #define XcbScreen() (OsApplication::GetInst()->GetScreen()) #define XcbCheck(cookie) (OsApplication::GetInst()->Check(cookie, __FILE__, __LINE__)) #ifdef _DEBUG #define XcbDebug(cookie) (OsApplication::GetInst()->Check(cookie, __FILE__, __LINE__)) #else #define XcbDebug(cookie) cookie #endif // Threads #ifdef WIN32 typedef HANDLE OsThread; typedef DWORD OsThreadId; typedef CRITICAL_SECTION OsSemaphore; #define LgiGetCurrentThread() GetCurrentThreadId() #else typedef pthread_t OsThread; typedef pid_t OsThreadId; typedef pthread_mutex_t OsSemaphore; #define LgiGetCurrentThread() pthread_self() LgiFunc OsThreadId GetCurrentThreadId(); #endif #include "GMessage.h" // Sockets #define ValidSocket(s) ((s)>=0) #ifndef WIN32 #define INVALID_SOCKET -1 #endif typedef int OsSocket; /// Sleep the current thread for i milliseconds. #ifdef WIN32 LgiFunc void LgiSleep(DWORD i); #else -LgiFunc void LgiSleep(uint32 i); +LgiFunc void LgiSleep(uint32_t i); #endif #ifndef WIN32 #define atoi64 atoll #else #define atoi64 _atoi64 #endif #define _snprintf snprintf #define _vsnprintf vsnprintf #define wcscpy_s(dst, len, src) wcsncpy(dst, src, len) /// Process any pending messages in the applications message que and then return. #define LgiYield() LgiApp->Run(false) #define K_CHAR 0x0 /// Drag and drop format for a file #define LGI_FileDropFormat "text/uri-list" #define LGI_StreamDropFormat "application/x-file-stream" // FIXME #define LGI_IllegalFileNameChars "\t\r\n/\\:*?\"<>|" #ifdef WINDOWS #define LGI_WideCharset "ucs-2" #else #define LGI_WideCharset "utf-32" #endif #define LPrintfInt64 "%Li" #define LPrintfHex64 "%Lx" #define LPrintfSizeT "%zu" #define LPrintfSSizeT "%zi" #ifndef SND_ASYNC #define SND_ASYNC 0x0001 #endif #define DOUBLE_CLICK_THRESHOLD 5 #define DOUBLE_CLICK_TIME 400 // 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 #ifndef WIN32 #define ODS_SELECTED 0x1 #define ODS_DISABLED 0x2 #define ODS_CHECKED 0x4 #endif /// Edge type: Sunken #define SUNKEN 1 /// Edge type: Raised #define RAISED 2 /// Edge type: Chiseled #define CHISEL 3 /// Edge type: Flat #define FLAT 4 #ifdef WIN32 /// 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 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 "dll" /// The standard executable extension #define LGI_EXECUTABLE_EXT ".exe" #else /// 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 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 "so" /// The standard executable extension #define LGI_EXECUTABLE_EXT "" #endif /// 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)=='\'')) /// ID's returned by LgiMsg. /// \sa LgiMsg enum MessageBoxResponse { IDOK = 1, IDCANCEL = 2, IDABORT = 3, IDRETRY = 4, IDIGNORE = 5, IDYES = 6, IDNO = 7, IDTRYAGAIN = 10, IDCONTINUE = 11, }; /// Standard message box types. /// \sa LgiMsg enum MessageBoxType { MB_OK = 0, MB_OKCANCEL = 1, MB_ABORTRETRYIGNORE = 2, MB_YESNOCANCEL = 3, MB_YESNO = 4, MB_RETRYCANCEL = 5, MB_CANCELTRYCONTINUE = 6 }; #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 #define abs(a) ( (a) < 0 ? -(a) : (a) ) #ifndef WIN32 #include typedef enum { /* The keyboard syms have been cleverly chosen to map to ASCII */ VK_UNKNOWN = 0, VK_FIRST = 0, VK_BACKSPACE = 8, VK_TAB = 9, VK_CLEAR = GDK_Clear, VK_RETURN = 13, VK_PAUSE = GDK_Pause, VK_ESCAPE = GDK_Escape, VK_SPACE = 32, VK_EXCLAIM = 33, VK_QUOTEDBL = 34, VK_HASH = 35, VK_DOLLAR = 36, VK_AMPERSAND = 38, VK_QUOTE = 39, VK_LEFTPAREN = 40, VK_RIGHTPAREN = 41, VK_ASTERISK = 42, VK_PLUS = 43, VK_COMMA = 44, VK_MINUS = 45, VK_PERIOD = 46, VK_SLASH = 47, VK_0 = 48, VK_1 = 49, VK_2 = 50, VK_3 = 51, VK_4 = 52, VK_5 = 53, VK_6 = 54, VK_7 = 55, VK_8 = 56, VK_9 = 57, VK_COLON = 58, VK_SEMICOLON = 59, VK_LESS = 60, VK_EQUALS = 61, VK_GREATER = 62, VK_QUESTION = 63, VK_AT = 64, /* Skip uppercase letters */ VK_LEFTBRACKET = GDK_bracketleft, VK_BACKSLASH = GDK_backslash, VK_RIGHTBRACKET = GDK_bracketright, VK_CARET = 94, VK_UNDERSCORE = GDK_underscore, VK_BACKQUOTE = 96, VK_a = GDK_a, VK_b = GDK_b, VK_c = GDK_c, VK_d = GDK_d, VK_e = GDK_e, VK_f = GDK_f, VK_g = GDK_g, VK_h = GDK_h, VK_i = GDK_i, VK_j = GDK_j, VK_k = GDK_k, VK_l = GDK_l, VK_m = GDK_m, VK_n = GDK_n, VK_o = GDK_o, VK_p = GDK_p, VK_q = GDK_q, VK_r = GDK_r, VK_s = GDK_s, VK_t = GDK_t, VK_u = GDK_u, VK_v = GDK_v, VK_w = GDK_w, VK_x = GDK_x, VK_y = GDK_y, VK_z = GDK_z, /* End of ASCII mapped keysyms */ /* Numeric keypad */ VK_KP_ENTER = GDK_KP_Enter, VK_KP0 = GDK_KP_0, VK_KP1 = GDK_KP_1, VK_KP2 = GDK_KP_2, VK_KP3 = GDK_KP_3, VK_KP4 = GDK_KP_4, VK_KP5 = GDK_KP_5, VK_KP6 = GDK_KP_6, VK_KP7 = GDK_KP_7, VK_KP8 = GDK_KP_8, VK_KP9 = GDK_KP_9, VK_KP_PERIOD = GDK_KP_Decimal, VK_KP_DELETE = GDK_KP_Delete, VK_KP_MULTIPLY = GDK_KP_Multiply, VK_KP_PLUS = GDK_KP_Add, VK_KP_MINUS = GDK_KP_Subtract, VK_KP_DIVIDE = GDK_KP_Divide, VK_KP_EQUALS = GDK_KP_Equal, /* Arrows + Home/End pad */ VK_HOME = GDK_Home, VK_LEFT = GDK_Left, VK_UP = GDK_Up, VK_RIGHT = GDK_Right, VK_DOWN = GDK_Down, VK_PAGEUP = GDK_Page_Up, VK_PAGEDOWN = GDK_Page_Down, VK_END = GDK_End, VK_INSERT = GDK_Insert, /* Function keys */ VK_F1 = GDK_F1, VK_F2 = GDK_F2, VK_F3 = GDK_F3, VK_F4 = GDK_F4, VK_F5 = GDK_F5, VK_F6 = GDK_F6, VK_F7 = GDK_F7, VK_F8 = GDK_F8, VK_F9 = GDK_F9, VK_F10 = GDK_F10, VK_F11 = GDK_F11, VK_F12 = GDK_F12, VK_F13 = GDK_F13, VK_F14 = GDK_F14, VK_F15 = GDK_F15, /* Key state modifier keys */ VK_NUMLOCK = GDK_Num_Lock, VK_CAPSLOCK = GDK_Caps_Lock, VK_SCROLLOCK = GDK_Scroll_Lock, VK_LSHIFT = GDK_Shift_L, VK_RSHIFT = GDK_Shift_R, VK_LCTRL = GDK_Control_L, VK_RCTRL = GDK_Control_R, VK_LALT = GDK_Alt_L, VK_RALT = GDK_Alt_R, VK_LMETA = GDK_Hyper_L, VK_RMETA = GDK_Hyper_R, VK_LSUPER = GDK_Super_L, /* "Windows" key */ VK_RSUPER = GDK_Super_R, /* Miscellaneous function keys */ VK_HELP = GDK_Help, VK_PRINT = GDK_Print, VK_SYSREQ = GDK_Sys_Req, VK_BREAK = GDK_Break, VK_MENU = GDK_Menu, VK_UNDO = GDK_Undo, VK_REDO = GDK_Redo, VK_EURO = GDK_EuroSign, /* Some european keyboards */ VK_COMPOSE = GDK_Multi_key, /* Multi-key compose key */ VK_MODE = GDK_Mode_switch, /* "Alt Gr" key (could be wrong) */ VK_DELETE = GDK_Delete, VK_POWER = 0x10000, /* Power Macintosh power key */ VK_CONTEXTKEY = GDK_Menu, /* Add any other keys here */ VK_LAST } LgiKey; #else #define VK_BACKSPACE VK_BACK #define VK_PAGEUP VK_PRIOR #define VK_PAGEDOWN VK_NEXT #define VK_RALT VK_MENU #define VK_LALT VK_MENU #define VK_RCTRL VK_CONTROL #define VK_LCTRL VK_CONTROL #endif ///////////////////////////////////////////////////////////////////////////////////// // Externs #define vsprintf_s vsnprintf #define swprintf_s swprintf #ifndef WIN32 // __CYGWIN__ // LgiFunc char *strnistr(char *a, char *b, int n); #define _strnicmp strncasecmp // LgiFunc int _strnicmp(char *a, char *b, int i); LgiFunc char *strupr(char *a); LgiFunc char *strlwr(char *a); LgiFunc int stricmp(const char *a, const char *b); #define _stricmp strcasecmp // LgiFunc int _stricmp(const char *a, const char *b); #define sprintf_s snprintf #else LgiFunc class GViewI *GWindowFromHandle(OsView hWnd); LgiFunc int GetMouseWheelLines(); LgiFunc int WinPointToHeight(int Pt, HDC hDC = NULL); LgiFunc int WinHeightToPoint(int Ht, HDC hDC = NULL); LgiExtern class GString WinGetSpecialFolderPath(int Id); typedef BOOL (__stdcall *pSHGetSpecialFolderPathA)(HWND hwndOwner, LPSTR lpszPath, int nFolder, BOOL fCreate); typedef BOOL (__stdcall *pSHGetSpecialFolderPathW)(HWND hwndOwner, LPWSTR lpszPath, int nFolder, BOOL fCreate); typedef int (__stdcall *pSHFileOperationA)(LPSHFILEOPSTRUCTA lpFileOp); typedef int (__stdcall *pSHFileOperationW)(LPSHFILEOPSTRUCTW lpFileOp); typedef int (__stdcall *p_vscprintf)(const char *format, va_list argptr); #if _MSC_VER >= 1400 #define snprintf sprintf_s #endif /// 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 #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_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_t LDbRow::GetInitialSize() { 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_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_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_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_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/Filters/GFilter.cpp b/src/common/Gdc2/Filters/GFilter.cpp --- a/src/common/Gdc2/Filters/GFilter.cpp +++ b/src/common/Gdc2/Filters/GFilter.cpp @@ -1,2218 +1,2224 @@ /** \file \author Matthew Allen \date 25/3/97 \brief Graphics file filters */ #if WINDOWS #include #include #include "Lgi.h" #ifdef _MSC_VER #include #endif #else #define BI_RGB 0L #define BI_RLE8 1L #define BI_RLE4 2L #define BI_BITFIELDS 3L #endif #include #include #include #include #include "Lgi.h" #include "GString.h" #include "GVariant.h" #include "GClipBoard.h" #include "GToken.h" #include "GPalette.h" int FindHeader(int Offset, const char *Str, GStream *f) { int i = 0; if (Offset >= 0) { f->SetPos(Offset); } while (true) { char c; if (!f->Read(&c, 1)) break; if (Str[i] == c || Str[i] == '?') { i++; if (!Str[i]) { return true; } } else { i = 0; } } return false; } /// Windows and OS/2 BMP file filter class GdcBmp : public GFilter { int ActualBits; int ScanSize; public: int GetCapabilites() { return FILTER_CAP_READ | FILTER_CAP_WRITE; } Format GetFormat() { return FmtBmp; } /// Reads a BMP file IoStatus ReadImage(GSurface *Out, GStream *In); /// Writes a Windows BMP file IoStatus WriteImage(GStream *Out, GSurface *In); bool GetVariant(const char *n, GVariant &v, char *a) { if (!stricmp(n, LGI_FILTER_TYPE)) { v = "Windows or OS/2 Bitmap"; } else if (!stricmp(n, LGI_FILTER_EXTENSIONS)) { v = "BMP"; } else return false; return true; } }; class GdcBmpFactory : public GFilterFactory { bool CheckFile(const char *File, int Access, const uchar *Hint) { if (File) { const char *Ext = LgiGetExtension((char*)File); if (Ext && !_stricmp(Ext, "bmp")) return true; } if (Hint) { if (Hint[0] == 'B' && Hint[1] == 'M') return true; } return false; } GFilter *NewObject() { return new GdcBmp; } } BmpFactory; #define BMP_ID 0x4D42 #ifdef WIN32 #pragma pack(push, before_pack) #pragma pack(1) #endif class BMP_FILE { public: char Type[2]; uint32_t Size; uint16 Reserved1; uint16 Reserved2; uint32_t OffsetToData; }; class BMP_WININFO { public: // Win2 hdr (12 bytes) int32 Size; int32 Sx; int32 Sy; uint16 Planes; uint16 Bits; // Win3 hdr (40 bytes) uint32_t Compression; uint32_t DataSize; int32 XPels; int32 YPels; uint32_t ColoursUsed; uint32_t ColourImportant; // Win4 hdr (108 bytes) uint32_t RedMask; /* Mask identifying bits of red component */ uint32_t GreenMask; /* Mask identifying bits of green component */ uint32_t BlueMask; /* Mask identifying bits of blue component */ uint32_t AlphaMask; /* Mask identifying bits of alpha component */ uint32_t CSType; /* Color space type */ uint32_t RedX; /* X coordinate of red endpoint */ uint32_t RedY; /* Y coordinate of red endpoint */ uint32_t RedZ; /* Z coordinate of red endpoint */ uint32_t GreenX; /* X coordinate of green endpoint */ uint32_t GreenY; /* Y coordinate of green endpoint */ uint32_t GreenZ; /* Z coordinate of green endpoint */ uint32_t BlueX; /* X coordinate of blue endpoint */ uint32_t BlueY; /* Y coordinate of blue endpoint */ uint32_t BlueZ; /* Z coordinate of blue endpoint */ uint32_t GammaRed; /* Gamma red coordinate scale value */ uint32_t GammaGreen; /* Gamma green coordinate scale value */ uint32_t GammaBlue; /* Gamma blue coordinate scale value */ bool Read(GStream &f) { ZeroObj(*this); int64 Start = f.GetPos(); #define Rd(var) if (f.Read(&var, sizeof(var)) != sizeof(var)) \ { LgiTrace("Bmp.Read(%i) failed\n", (int)sizeof(var)); return false; } Rd(Size); // 4 Rd(Sx); // 4 Rd(Sy); // 4 Rd(Planes); // 2 Rd(Bits); // 2 // = 16 if (Size >= 40) { Rd(Compression); // 4 Rd(DataSize); // 4 Rd(XPels); // 4 Rd(YPels); // 4 Rd(ColoursUsed); // 4 Rd(ColourImportant); // 4 // = 24 + 16 = 40 } if (Size >= 52) { Rd(RedMask); // 4 Rd(GreenMask); // 4 Rd(BlueMask); // 4 // 12 + 40 = 52 } if (Size >= 56) { Rd(AlphaMask); // 4 // = 4 + 52 = 56 } if (Size >= 108) { Rd(CSType); // 4 Rd(RedX); // 4 Rd(RedY); // 4 Rd(RedZ); // 4 Rd(GreenX); // 4 Rd(GreenY); // 4 Rd(GreenZ); // 4 Rd(BlueX); // 4 Rd(BlueY); // 4 Rd(BlueZ); // 4 Rd(GammaRed); // 4 Rd(GammaGreen); // 4 Rd(GammaBlue); // 4 // = 52 + 56 = 108 } int64 End = f.GetPos(); int64 Bytes = End - Start; return Bytes >= 12; } }; #ifdef WIN32 #pragma pack(pop, before_pack) #endif static int CountSetBits(uint32_t b) { int Count = 0; for (int i=0; iMask < a->Mask ? 1 : -1; } int DownSort(MaskComp *a, MaskComp *b) { return b->Mask > a->Mask ? 1 : -1; } GFilter::IoStatus GdcBmp::ReadImage(GSurface *pDC, GStream *In) { if (!pDC || !In) return GFilter::IoError; ActualBits = 0; ScanSize = 0; BMP_FILE File; BMP_WININFO Info; GRect Cr; Read(In, &File.Type, sizeof(File.Type)); Read(In, &File.Size, sizeof(File.Size)); Read(In, &File.Reserved1, sizeof(File.Reserved1)); Read(In, &File.Reserved2, sizeof(File.Reserved2)); Read(In, &File.OffsetToData, sizeof(File.OffsetToData)); if (!Info.Read(*In)) { LgiTrace("%s:%i - BmpHdr read failed.\n", _FL); return GFilter::IoError; } if (File.Type[0] != 'B' || File.Type[1] != 'M') { LgiTrace("%s:%i - Bmp file id failed: '%.2s'.\n", _FL, File.Type); return GFilter::IoUnsupportedFormat; } ActualBits = Info.Bits; ScanSize = BMPWIDTH(Info.Sx * Info.Bits); int MemBits = MAX(Info.Bits, 8); if (!pDC->Create(Info.Sx, Info.Sy, GBitsToColourSpace(MemBits), ScanSize)) { LgiTrace("%s:%i - MemDC(%i,%i,%i) failed.\n", _FL, Info.Sx, Info.Sy, MAX(Info.Bits, 8)); return GFilter::IoError; } if (pDC->GetBits() <= 8) { int Colours = 1 << ActualBits; GPalette *Palette = new GPalette; if (Palette) { Palette->SetSize(Colours); GdcRGB *Start = (*Palette)[0]; if (Start) { In->Read(Start, sizeof(GdcRGB)*Colours); Palette->SwapRAndB(); } Palette->Update(); pDC->Palette(Palette); } } GBmpMem *pMem = GetSurface(pDC); In->SetPos(File.OffsetToData); GFilter::IoStatus Status = IoSuccess; if (Info.Compression == BI_RLE8) { // 8 bit RLE compressed image int64 Remaining = In->GetSize() - In->GetPos(); uchar *Data = new uchar[Remaining]; if (Data) { if (In->Read(Data, (int)Remaining) == Remaining) { int x=0, y=pDC->Y()-1; uchar *p = Data; bool Done = false; while (!Done && p < Data + Remaining) { uchar Length = *p++; uchar Colour = *p++; if (Length == 0) { switch (Colour) { case 0: { x = 0; y--; break; } case 1: { Done = true; break; } case 2: { x += *p++; y -= *p++; break; } default: { // absolute mode uchar *Pixel = (*pDC)[y]; if (Pixel && y >= 0 && y < pDC->Y()) { int Len = MIN(Colour, pDC->X() - x); if (Len > 0) { memcpy(Pixel + x, p, Len); x += Colour; p += Colour; } } else { p += Colour; } if ((NativeInt) p & 1) p++; break; } } } else { // encoded mode uchar *Pixel = (*pDC)[y]; if (Pixel && y >= 0 && y < pDC->Y()) { int Len = MIN(Length, pDC->X() - x); if (Len > 0) { memset(Pixel + x, Colour, Len); x += Length; } } } } } } } else if (Info.Compression == BI_RLE4) { // 4 bit RLE compressed image // not implemented LgiTrace("%s:%i - BI_RLE4 not implemented.\n", _FL); } else { // Progress if (Meter) { Meter->SetDescription("scanlines"); Meter->SetLimits(0, pMem->y-1); } GColourSpace SrcCs = CsNone; switch (ActualBits) { case 8: SrcCs = CsIndex8; break; case 15: SrcCs = CsRgb15; break; case 16: SrcCs = CsRgb16; break; case 24: SrcCs = CsBgr24; break; case 32: SrcCs = CsBgra32; break; } #if 1 if (Info.Compression == BI_BITFIELDS) { // Should we try and create a colour space from these fields? GArray Comps; Comps.New().Set(CtRed, Info.RedMask); Comps.New().Set(CtGreen, Info.GreenMask); Comps.New().Set(CtBlue, Info.BlueMask); if (Info.AlphaMask) Comps.New().Set(CtAlpha, Info.AlphaMask); Comps.Sort(ActualBits == 16 ? DownSort : UpSort); GColourSpaceBits Cs; Cs.All = 0; for (int i=0; iy-1; y>=0; y--) { if (In->Read(Buffer, ScanSize) == ScanSize) { uchar Mask = 0x80; uchar *d = Buffer; for (int x=0; xX(); x++) { pDC->Colour((*d & Mask) ? 1 : 0); pDC->Set(x, y); Mask >>= 1; if (!Mask) { Mask = 0x80; d++; } } } else { Status = IoError; break; } if (Meter) Meter->Value(pMem->y-1-y); } DeleteArray(Buffer); } break; } case 4: { uchar *Buffer = new uchar[ScanSize]; if (Buffer) { for (int y=pMem->y-1; y>=0; y--) { if (In->Read(Buffer, ScanSize) != ScanSize) { Status = IoError; break; } uchar *d = Buffer; for (int x=0; xX(); x++) { if (x & 1) { pDC->Colour(*d & 0xf); d++; } else { pDC->Colour(*d >> 4); } pDC->Set(x, y); } if (Meter) Meter->Value(pMem->y-1-y); } DeleteArray(Buffer); } break; } default: { GColourSpace DstCs = pDC->GetColourSpace(); for (int i=pMem->y-1; i>=0; i--) { uint8_t *Ptr = pMem->Base + (pMem->Line * i); ssize_t r = In->Read(Ptr, ScanSize); if (r != ScanSize) { Status = IoError; LgiTrace("%s:%i - Bmp read err, wanted %i, got %i.\n", _FL, ScanSize, r); break; } if (DstCs != SrcCs) { if (!LgiRopRgb(Ptr, DstCs, Ptr, SrcCs, pMem->x, false)) { Status = IoUnsupportedFormat; LgiTrace("%s:%i - Bmp had unsupported bit depth.\n", _FL); break; } } if (Meter) Meter->Value(pMem->y-1-i); } break; } } } Cr.ZOff(pDC->X()-1, pDC->Y()-1); pDC->ClipRgn(&Cr); pDC->Update(GDC_BITS_CHANGE|GDC_PAL_CHANGE); return Status; } template ssize_t SwapWrite(GStream *Out, T v) { #if 0 // __ORDER_BIG_ENDIAN__ uint8 *s = (uint8*)&v; uint8 *e = s + sizeof(v) - 1; while (s < e) { uint8 tmp = *s; *s++ = *e; *e-- = tmp; } #endif return Out->Write(&v, sizeof(v)); } GFilter::IoStatus GdcBmp::WriteImage(GStream *Out, GSurface *pDC) { GFilter::IoStatus Status = IoError; if (!pDC || !Out) return GFilter::IoError; BMP_FILE File; BMP_WININFO Info; GBmpMem *pMem = GetSurface(pDC); int Colours = (pMem->Cs == CsIndex8) ? 1 << 8 : 0; int UsedBits = GColourSpaceToBits(pMem->Cs); GPalette *Palette = pDC->Palette(); if (pMem->Cs == CsIndex8 && Palette) { int Size = Palette->GetSize(); int Bits = 8; for (int c=256; c; c>>=1, Bits--) { if (c & Size) { Colours = c; UsedBits = Bits; break; } } } if (!pMem || !pMem->x || !pMem->y) { return GFilter::IoError; } Out->SetSize(0); #define Wr(var) SwapWrite(Out, var) Info.Compression = UsedBits == 16 || UsedBits == 32 ? BI_BITFIELDS : BI_RGB; int BitFieldSize = Info.Compression == BI_BITFIELDS ? 16 : 0; Info.Size = 40 + BitFieldSize; File.Type[0] = 'B'; File.Type[1] = 'M'; File.OffsetToData = 14 + Info.Size; File.Size = File.OffsetToData + (int)(ABS(pMem->Line) * pMem->y); File.Reserved1 = 0; File.Reserved2 = 0; Info.Sx = pMem->x; Info.Sy = pMem->y; Info.Planes = 1; Info.Bits = UsedBits; Info.DataSize = 0; Info.XPels = 3000; Info.YPels = 3000; Info.ColoursUsed = Colours; Info.ColourImportant = Colours; bool Written = Out->Write(File.Type, 2) && Wr(File.Size) && Wr(File.Reserved1) && Wr(File.Reserved2) && Wr(File.OffsetToData); Written = Wr(Info.Size) && Wr(Info.Sx) && Wr(Info.Sy) && Wr(Info.Planes) && Wr(Info.Bits) && Wr(Info.Compression) && Wr(Info.DataSize) && Wr(Info.XPels) && Wr(Info.YPels) && Wr(Info.ColoursUsed) && Wr(Info.ColourImportant); if (Written) { if (pMem->Cs == CsIndex8) { int Done = 0; if (Palette) { GdcRGB *Start = (*Palette)[0]; if (Start) { Palette->SwapRAndB(); Out->Write(Start, sizeof(GdcRGB)*Palette->GetSize()); Palette->SwapRAndB(); Done += Palette->GetSize(); } } char Grey[4]; Grey[3] = 0; while (Done < Colours) { int C = Done * 256 / Colours; Grey[0] = C; Grey[1] = C; Grey[2] = C; Out->Write(Grey, 4); Done++; } } // Progress if (Meter) { Meter->SetDescription("scanlines"); Meter->SetLimits(0, pMem->y-1); } int Bytes = BMPWIDTH(pMem->x * UsedBits); Status = GFilter::IoSuccess; switch (UsedBits) { case 1: { uchar *Buffer = new uchar[Bytes]; if (Buffer) { for (int i=pMem->y-1; i>=0; i--) { uchar *Src = pMem->Base + (i * pMem->Line); // assemble the bits memset(Buffer, 0, Bytes); for (int x=0; xx; x++) { Buffer[x>>3] |= (Src[x] & 1) << (x & 3); } // write the bits if (Out->Write(Buffer, Bytes) != Bytes) { Status = IoError; break; } // update status if (Meter) Meter->Value(pMem->y-1-i); } DeleteArray(Buffer); } break; } case 4: { uchar *Buffer = new uchar[Bytes]; if (Buffer) { for (int i=pMem->y-1; i>=0; i--) { uchar *Src = pMem->Base + (i * pMem->Line); // assemble the nibbles for (int x=0; xx; x+=2) { Buffer[x>>1] = (Src[x]<<4) | (Src[x+1]&0x0f); } // write the line if (Out->Write(Buffer, Bytes) != Bytes) { Status = IoError; break; } // update status if (Meter) Meter->Value(pMem->y-1-i); } DeleteArray(Buffer); } break; } default: { if (UsedBits == 16) { System16BitPixel px[8]; ZeroObj(px); px[0].r = 0x1f; px[2].g = 0x3f; px[4].b = 0x1f; Out->Write(px, sizeof(px)); } else if (UsedBits == 32) { System32BitPixel px[4]; ZeroObj(px); px[0].r = 0xff; px[1].g = 0xff; px[2].b = 0xff; px[3].a = 0xff; Out->Write(px, sizeof(px)); } for (int i=pMem->y-1; i>=0; i--) { ssize_t w = Out->Write(pMem->Base + (i * pMem->Line), Bytes); if (w != Bytes) { Status = IoError; break; } if (Meter) Meter->Value(pMem->y-1-i); } break; } } } Out->Close(); return Status; } ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// // ICO file format class GdcIco : public GFilter { int TruncSize(int i) { if (i >= 64) return 64; if (i >= 32) return 32; return 16; } public: GdcIco(); Format GetFormat() { return FmtIco; } int GetCapabilites() { return FILTER_CAP_READ | FILTER_CAP_WRITE; } int GetImages() { return 1; } IoStatus ReadImage(GSurface *pDC, GStream *In); IoStatus WriteImage(GStream *Out, GSurface *pDC); bool GetVariant(const char *n, GVariant &v, char *a); }; class GdcIcoFactory : public GFilterFactory { bool CheckFile(const char *File, int Access, const uchar *Hint) { return (File) ? stristr(File, ".ico") != 0 : false; } GFilter *NewObject() { return new GdcIco; } } IcoFactory; GdcIco::GdcIco() { } bool GdcIco::GetVariant(const char *n, GVariant &v, char *a) { if (!stricmp(n, LGI_FILTER_TYPE)) { v = "Icon file"; } else if (!stricmp(n, LGI_FILTER_EXTENSIONS)) { v = "ICO"; } else return false; return true; } GFilter::IoStatus GdcIco::ReadImage(GSurface *pDC, GStream *In) { GFilter::IoStatus Status = IoError; int MyBits = 0; int16 Reserved_1; int16 Type; int16 Count; Read(In, &Reserved_1, sizeof(Reserved_1)); Read(In, &Type, sizeof(Type)); Read(In, &Count, sizeof(Count)); for (int Image = 0; Image < Count; Image++) { int8 Width; int8 Height; int8 ColorCount; int8 Reserved_2; int16 Planes; int16 BitCount; int32 BytesInRes; int32 ImageOffset; Read(In, &Width, sizeof(Width)); Read(In, &Height, sizeof(Height)); Read(In, &ColorCount, sizeof(ColorCount)); Read(In, &Reserved_2, sizeof(Reserved_2)); Read(In, &Planes, sizeof(Planes)); Read(In, &BitCount, sizeof(BitCount)); Read(In, &BytesInRes, sizeof(BytesInRes)); Read(In, &ImageOffset, sizeof(ImageOffset)); int64 BytesLeft = BytesInRes; int64 CurrentPos = In->GetPos(); In->SetPos(ImageOffset); BMP_WININFO Header; GdcRGB *Colours; uchar *XorBytes = 0; uchar *AndBytes = 0; int64 StartHdrPos = In->GetPos(); if (!Header.Read(*In)) return GFilter::IoError; BytesLeft -= In->GetPos() - StartHdrPos; if (!Header.Sx) Header.Sx = Width; if (!Header.Sy) Header.Sy = Height; if (!Header.Bits) { if (BitCount) Header.Bits = BitCount; else if (ColorCount) { for (int i=1; i<=8; i++) { if (1 << i >= ColorCount) { BitCount = Header.Bits = i; break; } } } } Colours = new GdcRGB[ColorCount]; if (Colours) { In->Read(Colours, sizeof(GdcRGB) * ColorCount); BytesLeft -= sizeof(GdcRGB) * ColorCount; } Header.Sy >>= 1; int XorSize = BMPWIDTH(Header.Sx * Header.Bits) * Height; int AndSize = BMPWIDTH(Header.Sx) * Height; if (BytesLeft >= XorSize) { XorBytes = new uchar[XorSize]; if (XorBytes) { In->Read(XorBytes, XorSize); BytesLeft -= XorSize; } } if (BytesLeft >= AndSize) { AndBytes = new uchar[AndSize]; if (AndBytes) { In->Read(AndBytes, AndSize); BytesLeft -= AndSize; } } /* LgiTrace("BytesInRes=%i, Xor=%i, And=%i, Pal=%i, BytesLeft=%i\n", BytesInRes, XorSize, AndSize, sizeof(GdcRGB) * Cols, BytesLeft); */ if (Colours && XorBytes && (Header.Bits > MyBits || Width > pDC->X() || Height > pDC->Y()) && pDC->Create(Width, Height, GBitsToColourSpace(MAX(8, Header.Bits)) )) { MyBits = Header.Bits; pDC->Colour(0, 24); pDC->Rectangle(); GPalette *Pal = new GPalette; if (Pal) { Pal->SetSize(ColorCount); memcpy((*Pal)[0], Colours, sizeof(GdcRGB) * ColorCount); Pal->SwapRAndB(); pDC->Palette(Pal, true); } if (AndBytes) { pDC->HasAlpha(true); } GSurface *pAlpha = pDC->AlphaDC(); int XorLine = XorSize / Height; int AndLine = AndSize / Height; for (int y=0; y>3] & (0x80 >> (x&7))) ? 0 : 0xff; } if (Src[x/8] & m) { Dest[x] = 1; } else { Dest[x] = 0; } m >>= 1; if (!m) m = 0x80; } Status = IoSuccess; break; } case 4: { for (int x=0; x>3] & (0x80 >> (x&7))) ? 0 : 0xff; } if (x & 1) { Dest[x] = Src[x>>1] & 0xf; } else { Dest[x] = Src[x>>1] >> 4; } } Status = IoSuccess; break; } case 8: { for (int x=0; x>3] & (0x80 >> (x&7))) ? 0 : 0xff; } Dest[x] = Src[x]; } Status = IoSuccess; break; } } } } else { LgiTrace("%s:%i - Header size error: %i != %i + %i, Img: %ix%i @ %i bits\n", _FL, Header.DataSize, XorSize, AndSize, Header.Sx, Header.Sy, Header.Bits); } DeleteArray(Colours); DeleteArray(XorBytes); DeleteArray(AndBytes); In->SetPos(CurrentPos); } return Status; } GFilter::IoStatus GdcIco::WriteImage(GStream *Out, GSurface *pDC) { GFilter::IoStatus Status = IoError; if (!pDC || pDC->GetBits() > 8 || !Out) return GFilter::IoError; Out->SetSize(0); int ActualBits = pDC->GetBits(); GPalette *Pal = pDC->Palette(); if (Pal) { if (Pal->GetSize() <= 2) { ActualBits = 1; } else if (Pal->GetSize() <= 16) { ActualBits = 4; } } // write file header int Colours = 1 << ActualBits; int16 Reserved_1 = 0; int16 Type = 1; int16 Count = 1; Write(Out, &Reserved_1, sizeof(Reserved_1)); Write(Out, &Type, sizeof(Type)); Write(Out, &Count, sizeof(Count)); // write directory list int8 Width = TruncSize(pDC->X()); int8 Height = TruncSize(pDC->Y()); int8 ColorCount = Colours; int8 Reserved_2 = 0; int16 Planes = 0; int16 BitCount = 0; int Line = (ActualBits * Width) / 8; int MaskLine = BMPWIDTH(Width/8); int32 BytesInRes = sizeof(BMP_WININFO) + (sizeof(GdcRGB) * Colours) + (Line * Height) + (MaskLine * Height); Write(Out, &Width, sizeof(Width)); Write(Out, &Height, sizeof(Height)); Write(Out, &ColorCount, sizeof(ColorCount)); Write(Out, &Reserved_2, sizeof(Reserved_2)); Write(Out, &Planes, sizeof(Planes)); Write(Out, &BitCount, sizeof(BitCount)); Write(Out, &BytesInRes, sizeof(BytesInRes)); int32 ImageOffset = (int32)(Out->GetPos() + sizeof(ImageOffset)); Write(Out, &ImageOffset, sizeof(ImageOffset)); // Write icon itself BMP_WININFO Header; LgiAssert(sizeof(Header) == 40); Header.Size = sizeof(Header); Header.Sx = Width; Header.Sy = Height * 2; Header.Planes = 1; Header.Bits = ActualBits; Header.Compression = 0; // BI_RGB; Header.DataSize = (Line * Height) + (MaskLine * Height); Header.XPels = 0; Header.YPels = 0; Header.ColoursUsed = 0; Header.ColourImportant = 0; Out->Write(&Header, sizeof(Header)); // Write palette if (Pal) { Pal->SwapRAndB(); Out->Write((*Pal)[0], sizeof(GdcRGB) * (int)(1 << ActualBits)); Pal->SwapRAndB(); } // Get background colour COLOUR Back = 0xffffffff; if (Props) { GVariant v; if (Props->GetValue(LGI_FILTER_BACKGROUND, v)) Back = v.CastInt32(); } // Write "Colour" bits int y; for (y=Height-1; y>=0; y--) { uchar *Src = (*pDC)[y]; // xor bits (colour) switch (ActualBits) { case 4: { uchar Dest = 0; for (int x=0; x=0; y--) { uchar *Src = (*pDC)[y]; uchar Dest = 0; uchar Mask = 0x80; int x; for (x = 0; x < Width; x++) { if (Src[x] == Back) Dest |= Mask; Mask >>= 1; if (!Mask) { Write(Out, &Dest, sizeof(Dest)); Dest = 0; Mask = 0x80; } } x = Width / 8; while (x < MaskLine) { uchar c = 0; Write(Out, &c, sizeof(c)); x++; } Status = IoSuccess; } return Status; } ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// // Run length encoded DC GdcRleDC::GdcRleDC() { Flags = GDC_RLE_COLOUR | GDC_RLE_READONLY; Length = 0; Alloc = 0; Data = 0; ScanLine = 0; Key = 0; pMem = new GBmpMem; if (pMem) { pMem->Base = 0; pMem->x = 0; pMem->y = 0; pMem->Cs = CsNone; pMem->Line = 0; pMem->Flags = 0; } } GdcRleDC::~GdcRleDC() { Empty(); } void GdcRleDC::Empty() { Length = 0; Alloc = 0; DeleteArray(Data); DeleteArray(ScanLine); } bool GdcRleDC::Create(int x, int y, GColourSpace cs, int flags) { bool Status = GMemDC::Create(x, y, cs, flags); if (Status) { Flags &= ~GDC_RLE_READONLY; } return Status; } bool GdcRleDC::CreateInfo(int x, int y, GColourSpace cs) { bool Status = false; DeleteObj(pMem); pMem = new GBmpMem; if (pMem) { pMem->x = x; pMem->y = y; pMem->Cs = cs; pMem->Base = 0; pMem->Line = 0; pMem->Flags = 0; Flags |= GDC_RLE_READONLY; if (cs == CsIndex8) { Flags |= GDC_RLE_MONO; } } return Status; } void GdcRleDC::ReadOnly(bool Read) { if (Read) { Flags |= GDC_RLE_READONLY; } else { if (pMem) { pMem->Base = new uchar[pMem->Line * pMem->y]; } else { // just in case... shouldn't ever happen // am I paranoid? // 'course :) Create(1, 1, System24BitColourSpace); } Flags &= ~GDC_RLE_READONLY; } } bool GdcRleDC::ReadOnly() { return (Flags & GDC_RLE_READONLY) != 0; } void GdcRleDC::Mono(bool Mono) { if (Mono) { Flags |= GDC_RLE_MONO; Flags &= ~GDC_RLE_COLOUR; } else { Flags &= ~GDC_RLE_MONO; Flags |= GDC_RLE_COLOUR; } } bool GdcRleDC::Mono() { return (Flags & GDC_RLE_MONO) != 0; } bool GdcRleDC::SetLength(ssize_t Len) { bool Status = true; if (Len + 1 > Alloc) { int NewAlloc = (Len + 0x4000) & (~0x3FFF); uchar *Temp = new uchar[NewAlloc]; if (Temp) { if (NewAlloc > Length) { memset(Temp + Length, 0, NewAlloc - Length); } memcpy(Temp, Data, Length); DeleteArray(Data); DeleteArray(ScanLine); Data = Temp; Length = Len; Alloc = NewAlloc; } else { Status = false; } } else { // TODO: resize Data to be smaller if theres a // considerable size change Length = Len; } return Status; } void GdcRleDC::Update(int UpdateFlags) { bool Error = false; if ( (UpdateFlags & GDC_BITS_CHANGE) && !(Flags & GDC_RLE_READONLY)) { Empty(); COLOUR Key = Get(0, 0); ssize_t Pos = 0; ulong PixelSize = GetBits() / 8; for (int y=0; !Error && y Length) { Error = true; } } } if (Error) { DeleteArray(ScanLine); } else { Status = true; } } return Status; } void GdcRleDC::Draw(GSurface *Dest, int Ox, int Oy) { int OriX, OriY; Dest->GetOrigin(OriX, OriY); Ox += OriX; Oy += OriY; if (Dest && Data) { bool ReBuildScanInfo = false; if (!ScanLine) ReBuildScanInfo = true; if (ScanLine && Data && ScanLine[0] != Data) ReBuildScanInfo = true; if (ReBuildScanInfo) { // need to rebuild scan line info if (!FindScanLines()) { // couldn't create scan line info so quit return; } } GRect S; S.ZOff(X()-1, Y()-1); GRect D; D = S; D.Offset(Ox, Oy); GRect DClip = D; GRect Temp = Dest->ClipRgn(); D.Bound(&Temp); if (DClip == D && (*Dest)[0]) { // no clipping needed int PixLen = Dest->GetBits() / 8; if (Flags & GDC_RLE_COLOUR) { if (Dest->GetBits() == GetBits()) { for (int y=0; yApplicator(); for (int y=0; ySetPtr(Ox+x, Oy+y); pDApp->Rectangle((int)Pixels, 1); x += Pixels; } } } } else if (DClip.Valid()) { // clip int PixLen = Dest->GetBits() / 8; if (Flags & GDC_RLE_COLOUR) { if (Dest->GetBits() == GetBits()) { for (int y=0; y= Temp.y1 && Fy < Temp.y2) // clip y { uchar *s = ScanLine[y]; uchar *d = (*Dest)[Fy]; for (int x=0; x 0) // clip x { memcpy( d + ((Fx - PreClipPixels) * PixLen), s + (PreClipPixels * PixLen), PixelsLeft * PixLen); } x += Pixels; s += Pixels * PixLen; } } } } } /* else if (Flags & GDC_RLE_MONO) { GApplicator *pDApp = Dest->Applicator(); for (int y=0; ySetPtr(Ox+x, Oy+y); pDApp->Rectangle(Pixels, 1); x += Pixels; } } } */ } } } ssize_t GdcRleDC::SizeOf() { return (sizeof(int) * 5) + Length; } bool GdcRleDC::Read(GFile &F) { Empty(); int32 Len = 0, x = 0, y = 0, bits = 0; int8 Monochrome; F >> Len; if (SetLength(Len)) { F >> Key; F >> x; F >> y; F >> bits; F >> Monochrome; Mono(Monochrome != 0); CreateInfo(x, y, GBitsToColourSpace(bits)); F.Read(Data, Len); return !F.GetStatus(); } return false; } bool GdcRleDC::Write(GFile &F) { if (Data) { char Monochrome = Mono(); F << Length; F << Key; F << X(); F << Y(); F << GetBits(); F << Monochrome; F.Write(Data, Length); return !F.GetStatus(); } return false; } void GdcRleDC::Move(GdcRleDC *pDC) { if (pDC) { Key = pDC->Key; Flags = pDC->Flags; Length = pDC->Length; Alloc = pDC->Alloc; Data = pDC->Data; ScanLine = pDC->ScanLine; pMem = pDC->pMem; pPalette = pDC->pPalette; pDC->pPalette = 0; pDC->pMem = 0; pDC->Length = 0; pDC->Alloc = 0; pDC->Data = 0; pDC->ScanLine = 0; } } //////////////////////////////////////////////////////////////////// GFilterFactory *GFilterFactory::First = 0; GFilterFactory::GFilterFactory() { // add this filter to the global list Next = First; First = this; } GFilterFactory::~GFilterFactory() { // delete from the global list if (First == this) { First = First->Next; } else { GFilterFactory *i = First; while (i->Next && i->Next != this) { i = i->Next; } if (i->Next == this) { i->Next = Next; } else { // we aren't in the list // thats bad LgiAssert(0); } } } GFilter *GFilterFactory::New(const char *File, int Access, const uchar *Hint) { GFilterFactory *i = First; while (i) { if (i->CheckFile(File, Access, Hint)) { return i->NewObject(); } i = i->Next; } return 0; } GFilter *GFilterFactory::NewAt(int n) { int Status = 0; GFilterFactory *i = First; while (i) { if (Status++ == n) { return i->NewObject(); } i = i->Next; } return 0; } int GFilterFactory::GetItems() { int Status = 0; GFilterFactory *i = First; while (i) { Status++; i = i->Next; } return Status; } ////////////////////////////////////////////////////////////////////////// /// Legacy wrapper that calls the new method GSurface *LoadDC(const char *Name, bool UseOSLoader) { return GdcD->Load(Name, UseOSLoader); } GSurface *GdcDevice::Load(const char *Name, bool UseOSLoader) { if (!FileExists(Name)) { // Loading non-file resource... #if WINNATIVE GAutoWString WName(Utf8ToWide(Name)); // a resource... lock and load gentlemen HRSRC hRsrc = FindResource(NULL, WName, RT_BITMAP); if (hRsrc) { int Size = SizeofResource(NULL, hRsrc); HGLOBAL hMem = LoadResource(NULL, hRsrc); if (hMem) { uchar *Ptr = (uchar*) LockResource(hMem); if (Ptr) { GClipBoard Clip(NULL); GSurface *pDC = Clip.ConvertFromPtr(Ptr); GlobalUnlock(hMem); return pDC; } } } #endif return NULL; } GFile File; if (!File.Open(Name, O_READ)) { LgiTrace("%s:%i - Couldn't open '%s' for reading.\n", _FL, Name); return NULL; } return Load(&File, Name, UseOSLoader); } GSurface *GdcDevice::Load(GStream *In, const char *Name, bool UseOSLoader) { if (!In) return NULL; GFilter::IoStatus FilterStatus = GFilter::IoError; int64 Size = In->GetSize(); if (Size <= 0) { return NULL; } GAutoPtr Hint(new uchar[16]); memset(Hint, 0, 16); if (In->Read(Hint, 16) == 0) { Hint.Reset(0); } int64 SeekResult = In->SetPos(0); if (SeekResult != 0) { LgiTrace("%s:%i - Seek failed after reading hint.\n", _FL); return NULL; } GAutoPtr Filter(GFilterFactory::New(Name, FILTER_CAP_READ, Hint)); GAutoPtr pDC; if (Filter && pDC.Reset(new GMemDC)) { FilterStatus = Filter->ReadImage(pDC, In); if (FilterStatus != GFilter::IoSuccess) { pDC.Reset(); LgiTrace("%s:%i - Filter couldn't cope with '%s'.\n", _FL, Name); } } if (UseOSLoader && !pDC) { #if LGI_SDL #elif defined MAC && !defined COCOA CGImageRef Img = NULL; if (FileExists(Name)) { CFURLRef FileUrl = CFURLCreateFromFileSystemRepresentation(0, (const UInt8*)Name, strlen(Name), false); if (!FileUrl) LgiTrace("%s:%i - CFURLCreateFromFileSystemRepresentation failed.\n", _FL); else { CGImageSourceRef Src = CGImageSourceCreateWithURL(FileUrl, 0); if (!Src) LgiTrace("%s:%i - CGImageSourceCreateWithURL failed.\n", _FL); else { Img = CGImageSourceCreateImageAtIndex(Src, 0, 0); if (!Img) LgiTrace("%s:%i - CGImageSourceCreateImageAtIndex failed.\n", _FL); CFRelease(Src); } CFRelease(FileUrl); } } else { GMemStream ms(In, 0, -1); CFDataRef data = CFDataCreate(NULL, (const UInt8 *)ms.GetBasePtr(), ms.GetSize()); CGImageSourceRef Src = CGImageSourceCreateWithData(data, NULL); if (!Src) LgiTrace("%s:%i - CGImageSourceCreateWithURL failed.\n", _FL); else { Img = CGImageSourceCreateImageAtIndex(Src, 0, 0); if (!Img) LgiTrace("%s:%i - CGImageSourceCreateImageAtIndex failed.\n", _FL); CFRelease(Src); } } if (Img) { size_t x = CGImageGetWidth(Img); size_t y = CGImageGetHeight(Img); // size_t bits = CGImageGetBitsPerPixel(Img); if (pDC.Reset(new GMemDC) && pDC->Create(x, y, System32BitColourSpace)) { pDC->Colour(0); pDC->Rectangle(); CGRect r = {{0, 0}, {(CGFloat)x, (CGFloat)y}}; CGContextDrawImage(pDC->Handle(), r, Img); } else { LgiTrace("%s:%i - pMemDC->Create failed.\n", _FL); pDC.Reset(); } CGImageRelease(Img); } #elif WINNATIVE && defined(_MSC_VER) /* char *Ext = LgiGetExtension((char*)Name); if (Ext && stricmp(Ext, "gif") && stricmp(Ext, "png")) */ { IImgCtx *Ctx = 0; HRESULT hr = CoCreateInstance(CLSID_IImgCtx, NULL, CLSCTX_INPROC_SERVER, IID_IImgCtx, (LPVOID*)&Ctx); if (SUCCEEDED(hr)) { GVariant Fn = Name; hr = Ctx->Load(Fn.WStr(), 0); if (SUCCEEDED(hr)) { SIZE Size = { -1, -1 }; ULONG State = 0; int64 Start = LgiCurrentTime(); while (LgiCurrentTime() - Start < 3000) // just in case it gets stuck.... { hr = Ctx->GetStateInfo(&State, &Size, false); if (SUCCEEDED(hr)) { if ((State & IMGLOAD_COMPLETE) || (State & IMGLOAD_STOPPED) || (State & IMGLOAD_ERROR)) { break; } else { LgiSleep(10); } } else break; } if (Size.cx > 0 && Size.cy > 0 && pDC.Reset(new GMemDC)) { if (pDC->Create(Size.cx, Size.cy, System24BitColourSpace)) { HDC hDC = pDC->StartDC(); if (hDC) { RECT rc = { 0, 0, pDC->X(), pDC->Y() }; Ctx->Draw(hDC, &rc); pDC->EndDC(); } if (pDC->GetBits() == 32) { // Make opaque int Op = pDC->Op(GDC_OR); pDC->Colour(Rgba32(0, 0, 0, 255), 32); pDC->Rectangle(); pDC->Op(GDC_SET); } } else pDC.Reset(); } } Ctx->Release(); } } #endif if (pDC) return pDC.Release(); } if (FilterStatus == GFilter::IoComponentMissing) { const char *c = Filter->GetComponentName(); LgiAssert(c != NULL); if (c) { GToken t(c, ","); for (int i=0; iGetOption(GDC_PROMOTE_ON_LOAD); if (PromoteTo > 0 && PromoteTo != pDC->GetBits()) { GAutoPtr pOld;; // GPalette *pPal = pDC->Palette(); pOld = pDC; pDC.Reset(new GMemDC); if (pOld && pDC && pDC->Create(pOld->X(), pOld->Y(), GBitsToColourSpace(PromoteTo))) { pDC->Blt(0, 0, pOld); pOld.Reset(); } } */ #ifdef BEOS else if (pDC->GetBits() == 8) { // Create remap table color_map *Map = (color_map*) system_colors(); GPalette *Palette = pDC->Palette(); if (Map && Palette) { char ReMap[256]; for (int i=0; i<256; i++) { GdcRGB *p = (*Palette)[i]; if (p) { COLOUR c = Rgb15(p->r, p->g, p->b); ReMap[i] = Map->index_map[c]; p->r = Map->color_list[i].red; p->g = Map->color_list[i].green; p->b = Map->color_list[i].blue; } else { ReMap[i] = 0; } } // Remap colours to BeOS palette for (int y=0; yY(); y++) { uchar *d = (*pDC)[y]; if (d) { for (int x=0; xX(); x++) { d[x] = ReMap[d[x]]; } } } } } #endif } #endif return pDC.Release(); } bool WriteDC(const char *Name, GSurface *pDC) { return GdcD->Save(Name, pDC); } +bool GdcDevice::Save(GStream *Out, GSurface *In, const char *FileType) +{ + if (!Out || !In || !FileType) + return false; + + GAutoPtr F(GFilterFactory::New(FileType, FILTER_CAP_WRITE, 0)); + if (!F) + { + LgiTrace("%s:%i - No filter for '%s'\n", _FL, FileType); + return false; + } + + return F->WriteImage(Out, In) == GFilter::IoSuccess; +} + bool GdcDevice::Save(const char *Name, GSurface *pDC) { - bool Status = false; + if (!Name || !pDC) + return false; - if (Name && pDC) + GFile File; + if (!File.Open(Name, O_WRITE)) { - GAutoPtr F(GFilterFactory::New(Name, FILTER_CAP_WRITE, 0)); - if (F) - { - GFile File; - if (File.Open(Name, O_WRITE)) - { - Status = F->WriteImage(&File, pDC) == GFilter::IoSuccess; - } - } - else - { - LgiTrace("No filter to write '%s'\n", Name); - } + LgiTrace("%s:%i - Can't open '%s'\n", _FL, Name); + return false; } - return Status; + return Save(&File, pDC, Name); } 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/Gradient.cpp b/src/common/Gdc2/Gradient.cpp --- a/src/common/Gdc2/Gradient.cpp +++ b/src/common/Gdc2/Gradient.cpp @@ -1,363 +1,363 @@ #include #include "Gdc2.h" class GGradient : public GApplicator { protected: int Sx, Sy; uchar *Ptr; int Bytes; COLOUR Background; int Angle; GRect Rect; public: const char *GetClass() { return "GGradient"; } bool SetSurface(GBmpMem *d, GPalette *p = 0, GBmpMem *a = 0) { Ptr = 0; Bytes = 0; Background = 0; Angle = 0; Rect.ZOff(0, 0); Sx = Sy = 0; if (d) { Dest = d; Pal = p; Alpha = 0; Ptr = d->Base; Bytes = GColourSpaceToBits(d->Cs) / 8; return true; } return false; } void SetPtr(int x, int y) { Sx = x; Sy = y; Ptr = Dest->Base + (y * Dest->Line) + (x * Bytes); } void IncX() { Ptr += Bytes; } void IncY() { Ptr += Dest->Line; } void IncPtr(int X, int Y) { Ptr += (Y * Dest->Line) + (X * Bytes); } void Set() { } COLOUR Get() { switch (Bytes) { case 2: { ushort *p = (ushort*)Ptr; return *p; break; } case 3: { uchar *p = (uchar*)Ptr; return Rgb24(p[2], p[1], p[0]); break; } case 4: { ulong *p = (ulong*)Ptr; return *p; break; } } return -1; } void VLine(int height) { } bool Blt(GBmpMem *Src, GPalette *SPal, GBmpMem *SrcAlpha = 0) { return false; } int GetVar(int Var) { switch (Var) { case GAPP_ANGLE: { return Angle; } case GAPP_BOUNDS: { return (int)&Rect; } case GAPP_BACKGROUND: { return Background; } } return -1; } int SetVar(int Var, int Value) { int o = GetVar(Var); switch (Var) { case GAPP_ANGLE: { Angle = Value; break; } case GAPP_BOUNDS: { if (Value) { Rect = *((GRect*)Value); } break; } case GAPP_BACKGROUND: { Background = Value; break; } } return o; } }; class GLinearGradient : public GGradient { public: void Rectangle(int X, int Y) { COLOUR Fore = CBit(24, c, GColourSpaceToBits(Dest->Cs)); int Fr = R24(Fore); int Fg = G24(Fore); int Fb = B24(Fore); COLOUR Back = CBit(24, Background, GColourSpaceToBits(Dest->Cs)); int Br = R24(Back); int Bg = G24(Back); int Bb = B24(Back); double Ang = LGI_DegToRad(Angle); while (Ang > LGI_PI) Ang -= LGI_PI; while (Ang < 0) Ang += LGI_PI; uchar *Div255 = Div255Lut; int a, oma, r, g, b; int Cx = (Rect.X() / 2); int Cy = (Rect.Y() / 2); #define Rx(x, y) ((cos(Ang)*(x-Cx)) - (sin(Ang)*(y-Cy))) #define Ry(x, y) ((sin(Ang)*(x-Cx)) + (cos(Ang)*(y-Cy))) GdcPt2 p[4] = { GdcPt2(0, 0), GdcPt2(Rect.X()-1, 0), GdcPt2(0, Rect.Y()-1), GdcPt2(Rect.X()-1, Rect.Y()-1) }; int MinX = 100000, MaxX = -100000; int MinY = 100000, MaxY = -100000; for (int i=0; i<4; i++) { p[i].x = Rx(p[i].x, p[i].y); p[i].y = Ry(p[i].x, p[i].y); MinX = MIN(MinX, p[i].x); MaxX = MAX(MaxX, p[i].x); MinY = MIN(MinY, p[i].y); MaxY = MAX(MaxY, p[i].y); } int Scale = MaxX - MinX; int Offset = -MinX; if (Scale == 0) { return; } for (int y=0; yCs)) { case 16: { for (int x=0; xLine; } } }; class GRadialGradient : public GGradient { public: const char *GetClass() { return "GRadialGradient"; } void Rectangle(int X, int Y) { COLOUR Fore = CBit(24, c, GColourSpaceToBits(Dest->Cs)); int Fr = R24(Fore); int Fg = G24(Fore); int Fb = B24(Fore); COLOUR Back = CBit(24, Background, GColourSpaceToBits(Dest->Cs)); int Br = R24(Back); int Bg = G24(Back); int Bb = B24(Back); uchar *Div255 = Div255Lut; int a, oma, r, g, b; double Cx = Rect.X() / 2; double Cy = Rect.Y() / 2; double Radius = sqrt(Cx*Cx + Cy*Cy); for (int y=0; yCs)) { case 16: { for (int x=0; xLine; } } }; class GGradientFactory : public GApplicatorFactory { public: GApplicator *Create(GColourSpace Cs, int Op) { if (Op == 5) { return new GLinearGradient; } else if (Op == 6) { return new GRadialGradient; } return 0; } } GradientFactory; 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_t Size; GVariant Value; 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_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_t Sig; uint16 Key; TnefFileInfo *Cur = 0; if (Tnef->Read(&Sig, sizeof(Sig)) && Tnef->Read(&Key, sizeof(Key)) && Sig == TNEF_SIGNATURE && Key > 0) { uint8_t b; bool Done = false; 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/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_t 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_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_t Txt[128]; - int InLen = strlen(Sp); + 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_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_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_t)digest[i]); // Base64 encode 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_t i=0; i", t[i]); if (i < t.Length()-1) strcat(Buffer, ",\r\n\t"); StrBuf.Push(Buffer); Buffer[0] = 0; } } else if (a->Name) { // Name and addr char *Mem = 0; char *Name = a->Name; if (Is8Bit(Name)) { Name = Mem = EncodeRfc2047(NewStr(Name), 0, CharsetPrefs); } if (strchr(Name, '\"')) sprintf_s(Buffer, sizeof(Buffer), "'%s' <%s>", Name, a->Addr); else sprintf_s(Buffer, sizeof(Buffer), "\"%s\" <%s>", Name, a->Addr); DeleteArray(Mem); } else if (a->Addr) { // Just addr sprintf_s(Buffer, sizeof(Buffer), "<%s>", a->Addr); } if (NextA) strcat(Buffer, ",\r\n\t"); StrBuf.Push(Buffer); a = NextA; } StrBuf.Push("\r\n"); Result = StrBuf.NewStr(); } return Result; } // This class implements a pipe that writes to a socket class SocketPipe : public GStringPipe { GSocketI *s; MailProtocolProgress *p; public: bool Status; SocketPipe(GSocketI *socket, MailProtocolProgress *progress) { s = socket; p = progress; Status = true; } ssize_t Read(void *Ptr, ssize_t Size, int Flags) { return false; } int64 SetSize(int64 Size) { if (p) { p->Start = LgiCurrentTime(); p->Range = (int)Size; return Size; } return -1; } ssize_t Write(const void *InPtr, ssize_t Size, int Flags) { char *Ptr = (char*)InPtr; char *e = Ptr + Size; while (Ptr < e) { ssize_t w = s->Write(Ptr, e - Ptr, 0); if (w > 0) { Ptr += w; if (p && p->Range && w > 0) p->Value += w; } else break; } return Ptr - (char*)InPtr; } }; bool MailSmtp::SendToFrom(List &To, AddressDescriptor *From, MailProtocolError *Err) { bool AddrOk = false; if (To.Length() == 0) { ErrMsgId = L_ERROR_ESMTP_NO_RECIPIENT; ErrMsgFmt = "No recipients to send to."; ErrMsgParam.Empty(); return false; } // send MAIL message if (From && ValidStr(From->Addr)) { sprintf_s(Buffer, sizeof(Buffer), "MAIL FROM: <%s>\r\n", From->Addr); } else { ErrMsgId = L_ERROR_ESMTP_NO_FROM; ErrMsgFmt = "No 'from' address in email."; ErrMsgParam.Empty(); return false; } VERIFY_RET_VAL(Write(0, true)); VERIFY_RET_VAL(ReadReply("250", 0, Err)); // send RCPT message AddrOk = true; List::I Recip = To.begin(); for (AddressDescriptor *a = *Recip; a; a = *++Recip) { char *Addr = ValidStr(a->Addr) ? a->Addr : a->Name; if (ValidStr(Addr)) { GToken Parts(Addr, ","); for (unsigned p=0; p\r\n", Parts[p]); VERIFY_RET_VAL(Write(0, true)); a->Status = ReadReply("25", 0, Err); AddrOk |= a->Status != 0; // at least one address is ok } } else if (Err) { ErrMsgId = L_ERROR_ESMTP_BAD_RECIPIENT; ErrMsgFmt = "Invalid recipient '%s'."; ErrMsgParam = Addr; } } return AddrOk; } GStringPipe *MailSmtp::SendData(MailProtocolError *Err) { // send DATA message sprintf_s(Buffer, sizeof(Buffer), "DATA\r\n"); VERIFY_RET_VAL(Write(0, true)); VERIFY_RET_VAL(ReadReply("354", 0, Err)); return new SocketPipe(Socket, Transfer); } GStringPipe *MailSmtp::SendStart(List &To, AddressDescriptor *From, MailProtocolError *Err) { return SendToFrom(To, From, Err) ? SendData(Err) : NULL; } bool MailSmtp::SendEnd(GStringPipe *m) { bool Status = false; SocketPipe *Msg = dynamic_cast(m); if (Msg) { // send message terminator and receive reply if (Msg->Status && Msg->Write((void*)"\r\n.\r\n", 5, 0)) { Status = ReadReply("250"); } // else // just close the connection on them // so nothing gets sent } DeleteObj(m); return Status; } /* bool MailSmtp::Send(MailMessage *Msg, bool Mime) { bool Status = false; if (Socket && Msg) { GStringPipe *Sink = SendStart(Msg->To, Msg->From); if (Sink) { // setup a gui progress meter to send the email, // the length is just a guesstimate as we won't know the exact // size until we encode it all, and I don't want it hanging around // in memory at once, so we encode and send on the fly. int Length = 1024 + (Msg->GetBody() ? strlen(Msg->GetBody()) : 0); for (FileDescriptor *f=Msg->FileDesc.First(); f; f=Msg->FileDesc.Next()) { Length += f->Sizeof() * 4 / 3; } // encode and send message for transport Msg->Encode(*Sink, 0, this); Status = SendEnd(Sink); } } return Status; } */ bool MailSmtp::Close() { if (Socket) { // send QUIT message sprintf_s(Buffer, sizeof(Buffer), "QUIT\r\n"); VERIFY_RET_VAL(Write(0, true)); VERIFY_RET_VAL(ReadReply("221")); LMutex::Auto Lock(&SocketLock, _FL); Socket.Reset(0); return true; } return false; } bool MailSmtp::ReadReply(const char *Str, GStringPipe *Pipe, MailProtocolError *Err) { bool Status = false; if (Socket && Str) { int Pos = 0; char *Start = Buffer; ZeroObj(Buffer); while (Pos < sizeof(Buffer)) { ssize_t Len = Socket->Read(Buffer+Pos, sizeof(Buffer)-Pos, 0); if (Len > 0) { char *Eol = strstr(Start, "\r\n"); while (Eol) { // wipe EOL chars *Eol++ = 0; *Eol++ = 0; // process if (Pipe) { if (Pipe->GetSize()) Pipe->Push("\n"); Pipe->Push(Start); } if (Start[3] == ' ') { // end of response if (!strncmp(Start, Str, strlen(Str))) { Status = true; } if (Err) { Err->Code = atoi(Start); char *Sp = strchr(Start, ' '); Err->ErrMsg = Sp ? Sp + 1 : Start; } // Log Log(Start, atoi(Start) >= 400 ? GSocketI::SocketMsgError : GSocketI::SocketMsgReceive); // exit loop Pos = sizeof(Buffer); break; } else { Log(Start, GSocketI::SocketMsgReceive); // more lines follow Start = Eol; Eol = strstr(Start, "\r\n"); } } Pos += Len; } else break; } if (!Status) { SetError(L_ERROR_GENERIC, "Error: %s", Buffer); } } return Status; } ////////////////////////////////////////////////////////////////////////////////////////////////// class Mail2Folder : public GStringPipe { char File[256]; GFile F; public: Mail2Folder(char *Path, List &To) { do { char n[32]; sprintf_s(n, sizeof(n), "%u.mail", LgiRand()); LgiMakePath(File, sizeof(File), Path, n); } while (FileExists(File)); if (F.Open(File, O_WRITE)) { F.Print("Forward-Path: "); int i = 0; for (AddressDescriptor *a=To.First(); a; a=To.Next()) { a->Status = true; GToken Addrs(a->Addr, ","); for (unsigned n=0; n", Addrs[n]); } } F.Print("\r\n"); } } ~Mail2Folder() { F.Close(); } ssize_t Read(void *Buffer, ssize_t Size, int Flags = 0) { return F.Read(Buffer, Size, Flags); } ssize_t Write(const void *Buffer, ssize_t Size, int Flags = 0) { return F.Write(Buffer, Size, Flags); } }; class MailPostFolderPrivate { public: char *Path; MailPostFolderPrivate() { Path = 0; } ~MailPostFolderPrivate() { DeleteArray(Path); } }; MailSendFolder::MailSendFolder(char *Path) { d = new MailPostFolderPrivate; d->Path = NewStr(Path); } MailSendFolder::~MailSendFolder() { DeleteObj(d); } bool MailSendFolder::Open(GSocketI *S, const char *RemoteHost, const char *LocalDomain, const char *UserName, const char *Password, int Port, int Flags) { return DirExists(d->Path); } bool MailSendFolder::Close() { return true; } GStringPipe *MailSendFolder::SendStart(List &To, AddressDescriptor *From, MailProtocolError *Err) { return new Mail2Folder(d->Path, To); } bool MailSendFolder::SendEnd(GStringPipe *Sink) { DeleteObj(Sink); return true; } ////////////////////////////////////////////////////////////////////////////////////////////////// class MailItem { public: char *File; bool Delete; MailItem(char *f) { File = NewStr(f); Delete = false; } ~MailItem() { DeleteArray(File); } }; class MailReceiveFolderPrivate { public: char *Path; List Mail; MailReceiveFolderPrivate() { Path = 0; } ~MailReceiveFolderPrivate() { DeleteArray(Path); Mail.DeleteObjects(); } void Empty() { for (MailItem *m = Mail.First(); m; m = Mail.Next()) { if (m->Delete) { FileDev->Delete(m->File, false); } } Mail.DeleteObjects(); } }; MailReceiveFolder::MailReceiveFolder(char *Path) { d = new MailReceiveFolderPrivate; d->Path = NewStr(Path); } MailReceiveFolder::~MailReceiveFolder() { DeleteObj(d); } bool MailReceiveFolder::Open(GSocketI *S, const char *RemoteHost, int Port, const char *User, const char *Password, GDom *SettingStore, int Flags) { // We don't use the socket so just free it here... DeleteObj(S); // Argument check if (!DirExists(d->Path)) return false; GDirectory Dir; // Loop through files, looking for email for (int b = Dir.First(d->Path, LGI_ALL_FILES); b; b = Dir.Next()) { if (!Dir.IsDir()) { if (MatchStr("*.eml", Dir.GetName()) || MatchStr("*.mail", Dir.GetName())) { char p[300]; Dir.Path(p, sizeof(p)); d->Mail.Insert(new MailItem(p)); } } } return true; } bool MailReceiveFolder::Close() { d->Empty(); return true; } int MailReceiveFolder::GetMessages() { return (int)d->Mail.Length(); } bool MailReceiveFolder::Receive(GArray &Trans, MailCallbacks *Callbacks) { bool Status = false; for (unsigned i=0; iStream) { t->Status = false; MailItem *m = d->Mail[t->Index]; if (m) { GFile i; if (i.Open(m->File, O_READ)) { GCopyStreamer c; if (c.Copy(&i, t->Stream)) { Status = t->Status = true; if (Callbacks && Callbacks->OnReceive) { Callbacks->OnReceive(t, Callbacks->CallbackData); } } } } } } return Status; } bool MailReceiveFolder::Delete(int Message) { MailItem *m = d->Mail[Message]; if (m) { m->Delete = true; return false; } return false; } int MailReceiveFolder::Sizeof(int Message) { MailItem *m = d->Mail[Message]; if (m) { return (int)LgiFileSize(m->File); } return 0; } bool MailReceiveFolder::GetUid(int Message, char *Id, int IdLen) { if (Id) { MailItem *m = d->Mail[Message]; if (m) { char *s = strrchr(m->File, DIR_CHAR); if (s++) { char *e = strchr(s, '.'); if (!e) e = s + strlen(s); ssize_t Len = e - s; memcpy(Id, s, Len); Id[Len] = 0; return true; } } } return false; } bool MailReceiveFolder::GetUidList(List &Id) { bool Status = false; for (int i=0; iMail.Length(); i++) { char Uid[256]; if (GetUid(i, Uid, sizeof(Uid))) { Status = true; Id.Insert(NewStr(Uid)); } else { Id.DeleteArrays(); Status = false; break; } } return Status; } char *MailReceiveFolder::GetHeaders(int Message) { MailItem *m = d->Mail[Message]; if (m) { GFile i; if (i.Open(m->File, O_READ)) { GStringPipe o; GCopyStreamer c; GLinePrefix e("", false); if (c.Copy(&i, &o, &e)) { return o.NewStr(); } } } return 0; } ////////////////////////////////////////////////////////////////////////////////////////////////// MailPop3::MailPop3() { End = "\r\n.\r\n"; Marker = End; Messages = -1; } MailPop3::~MailPop3() { } int MailPop3::GetMessages() { if (Messages < 0) { if (Socket && Socket->IsOpen()) { // see how many messages there are VERIFY_ONERR(Write("STAT\r\n", true)); VERIFY_ONERR(ReadReply()); Messages = GetInt(); } else LgiAssert(!"No socket to get message count."); } CleanUp: return Messages; } int MailPop3::GetInt() { char Buf[32]; char *Start = strchr(Buffer, ' '); if (Start) { Start++; char *End = strchr(Start, ' '); if (End) { int Len = (int) (End - Start); memcpy(Buf, Start, Len); Buf[Len] = 0; return atoi(Buf); } } return 0; } bool MailPop3::ReadReply() { bool Status = false; if (Socket) { int Pos = 0; ZeroObj(Buffer); do { ssize_t Result = Socket->Read(Buffer+Pos, sizeof(Buffer)-Pos, 0); if (Result <= 0) // an error? { // Leave the loop... break; } Pos += Result; } while ( !strstr(Buffer, "\r\n") && sizeof(Buffer)-Pos > 0); Status = (Buffer[0] == '+') && strstr(Buffer, "\r\n"); char *Cr = strchr(Buffer, '\r'); if (Cr) *Cr = 0; if (ValidStr(Buffer)) Log(Buffer, (Status) ? GSocketI::SocketMsgReceive : GSocketI::SocketMsgError); if (Cr) *Cr = '\r'; if (!Status) { SetError(L_ERROR_GENERIC, "Error: %s", Buffer); } } return Status; } bool MailPop3::ListCmd(const char *Cmd, LHashTbl, bool> &Results) { sprintf_s(Buffer, sizeof(Buffer), "%s\r\n", Cmd); if (!Write(0, true)) return false; char *b = Buffer; ssize_t r; while ((r = Socket->Read(b, sizeof(Buffer)-(b-Buffer))) > 0) { b += r; if (strnstr(Buffer, "\r\n.\r\n", b-Buffer)) break; } if (r <= 0) return false; GToken t(Buffer, "\r\n"); for (unsigned i=1; iGetValue("IsSSL", IsSsl) && IsSsl.CastInt32()) Port = POP3_SSL_PORT; else Port = POP3_PORT; } strcpy_s(Str, sizeof(Str), RemoteHost); char *Colon = strchr(Str, ':'); if (Colon) { *Colon = 0; Colon++; Port = atoi(Colon); } if (S && User && Password && (Server = TrimStr(Str))) { S->SetTimeout(30 * 1000); ReStartConnection: if (SocketLock.Lock(_FL)) { Socket.Reset(S); SocketLock.Unlock(); } if (Socket && Socket->Open(Server, Port) && ReadReply()) { GVariant NoAPOP = false; if (SettingStore) SettingStore->GetValue(OPT_Pop3NoApop, NoAPOP); if (!NoAPOP.CastInt32()) { char *s = strchr(Buffer + 3, '<'); if (s) { char *e = strchr(s + 1, '>'); if (e) { Apop = NewStr(s, e - s + 1); } } } // login bool Authed = false; char *user = (char*) LgiNewConvertCp("iso-8859-1", User, "utf-8"); char *pass = (char*) LgiNewConvertCp("iso-8859-1", Password, "utf-8"); if (user && (pass || SecureAuth)) { bool SecurityError = false; if (TestFlag(Flags, MAIL_USE_STARTTLS)) { strcpy_s(Buffer, sizeof(Buffer), "STARTTLS\r\n"); VERIFY_RET_VAL(Write(0, true)); VERIFY_RET_VAL(ReadReply()); GVariant v; if (Socket->SetValue(GSocket_Protocol, v="SSL")) { Flags &= ~MAIL_USE_STARTTLS; } else { SecurityError = true; } } if (!SecurityError && Apop) // GotKey, not implemented { // using encrypted password unsigned char Digest[16]; char HexDigest[33]; // append password char Key[256]; sprintf_s(Key, sizeof(Key), "%s%s", Apop, pass); ZeroObj(Digest); MDStringToDigest(Digest, Key); for (int i = 0; i < 16; i++) sprintf_s(HexDigest + (i*2), 3, "%2.2x", Digest[i]); HexDigest[32] = 0; sprintf_s(Buffer, sizeof(Buffer), "APOP %s %s\r\n", user, HexDigest); VERIFY_ONERR(Write(0, true)); Authed = ReadReply(); if (!Authed) { DeleteArray(Apop); GVariant NoAPOP = true; if (SettingStore) SettingStore->SetValue(OPT_Pop3NoApop, NoAPOP); S->Close(); goto ReStartConnection; } } if (!SecurityError && SecureAuth) { LHashTbl, bool> AuthTypes, Capabilities; if (ListCmd("AUTH", AuthTypes) && ListCmd("CAPA", Capabilities)) { if (AuthTypes.Find("GSSAPI")) { sprintf_s(Buffer, sizeof(Buffer), "AUTH GSSAPI\r\n"); VERIFY_ONERR(Write(0, true)); VERIFY_ONERR(ReadReply()); // http://www.faqs.org/rfcs/rfc2743.html } } } else if (!SecurityError && !Authed) { // have to use non-key method sprintf_s(Buffer, sizeof(Buffer), "USER %s\r\n", user); VERIFY_ONERR(Write(0, true)); VERIFY_ONERR(ReadReply()); sprintf_s(Buffer, sizeof(Buffer), "PASS %s\r\n", pass); VERIFY_ONERR(Write(0, false)); Log("PASS *******", GSocketI::SocketMsgSend); Authed = ReadReply(); } DeleteArray(user); DeleteArray(pass); } if (Authed) { Status = true; } else { if (SocketLock.Lock(_FL)) { Socket.Reset(0); SocketLock.Unlock(); } LgiTrace("%s:%i - Failed auth.\n", _FL); } } else Error(_FL, "Failed to open socket to %s:%i and read reply.\n", Server, Port); } else Error(_FL, "No user/pass.\n"); } CleanUp: DeleteArray(Apop); DeleteArray(Server); return Status; } bool MailPop3::MailIsEnd(char *Ptr, ssize_t Len) { for (char *c = Ptr; Len-- > 0; c++) { if (*c != *Marker) { Marker = End; } if (*c == *Marker) { Marker++; if (!*Marker) { return true; } } } return false; } bool MailPop3::Receive(GArray &Trans, MailCallbacks *Callbacks) { bool Status = false; if (Trans.Length() > 0 && Socket) { for (unsigned n = 0; nIndex; GStreamI *Msg = Trans[n]->Stream; if (Msg) { int Size = 0; // Transfer is not null when the caller wants info on the bytes comming in if (Transfer || Callbacks) { // get message size sprintf_s(Buffer, sizeof(Buffer), "LIST %i\r\n", Message + 1); VERIFY_RET_VAL(Write(0, true)); VERIFY_RET_VAL(ReadReply()); char *s = strchr(Buffer, ' '); if (s) { s = strchr(s+1, ' '); if (s) { Size = atoi(s); } } } MailSrcStatus Action = DownloadAll; int TopLines = 100; if (Callbacks && Callbacks->OnSrc) { Action = Callbacks->OnSrc(Trans[n], Size, &TopLines, Callbacks->CallbackData); } if (Action == DownloadAbort) { break; } if (Action == DownloadAll || Action == DownloadTop) { if (Action == DownloadAll) { sprintf_s(Buffer, sizeof(Buffer), "RETR %i\r\n", Message + 1); } else { sprintf_s(Buffer, sizeof(Buffer), "TOP %i %i\r\n", Message + 1, TopLines); } VERIFY_RET_VAL(Write(0, true)); GLinePrefix End(".\r\n"); if (Transfer) { Transfer->Value = 0; Transfer->Range = Size; Transfer->Start = LgiCurrentTime(); } // Read status line ZeroObj(Buffer); int Used = 0; bool Ok = false; bool Finished = false; int64 DataPos = 0; while (Socket->IsOpen()) { ssize_t r = Socket->Read(Buffer+Used, sizeof(Buffer)-Used-1, 0); if (r > 0) { DeNullText(Buffer + Used, r); if (Transfer) { Transfer->Value += r; } char *Eol = strchr(Buffer, '\n'); if (Eol) { Eol++; Ok = Buffer[0] == '+'; if (Ok) { // Log(Buffer, GSocketI::SocketMsgReceive); // The Buffer was zero'd at the beginning garrenteeing // NULL termination size_t Len = strlen(Eol); ssize_t EndPos = End.IsEnd(Eol, Len); if (EndPos >= 0) { Msg->Write(Eol, EndPos - 3); Status = Trans[n]->Status = true; Finished = true; } else { Msg->Write(Eol, Len); DataPos += Len; } } else { Log(Buffer, GSocketI::SocketMsgError); Finished = true; } break; } Used += r; } else break; } if (!Finished) { if (Ok) { // Read rest of message while (Socket->IsOpen()) { ssize_t r = Socket->Read(Buffer, sizeof(Buffer), 0); if (r > 0) { DeNullText(Buffer, r); if (Transfer) { Transfer->Value += r; } ssize_t EndPos = End.IsEnd(Buffer, r); if (EndPos >= 0) { ssize_t Actual = EndPos - DataPos - 3; if (Actual > 0) { ssize_t w = Msg->Write(Buffer, Actual); LgiAssert(w == Actual); } // else the end point was in the last buffer Status = Trans[n]->Status = true; break; } else { ssize_t w = Msg->Write(Buffer, r); LgiAssert(w == r); DataPos += r; } } else { break; } } if (!Status) { LgiTrace("%s:%i - Didn't get end-of-mail marker.\n", _FL); } } else { LgiTrace("%s:%i - Didn't get Ok.\n", _FL); break; } } if (Callbacks && Callbacks->OnReceive) { Callbacks->OnReceive(Trans[n], Callbacks->CallbackData); } if (Transfer) { Transfer->Empty(); } } else { Trans[n]->Oversize = Status = true; } if (Items) { Items->Value++; } } else { LgiTrace("%s:%i - No stream.\n", _FL); } } } else { LgiTrace("%s:%i - Arg check failed, len=%p, sock=%p.\n", _FL, Trans.Length(), Socket.Get()); } return Status; } bool MailPop3::GetSizes(GArray &Sizes) { if (Socket) { strcpy_s(Buffer, sizeof(Buffer), (char*)"LIST\r\n"); VERIFY_RET_VAL(Write(0, true)); char *s = 0; if (ReadMultiLineReply(s)) { GToken l(s, "\r\n"); DeleteArray(s); for (unsigned i=0; i 0; } int MailPop3::Sizeof(int Message) { int Size = 0; if (Socket) { sprintf_s(Buffer, sizeof(Buffer), "LIST %i\r\n", Message + 1); VERIFY_RET_VAL(Write(0, true)); VERIFY_RET_VAL(ReadReply()); char *s = strchr(Buffer, ' '); if (s) { s = strchr(s+1, ' '); if (s) { Size = atoi(s); } } } return Size; } bool MailPop3::Delete(int Message) { if (Socket) { sprintf_s(Buffer, sizeof(Buffer), "DELE %i\r\n", Message + 1); VERIFY_RET_VAL(Write(0, true)); VERIFY_RET_VAL(ReadReply()); return true; } return false; } bool MailPop3::GetUid(int Index, char *Id, int IdLen) { if (Socket && Id) { sprintf_s(Buffer, sizeof(Buffer), "UIDL %i\r\n", Index + 1); VERIFY_RET_VAL(Write(0, true)); VERIFY_RET_VAL(ReadReply()); char *Space = strchr(Buffer, ' '); if (Space) { Space = strchr(Space+1, ' '); if (Space) { for (char *s = Space+1; *s; s++) { if (*s == '\r' || *s == '\n') { *s = 0; break; } } strcpy_s(Id, IdLen, Space+1); return true; } } } return false; } bool MailPop3::GetUidList(List &Id) { bool Status = false; if (Socket) { char *Str = 0; sprintf_s(Buffer, sizeof(Buffer), "UIDL\r\n"); VERIFY_RET_VAL(Write(0, true)); VERIFY_RET_VAL(ReadMultiLineReply(Str)); if (Str) { Status = true; GToken T(Str, "\r\n"); for (unsigned i=0; iRead(Buffer, sizeof(Buffer), 0); if (ReadLen > 0 && Buffer[0] == '+') { // positive response char *Eol = strchr(Buffer, '\n'); if (Eol) { char *Ptr = Eol + 1; ReadLen -= Ptr-Buffer; memmove(Buffer, Ptr, ReadLen); Temp.Write((uchar*) Buffer, ReadLen); while (!MailIsEnd(Buffer, ReadLen)) { ReadLen = Socket->Read(Buffer, sizeof(Buffer), 0); if (ReadLen > 0) { Temp.Write((uchar*) Buffer, ReadLen); } else break; } int Len = (int)Temp.GetSize(); Str = new char[Len+1]; if (Str) { Temp.Read((uchar*)Str, Len); Str[Len] = 0; Status = true; } } } } return Status; } bool MailPop3::Close() { if (Socket) { // logout VERIFY_RET_VAL(Write("QUIT\r\n", true)); // 2 sec timeout, we don't really care about the server's response Socket->SetTimeout(2000); ReadReply(); if (SocketLock.Lock(_FL)) { Socket.Reset(0); SocketLock.Unlock(); } Messages = 0; return true; } return false; } ////////////////////////////////////////////////////////////////////////////////////////////////// /* MailMessage::MailMessage() { Log = this; From = 0; Reply = 0; InternetHeader = 0; Priority = MAIL_PRIORITY_NORMAL; Text = 0; TextCharset = 0; Html = 0; HtmlCharset = 0; MarkColour = -1; DispositionNotificationTo = false; Raw = 0; } MailMessage::~MailMessage() { Empty(); DeleteObj(From); DeleteObj(Reply); DeleteObj(Raw); } int MailMessage::Write(const void *Ptr, int Size, int Flags) { LgiTrace("%.*s", Size, Ptr); return Size; } void MailMessage::Empty() { Subject.Reset(); MessageID.Reset(); References.Reset(); FwdMsgId.Reset(); BounceMsgId.Reset(); DeleteArray(InternetHeader); DeleteArray(Text); DeleteArray(TextCharset); DeleteArray(Html); DeleteArray(HtmlCharset); To.DeleteObjects(); FileDesc.DeleteObjects(); } char *MailMessage::GetBody() { return Text; } char *MailMessage::GetBodyCharset() { return TextCharset; } bool MailMessage::SetBodyCharset(const char *Cs) { DeleteArray(TextCharset); TextCharset = NewStr(Cs); return true; } bool MailMessage::SetBody(const char *Txt, int Bytes, bool Copy, const char *Cs) { if (Txt != Text) { DeleteArray(Text); Text = Copy ? NewStr(Txt, Bytes) : (char*)Txt; if (Txt && !Text) return false; } if (Cs != TextCharset) { DeleteArray(TextCharset); if (!(TextCharset = NewStr(Cs))) return false; } return true; } char *MailMessage::GetHtml() { return Html; } char *MailMessage::GetHtmlCharset() { return HtmlCharset; } bool MailMessage::SetHtmlCharset(const char *Cs) { DeleteArray(HtmlCharset); HtmlCharset = NewStr(Cs); return true; } bool MailMessage::SetHtml(const char *Txt, int Bytes, bool Copy, const char *Cs) { if (Txt != Html) { DeleteArray(Html); Html = Copy ? NewStr(Txt, Bytes) : (char*)Txt; if (Txt && !Html) return false; } if (Cs != HtmlCharset) { DeleteArray(HtmlCharset); if (!(HtmlCharset = NewStr(Cs))) return false; } return true; } int MailMessage::EncodeBase64(GStreamI &Out, GStreamI &In) { int64 Start = LgiCurrentTime(); int Status = 0; int BufSize = 4 << 10; char *InBuf = new char[BufSize]; char *OutBuf = new char[BufSize]; if (InBuf && OutBuf) { int InLen = (int)In.GetSize(); // file remaining to read int InUsed = 0; int InDone = 0; int OutUsed = 0; do { if (InUsed - InDone < 256 && InLen > 0) { // Move any bit left over down to the start memmove(InBuf, InBuf + InDone, InUsed - InDone); InUsed -= InDone; InDone = 0; // Read in as much data as we can int Max = min(BufSize-InUsed, InLen); int r = In.Read(InBuf + InUsed, Max); if (r <= 0) break; // FilePos += r; InUsed += r; InLen -= r; } if (OutUsed > BufSize - 256) { int w = Out.Write(OutBuf, OutUsed); if (w > 0) { OutUsed = 0; Status += w; } else { break; } } int OutLen = ConvertBinaryToBase64( OutBuf + OutUsed, 76, (uchar*)InBuf + InDone, InUsed - InDone); int In = OutLen * 3 / 4; InDone += In; OutUsed += OutLen; OutBuf[OutUsed++] = '\r'; OutBuf[OutUsed++] = '\n'; } while (InDone < InUsed); if (OutUsed > 0) { int w = Out.Write(OutBuf, OutUsed); if (w >= 0) Status += w; w = Out.Write((char*)"\r\n", 2); if (w >= 0) Status += w; } #if 0 double Sec = (double)((int64)LgiCurrentTime() - Start) / 1000.0; double Kb = (double)FileDes->Sizeof() / 1024.0; LgiTrace("rate: %ikb/s\n", (int)(Kb / Sec)); #endif } else Log->Print("%s:%i - Error allocating buffers\n", _FL); DeleteArray(InBuf); DeleteArray(OutBuf); return Status; } int MailMessage::EncodeQuotedPrintable(GStreamI &Out, GStreamI &In) { int Status = 0; char OutBuf[100], InBuf[1024]; int ch = 0; int InLen; // Read the input data one chunk at a time while ((InLen = In.Read(InBuf, sizeof(InBuf))) > 0) { // For all the input bytes we just got for (char *s = InBuf; s - InBuf < InLen; ) { if (*s == '\n') { ch += sprintf_s(OutBuf+ch, sizeof(OutBuf)-ch, "\r\n"); int w = Out.Write(OutBuf, ch); if (w <= 0) break; ch = 0; Status += w; } else if (*s == '.') { // If the '.' character happens to fall at the // end of a paragraph and gets pushed onto the next line it // forms the magic \r\n.\r\n sequence that ends an SMTP data // session. Which is bad. The solution taken here is to // hex encode it if it falls at the start of the line. // Otherwise allow it through unencoded. if (ch == 0) { ch += sprintf_s(OutBuf+ch, sizeof(OutBuf)-ch, "=%2.2X", (uchar)*s); } else { OutBuf[ch++] = *s; } } else if (*s & 0x80 || *s == '=') { // Require hex encoding of 8-bit chars and the equals itself. ch += sprintf_s(OutBuf+ch, sizeof(OutBuf)-ch, "=%2.2X", (uchar)*s); } else if (*s != '\r') { OutBuf[ch++] = *s; } s++; if (ch > 73) { // time for a new line. ch += sprintf_s(OutBuf+ch, sizeof(OutBuf)-ch, "=\r\n"); int w = Out.Write(OutBuf, ch); if (w <= 0) break; ch = 0; Status += w; } } } ch += sprintf_s(OutBuf+ch, sizeof(OutBuf)-ch, "\r\n"); int w = Out.Write(OutBuf, ch); if (w > 0) Status += w; return Status; } int MailMessage::EncodeText(GStreamI &Out, GStreamI &In) { int Status = 0; char InBuf[4096]; int InLen, InUsed = 0; const char *Eol = "\r\n"; while ((InLen = In.Read(InBuf+InUsed, sizeof(InBuf)-InUsed)) > 0) { InUsed += InLen; char *s; for (s = InBuf; s - InBuf < InUsed; ) { // Do we have a complete line? int RemainingBytes = InUsed - (s - InBuf); char *NewLine = strnchr(s, '\n', RemainingBytes); if (NewLine) { // Yes... write that out. int Len = NewLine - s; if (Len > 0 && s[Len-1] == '\r') Len--; if (Len == 1 && s[0] == '.') { // this removes the sequence ".\n" // which is the END OF MAIL in the SMTP protocol. int w = Out.Write((char*)". ", 2); if (w <= 0) break; Status += w; } else if (Len) { int w = Out.Write(s, Len); if (w <= 0) break; Status += w; } int w = Out.Write(Eol, 2); if (w <= 0) break; s = NewLine + 1; Status += w; } else { // No... move the data down to the start of the buffer memmove(InBuf, s, RemainingBytes); InUsed = RemainingBytes; s = 0; break; } } if (s) InUsed -= s - InBuf; } if (InUsed) { int w = Out.Write(InBuf, InUsed); if (w > 0) Status += w; w = Out.Write(Eol, 2); if (w > 0) Status += w; } return Status; } #define SEND_BUF_SIZE (16 << 10) class SendBuf : public GStream { GStreamI *Out; int Used; uchar Buf[SEND_BUF_SIZE]; public: bool Status; SendBuf(GStreamI *out) { Out = out; Used = 0; Status = true; } ~SendBuf() { Flush(); } int64 GetSize() { return Used; } int64 SetSize(int64 Size) { return Out->SetSize(Size); } int Read(void *Buffer, int Size, int Flags = 0) { return -1; } void Flush() { if (Used > 0) { int w = Out->Write(Buf, Used, 0); if (w < Used) { Status = false; } Used = 0; } } int Write(const void *Buffer, int Size, int Flags = 0) { int64 w = 0; uchar *Ptr = (uchar*)Buffer; while (Ptr && Size > 0) { if (Size + Used >= SEND_BUF_SIZE) { int Chunk = SEND_BUF_SIZE - Used; memcpy(Buf + Used, Ptr, Chunk); int s = Out->Write(Buf, SEND_BUF_SIZE, 0); if (s < SEND_BUF_SIZE) { return -1; break; } Ptr += Chunk; Size -= Chunk; w += Chunk; Used = 0; } else { memcpy(Buf + Used, Ptr, Size); Used += Size; w += Size; Size = 0; } } return (int)w; } }; // Encode the whole email bool MailMessage::Encode(GStreamI &Out, GStream *HeadersSink, MailProtocol *Protocol, bool Mime) { GStringPipe p; bool Status = EncodeHeaders(p, Protocol, Mime); if (Status) { int Len = (int)p.GetSize(); char *Headers = p.NewStr(); if (HeadersSink) { HeadersSink->Write(Headers, Len); } else { InternetHeader = NewStr(Headers); } if (Headers && Out.Write(Headers, Len)) { SendBuf *Buf = new SendBuf(&Out); if (Buf) { Status = EncodeBody(*Buf, Protocol, Mime); if (Status) { Buf->Flush(); Status = Buf->Status; if (!Status) Log->Print("%s:%i - Buffer status failed.\n", _FL); } else Log->Print("%s:%i - EncodeBody failed.\n", _FL); DeleteObj(Buf); } } else Log->Print("%s:%i - Headers output failed.\n", _FL); DeleteArray(Headers); } else Log->Print("%s:%i - EncodeHeaders failed.\n", _FL); return Status; } #define WriteOutput() \ if (Out.Write(Buffer, Len) != Len) \ { \ Log->Print("%s:%i - Write failed.\n", _FL); \ Status = false; \ } // This encodes the main headers but not the headers relating to the // actual content. Thats done by the ::EncodeBody function. bool MailMessage::EncodeHeaders(GStreamI &Out, MailProtocol *Protocol, bool Mime) { bool Status = true; // Setup char Buffer[1025]; // Construct date const char *Weekday[7] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; const char *Month[12] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; LDateTime Dt; int TimeZone = Dt.SystemTimeZone(); Dt.SetNow(); int Len = sprintf_s(Buffer, sizeof(Buffer), "Date: %s, %i %s %i %i:%2.2i:%2.2i %s%2.2d%2.2d\r\n", Weekday[Dt.DayOfWeek()], Dt.Day(), Month[Dt.Month()-1], Dt.Year(), Dt.Hours(), Dt.Minutes(), Dt.Seconds(), (TimeZone >= 0) ? "+" : "", TimeZone / 60, abs(TimeZone) % 60); WriteOutput(); if (Protocol && Protocol->ProgramName) { // X-Mailer: Len = sprintf_s(Buffer, sizeof(Buffer), "X-Mailer: %s\r\n", Protocol->ProgramName.Get()); WriteOutput(); } if (Protocol && Protocol->ExtraOutgoingHeaders) { for (char *s=Protocol->ExtraOutgoingHeaders; s && *s; ) { char *e = s; while (*e && *e != '\r' && *e != '\n') e++; int l = e-s; if (l > 0) { Status &= Out.Write(s, l) > 0; Status &= Out.Write((char*)"\r\n", 2) > 0; } while (*e && (*e == '\r' || *e == '\n')) e++; s = e; } if (!Status) Log->Print("%s:%i - Writing ExtraOutgoingHeaders failed.\n", _FL); } if (Priority != MAIL_PRIORITY_NORMAL) { // X-Priority: Len = sprintf_s(Buffer, sizeof(Buffer), "X-Priority: %i\r\n", Priority); WriteOutput(); } if (MarkColour >= 0) { // X-Color (HTML Colour Ref for email marking) Len = sprintf_s(Buffer, sizeof(Buffer), "X-Color: #%2.2X%2.2X%2.2X\r\n", R24(MarkColour), G24(MarkColour), B24(MarkColour)); WriteOutput(); } // Message-ID: if (MessageID) { for (char *m=MessageID; *m; m++) { if (*m <= ' ') { printf("%s:%i - Bad message ID '%s'\n", _FL, MessageID.Get()); return false; } } Len = sprintf_s(Buffer, sizeof(Buffer), "Message-ID: %s\r\n", MessageID.Get()); WriteOutput(); } // References: if (ValidStr(References)) { char *Dir = strrchr(References, '/'); GAutoString a; char *Ref = 0; if (Dir) { GUri u; a = u.Decode(Dir + 1); Ref = a; } else Ref = References; int Len = sprintf_s(Buffer, sizeof(Buffer), "References: <%s>\r\n", Ref); WriteOutput(); } // To: char *ToAddr = CreateAddressTag(To, 0, &Protocol->CharsetPrefs); if (ToAddr) { Status &= Out.Write(ToAddr, strlen(ToAddr)) > 0; DeleteArray(ToAddr); if (!Status) Log->Print("%s:%i - Writing ToAddr failed.\n", _FL); } char *CcAddr = CreateAddressTag(To, 1, &Protocol->CharsetPrefs); if (CcAddr) { Status &= Out.Write(CcAddr, strlen(CcAddr)) > 0; DeleteArray(CcAddr); if (!Status) Log->Print("%s:%i - Writing CcAddr failed.\n", _FL); } // From: if (From && From->Addr) { Len = sprintf_s(Buffer, sizeof(Buffer), "From: "); char *Nme = EncodeRfc2047(NewStr(From->Name), 0, &Protocol->CharsetPrefs); if (Nme) { if (strchr(Nme, '\"')) Len += sprintf_s(Buffer+Len, sizeof(Buffer)-Len, "'%s' ", Nme); else Len += sprintf_s(Buffer+Len, sizeof(Buffer)-Len, "\"%s\" ", Nme); DeleteArray(Nme); } Len += sprintf_s(Buffer+Len, sizeof(Buffer)-Len, "<%s>\r\n", From->Addr); WriteOutput(); } else { Log->Print("%s:%i - No 'from' address to send email.\n", _FL); return false; } // Reply-To: if (Reply && ValidStr(Reply->Addr)) { Len = sprintf_s(Buffer, sizeof(Buffer), "Reply-To: "); char *Nme = EncodeRfc2047(NewStr(Reply->Name), 0, &Protocol->CharsetPrefs); if (Nme) { if (strchr(Nme, '\"')) Len += sprintf_s(Buffer+Len, sizeof(Buffer)-Len, "'%s' ", Nme); else Len += sprintf_s(Buffer+Len, sizeof(Buffer)-Len, "\"%s\" ", Nme); DeleteArray(Nme); } Len += sprintf_s(Buffer+Len, sizeof(Buffer)-Len, "<%s>\r\n", Reply->Addr); WriteOutput(); } // Subject: char *Subj = EncodeRfc2047(NewStr(Subject), 0, &Protocol->CharsetPrefs, 9); Len = sprintf_s(Buffer, sizeof(Buffer), "Subject: %s\r\n", (Subj) ? Subj : ""); WriteOutput(); DeleteArray(Subj); // DispositionNotificationTo if (DispositionNotificationTo) { Len = sprintf_s(Buffer, sizeof(Buffer), "Disposition-Notification-To:"); char *Nme = EncodeRfc2047(NewStr(From->Name), 0, &Protocol->CharsetPrefs); if (Nme) { Len += sprintf_s(Buffer+Len, sizeof(Buffer)-Len, " \"%s\"", Nme); DeleteArray(Nme); } Len += sprintf_s(Buffer+Len, sizeof(Buffer)-Len, " <%s>\r\n", From->Addr); WriteOutput(); } return Status; } bool MailMessage::EncodeBody(GStreamI &Out, MailProtocol *Protocol, bool Mime) { bool Status = true; char Buffer[1025]; if (Mime) { bool MultiPart = ((Text ? 1 : 0) + (Html ? 1 : 0) + FileDesc.Length()) > 1; bool MultipartAlternate = ValidStr(Text) && ValidStr(Html); bool MultipartMixed = FileDesc.Length() > 0; uint64 Now = LgiCurrentTime(); char Separator[256]; sprintf_s(Separator, sizeof(Separator), "----=_NextPart_%8.8X.%8.8X", (uint32)Now, (unsigned)(int64)LgiGetCurrentThread()); int Len = sprintf_s(Buffer, sizeof(Buffer), "MIME-Version: 1.0\r\n"); Status &= Out.Write(Buffer, Len) > 0; if (MultiPart) { const char *Type = MultipartMixed ? EncryptedMsg ? sMultipartEncrypted : sMultipartMixed : sMultipartAlternative; Len = sprintf_s(Buffer, sizeof(Buffer), "Content-Type: %s;\r\n\tboundary=\"%s\"\r\n", Type, Separator); Status &= Out.Write(Buffer, Len) > 0; } if (ValidStr(Text) || ValidStr(Html)) { char AlternateBoundry[128] = ""; if (MultiPart) { Len = sprintf_s(Buffer, sizeof(Buffer), "\r\n--%s\r\n", Separator); Status &= Out.Write(Buffer, Len) > 0; if (MultipartMixed && MultipartAlternate) { sprintf_s(AlternateBoundry, sizeof(AlternateBoundry), "----=_NextPart_%8.8X.%8.8X", (uint32)++Now, (uint32)(int64)LgiGetCurrentThread()); Len = sprintf_s(Buffer, sizeof(Buffer), "Content-Type: %s;\r\n\tboundary=\"%s\"\r\n", sMultipartAlternative, AlternateBoundry); Status &= Out.Write(Buffer, Len) > 0; Len = sprintf_s(Buffer, sizeof(Buffer), "\r\n--%s\r\n", AlternateBoundry); Status &= Out.Write(Buffer, Len) > 0; } } if (!Status) Log->Print("%s:%i - Status=%i\n", _FL, Status); if (ValidStr(Text)) { const char *Cs = 0; char *Txt = Text, *Mem = 0; // Detect charset if (!TextCharset || _stricmp(TextCharset, "utf-8") == 0) { Cs = LgiDetectCharset(Text, -1, Protocol ? &Protocol->CharsetPrefs : 0); if (Cs) { Mem = Txt = (char*)LgiNewConvertCp(Cs, Text, "utf-8", -1); } } if (!Cs) Cs = TextCharset; // Content type Len = sprintf_s(Buffer, sizeof(Buffer), "Content-Type: text/plain; charset=\"%s\"\r\n", Cs ? Cs : "us-ascii"); Status &= Out.Write(Buffer, Len) > 0; if (!Status) Log->Print("%s:%i - Status=%i\n", _FL, Status); // Transfer encoding if (Txt && (Is8Bit(Txt) || MaxLineLen(Txt) >= 80)) { char QuotPrint[] = "Content-Transfer-Encoding: quoted-printable\r\n\r\n"; Status &= Out.Write(QuotPrint, strlen(QuotPrint)) > 0; if (!Status) Log->Print("%s:%i - Status=%i\n", _FL, Status); GMemStream TxtStr(Txt, strlen(Txt), false); Status &= EncodeQuotedPrintable(Out, TxtStr) > 0; if (!Status) Log->Print("%s:%i - Status=%i\n", _FL, Status); } else { char Cte[] = "Content-Transfer-Encoding: 7bit\r\n\r\n"; Status &= Out.Write(Cte, strlen(Cte)) > 0; if (!Status) Log->Print("%s:%i - Status=%i\n", _FL, Status); GMemStream TxtStr(Txt, strlen(Txt), false); Status &= EncodeText(Out, TxtStr) > 0; if (!Status) Log->Print("%s:%i - Status=%i\n", _FL, Status); } DeleteArray(Mem); } if (!Status) Log->Print("%s:%i - Status=%i\n", _FL, Status); // Break alternate part if (AlternateBoundry[0]) { Len = sprintf_s(Buffer, sizeof(Buffer), "--%s\r\n", AlternateBoundry); Status &= Out.Write(Buffer, Len) > 0; } else if (MultipartAlternate) { Len = sprintf_s(Buffer, sizeof(Buffer), "--%s\r\n", Separator); Status &= Out.Write(Buffer, Len) > 0; } if (ValidStr(Html)) { // Content type Len = sprintf_s(Buffer, sizeof(Buffer), "Content-Type: text/html; charset=\"%s\"\r\n", TextCharset ? TextCharset : "us-ascii"); Status &= Out.Write(Buffer, Len) > 0; // Transfer encoding if (Is8Bit(Html) || MaxLineLen(Html) >= 80) { char Qp[] = "Content-Transfer-Encoding: quoted-printable\r\n\r\n"; Status &= Out.Write(Qp, strlen(Qp)) > 0; GMemStream HtmlStr(Html, strlen(Html), false); Status &= EncodeQuotedPrintable(Out, HtmlStr) > 0; } else { char Sb[] = "Content-Transfer-Encoding: 7bit\r\n\r\n"; Status &= Out.Write(Sb, strlen(Sb)) > 0; GMemStream HtmlStr(Html, strlen(Html), false); Status &= EncodeText(Out, HtmlStr) > 0; } } if (!Status) Log->Print("%s:%i - Status=%i\n", _FL, Status); if (AlternateBoundry[0]) { // End alternate part Len = sprintf_s(Buffer, sizeof(Buffer), "\r\n--%s--\r\n", AlternateBoundry); Status &= Out.Write(Buffer, Len) > 0; } } if (!Status) Log->Print("%s:%i - Status=%i\n", _FL, Status); int SizeEst = 1024; FileDescriptor *FileDes = FileDesc.First(); for (; FileDes; FileDes=FileDesc.Next()) { SizeEst += FileDes->Sizeof() * 4 / 3; } Out.SetSize(SizeEst); FileDes = FileDesc.First(); while (FileDes) { GStreamI *F = FileDes->GotoObject(); // write a MIME segment for this attachment if (MultiPart) { Len = sprintf_s(Buffer, sizeof(Buffer), "--%s\r\n", Separator); Status &= Out.Write(Buffer, Len) > 0; } char FileName[256]; char *s = FileDes->Name(), *d = FileName; if (!s) Log->Print("%s:%i - File descriptor has no name.\n", _FL); else { while (*s) { if (*s != '\\') { *d++ = *s++; } else { *d++ = '\\'; *d++ = '\\'; s++; } } *d = 0; char *FName = EncodeRfc2047(NewStr(FileName), 0, &Protocol->CharsetPrefs); Len = sprintf_s(Buffer, sizeof(Buffer), "Content-Type: %s; name=\"%s\"\r\n" "Content-Disposition: attachment\r\n", FileDes->GetMimeType() ? FileDes->GetMimeType() : "application/x-zip-compressed", (FName) ? FName : FileName); Status &= Out.Write(Buffer, Len) > 0; DeleteArray(FName); if (FileDes->GetContentId()) { Len = sprintf_s(Buffer, sizeof(Buffer), "Content-Id: %s\r\n", FileDes->GetContentId()); Status &= Out.Write(Buffer, Len) > 0; } Len = sprintf_s(Buffer, sizeof(Buffer), "Content-Transfer-Encoding: base64\r\n\r\n"); Status &= Out.Write(Buffer, Len) > 0; Status &= F ? EncodeBase64(Out, *F) > 0 : false; } FileDes = FileDesc.Next(); } if (!Status) Log->Print("%s:%i - Status=%i\n", _FL, Status); if (MultiPart) { // write final separator Len = sprintf_s(Buffer, sizeof(Buffer), "--%s--\r\n\r\n", Separator); Status &= Out.Write(Buffer, Len) > 0; } } else { // send content type int Len = sprintf_s(Buffer, sizeof(Buffer), "Content-Type: text/plain; charset=\"%s\"\r\n", TextCharset ? TextCharset : "us-ascii"); Status &= Out.Write(Buffer, Len) > 0; if (Is8Bit(Text)) { // send the encoding and a blank line char Qp[] = "Content-Transfer-Encoding: quoted-printable\r\n\r\n"; Status &= Out.Write(Qp, strlen(Qp)) > 0; // send message text GMemStream TextStr(Text, strlen(Text), false); Status &= EncodeQuotedPrintable(Out, TextStr) > 0; } else { // send a blank line Status &= Out.Write((char*)"\r\n", 2) > 0; // send message text GMemStream TextStr(Text, strlen(Text), false); Status &= EncodeText(Out, TextStr) > 0; } } if (!Status) Log->Print("%s:%i - Status=%i\n", _FL, Status); return true; } -*/ \ No newline at end of file +*/ diff --git a/src/common/INet/libntlm-0.4.2/lib/md4.h b/src/common/INet/libntlm-0.4.2/lib/md4.h --- a/src/common/INet/libntlm-0.4.2/lib/md4.h +++ b/src/common/INet/libntlm-0.4.2/lib/md4.h @@ -1,82 +1,86 @@ /* Declarations of functions and data types used for MD4 sum library functions. Copyright (C) 2000, 2001, 2003, 2005, 2008 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef MD4_H # define MD4_H 1 # include # include # define MD4_DIGEST_SIZE 16 +#ifdef _WIN32 + #define inline +#endif + /* Structure to save state of computation between the single steps. */ struct md4_ctx { uint32_t A; uint32_t B; uint32_t C; uint32_t D; uint32_t total[2]; uint32_t buflen; uint32_t buffer[32]; }; /* Initialize structure containing state of computation. */ extern void md4_init_ctx (struct md4_ctx *ctx); /* Starting with the result of former calls of this function (or the initialization function update the context for the next LEN bytes starting at BUFFER. It is necessary that LEN is a multiple of 64!!! */ extern void md4_process_block (const void *buffer, size_t len, struct md4_ctx *ctx); /* Starting with the result of former calls of this function (or the initialization function update the context for the next LEN bytes starting at BUFFER. It is NOT required that LEN is a multiple of 64. */ extern void md4_process_bytes (const void *buffer, size_t len, struct md4_ctx *ctx); /* Process the remaining bytes in the buffer and put result from CTX in first 16 bytes following RESBUF. The result is always in little endian byte order, so that a byte-wise output yields to the wanted ASCII representation of the message digest. */ extern void *md4_finish_ctx (struct md4_ctx *ctx, void *resbuf); /* Put result from CTX in first 16 bytes following RESBUF. The result is always in little endian byte order, so that a byte-wise output yields to the wanted ASCII representation of the message digest. */ extern void *md4_read_ctx (const struct md4_ctx *ctx, void *resbuf); /* Compute MD4 message digest for bytes read from STREAM. The resulting message digest number will be written into the 16 bytes beginning at RESBLOCK. */ extern int md4_stream (FILE * stream, void *resblock); /* Compute MD4 message digest for LEN bytes beginning at BUFFER. The result is always in little endian byte order, so that a byte-wise output yields to the wanted ASCII representation of the message digest. */ extern void *md4_buffer (const char *buffer, size_t len, void *resblock); #endif diff --git a/src/common/Lgi/GFileSelect.cpp b/src/common/Lgi/GFileSelect.cpp --- a/src/common/Lgi/GFileSelect.cpp +++ b/src/common/Lgi/GFileSelect.cpp @@ -1,2101 +1,2101 @@ /*hdr ** FILE: GFileSelect.cpp ** AUTHOR: Matthew Allen ** DATE: 20/5/2002 ** DESCRIPTION: Common file/directory selection dialog ** ** Copyright (C) 1998-2002, Matthew Allen ** fret@memecode.com */ #include #include #include "Lgi.h" #include "GPopup.h" #include "GToken.h" #include "LList.h" #include "GTextLabel.h" #include "GEdit.h" #include "GButton.h" #include "GCheckBox.h" #include "GCombo.h" #include "GTree.h" #include "GTableLayout.h" #include "GBox.h" #define FSI_FILE 0 #define FSI_DIRECTORY 1 #define FSI_BACK 2 #define FSI_UPDIR 3 #define FSI_NEWDIR 4 #define FSI_DESKTOP 5 #define FSI_HARDDISK 6 #define FSI_CDROM 7 #define FSI_FLOPPY 8 #define FSI_NETWORK 9 enum DlgType { TypeNone, TypeOpenFile, TypeOpenFolder, TypeSaveFile }; char ModuleName[] = "File Select"; -uint32 IconBits[] = { +uint32_t IconBits[] = { 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x0000F81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xC980FA8A, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x738E738E, 0xF81F738E, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x8430F81F, 0x84308430, 0x84308430, 0xF81F8430, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x00000000, 0x00000000, 0x00000000, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x9CE09CE0, 0x9CE09CE0, 0x00009CE0, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xCCE0F81F, 0x9800FCF9, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x738E738E, 0xCE6C9E73, 0x738EC638, 0xF81F738E, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x9FFF738E, 0x9FFF9FFF, 0x9FFF9FFF, 0x00009FFF, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xFFFF0000, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x9CE0F81F, 0xFFF9F7BE, 0xFFF3FFF9, 0x9CE0FFF3, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x9CE09CE0, 0x9CE09CE0, 0x00009CE0, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x0000F81F, 0xF81FF81F, 0xF81FF81F, 0x04F904F9, 0x04F904F9, 0xAD720313, 0xAD72AD72, 0xAD72AD72, 0xFE60CCE0, 0x00009B00, 0x0000AD72, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x738EF81F, 0x667334F3, 0xCE6C9E73, 0xC638B5B6, 0x3186C638, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xFFFF738E, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000000, 0xF81FF81F, 0x738E738E, 0x738E738E, 0x738E738E, 0x738E738E, 0x738E738E, 0x738E738E, 0xF81F738E, 0xFFFF0000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFF0000, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xCE6C9CE0, 0xCE6CCE6C, 0xCE6CCE6C, 0xCE6CCE6C, 0x9CE09CE0, 0x9CE09CE0, 0x9CE09CE0, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x0000F81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x9CE0F81F, 0xFFF9F7BE, 0xFFF3FFF9, 0x9CE0FFF3, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xF81F0000, 0x667F04F9, 0x031304F9, 0xFFFFCE73, 0xFFF9FFFF, 0xCCE0FFFF, 0x9B00FFF3, 0x04F90000, 0x00000313, 0xF81FF81F, 0x738E738E, 0x738E738E, 0x738E738E, 0x738E738E, 0x738E738E, 0x738E738E, 0xF81F738E, 0xF81FF81F, 0xF81FF81F, 0x6673738E, 0x34F36673, 0xCE736673, 0xB5B6C638, 0xB5B6DEFB, 0xF81F3186, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xFFFF738E, 0xFFFFFFFF, 0xCFFFCFFF, 0x0000CFFF, 0x738EF81F, 0xB5B6B5B6, 0xB5B6B5B6, 0xB5B6B5B6, 0xB5B6B5B6, 0xB5B6B5B6, 0xB5B6B5B6, 0x0000738E, 0xFFFF0000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFF0000, 0x0000FFFF, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xFFF99CE0, 0xFFF9FFF9, 0xFFF9FFF9, 0xFFF9FFF9, 0xFFF9FFF9, 0xFFF9FFF9, 0xCE6CFFF3, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0x00000000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xCE6C9CE0, 0xCE6CCE6C, 0xCE6CCE6C, 0xCE6CCE6C, 0x9CE09CE0, 0x9CE09CE0, 0x9CE09CE0, 0xF81FF81F, 0xF81FF81F, 0x9CE09CE0, 0x9CE09CE0, 0xF81F0000, 0x0000F81F, 0x0000F81F, 0x0000F81F, 0xF81FF81F, 0x04F904F9, 0xCE730313, 0xFFFFFFFF, 0xFFF9FFF9, 0xFE60CCE0, 0x00009B00, 0x667F3313, 0x00000313, 0x738EF81F, 0xB5B6B5B6, 0xB5B6B5B6, 0xB5B6B5B6, 0xB5B6B5B6, 0xB5B6B5B6, 0xB5B6B5B6, 0x0000738E, 0xF81FF81F, 0xF81FF81F, 0x34F98430, 0x667364F9, 0x84306679, 0xD6BAB5B6, 0xB5B6C638, 0xF81F3186, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xCFFF738E, 0xCFFFCFFF, 0xCFFFCFFF, 0x0000CFFF, 0xFFFF738E, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x9CF3FFFF, 0x0000738E, 0xFFFF0000, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000000, 0x00000000, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xFFF99CE0, 0xFFF3FFF3, 0xFFF3FFF3, 0xFFF3FFF3, 0xFFF3FFF3, 0xFFF3FFF3, 0xCE6CFE73, 0xF81F0000, 0xF81FF81F, 0x0000F81F, 0x000007FF, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xFFF99CE0, 0xFFF9FFF9, 0xFFF9FFF9, 0xFFF9FFF9, 0xFFF9FFF9, 0xFFF9FFF9, 0xCE6CFFF3, 0xF81F0000, 0x9CE0F81F, 0xFFF9F7BE, 0xFFF3FFF3, 0x00009CE0, 0xF81FF81F, 0xF81F0000, 0xF81F0000, 0xF81FF81F, 0x031304F9, 0xFFFFCE73, 0x94B294B2, 0xCCE094B2, 0x9B00FFF3, 0xAD720000, 0x3313FFF9, 0x00000313, 0xFFFF738E, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x9CF3FFFF, 0x0000738E, 0xF81FF81F, 0x738EF81F, 0xA53494B2, 0x667964F3, 0x00008430, 0xA5348430, 0xCE79B5B6, 0x3186CE79, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xCE79738E, 0xCE79C638, 0xC638B5B6, 0x0000B5B6, 0xD6BA738E, 0xC638C638, 0xC638C638, 0xC638C638, 0xB5B6C638, 0x04200660, 0x94B2B5B6, 0x0000738E, 0xFFFF0000, 0xFFFFFFFF, 0xFFFFFFFF, 0xD699FFFF, 0xD699D699, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xFFF99CE0, 0xFFF3FFF3, 0xFFF3FFF3, 0xFFF3FFF3, 0xFE73FFF3, 0xFE73FFF3, 0xCE6CFFF3, 0xF81F0000, 0xF81FF81F, 0x07FF0000, 0x000007FF, 0x00000000, 0x00000000, 0x00000000, 0xF81F0000, 0xF81FF81F, 0xFFF99CE0, 0xFFF3FFF3, 0x0000FFF3, 0xFFF3FFF3, 0xFFF3FFF3, 0xFFF3FFF3, 0xCE6CFE73, 0xF81F0000, 0xCE6C9CE0, 0xCE6CCE6C, 0xCE6CCE6C, 0x9CE0CE6C, 0x9CE09CE0, 0xF81F9CE0, 0x0000F81F, 0x0000F81F, 0xCE730313, 0xFFFFFFFF, 0xFFFF94B2, 0xFE60CCE0, 0x00009B00, 0xFFF9AD72, 0xCE73CE73, 0x00003313, 0xD6BA738E, 0xC638C638, 0xC638C638, 0xC638C638, 0xB5B6C638, 0x04200660, 0x94B2B5B6, 0x0000738E, 0xF81FF81F, 0x738EF81F, 0xB5B6B5B6, 0x8430CE79, 0xF81F0000, 0x84300000, 0xCE79CE79, 0x3186CE79, 0xF81FF81F, 0x84308430, 0x84308430, 0x84308430, 0xC638738E, 0x84308430, 0x84308430, 0x0000C638, 0xDEFB738E, 0xC638B5B6, 0xC638C638, 0xC638C638, 0xB5B6B5B6, 0xB5B6B5B6, 0x94B2B5B6, 0x0000738E, 0xFFFF0000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xD699FFFF, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xFFF99CE0, 0xFFF3FFF3, 0xFFF3FFF3, 0xFFF3FFF3, 0xFFF3FFF3, 0xFFF3FE73, 0xCE6CFE73, 0xF81F0000, 0x0000F81F, 0x07FF07FF, 0x07FF07FF, 0x07FF07FF, 0x07FF07FF, 0x07FF07FF, 0xF81F0000, 0xF81FF81F, 0xFFF99CE0, 0xFFF3FFF3, 0x00000000, 0xFFF30000, 0xFE73FFF3, 0xFE73FFF3, 0xCE6CFFF3, 0xF81F0000, 0xFFF99CE0, 0xFFF9FFF9, 0xFFF9FFF9, 0xFFF9FFF9, 0xFFF3FFF9, 0x0000CE6C, 0xF81F0000, 0xF81FF81F, 0xFFFFAD72, 0xFFF9FFFF, 0xFFFF94B2, 0x9B009CEC, 0x00000000, 0xCE73FFF9, 0xAD72FFF9, 0x000094B2, 0xDEFB738E, 0xC638B5B6, 0xC638C638, 0xC638C638, 0xB5B6B5B6, 0xB5B6B5B6, 0x94B2B5B6, 0x0000738E, 0xF81FF81F, 0x738EF81F, 0xF7BEE73C, 0xB5B6E73C, 0x00008430, 0xB5B68430, 0xF7BEEF7D, 0x3186CE79, 0x8430F81F, 0xC638C638, 0xC638C638, 0xC638C638, 0xCE79738E, 0x0000738E, 0xFFFF738E, 0x0000B5B6, 0xDEFB738E, 0x738E738E, 0x738E738E, 0x738E738E, 0x738E738E, 0x738E738E, 0x94B2B5B6, 0x0000738E, 0xFFFF0000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xD699FFFF, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xFFF99CE0, 0xFFF3FFF3, 0xFFF3FFF3, 0xFE73FFF3, 0xFE73FFF3, 0xFE73FFF3, 0xCE6CFFF3, 0xF81F0000, 0x0000F81F, 0x07FF07FF, 0x07FF07FF, 0x07FF07FF, 0x07FF07FF, 0x07FF07FF, 0xF81F0000, 0xF81FF81F, 0xFFF99CE0, 0x0000FFF3, 0x00000000, 0x00000000, 0xFFF3FFF3, 0xFFF3FE73, 0xCE6CFE73, 0xF81F0000, 0xFFF99CE0, 0xFFF3FFF3, 0xFFF3FFF3, 0xFFF3FFF3, 0xFE73FFF3, 0x0000CE6C, 0x0000F81F, 0xF81FF81F, 0xFFFFAD72, 0xFFF9FFF9, 0xFFFF94B2, 0xFFFF0000, 0x0000FFF9, 0xFFF9CE73, 0xCE73AD72, 0x000094B2, 0xDEFB738E, 0x738E738E, 0x738E738E, 0x738E738E, 0x738E738E, 0x738E738E, 0x94B2B5B6, 0x0000738E, 0x8430F81F, 0x84308430, 0xA5348430, 0xA534B5B6, 0x8430A534, 0xDEFB34F3, 0xFFFFE73C, 0xF81F3186, 0xFFFF8430, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x0000FFFF, 0x00000000, 0x00000000, 0xF81F0000, 0xD6BA738E, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x94B2B5B6, 0x0000738E, 0xFFFF0000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xD699FFFF, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xFFF99CE0, 0xFFF3FFF3, 0xFFF3FFF3, 0xFFF3FFF3, 0xFFF3FE73, 0xFFF3FE73, 0xCE6CFE73, 0xF81F0000, 0xF81FF81F, 0x07FF0000, 0x000007FF, 0x00000000, 0x00000000, 0x00000000, 0xF81F0000, 0xF81FF81F, 0xFFF99CE0, 0xFFF3FFF3, 0x0000FFF3, 0xFE73FFF3, 0xFE73FFF3, 0xFE73FFF3, 0xCE6CFFF3, 0xF81F0000, 0xFFF99CE0, 0xFFF3FFF3, 0xFFF3FFF3, 0xFFF3FFF3, 0xFFF3FE73, 0x0000CE6C, 0xF81FF81F, 0xF81F0000, 0xFFF9AD72, 0xFFF9FFF9, 0xFFFF94B2, 0xFFFFFFFF, 0x0000FFF9, 0xCE73FFF9, 0xAD72CE73, 0x000094B2, 0xD6BA738E, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x94B2B5B6, 0x0000738E, 0xFFFF8430, 0xFFFFFFFF, 0xA534738E, 0xB5B6A534, 0xCE73D6BA, 0x34F96673, 0xB5B6CFFF, 0xF81F3186, 0xC6388430, 0xC638C638, 0xC638C638, 0xC638C638, 0xC638C638, 0xC638F800, 0x738E8430, 0xF81F0000, 0x738E738E, 0x738E738E, 0x738E738E, 0x738E738E, 0x738E738E, 0x738E738E, 0x738E738E, 0xF81F0000, 0xFFFF0000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xD699FFFF, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xFFF99CE0, 0xFFF3FFF3, 0xFE73FFF3, 0xFE73FFF3, 0xFE73FFF3, 0xFE73FFF3, 0xCE6CFE73, 0xF81F0000, 0xF81FF81F, 0x0000F81F, 0x000007FF, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xFFF99CE0, 0xFFF3FFF3, 0x0000FFF3, 0xFFF3FFF3, 0xFFF3FE73, 0xFFF3FE73, 0xCE6CFE73, 0xF81F0000, 0xFFF99CE0, 0xFFF3FFF3, 0xFFF3FFF3, 0xFE73FFF3, 0xFE73FFF3, 0x0000CE6C, 0xF81FF81F, 0xF81FF81F, 0xFFF9AD72, 0xFFF9FFF9, 0xFFFF94B2, 0xFFFFFFFF, 0x0000FFF9, 0xCE73CE73, 0xCE73AD72, 0x000094B2, 0x738E738E, 0x738E738E, 0x738E738E, 0x738E738E, 0x738E738E, 0x738E738E, 0x738E738E, 0xF81F0000, 0xFFFF8430, 0xC638C638, 0x738EC638, 0xB5B6A534, 0xCE73CE79, 0x04F99E73, 0x000004F9, 0xF81FF81F, 0xC6388430, 0xC638C638, 0x84308430, 0x84308430, 0xC638C638, 0xC638C638, 0x738E8430, 0xF81F0000, 0x0000F81F, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xF81FF81F, 0xFFFF0000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xD699FFFF, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xFFF99CE0, 0xFFF3FE73, 0xFFF3FE73, 0xFFF3FE73, 0xFFF3FE73, 0xFE73FE73, 0xCE6CFE73, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0x00000000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xFFF99CE0, 0xFFF3FFF3, 0x0000FFF3, 0x00000000, 0x00000000, 0xFE730000, 0xCE6CFE73, 0xF81F0000, 0xFFF99CE0, 0xFFF3FFF3, 0xFFF3FE73, 0xFFF3FE73, 0xFFF3FE73, 0x0000CE6C, 0xF81FF81F, 0xF81FF81F, 0xFFF904F9, 0xFFF9FFF9, 0xFFF994B2, 0xFFF9FFF9, 0x0000FFF9, 0xAD72CE73, 0xAD72CE73, 0x00003313, 0x0000F81F, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xF81FF81F, 0xFFFF8430, 0x31863186, 0x31863186, 0x84308430, 0xCE73CE79, 0x31869E73, 0x00003186, 0xF81FF81F, 0xC6388430, 0x84308430, 0x00000000, 0x00000000, 0x84308430, 0xC6388430, 0x738E8430, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xB5B6F81F, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xFFFF0000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xD699FFFF, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xCE6C9CE0, 0xCE6CCE6C, 0xCE6CCE6C, 0xCE6CCE6C, 0xCE6CCE6C, 0xCE6CCE6C, 0xCE6CCE6C, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0x0000F81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xFFF99CE0, 0xFFF3FE73, 0xFFF3FE73, 0xFFF3FE73, 0xFFF3FE73, 0xFE73FE73, 0xCE6CFE73, 0xF81F0000, 0xFFF99CE0, 0xFFF3FE73, 0xFE73FFF3, 0xFE73FFF3, 0xFE73FFF3, 0x0000CE6C, 0xF81FF81F, 0xF81FF81F, 0x04F904F9, 0xFFF9FFF9, 0x00000000, 0x00000000, 0x00000000, 0xCE73AD72, 0x3313AD72, 0x00003313, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xFFFF8430, 0x042007E0, 0xFFFFFFFF, 0xC638C638, 0x31863186, 0xC6383186, 0x0000738E, 0xF81FF81F, 0xC6388430, 0xC638C638, 0xFFFFFFFF, 0xFFFFFFFF, 0xC638C638, 0xC638C638, 0x738E8430, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xB5B6F81F, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xFFFF0000, 0xD699D699, 0xD699D699, 0xD699D699, 0xD699D699, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0x0000F81F, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xCE6C9CE0, 0xCE6CCE6C, 0xCE6CCE6C, 0xCE6CCE6C, 0xCE6CCE6C, 0xCE6CCE6C, 0xCE6CCE6C, 0xF81F0000, 0xCE6C9CE0, 0xCE6CCE6C, 0xCE6CCE6C, 0xCE6CCE6C, 0xCE6CCE6C, 0x0000CE6C, 0xF81FF81F, 0xF81FF81F, 0x667F04F9, 0xFFF904F9, 0xCE73FFF9, 0xCE73FFF9, 0xAD72CE73, 0xAD72CE73, 0x667F3313, 0x00003313, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x8430738E, 0x84308430, 0x84308430, 0x84308430, 0x84308430, 0x84308430, 0x00008430, 0xF81FF81F, 0x84308430, 0x84308430, 0x84308430, 0x84308430, 0x84308430, 0x84308430, 0x00008430, 0xF81FF81F, 0xB5B6F81F, 0xB5B6F81F, 0xB5B6B5B6, 0xB5B6B5B6, 0xB5B6B5B6, 0xB5B6B5B6, 0xF81FB5B6, 0xF81FB5B6, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x0000F81F, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xF81F0000, 0x0000F81F, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xF81FF81F, 0xF81FF81F, 0x03130313, 0x04F90313, 0x94B294B2, 0x94B294B2, 0x94B294B2, 0x331394B2, 0x33133313, 0x00003313, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x0000F81F, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xF81F0000, 0xF81FF81F, 0x0000F81F, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xF81F0000, 0xF81FF81F, 0x0000F81F, 0x0000F81F, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xF81F0000, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F}; GInlineBmp FileSelectIcons = { 160, 16, 16, IconBits }; ////////////////////////////////////////////////////////////////////////// char *GFileType::DefaultExtension() { char *Status = 0; GToken T(Extension(), ";"); if (T.Length()) { char s[256]; strcpy(s, T[0]); char *Dir = strchr(s, '.'); if (Dir) { Status = NewStr(Dir+1); if (Status) strlwr(Status); } } return Status; } ////////////////////////////////////////////////////////////////////////// class GFolderItem : public LListItem { class GFileSelectDlg *Dlg; char *Path; public: char *File; bool IsDir; GFolderItem(GFileSelectDlg *dlg, char *FullPath, GDirectory *Dir); ~GFolderItem(); void OnActivate(); char *GetText(int i); int GetImage(int Flags); void OnSelect(); void OnDelete(bool Ask = true); void OnRename(); void OnMouseClick(GMouse &m); }; ////////////////////////////////////////////////////////////////////////// // This is just a private data container to make it easier to change the // implementation of this class without effecting headers and applications. class GFileSelectPrivate { friend class GFileSelect; friend class GFileSelectDlg; friend class GFolderList; GView *Parent; GFileSelect *Select; DlgType Type; char *Title; char *DefExt; bool MultiSelect; List Files; int CurrentType; List Types; List History; bool ShowReadOnly; bool ReadOnly; bool EatClose; public: static GImageList *Icons; static char *InitPath; static bool InitShowHiddenFiles; static GRect InitSize; GFileSelectPrivate(GFileSelect *select) { ShowReadOnly = false; ReadOnly = false; EatClose = false; Select = select; Type = TypeNone; Title = 0; DefExt = 0; MultiSelect = false; Parent = 0; CurrentType = -1; if (!Icons) Icons = new GImageList(16, 16, FileSelectIcons.Create(0xF81F)); } virtual ~GFileSelectPrivate() { DeleteArray(Title); DeleteArray(DefExt); Types.DeleteObjects(); Files.DeleteArrays(); History.DeleteArrays(); } }; GImageList *GFileSelectPrivate::Icons = 0; char *GFileSelectPrivate::InitPath = 0; bool GFileSelectPrivate::InitShowHiddenFiles = false; GRect GFileSelectPrivate::InitSize(0, 0, 600, 500); ////////////////////////////////////////////////////////////////////////// // This class implements the UI for the selector. class GFileSelectDlg; class GFolderView { protected: GFileSelectDlg *Dlg; public: GFolderView(GFileSelectDlg *dlg) { Dlg = dlg; } virtual void OnFolder() {} }; class GFolderDrop : public GDropDown, public GFolderView { public: GFolderDrop(GFileSelectDlg *dlg, int Id, int x, int y, int cx, int cy); void OnFolder(); bool OnLayout(GViewLayoutInfo &Inf) { Inf.Width.Min = Inf.Width.Max = 18; return true; } }; class GIconButton : public GLayout { GImageList *Icons; int Icon; bool Down; public: GIconButton(int Id, int x, int y, int cx, int cy, GImageList *icons, int icon) { Icons = icons; Icon = icon; SetId(Id); GRect r(x, y, x+cx, y+cy); SetPos(r); Down = false; SetTabStop(true); } void OnPaint(GSurface *pDC) { GRect c = GetClient(); GColour Background(LC_MED, 24); c.Offset(-c.x1, -c.y1); LgiWideBorder(pDC, c, Down ? DefaultSunkenEdge : DefaultRaisedEdge); pDC->Colour(Background); pDC->Rectangle(&c); int x = (c.X()-Icons->TileX()) / 2; int y = (c.Y()-Icons->TileY()) / 2; if (Focus()) { #if WINNATIVE RECT r = c; DrawFocusRect(pDC->Handle(), &r); #endif } Icons->Draw(pDC, c.x1+x+Down, c.y1+y+Down, Icon, Background, !Enabled()); } void OnFocus(bool f) { Invalidate(); } void OnMouseClick(GMouse &m) { if (Enabled()) { bool Trigger = Down && !m.Down(); Capture(Down = m.Down()); if (Down) Focus(true); Invalidate(); if (Trigger) { GViewI *n=GetNotify()?GetNotify():GetParent(); if (n) n->OnNotify(this, 0); } } } void OnMouseEnter(GMouse &m) { if (IsCapturing()) { Down = true; Invalidate(); } } void OnMouseExit(GMouse &m) { if (IsCapturing()) { Down = false; Invalidate(); } } bool OnKey(GKey &k) { if (k.c16 == ' ' || k.c16 == VK_RETURN) { if (Enabled() && Down ^ k.Down()) { Down = k.Down(); Invalidate(); if (!Down) { GViewI *n=GetNotify()?GetNotify():GetParent(); if (n) n->OnNotify(this, 0); } } return true; } return false; } bool OnLayout(GViewLayoutInfo &Inf) { Inf.Width.Min = Inf.Width.Max = Icons->TileX() + 4; Inf.Width.Max += 4; Inf.Height.Min = Inf.Height.Max = Icons->TileY() + 4; Inf.Height.Max += 4; return true; } }; class GFolderList : public LList, public GFolderView { GString FilterKey; public: GFolderList(GFileSelectDlg *dlg, int Id, int x, int y, int cx, int cy); ~GFolderList() { } void OnFolder(); bool OnKey(GKey &k); void SetFilterKey(GString s) { FilterKey = s; OnFolder(); } }; #define IDC_STATIC -1 #define IDD_FILE_SELECT 1000 #define IDC_PATH 1002 #define IDC_DROP 1003 #define IDC_BACK 1004 #define IDC_UP 1005 #define IDC_NEW 1006 #define IDC_VIEW 1007 #define IDC_FILE 1010 #define IDC_TYPE 1011 #define IDC_SHOWHIDDEN 1012 #define IDC_SUB_TBL 1013 #define IDC_BOOKMARKS 1014 #define IDC_FILTER 1015 #define IDC_FILTER_CLEAR 1016 #if 1 #define USE_FOLDER_CTRL 1 enum FolderCtrlMessages { M_DELETE_EDIT = M_USER + 100, M_NOTIFY_VALUE_CHANGED, }; class FolderCtrl : public GView { struct Part { GAutoPtr ds; GRect Arrow; GRect Text; }; GEdit *e; GArray p; Part *Over; int Cursor; Part *HitPart(int x, int y, int *Sub = NULL) { for (unsigned i=0; iGetHeight() + 4; Inf.Height.Max = Inf.Height.Min; } return true; } GString NameAt(int Level) { GString n; #ifndef WINDOWS n += "/"; #endif for (unsigned i=0; i<=Level && iTransparent(false); GDisplayString Arrow(f, ">"); for (unsigned i=0; iArrow.ZOff(Arrow.X()+1, c.Y()-1); n->Arrow.Offset(c.x1, c.y1); f->Colour(Rgb24(192,192,192), Bk); Arrow.DrawCenter(pDC, &n->Arrow); c.x1 = n->Arrow.x2 + 1; if (n->ds) { // Layout and draw text n->Text.ZOff(n->ds->X() + 4, c.Y()-1); n->Text.Offset(c.x1, c.y1); f->Colour(Fore, Bk); n->ds->DrawCenter(pDC, &n->Text); c.x1 = n->Text.x2 + 1; } } pDC->Colour(LC_WORKSPACE, 24); pDC->Rectangle(&c); } void OnMouseClick(GMouse &m) { if (m.IsContextMenu()) { } else if (m.Left()) { if (m.Down()) { if (p.PtrCheck(Over)) { // Over a path node... Cursor = Over - p.AddressOf(0); Part &o = p[Cursor]; Invalidate(); SendNotify(GNotifyValueChanged); if (o.Arrow.Overlap(m.x, m.y)) { // Show sub-menu at this level ShowMenu(Cursor); } } else { // In empty space if (!e) { GRect c = GetClient(); e = new GEdit(GetId()+1, c.x1, c.y1, c.X()-1, c.Y()-1); if (e) { e->Attach(this); GString s = Name(); e->Name(s); e->SetCaret(s.Length()); e->Focus(true); } } } } } } void OnMouseMove(GMouse &m) { Part *o = Over; Over = HitPart(m.x, m.y); if (o != Over) Invalidate(); } void OnMouseExit(GMouse &m) { if (Over) { Over = NULL; Invalidate(); } } int OnNotify(GViewI *c, int f) { if (e != NULL && c->GetId() == e->GetId()) { if (f == VK_RETURN) { GString s = e->Name(); Name(s); PostEvent(M_DELETE_EDIT); PostEvent(M_NOTIFY_VALUE_CHANGED); } } return 0; } GMessage::Result OnEvent(GMessage *m) { switch (m->Msg()) { case M_DELETE_EDIT: { DeleteObj(e); break; } case M_NOTIFY_VALUE_CHANGED: { SendNotify(GNotifyValueChanged); break; } } return GView::OnEvent(m); } virtual bool ShowMenu(int Level) { if (Level <= 0) return false; GString dir = NameAt(Level-1); GSubMenu s; GDirectory d; GString::Array Opts; for (int b = d.First(dir); b; b = d.Next()) { if (d.IsDir()) { Opts.New() = d.GetName(); s.AppendItem(d.GetName(), Opts.Length()); } } Part &i = p[Level]; GdcPt2 pt(i.Arrow.x1, i.Arrow.y2+1); PointToScreen(pt); int Cmd = s.Float(this, pt.x, pt.y, true); if (Cmd) { GString np; np = dir + DIR_STR + Opts[Cmd-1]; Name(np); PostEvent(M_NOTIFY_VALUE_CHANGED); } else return false; return true; } }; #else #define USE_FOLDER_CTRL 0 #endif class GFileSelectDlg : public GDialog { GRect OldPos; GRect MinSize; GArray Links; GArray Hidden; public: GFileSelectPrivate *d; GTableLayout *Tbl; GBox *Sub; GTree *Bookmarks; GTextLabel *Ctrl1; #if USE_FOLDER_CTRL FolderCtrl *Ctrl2; #else GEdit *Ctrl2; #endif GFolderDrop *Ctrl3; GIconButton *BackBtn; GIconButton *UpBtn; GIconButton *NewDirBtn; GFolderList *FileLst; GTextLabel *Ctrl8; GTextLabel *Ctrl9; GEdit *FileNameEdit; GCombo *FileTypeCbo; GButton *SaveBtn; GButton *CancelBtn; GCheckBox *ShowHidden; GEdit *FilterEdit; GFileSelectDlg(GFileSelectPrivate *Select); ~GFileSelectDlg(); int OnNotify(GViewI *Ctrl, int Flags); void OnUpFolder(); void SetFolder(char *f); void OnFolder(); void OnFile(char *f); void OnFilter(const char *Key); bool OnViewKey(GView *v, GKey &k) { if (k.vkey == VK_UP && k.Alt()) { if (k.Down()) { UpBtn->SendNotify(); } return true; } return false; } void Add(GTreeItem *i, GVolume *v) { if (!i || !v) return; i->SetText(v->Name(), 0); i->SetText(v->Path(), 1); for (unsigned n=0; nPath() && !_stricmp(v->Path(), Links[n])) { Links.DeleteAt(n--); break; } } for (GVolume *cv = v->First(); cv; cv = cv->Next()) { GTreeItem *ci = new GTreeItem; if (ci) { i->Insert(ci); i->Expanded(true); Add(ci, cv); } } } }; GFileSelectDlg::GFileSelectDlg(GFileSelectPrivate *select) { SaveBtn = 0; BackBtn = 0; CancelBtn = 0; ShowHidden = 0; FileTypeCbo = 0; FileNameEdit = 0; Ctrl8 = 0; Ctrl9 = 0; FileLst = 0; Ctrl3 = 0; BackBtn = 0; UpBtn = 0; NewDirBtn = 0; Ctrl2 = 0; Sub = NULL; Bookmarks = NULL; FilterEdit = NULL; d = select; SetParent(d->Parent); MinSize.ZOff(450, 300); OldPos.Set(0, 0, 475, 350 + LgiApp->GetMetric(LGI_MET_DECOR_Y) ); SetPos(OldPos); int x = 0, y = 0; AddView(Tbl = new GTableLayout); // Top Row GLayoutCell *c = Tbl->GetCell(x++, y); c->Add(Ctrl1 = new GTextLabel(IDC_STATIC, 0, 0, -1, -1, "Look in:")); c->VerticalAlign(GCss::Len(GCss::VerticalMiddle)); c = Tbl->GetCell(x++, y); #if USE_FOLDER_CTRL c->Add(Ctrl2 = new FolderCtrl(IDC_PATH)); #else c->Add(Ctrl2 = new GEdit(IDC_PATH, 0, 0, 245, 21, "")); #endif c = Tbl->GetCell(x++, y); c->Add(Ctrl3 = new GFolderDrop(this, IDC_DROP, 336, 7, 16, 21)); c = Tbl->GetCell(x++, y); c->Add(BackBtn = new GIconButton(IDC_BACK, 378, 7, 27, 21, d->Icons, FSI_BACK)); c = Tbl->GetCell(x++, y); c->Add(UpBtn = new GIconButton(IDC_UP, 406, 7, 27, 21, d->Icons, FSI_UPDIR)); c = Tbl->GetCell(x++, y); c->Add(NewDirBtn = new GIconButton(IDC_NEW, 434, 7, 27, 21, d->Icons, FSI_NEWDIR)); // Folders/items row x = 0; y++; c = Tbl->GetCell(x, y, true, 6, 1); c->Add(Sub = new GBox(IDC_SUB_TBL)); Sub->AddView(Bookmarks = new GTree(IDC_BOOKMARKS, 0, 0, -1, -1)); Bookmarks->GetCss(true)->Width(GCss::Len(GCss::LenPx, 150.0)); GTableLayout *t; Sub->AddView(t = new GTableLayout(11)); // Filter / search row c = t->GetCell(0, 0); c->Add(new GCheckBox(IDC_FILTER_CLEAR, 0, 0, -1, -1, "Filter items:")); c->VerticalAlign(GCss::Len(GCss::VerticalMiddle)); c = t->GetCell(1, 0); c->Add(FilterEdit = new GEdit(IDC_FILTER, 0, 0, 60, 20)); c = t->GetCell(0, 1, true, 2); c->Add(FileLst = new GFolderList(this, IDC_VIEW, 14, 35, 448, 226)); // File name row x = 0; y++; c = Tbl->GetCell(x++, y); c->Add(Ctrl8 = new GTextLabel(IDC_STATIC, 14, 275, -1, -1, "File name:")); c = Tbl->GetCell(x, y, true, 2); x += 2; c->Add(FileNameEdit = new GEdit(IDC_FILE, 100, 268, 266, 21, "")); c = Tbl->GetCell(x, y, true, 3); c->Add(SaveBtn = new GButton(IDOK, 392, 268, 70, 21, "Ok")); // 4th row x = 0; y++; c = Tbl->GetCell(x++, y); c->Add(Ctrl9 = new GTextLabel(IDC_STATIC, 14, 303, -1, -1, "Files of type:")); c = Tbl->GetCell(x, y, true, 2); x += 2; c->Add(FileTypeCbo = new GCombo(IDC_TYPE, 100, 296, 266, 21, "")); c = Tbl->GetCell(x++, y, true, 3); c->Add(CancelBtn = new GButton(IDCANCEL, 392, 296, 70, 21, "Cancel")); // 5th row x = 0; y++; c = Tbl->GetCell(x++, y, true, 6); c->Add(ShowHidden = new GCheckBox(IDC_SHOWHIDDEN, 14, 326, -1, -1, "Show hidden files.")); // Init if (BackBtn) BackBtn->Enabled(false); if (SaveBtn) SaveBtn->Enabled(false); if (FileLst) FileLst->MultiSelect(d->MultiSelect); if (ShowHidden) ShowHidden->Value(d->InitShowHiddenFiles); // Load types if (!d->Types.First()) { GFileType *t = new GFileType; if (t) { t->Description("All Files"); t->Extension(LGI_ALL_FILES); d->Types.Insert(t); } } for (GFileType *t=d->Types.First(); t; t=d->Types.Next()) { char s[256]; sprintf(s, "%s (%s)", t->Description(), t->Extension()); if (FileTypeCbo) FileTypeCbo->Insert(s); } d->CurrentType = 0; // File + Path char *File = d->Files.First(); if (File) { char *Dir = strrchr(File, DIR_CHAR); if (Dir) { OnFile(Dir + 1); } else { OnFile(File); } } if (d->InitPath) { SetFolder(d->InitPath); } else { char Str[256]; LgiGetExePath(Str, sizeof(Str)); SetFolder(Str); } OnFolder(); // Size/layout SetPos(d->InitSize); MoveToCenter(); RegisterHook(this, GKeyEvents); FileLst->Focus(true); LgiGetUsersLinks(Links); GTreeItem *RootItem = new GTreeItem; if (RootItem) { Bookmarks->Insert(RootItem); Add(RootItem, FileDev->GetRootVolume()); } for (unsigned n=0; nSetText(leaf?leaf+1:p, 0); ci->SetText(p, 1); RootItem->Insert(ci); } } } GFileSelectDlg::~GFileSelectDlg() { UnregisterHook(this); d->InitShowHiddenFiles = ShowHidden ? ShowHidden->Value() : false; d->InitSize = GetPos(); char *CurPath = GetCtrlName(IDC_PATH); if (ValidStr(CurPath)) { DeleteArray(d->InitPath); d->InitPath = NewStr(CurPath); } } void GFileSelectDlg::OnFile(char *f) { if (d->Type != TypeOpenFolder) { FileNameEdit->Name(f ? f : (char*)""); SaveBtn->Enabled(ValidStr(f)); } } void GFileSelectDlg::SetFolder(char *f) { char *CurPath = GetCtrlName(IDC_PATH); if (CurPath) { d->History.Insert(NewStr(CurPath)); SetCtrlEnabled(IDC_BACK, true); } SetCtrlName(IDC_PATH, f); } void GFileSelectDlg::OnFolder() { if (Ctrl3) Ctrl3->OnFolder(); if (FileLst) FileLst->OnFolder(); char *CurPath = GetCtrlName(IDC_PATH); if (CurPath && UpBtn) { UpBtn->Enabled(strlen(CurPath)>3); } } void GFileSelectDlg::OnUpFolder() { char *Cur = GetCtrlName(IDC_PATH); if (Cur) { char Dir[MAX_PATH]; strcpy(Dir, Cur); if (strlen(Dir) > 3) { LgiTrimDir(Dir); if (!strchr(Dir, DIR_CHAR)) strcat(Dir, DIR_STR); SetFolder(Dir); OnFolder(); } } } void GFileSelectDlg::OnFilter(const char *Key) { if (FileLst) FileLst->SetFilterKey(Key); } int GFileSelectDlg::OnNotify(GViewI *Ctrl, int Flags) { switch (Ctrl->GetId()) { case IDC_BOOKMARKS: { if (Flags == GNotifyItem_Select && Bookmarks) { GTreeItem *s = Bookmarks->Selection(); if (s) { char *p = s->GetText(1); if (DirExists(p)) { SetCtrlName(IDC_PATH, p); OnFolder(); } } } break; } case IDC_PATH: { // if (Flags == VK_RETURN) if (Flags == GNotifyValueChanged) { // Skip the IDOK message generated by the default button // d->EatClose = true; // printf("%s:%i - eat close true\n", _FL); OnFolder(); } break; } case IDC_VIEW: { if (FileLst) { /* These functions are handled by the list control's OnKey implementation if (Flags == GLIST_NOTIFY_RETURN) { List s; if (FileLst->GetSelection(s)) { GFolderItem *i = dynamic_cast(s.First()); if (i) { i->OnActivate(); } } } else if (Flags == GLIST_NOTIFY_BACKSPACE) { OnUpFolder(); } */ } break; } case IDC_FILE: { char *f = Ctrl->Name(); if (!f) break; if (Flags == VK_RETURN) { // allow user to insert new type by typing the pattern into the file name edit box and // hitting enter if (strchr(f, '?') || strchr(f, '*')) { // it's a mask, push the new type on the the type stack and // refilter the content int TypeIndex = -1; int n = 0; for (GFileType *t = d->Types.First(); t; t = d->Types.Next(), n++) { if (t->Extension() && stricmp(t->Extension(), f) == 0) { TypeIndex = n; break; } } // insert the new type if not already there if (TypeIndex < 0) { GFileType *n = new GFileType; if (n) { n->Description(f); n->Extension(f); TypeIndex = d->Types.Length(); d->Types.Insert(n); FileTypeCbo->Insert(f); } } // select the new type if (TypeIndex >= 0) { FileTypeCbo->Value(d->CurrentType = TypeIndex); } // clear the edit box Ctrl->Name(""); // Update and don't do normal save btn processing. OnFolder(); // Skip the IDOK message generated by the default button d->EatClose = true; printf("%s:%i - eat close true\n", _FL); break; } if (DirExists(f)) { // Switch to the folder... SetCtrlName(IDC_PATH, f); OnFolder(); Ctrl->Name(NULL); d->EatClose = true; } else if (FileExists(f)) { // Select the file... d->Files.Insert(NewStr(f)); EndModal(IDOK); break; } } bool HasFile = ValidStr(f); bool BtnEnabled = SaveBtn->Enabled(); if (HasFile ^ BtnEnabled) { SaveBtn->Enabled(HasFile); } break; } case IDC_BACK: { char *Dir = d->History.Last(); if (Dir) { d->History.Delete(Dir); SetCtrlName(IDC_PATH, Dir); OnFolder(); DeleteArray(Dir); if (!d->History.First()) { SetCtrlEnabled(IDC_BACK, false); } } break; } case IDC_SHOWHIDDEN: { FileLst->OnFolder(); break; } case IDC_TYPE: { d->CurrentType = FileTypeCbo->Value(); FileLst->OnFolder(); if (d->Type == TypeSaveFile) { // change extension of current file GFileType *Type = d->Types.ItemAt(d->CurrentType); char *File = FileNameEdit->Name(); if (Type && File) { char *Ext = strchr(File, '.'); if (Ext) { char *DefExt = Type->DefaultExtension(); if (DefExt) { Ext++; char s[256]; ZeroObj(s); memcpy(s, File, (int)Ext-(int)File); strcat(s, DefExt); OnFile(s); DeleteArray(DefExt); } } } } break; } case IDC_UP: { OnUpFolder(); break; } case IDC_FILTER: { const char *n = Ctrl->Name(); SetCtrlValue(IDC_FILTER_CLEAR, ValidStr(n)); OnFilter(n); break; } case IDC_FILTER_CLEAR: { if (!Ctrl->Value()) { SetCtrlName(IDC_FILTER, NULL); OnFilter(NULL); } break; } case IDC_NEW: { GInput Dlg(this, "", "Create new folder:", "New Folder"); if (Dlg.DoModal()) { char New[256]; strcpy(New, GetCtrlName(IDC_PATH)); if (New[strlen(New)-1] != DIR_CHAR) strcat(New, DIR_STR); strcat(New, Dlg.GetStr()); FileDev->CreateFolder(New); OnFolder(); } break; } case IDOK: { if (d->EatClose) { printf("%s:%i - SKIPPING eat close false\n", _FL); d->EatClose = false; break; } char *Path = GetCtrlName(IDC_PATH); char *File = GetCtrlName(IDC_FILE); if (Path) { char f[MAX_PATH]; d->Files.DeleteArrays(); if (d->Type == TypeOpenFolder) { d->Files.Insert(NewStr(Path)); } else { List Sel; if (d->Type != TypeSaveFile && FileLst && FileLst->GetSelection(Sel) && Sel.Length() > 1) { for (LListItem *i=Sel.First(); i; i=Sel.Next()) { LgiMakePath(f, sizeof(f), Path, i->GetText(0)); d->Files.Insert(NewStr(f)); } } else if (ValidStr(File)) { if (strchr(File, DIR_CHAR)) strcpy_s(f, sizeof(f), File); else LgiMakePath(f, sizeof(f), Path, File); d->Files.Insert(NewStr(f)); } } } // fall thru } case IDCANCEL: { EndModal(Ctrl->GetId()); break; } } return 0; } ////////////////////////////////////////////////////////////////////////// class GFileSystemItem : public GTreeItem { class GFileSystemPopup *Popup; char *Path; public: GFileSystemItem(GFileSystemPopup *popup, GVolume *vol, char *path = 0); char *GetPath() { return Path; } void OnPath(char *p); void OnMouseClick(GMouse &m); bool OnKey(GKey &k); }; #define IDC_TREE 100 class GFileSystemPopup : public GPopup { friend class GFileSystemItem; GFileSelectDlg *Dlg; GTree *Tree; GFileSystemItem *Root; public: GFileSystemPopup(GView *owner, GFileSelectDlg *dlg, int x) : GPopup(owner) { Dlg = dlg; GRect r(0, 0, x, 150); SetPos(r); Children.Insert(Tree = new GTree(IDC_TREE, 1, 1, X()-3, Y()-3)); if (Tree) { Tree->Sunken(false); GVolume *v = FileDev->GetRootVolume(); if (v) { Tree->SetImageList(Dlg->d->Icons, false); Tree->Insert(Root = new GFileSystemItem(this, v)); } } } ~GFileSystemPopup() { } void Visible(bool i) { if (i && Root) { Root->OnPath(Dlg->GetCtrlName(IDC_PATH)); } GPopup::Visible(i); } void OnPaint(GSurface *pDC) { // Draw border GRect c = GetClient(); c.Offset(-c.x1, -c.y1); pDC->Colour(LC_BLACK, 24); pDC->Box(&c); c.Size(1, 1); } void OnActivate(GFileSystemItem *i) { if (i) { Dlg->SetFolder(i->GetPath()); Dlg->OnFolder(); Visible(false); } } }; GFileSystemItem::GFileSystemItem(GFileSystemPopup *popup, GVolume *Vol, char *path) { Popup = popup; Expanded(true); if (Vol) { Path = NewStr(Vol->Path()); SetText(Vol->Name()); switch (Vol->Type()) { case VT_3_5FLOPPY: case VT_5_25FLOPPY: case VT_REMOVABLE: SetImage(FSI_FLOPPY); break; case VT_HARDDISK: case VT_RAMDISK: SetImage(FSI_HARDDISK); break; case VT_CDROM: SetImage(FSI_CDROM); break; case VT_NETWORK_SHARE: SetImage(FSI_NETWORK); break; case VT_DESKTOP: SetImage(FSI_DESKTOP); break; default: SetImage(FSI_DIRECTORY); break; } for (GVolume *v=Vol->First(); v; v=Vol->Next()) { Insert(new GFileSystemItem(Popup, v)); } } else { Path = NewStr(path); SetText(strrchr(Path, DIR_CHAR)+1); SetImage(FSI_DIRECTORY); } } void GFileSystemItem::OnPath(char *p) { switch (GetImage()) { case FSI_DESKTOP: { if (p && Path && stricmp(Path, p) == 0) { Select(true); p = 0; } break; } case FSI_DIRECTORY: { return; } default: { GTreeItem *Old = Items.First(); if (Old) { Old->Remove(); DeleteObj(Old); } break; } } if (p) { int PathLen = strlen(Path); if (Path && strnicmp(Path, p, PathLen) == 0 && (p[PathLen] == DIR_CHAR || p[PathLen] == 0) #ifdef LINUX && strcmp(Path, "/") != 0 #endif ) { GTreeItem *Item = this; if (GetImage() != FSI_DESKTOP && strlen(p) > 3) { char *Start = p + strlen(Path); if (Start) { char s[256]; strcpy(s, Path); GToken T(Start, DIR_STR); for (int i=0; iInsert(New); Item = New; } } } } if (Item) { Item->Select(true); } } } for (GFileSystemItem *i=dynamic_cast(Items.First()); i; i=dynamic_cast(Items.Next())) { i->OnPath(p); } } void GFileSystemItem::OnMouseClick(GMouse &m) { if (m.Left() && m.Down()) { Popup->OnActivate(this); } } bool GFileSystemItem::OnKey(GKey &k) { if ((k.c16 == ' ' || k.c16 == VK_RETURN)) { if (k.Down() && k.IsChar) { Popup->OnActivate(this); } return true; } return false; } GFolderDrop::GFolderDrop(GFileSelectDlg *dlg, int Id, int x, int y, int cx, int cy) : GDropDown(Id, x, y, cx, cy, 0), GFolderView(dlg) { SetPopup(new GFileSystemPopup(this, dlg, cx + (dlg->Ctrl2 ? dlg->Ctrl2->X() : 0) )); } void GFolderDrop::OnFolder() { } ////////////////////////////////////////////////////////////////////////// #define IDM_OPEN 1000 #define IDM_CUT 1001 #define IDM_COPY 1002 #define IDM_RENAME 1003 #define IDM_PROPERTIES 1004 #define IDM_CREATE_SHORTCUT 1005 #define IDM_DELETE 1006 GFolderItem::GFolderItem(GFileSelectDlg *dlg, char *FullPath, GDirectory *Dir) { Dlg = dlg; Path = NewStr(FullPath); File = strrchr(Path, DIR_CHAR); if (File) File++; IsDir = Dir->IsDir(); } GFolderItem::~GFolderItem() { DeleteArray(Path); } char *GFolderItem::GetText(int i) { return File; } int GFolderItem::GetImage(int Flags) { return IsDir ? 1 : 0; } void GFolderItem::OnSelect() { if (!IsDir && File) { Dlg->OnFile(Select() ? File : 0); } } void GFolderItem::OnDelete(bool Ask) { if (!Ask || LgiMsg(Parent, "Do you want to delete '%s'?", ModuleName, MB_YESNO, Path) == IDYES) { bool Status = false; if (IsDir) { Status = FileDev->RemoveFolder(Path, true); } else { Status = FileDev->Delete(Path); } if (Status) { Parent->Remove(this); delete this; } } } void GFolderItem::OnRename() { GInput Inp(Dlg, File, "New name:", Dlg->Name()); if (Inp.DoModal()) { char Old[256]; strcpy(Old, Path); char New[256]; File[0] = 0; LgiMakePath(New, sizeof(New), Path, Inp.GetStr()); if (FileDev->Move(Old, New)) { DeleteArray(Path); Path = NewStr(New); File = strrchr(Path, DIR_CHAR); if (File) File++; Update(); } else { LgiMsg(Dlg, "Renaming '%s' failed.", Dlg->Name(), MB_OK); } } } void GFolderItem::OnActivate() { if (File) { if (IsDir) { char Dir[256]; strcpy(Dir, Dlg->GetCtrlName(IDC_PATH)); if (Dir[strlen(Dir)-1] != DIR_CHAR) strcat(Dir, DIR_STR); strcat(Dir, File); Dlg->SetFolder(Dir); Dlg->OnFolder(); } else // Is file { Dlg->OnNotify(Dlg->SaveBtn, 0); } } } void GFolderItem::OnMouseClick(GMouse &m) { if (m.Down()) { if (m.Left()) { if (m.Double()) { OnActivate(); } } else if (m.Right()) { GSubMenu *RClick = new GSubMenu; if (RClick) { RClick->AppendItem("Select", IDM_OPEN, true); RClick->AppendSeparator(); RClick->AppendItem("Cut", IDM_CUT, false); RClick->AppendItem("Copy", IDM_COPY, false); RClick->AppendSeparator(); RClick->AppendItem("Create Shortcut", IDM_CREATE_SHORTCUT, false); RClick->AppendItem("Delete", IDM_DELETE, true); RClick->AppendItem("Rename", IDM_RENAME, true); RClick->AppendSeparator(); RClick->AppendItem("Properties", IDM_PROPERTIES, false); if (Parent->GetMouse(m, true)) { switch (RClick->Float(Parent, m.x, m.y)) { case IDM_OPEN: { break; } case IDM_DELETE: { OnDelete(); break; } case IDM_RENAME: { OnRename(); break; } } } DeleteObj(RClick); } } } } int GFolderItemCompare(LListItem *A, LListItem *B, NativeInt Data) { GFolderItem *a = dynamic_cast(A); GFolderItem *b = dynamic_cast(B); if (a && b) { if (a->IsDir ^ b->IsDir) { if (a->IsDir) return -1; else return 1; } else if (a->File && b->File) { return stricmp(a->File, b->File); } } return 0; } GFolderList::GFolderList(GFileSelectDlg *dlg, int Id, int x, int y, int cx, int cy) : LList(Id, x, y, cx, cy), GFolderView(dlg) { SetImageList(Dlg->d->Icons, false); ShowColumnHeader(false); AddColumn("Name", cx-20); SetMode(LListColumns); } bool GFolderList::OnKey(GKey &k) { bool Status = LList::OnKey(k); switch (k.vkey) { case VK_BACKSPACE: { if (k.Down() && GetWindow()) { // Go up a directory GViewI *v = GetWindow()->FindControl(IDC_UP); if (v) { GetWindow()->OnNotify(v, 0); } } Status = true; break; } case VK_RETURN: #ifdef VK_KP_ENTER case VK_KP_ENTER: #endif { if (k.Down() && GetWindow()) { GFolderItem *Sel = dynamic_cast(GetSelected()); if (Sel) { if (Sel->IsDir) { char *Cur = GetWindow()->GetCtrlName(IDC_PATH); if (Cur) { char Path[256]; LgiMakePath(Path, sizeof(Path), Cur, Sel->GetText(0)); if (DirExists(Path)) { GetWindow()->SetCtrlName(IDC_PATH, Path); Dlg->OnFolder(); } } } else { GViewI *Ok = GetWindow()->FindControl(IDOK); if (Ok) { GetWindow()->SetCtrlName(IDC_FILE, Sel->GetText(0)); GetWindow()->OnNotify(Ok, 0); } } } } Status = true; break; } case VK_DELETE: { if (k.Down() && !k.IsChar && GetWindow()) { List Sel; if (GetSelection(Sel)) { GStringPipe Msg; Msg.Push("Do you want to delete:\n\n"); List Delete; for (LListItem *i=Sel.First(); i; i=Sel.Next()) { GFolderItem *s = dynamic_cast(i); if (s) { Delete.Insert(s); Msg.Push("\t"); Msg.Push(s->GetText(0)); Msg.Push("\n"); } } char *Mem = Msg.NewStr(); if (Mem) { if (LgiMsg(this, Mem, ModuleName, MB_YESNO) == IDYES) { for (GFolderItem *d=Delete.First(); d; d=Delete.Next()) { d->OnDelete(false); } } DeleteArray(Mem); } } } Status = true; break; } } // LgiTrace("%s:%i GFolderList::OnKey, key=%i down=%i status=%i\n", _FL, k.vkey, k.Down(), Status); return Status; } void GFolderList::OnFolder() { Empty(); GDirectory Dir; List New; // Get current type GFileType *Type = Dlg->d->Types.ItemAt(Dlg->d->CurrentType); List Ext; if (Type) { GToken T(Type->Extension(), ";"); for (int i=0; iCtrl2) return; bool ShowHiddenFiles = Dlg->ShowHidden ? Dlg->ShowHidden->Value() : false; for (bool Found = Dir.First(Dlg->Ctrl2->Name()); Found; Found = Dir.Next()) { char Name[MAX_PATH]; Dir.Path(Name, sizeof(Name)); bool Match = true; if (!ShowHiddenFiles && Dir.IsHidden()) { Match = false; } else if (!Dir.IsDir() && Ext.Length() > 0) { Match = false; for (char *e=Ext.First(); e && !Match; e=Ext.Next()) { bool m = MatchStr(e, Name); if (m) Match = true; } } if (FilterKey && Match) Match = stristr(Dir.GetName(), FilterKey) != NULL; if (Match) New.Insert(new GFolderItem(Dlg, Name, &Dir)); } // Sort items... New.Sort(GFolderItemCompare); // Display items... Insert(New); } ////////////////////////////////////////////////////////////////////////// GFileSelect::GFileSelect() { d = new GFileSelectPrivate(this); } GFileSelect::~GFileSelect() { DeleteObj(d); } void GFileSelect::ShowReadOnly(bool b) { d->ShowReadOnly = b;; } bool GFileSelect::ReadOnly() { return d->ReadOnly; } char *GFileSelect::Name() { return d->Files.First(); } bool GFileSelect::Name(const char *n) { d->Files.DeleteArrays(); if (n) { d->Files.Insert(NewStr(n)); } return true; } char *GFileSelect::operator [](size_t i) { return d->Files.ItemAt(i); } size_t GFileSelect::Length() { return d->Files.Length(); } size_t GFileSelect::Types() { return d->Types.Length(); } void GFileSelect::ClearTypes() { d->Types.DeleteObjects(); } GFileType *GFileSelect::TypeAt(ssize_t n) { return d->Types.ItemAt(n); } bool GFileSelect::Type(const char *Description, const char *Extension, int Data) { GFileType *Type = new GFileType; if (Type) { Type->Description(Description); Type->Extension(Extension); d->Types.Insert(Type); } return Type != 0; } ssize_t GFileSelect::SelectedType() { return d->CurrentType; } GViewI *GFileSelect::Parent() { return d->Parent; } void GFileSelect::Parent(GViewI *Window) { d->Parent = dynamic_cast(Window); } bool GFileSelect::MultiSelect() { return d->MultiSelect; } void GFileSelect::MultiSelect(bool Multi) { d->MultiSelect = Multi; } #define CharPropImpl(Func, Var) \ char *GFileSelect::Func() \ { \ return Var; \ } \ void GFileSelect::Func(const char *i) \ { \ DeleteArray(Var); \ if (i) \ { \ Var = NewStr(i); \ } \ } CharPropImpl(InitialDir, d->InitPath); CharPropImpl(Title, d->Title); CharPropImpl(DefaultExtension, d->DefExt); bool GFileSelect::Open() { GFileSelectDlg Dlg(d); d->Type = TypeOpenFile; Dlg.Name("Open"); if (Dlg.SaveBtn) Dlg.SaveBtn->Name("Open"); return Dlg.DoModal() == IDOK; } bool GFileSelect::OpenFolder() { GFileSelectDlg Dlg(d); d->Type = TypeOpenFolder; Dlg.SaveBtn->Enabled(true); Dlg.FileNameEdit->Enabled(false); Dlg.Name("Open Folder"); Dlg.SaveBtn->Name("Open"); return Dlg.DoModal() == IDOK; } bool GFileSelect::Save() { GFileSelectDlg Dlg(d); d->Type = TypeSaveFile; Dlg.Name("Save As"); Dlg.SaveBtn->Name("Save As"); return Dlg.DoModal() == IDOK; } /////////////////////////////////////////////////////////////////////////////////// #if defined(LINUX) #include "INet.h" #endif bool LgiGetUsersLinks(GArray &Links) { GString Folder = LGetSystemPath(LSP_USER_LINKS); if (!Folder) return false; #if defined(WINDOWS) GDirectory d; for (int b = d.First(Folder); b; b = d.Next()) { char *s = d.GetName(); if (s && stristr(s, ".lnk")) { char lnk[MAX_PATH]; if (d.Path(lnk, sizeof(lnk)) && ResolveShortcut(lnk, lnk, sizeof(lnk))) { Links.New() = lnk; } } } #elif defined(LINUX) char p[MAX_PATH]; if (!LgiMakePath(p, sizeof(p), Folder, "bookmarks")) { LgiTrace("%s:%i - Failed to make path '%s'\n", _FL, Folder.Get()); return false; } GAutoString Txt(ReadTextFile(p)); if (!Txt) { LgiTrace("%s:%i - failed to read '%s'\n", _FL, p); return false; } GString s = Txt.Get(); GString::Array a = s.Split("\n"); for (unsigned i=0; i #include "Lgi.h" #include "StoreCommon.h" /// \brief This class can limit the reading/writing to a specific sub section of a file /// Which should protect the application using the storage system from overwriting parts /// of the file is shouldn't have access to. The default region is the whole file. GSubFilePtr::GSubFilePtr(GSubFile *Parent, const char *file, int line) { File = Parent; SrcFile = NewStr(file); SrcLine = line; OurStatus = true; Pos = 0; SetSwap(Parent->GetSwap()); ClearSub(); } GSubFilePtr::~GSubFilePtr() { File->Detach(this); DeleteArray(SrcFile); } bool GSubFilePtr::SaveState() { ActualPos = File->GetPos(); ActualStatus = File->GetStatus(); ActualSwap = File->GetSwap(); File->SetSwap(GetSwap()); File->SetStatus(OurStatus); int64 p = File->SetPos(Start + Pos); if (p == Start + Pos) { return true; } else { printf("%s:%i SaveState failed Pos=" LPrintfInt64 " OurPos=" LPrintfInt64 "\n", _FL, Pos, Pos); } return false; } bool GSubFilePtr::RestoreState() { SetSwap(File->GetSwap()); OurStatus = File->GetStatus(); Pos = File->GetPos() - Start; File->SetStatus(ActualStatus); File->SetSwap(ActualSwap); return File->SetPos(ActualPos) == ActualPos; } bool GSubFilePtr::GetSub(int64 &start, int64 &len) { start = Start; len = Len; return Sub; } bool GSubFilePtr::SetSub(int64 start, int64 len) { Sub = true; Start = start; Len = len; Pos = 0; return true; } void GSubFilePtr::ClearSub() { Sub = false; Start = Len = 0; } int GSubFilePtr::Open(const char *Str, int Int) { LgiAssert(0); return 0; } bool GSubFilePtr::IsOpen() { return true; } int GSubFilePtr::Close() { LgiAssert(0); return 0; } int64 GSubFilePtr::GetSize() { int64 s = -1; if (Sub) { s = Len; } else { GSubFile::SubLock Lock = File->Lock(_FL); s = File->GetSize(); } return s; } int64 GSubFilePtr::SetSize(int64 Size) { LgiAssert(0); return -1; } int64 GSubFilePtr::GetPos() { return Pos; } int64 GSubFilePtr::SetPos(int64 pos) { if (pos < 0) { printf("%s:%i - Pos < 0???\n", __FILE__, __LINE__); } return Pos = pos; } int64 GSubFilePtr::Seek(int64 To, int Whence) { switch (Whence) { case SEEK_SET: SetPos(To); break; case SEEK_CUR: SetPos(GetPos() + To); break; case SEEK_END: SetPos(GetSize() + To); break; } return GetPos(); } GStreamI *GSubFilePtr::Clone() { LgiAssert(0); return 0; } bool GSubFilePtr::Eof() { bool Status = false; if (Sub) { Status = Pos < 0 || Pos >= Len; } else { GSubFile::SubLock Lock = File->Lock(_FL); Status = File->Eof(); } return Status; } bool GSubFilePtr::GetStatus() { return OurStatus; } void GSubFilePtr::SetStatus(bool s) { OurStatus = s; } ssize_t GSubFilePtr::Read(void *Buffer, ssize_t Size, int Flags) { int Status = 0; GSubFile::SubLock Lock = File->Lock(_FL); if (!Sub || (Pos >= 0 && Pos <= Len)) { if (SaveState()) { int64 Remaining = Len - Pos; - uint32 RdSize = (int) (Sub ? MIN(Remaining, Size) : Size); + uint32_t RdSize = (int) (Sub ? MIN(Remaining, Size) : Size); Status = File->Read(Buffer, RdSize, Flags); RestoreState(); } } else { LgiAssert(0); } return Status; } ssize_t GSubFilePtr::Write(const void *Buffer, ssize_t Size, int Flags) { int Status = 0; GSubFile::SubLock Lock = File->Lock(_FL); int64 End = Pos + Size; if (!Sub || (Pos >= 0 && End <= Len)) { if (SaveState()) { Status = File->Write(Buffer, Size, Flags); RestoreState(); } } else { LgiTrace("GSubFilePtr error, Pos=" LPrintfInt64 " Start=" LPrintfInt64 " End=" LPrintfInt64 " Len=" LPrintfInt64 "\n", Pos, Start, End, Len); } return Status; } ssize_t GSubFilePtr::ReadStr(char *Buf, ssize_t Size) { LgiAssert(0); return 0; } ssize_t GSubFilePtr::WriteStr(char *Buf, ssize_t Size) { LgiAssert(0); return 0; } /////////////////////////////////////////////////////////////////////// GSubFile::GSubFile(LMutex *lock, bool Buffering) { Lck = lock; #if GSUBFILE_NOBUFFERING Buffer = Buffering; Block = 0; Buf = 0; Shift = 0; Cur = -1; Pos = -1; Dirty = false; #endif } GSubFile::~GSubFile() { while (Ptrs.Length()) { GSubFilePtr *p = Ptrs[0]; if (p) { printf("%s:%i - GSubFilePtr not released.\n", p->SrcFile, p->SrcLine); DeleteObj(p); } else break; } #if GSUBFILE_NOBUFFERING DeleteArray(Buf); #endif } GSubFilePtr *GSubFile::Create(const char *file, int line) { GSubFilePtr *p = 0; p = new GSubFilePtr(this, file, line); if (p) { SubLock Lck = Lock(_FL); Ptrs[Ptrs.Length()] = p; } return p; } void GSubFile::Detach(GSubFilePtr *Ptr) { SubLock Lck = Lock(_FL); LgiAssert(Ptrs.HasItem(Ptr)); Ptrs.Delete(Ptr); } GSubFile::SubLock GSubFile::Lock(const char *file, int line) { return SubLock(new LMutex::Auto(Lck, file, line)); } #if GSUBFILE_NOBUFFERING int GSubFile::Open(char *Str, int Int) { int s = GFile::Open(Str, Int | (!Buffer ? O_NO_CACHE : 0)); if (s && !Buffer) { Block = GetBlockSize(); Shift = 1; while ((1 << Shift) < Block) Shift++; Buf = new uint8[Block]; Pos = 0; } return s; } int GSubFile::Read(void *OutBuf, int Size, int Flags) { int Status = 0; if (!Buffer && Buf) { uint8 *Out = (uint8*)OutBuf; int64 Blk = Pos >> Shift; int Mask = Block - 1; while (Size) { if (Blk != Cur) { SetBlock(Blk); } int Off = Pos & Mask; int Len = Block - Off; if (Len > Size) Len = Size; memcpy(Out, Buf + Off, Len); Pos += Len; Out += Len; Size -= Len; Status += Len; Blk++; } } else { Status = GFile::Read(OutBuf, Size, Flags); if (Status > 0) { Pos += Status; } } return Status; } int GSubFile::Write(const void *InBuf, int Size, int Flags) { int Status = 0; if (!Buffer && Buf) { uint8 *In = (uint8*)InBuf; int64 Blk = Pos >> Shift; int Mask = Block - 1; while (Size) { if (Blk != Cur) { SetBlock(Blk); } int Off = Pos & Mask; int Len = Block - Off; if (Len > Size) Len = Size; memcpy(Buf + Off, In, Len); Dirty = true; Pos += Len; In += Len; Size -= Len; Status += Len; Blk++; } } else { Status = GFile::Write(InBuf, Size, Flags); if (Status > 0) { Pos += Status; } } return Status; } int64 GSubFile::GetPos() { return Pos; } int64 GSubFile::SetPos(int64 pos) { return Pos = pos; } bool GSubFile::SetBlock(int64 Blk) { if (Blk != Cur) { int64 p, n; if (Dirty) { p = GFile::SetPos(Cur << Shift); n = GFile::Write(Buf, Block); if (n != Block) return false; Dirty = false; } Cur = Blk; p = GFile::SetPos(Cur << Shift); n = GFile::Read(Buf, Block); return n > 0; } return true; } #endif 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_t WChar; #else typedef wchar_t WChar; #endif bool HasEmoji(char *Txt) { if (!Txt) return false; GUtf8Ptr p(Txt); WChar u; while ((u = p++)) { int IcoIdx = EmojiToIconIndex((uint32_t*)&u, 1); if (IcoIdx >= 0) return true; } return false; } 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_t*)LgiNewConvertCp("utf-32", s, LGI_WideCharset, bytes)); int len = Strlen(c.Get()); return GMemQueue::Write(c, len * sizeof(uint32_t)); } #endif ssize_t WriteWide(const WChar *s, ssize_t bytes) { return GMemQueue::Write(s, bytes); } }; GAutoWString TextToEmoji(uint32_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/GHtml.cpp b/src/common/Text/GHtml.cpp --- a/src/common/Text/GHtml.cpp +++ b/src/common/Text/GHtml.cpp @@ -1,9187 +1,9188 @@ #include #include #include #include #include "Lgi.h" #include "GHtml.h" #include "GHtmlPriv.h" #include "GToken.h" #include "GScrollBar.h" #include "GVariant.h" #include "GFindReplaceDlg.h" #include "GUnicode.h" #include "Emoji.h" #include "GClipBoard.h" #include "GButton.h" #include "GEdit.h" #include "GCombo.h" #include "GdcTools.h" #include "GDisplayString.h" #include "GPalette.h" #include "GPath.h" #include "GCssTools.h" #include "LgiRes.h" #include "INet.h" #define DEBUG_TABLE_LAYOUT 0 #define DEBUG_DRAW_TD 0 #define DEBUG_RESTYLE 0 #define DEBUG_TAG_BY_POS 0 #define DEBUG_SELECTION 0 #define DEBUG_TEXT_AREA 0 #define ENABLE_IMAGE_RESIZING 1 #define DOCUMENT_LOAD_IMAGES 1 #define MAX_RECURSION_DEPTH 300 #define ALLOW_TABLE_GROWTH 1 #define LUIS_DEBUG 0 #define CRASH_TRACE 0 #ifdef MAC #define GHTML_USE_DOUBLE_BUFFER 0 #else #define GHTML_USE_DOUBLE_BUFFER 1 #endif #define GT_TRANSPARENT 0x00000000 #ifndef IDC_HAND #define IDC_HAND MAKEINTRESOURCE(32649) #endif #define M_JOBS_LOADED (M_USER+4000) #undef CellSpacing #define DefaultCellSpacing 0 #define DefaultCellPadding 1 #ifdef MAC #define MinimumPointSize 9 #define MinimumBodyFontSize 12 #else #define MinimumPointSize 8 #define MinimumBodyFontSize 11 #endif // #define DefaultFont "font-family: Times; font-size: 16pt;" #define DefaultBodyMargin "5px" #define DefaultImgSize 16 #define DefaultMissingCellColour GT_TRANSPARENT // Rgb32(0xf0,0xf0,0xf0) #define ShowNbsp 0 #define FontPxHeight(fnt) (fnt->GetHeight() - (int)(fnt->Leading() + 0.5)) #if 0 // def _DEBUG #define DefaultTableBorder Rgb32(0xf8, 0xf8, 0xf8) #else #define DefaultTableBorder GT_TRANSPARENT #endif #define IsTableCell(id) ( ((id) == TAG_TD) || ((id) == TAG_TH) ) #define IsTableTag() (TagId == TAG_TABLE || TagId == TAG_TR || TagId == TAG_TD || TagId == TAG_TH) #define GetCssLen(a, b) a().Type == GCss::LenInherit ? b() : a() static char WordDelim[] = ".,<>/?[]{}()*&^%$#@!+|\'\""; static char16 WhiteW[] = {' ', '\t', '\r', '\n', 0}; #if 0 static char DefaultCss[] = { "a { color: blue; text-decoration: underline; }" "body { margin: 8px; }" "strong { font-weight: bolder; }" "pre { font-family: monospace }" "h1 { font-size: 2em; margin: .67em 0px; }" "h2 { font-size: 1.5em; margin: .75em 0px; }" "h3 { font-size: 1.17em; margin: .83em 0px; }" "h4, p," "blockquote, ul," "fieldset, form," "ol, dl, dir," "menu { margin: 1.12em 0px; }" "h5 { font-size: .83em; margin: 1.5em 0px; }" "h6 { font-size: .75em; margin: 1.67em 0px; }" "strike, del { text-decoration: line-through; }" "hr { border: 1px inset; }" "center { text-align: center; }" "h1, h2, h3, h4," "h5, h6, b," "strong { font-weight: bolder; }" }; #endif template void RemoveChars(T *str, T *remove_list) { T *i = str, *o = str, *c; while (*i) { for (c = remove_list; *c; c++) { if (*c == *i) break; } if (*c == 0) *o++ = *i; i++; } *o++ = NULL; } ////////////////////////////////////////////////////////////////////// using namespace Html1; namespace Html1 { class GHtmlPrivate { public: LHashTbl, GTag*> Loading; GHtmlStaticInst Inst; bool CursorVis; GRect CursorPos; GdcPt2 Content; bool WordSelectMode; bool LinkDoubleClick; GAutoString OnLoadAnchor; bool DecodeEmoji; GAutoString EmojiImg; int NextCtrlId; uint64 SetScrollTime; int DeferredLoads; bool IsParsing; bool IsLoaded; bool StyleDirty; // Find settings GAutoWString FindText; bool MatchCase; GHtmlPrivate() { IsLoaded = false; StyleDirty = false; IsParsing = false; LinkDoubleClick = true; WordSelectMode = false; NextCtrlId = 2000; SetScrollTime = 0; CursorVis = false; CursorPos.ZOff(-1, -1); DeferredLoads = 0; char EmojiPng[MAX_PATH]; #ifdef MAC LgiGetExeFile(EmojiPng, sizeof(EmojiPng)); LgiMakePath(EmojiPng, sizeof(EmojiPng), EmojiPng, "Contents/Resources/Emoji.png"); #else LGetSystemPath(LSP_APP_INSTALL, EmojiPng, sizeof(EmojiPng)); LgiMakePath(EmojiPng, sizeof(EmojiPng), EmojiPng, "resources/emoji.png"); #endif if (FileExists(EmojiPng)) { DecodeEmoji = true; EmojiImg.Reset(NewStr(EmojiPng)); } else DecodeEmoji = false; } ~GHtmlPrivate() { } }; }; ////////////////////////////////////////////////////////////////////// namespace Html1 { class InputButton : public GButton { GTag *Tag; public: InputButton(GTag *tag, int Id, const char *Label) : GButton(Id, 0, 0, -1, -1, Label) { Tag = tag; } void OnClick() { Tag->OnClick(); } }; class GFontCache { GHtml *Owner; List Fonts; public: GFontCache(GHtml *owner) { Owner = owner; } ~GFontCache() { Fonts.DeleteObjects(); } GFont *FontAt(int i) { return Fonts.ItemAt(i); } GFont *FindMatch(GFont *m) { for (GFont *f = Fonts.First(); f; f = Fonts.Next()) { if (*f == *m) { return f; } } return 0; } GFont *GetFont(GCss *Style) { if (!Style) return NULL; GFont *Default = Owner->GetFont(); GCss::StringsDef Face = Style->FontFamily(); if (Face.Length() < 1 || !ValidStr(Face[0])) { Face.Empty(); const char *DefFace = Default->Face(); LgiAssert(ValidStr(DefFace)); Face.Add(NewStr(DefFace)); } LgiAssert(ValidStr(Face[0])); GCss::Len Size = Style->FontSize(); GCss::FontWeightType Weight = Style->FontWeight(); bool IsBold = Weight == GCss::FontWeightBold || Weight == GCss::FontWeightBolder || Weight > GCss::FontWeight400; bool IsItalic = Style->FontStyle() == GCss::FontStyleItalic; bool IsUnderline = Style->TextDecoration() == GCss::TextDecorUnderline; if (Size.Type == GCss::LenInherit || Size.Type == GCss::LenNormal) { Size.Type = GCss::LenPt; Size.Value = (float)Default->PointSize(); } GFont *f = 0; if (Size.Type == GCss::LenPx) { int RequestPx = (int)Size.Value; // Look for cached fonts of the right size... for (f=Fonts.First(); f; f=Fonts.Next()) { if (f->Face() && _stricmp(f->Face(), Face[0]) == 0 && f->Bold() == IsBold && f->Italic() == IsItalic && f->Underline() == IsUnderline) { int Px = FontPxHeight(f); if (abs(Px - RequestPx) < 2) return f; } } } else if (Size.Type == GCss::LenPt) { double Pt = MAX(MinimumPointSize, Size.Value); for (f=Fonts.First(); f; f=Fonts.Next()) { if (!f->Face() || Face.Length() == 0) { LgiAssert(0); break; } auto FntSz = f->Size(); if (f->Face() && _stricmp(f->Face(), Face[0]) == 0 && FntSz.Type == GCss::LenPt && abs(FntSz.Value - Pt) < 0.001 && f->Bold() == IsBold && f->Italic() == IsItalic && f->Underline() == IsUnderline) { // Return cached font return f; } } } else if (Size.Type == GCss::LenPercent) { // Most of the percentages will be resolved in the "Apply" stage // of the CSS calculations, any that appear here have no "font-size" // in their parent tree, so we just use the default font size times // the requested percent Size.Type = GCss::LenPt; Size.Value *= Default->PointSize() / 100.0; if (Size.Value < MinimumPointSize) Size.Value = MinimumPointSize; } else if (Size.Type == GCss::LenEm) { // Most of the relative sizes will be resolved in the "Apply" stage // of the CSS calculations, any that appear here have no "font-size" // in their parent tree, so we just use the default font size times // the requested percent Size.Type = GCss::LenPt; Size.Value *= Default->PointSize(); if (Size.Value < MinimumPointSize) Size.Value = MinimumPointSize; } else if (Size.Type == GCss::SizeXXSmall || Size.Type == GCss::SizeXSmall || Size.Type == GCss::SizeSmall || Size.Type == GCss::SizeMedium || Size.Type == GCss::SizeLarge || Size.Type == GCss::SizeXLarge || Size.Type == GCss::SizeXXLarge) { int Idx = Size.Type-GCss::SizeXXSmall; LgiAssert(Idx >= 0 && Idx < CountOf(GCss::FontSizeTable)); Size.Type = GCss::LenPt; Size.Value = Default->PointSize() * GCss::FontSizeTable[Idx]; if (Size.Value < MinimumPointSize) Size.Value = MinimumPointSize; } else if (Size.Type == GCss::SizeSmaller) { Size.Type = GCss::LenPt; Size.Value = (float)(Default->PointSize() - 1); } else if (Size.Type == GCss::SizeLarger) { Size.Type = GCss::LenPt; Size.Value = (float)(Default->PointSize() + 1); } else LgiAssert(!"Not impl."); if ((f = new GFont)) { char *ff = ValidStr(Face[0]) ? Face[0] : Default->Face(); f->Face(ff); f->Size(Size.IsValid() ? Size : Default->Size()); f->Bold(IsBold); f->Italic(IsItalic); f->Underline(IsUnderline); // printf("Add cache font %s,%i %i,%i,%i\n", f->Face(), f->PointSize(), f->Bold(), f->Italic(), f->Underline()); if (!f->Create((char*)0, 0)) { // Broken font... f->Face(Default->Face()); GFont *DefMatch = FindMatch(f); // printf("Falling back to default face for '%s:%i', DefMatch=%p\n", ff, f->PointSize(), DefMatch); if (DefMatch) { DeleteObj(f); return DefMatch; } else { if (!f->Create((char*)0, 0)) { DeleteObj(f); return Fonts.First(); } } } // Not already cached Fonts.Insert(f); if (!f->Face()) { LgiAssert(0); } return f; } return 0; } }; class GFlowRegion { List Line; // These pointers aren't owned by the flow region // When the line is finish, all the tag regions // will need to be vertically aligned struct GFlowStack { int LeftAbs; int RightAbs; int TopAbs; }; GArray Stack; public: GHtml *Html; int x1, x2; // Left and right margins int y1; // Current y position int y2; // Maximum used y position int cx; // Current insertion point int my; // How much of the area above y2 was just margin GdcPt2 MAX; // Max dimensions int Inline; int InBody; GFlowRegion(GHtml *html, bool inbody) { Html = html; x1 = x2 = y1 = y2 = cx = my = 0; Inline = 0; InBody = inbody; } GFlowRegion(GHtml *html, GRect r, bool inbody) { Html = html; MAX.x = cx = x1 = r.x1; MAX.y = y1 = y2 = r.y1; x2 = r.x2; my = 0; Inline = 0; InBody = inbody; } GFlowRegion(GFlowRegion &r) { Html = r.Html; x1 = r.x1; x2 = r.x2; y1 = r.y1; MAX.x = cx = r.cx; MAX.y = y2 = r.y2; my = r.my; Inline = r.Inline; InBody = r.InBody; } GString ToString() { GString s; s.Printf("Flow: x=%i(%i)%i y=%i,%i my=%i inline=%i", x1, cx, x2, y1, y2, my, Inline); return s; } int X() { return x2 - cx; } void X(int newx) { x2 = x1 + newx - 1; } GFlowRegion &operator +=(GRect r) { x1 += r.x1; cx += r.x1; x2 -= r.x2; y1 += r.y1; y2 += r.y1; return *this; } GFlowRegion &operator -=(GRect r) { x1 -= r.x1; cx -= r.x1; x2 += r.x2; y1 += r.y2; y2 += r.y2; return *this; } void FinishLine(GCss::LengthType Align, bool Margin = false); void EndBlock(GCss::LengthType Align); void Insert(GFlowRect *Tr); GRect *LineBounds(); void Indent(GTag *Tag, GCss::Len Left, GCss::Len Top, GCss::Len Right, GCss::Len Bottom, bool IsMargin) { GFlowRegion This(*this); GFlowStack &Fs = Stack.New(); Fs.LeftAbs = Left.IsValid() ? ResolveX(Left, Tag, IsMargin) : 0; Fs.RightAbs = Right.IsValid() ? ResolveX(Right, Tag, IsMargin) : 0; Fs.TopAbs = Top.IsValid() ? ResolveY(Top, Tag, IsMargin) : 0; x1 += Fs.LeftAbs; cx += Fs.LeftAbs; x2 -= Fs.RightAbs; y1 += Fs.TopAbs; y2 += Fs.TopAbs; if (IsMargin) my += Fs.TopAbs; } void Indent(GRect &Px, bool IsMargin) { GFlowRegion This(*this); GFlowStack &Fs = Stack.New(); Fs.LeftAbs = Px.x1; Fs.RightAbs = Px.x2; Fs.TopAbs = Px.y1; x1 += Fs.LeftAbs; cx += Fs.LeftAbs; x2 -= Fs.RightAbs; y1 += Fs.TopAbs; y2 += Fs.TopAbs; if (IsMargin) my += Fs.TopAbs; } void Outdent(GRect &Px, bool IsMargin) { GFlowRegion This = *this; ssize_t len = Stack.Length(); if (len > 0) { GFlowStack &Fs = Stack[len-1]; int &BottomAbs = Px.y2; x1 -= Fs.LeftAbs; cx -= Fs.LeftAbs; x2 += Fs.RightAbs; y2 += BottomAbs; if (IsMargin) my += BottomAbs; Stack.Length(len-1); } else LgiAssert(!"Nothing to pop."); } void Outdent(GTag *Tag, GCss::Len Left, GCss::Len Top, GCss::Len Right, GCss::Len Bottom, bool IsMargin) { GFlowRegion This = *this; ssize_t len = Stack.Length(); if (len > 0) { GFlowStack &Fs = Stack[len-1]; int BottomAbs = Bottom.IsValid() ? ResolveY(Bottom, Tag, IsMargin) : 0; x1 -= Fs.LeftAbs; cx -= Fs.LeftAbs; x2 += Fs.RightAbs; y2 += BottomAbs; if (IsMargin) my += BottomAbs; Stack.Length(len-1); } else LgiAssert(!"Nothing to pop."); } int ResolveX(GCss::Len l, GTag *t, bool IsMargin) { GFont *f = t->GetFont(); switch (l.Type) { default: case GCss::LenInherit: return IsMargin ? 0 : X(); case GCss::LenPx: // return MIN((int)l.Value, X()); return (int)l.Value; case GCss::LenPt: return (int) (l.Value * LgiScreenDpi() / 72.0); case GCss::LenCm: return (int) (l.Value * LgiScreenDpi() / 2.54); case GCss::LenEm: { if (!f) { LgiAssert(!"No font?"); f = SysFont; } return (int)(l.Value * f->GetHeight()); } case GCss::LenEx: { if (!f) { LgiAssert(!"No font?"); f = SysFont; } return (int) (l.Value * f->GetHeight() / 2); // More haha, who uses 'ex' anyway? } case GCss::LenPercent: { int my_x = X(); int px = (int) (l.Value * my_x / 100.0); return px; } case GCss::LenAuto: { if (IsMargin) return 0; else return X(); break; } case GCss::SizeSmall: { return 1; // px } case GCss::SizeMedium: { return 2; // px } case GCss::SizeLarge: { return 3; // px } } return 0; } bool LimitX(int &x, GCss::Len Min, GCss::Len Max, GFont *f) { bool Limited = false; if (Min.IsValid()) { int Px = Min.ToPx(x2 - x1 + 1, f, false); if (Px > x) { x = Px; Limited = true; } } if (Max.IsValid()) { int Px = Max.ToPx(x2 - x1 + 1, f, false); if (Px < x) { x = Px; Limited = true; } } return Limited; } int ResolveY(GCss::Len l, GTag *t, bool IsMargin) { GFont *f = t->GetFont(); switch (l.Type) { case GCss::LenInherit: case GCss::LenAuto: case GCss::LenNormal: case GCss::LenPx: return (int)l.Value; case GCss::LenPt: return (int) (l.Value * LgiScreenDpi() / 72.0); case GCss::LenCm: return (int) (l.Value * LgiScreenDpi() / 2.54); case GCss::LenEm: { if (!f) { f = SysFont; LgiAssert(!"No font"); } return (int) (l.Value * f->GetHeight()); } case GCss::LenEx: { if (!f) { f = SysFont; LgiAssert(!"No font"); } return (int) (l.Value * f->GetHeight() / 2); // More haha, who uses 'ex' anyway? } case GCss::LenPercent: { // Walk up tree of tags to find an absolute size... GCss::Len Ab; for (GTag *p = ToTag(t->Parent); p; p = ToTag(p->Parent)) { auto h = p->Height(); if (h.IsValid() && !h.IsDynamic()) { Ab = h; break; } } if (!Ab.IsValid()) { LgiAssert(Html != NULL); Ab.Type = GCss::LenPx; Ab.Value = Html->Y(); } GCss::Len m = Ab * l; return (int)m.ToPx(0, f);; } case GCss::SizeSmall: { return 1; // px } case GCss::SizeMedium: { return 2; // px } case GCss::SizeLarge: { return 3; // px } case GCss::AlignLeft: case GCss::AlignRight: case GCss::AlignCenter: case GCss::AlignJustify: case GCss::VerticalBaseline: case GCss::VerticalSub: case GCss::VerticalSuper: case GCss::VerticalTop: case GCss::VerticalTextTop: case GCss::VerticalMiddle: case GCss::VerticalBottom: case GCss::VerticalTextBottom: { // Meaningless in this context break; } default: { LgiAssert(!"Not supported."); break; } } return 0; } bool LimitY(int &y, GCss::Len Min, GCss::Len Max, GFont *f) { bool Limited = false; int TotalY = Html ? Html->Y() : 0; if (Min.IsValid()) { int Px = Min.ToPx(TotalY, f, false); if (Px > y) { y = Px; Limited = true; } } if (Max.IsValid()) { int Px = Max.ToPx(TotalY, f, false); if (Px < y) { y = Px; Limited = true; } } return Limited; } GRect ResolveMargin(GCss *Src, GTag *Tag) { GRect r; r.x1 = ResolveX(Src->MarginLeft(), Tag, true); r.y1 = ResolveY(Src->MarginTop(), Tag, true); r.x2 = ResolveX(Src->MarginRight(), Tag, true); r.y2 = ResolveY(Src->MarginBottom(), Tag, true); return r; } GRect ResolveBorder(GCss *Src, GTag *Tag) { GRect r; r.x1 = ResolveX(Src->BorderLeft(), Tag, true); r.y1 = ResolveY(Src->BorderTop(), Tag, true); r.x2 = ResolveX(Src->BorderRight(), Tag, true); r.y2 = ResolveY(Src->BorderBottom(), Tag, true); return r; } GRect ResolvePadding(GCss *Src, GTag *Tag) { GRect r; r.x1 = ResolveX(Src->PaddingLeft(), Tag, true); r.y1 = ResolveY(Src->PaddingTop(), Tag, true); r.x2 = ResolveX(Src->PaddingRight(), Tag, true); r.y2 = ResolveY(Src->PaddingBottom(), Tag, true); return r; } }; }; ////////////////////////////////////////////////////////////////////// static bool ParseDistance(char *s, float &d, char *units = 0) { if (!s) return false; while (*s && IsWhiteSpace(*s)) s++; if (!IsDigit(*s) && !strchr("-.", *s)) return false; d = (float)atof(s); while (*s && (IsDigit(*s) || strchr("-.", *s))) s++; while (*s && IsWhiteSpace(*s)) s++; char _units[128]; char *o = units = units ? units : _units; while (*s && (IsAlpha(*s) || *s == '%')) { *o++ = *s++; } *o++ = 0; return true; } GLength::GLength() { d = 0; PrevAbs = 0; u = GCss::LenInherit; } GLength::GLength(char *s) { Set(s); } bool GLength::IsValid() { return u != GCss::LenInherit; } bool GLength::IsDynamic() { return u == GCss::LenPercent || d == 0.0; } GLength::operator float () { return d; } GLength &GLength::operator =(float val) { d = val; u = GCss::LenPx; return *this; } GCss::LengthType GLength::GetUnits() { return u; } void GLength::Set(char *s) { if (ValidStr(s)) { char Units[256] = ""; if (ParseDistance(s, d, Units)) { if (Units[0]) { if (strchr(Units, '%')) { u = GCss::LenPercent; } else if (stristr(Units, "pt")) { u = GCss::LenPt; } else if (stristr(Units, "em")) { u = GCss::LenEm; } else if (stristr(Units, "ex")) { u = GCss::LenEx; } else { u = GCss::LenPx; } } else { u = GCss::LenPx; } } } } float GLength::Get(GFlowRegion *Flow, GFont *Font, bool Lock) { switch (u) { default: break; case GCss::LenEm: { return PrevAbs = d * (Font ? Font->GetHeight() : 14); break; } case GCss::LenEx: { return PrevAbs = (Font ? Font->GetHeight() * d : 14) / 2; break; } case GCss::LenPercent: { if (Lock || PrevAbs == 0.0) { return PrevAbs = (Flow->X() * d / 100); } else { return PrevAbs; } break; } } float FlowX = Flow ? Flow->X() : d; return PrevAbs = MIN(FlowX, d); } GLine::GLine() { LineStyle = -1; LineReset = 0x80000000; } GLine::~GLine() { } GLine &GLine::operator =(int i) { d = (float)i; return *this; } void GLine::Set(char *s) { GToken t(s, " \t"); LineReset = 0x80000000; LineStyle = -1; char *Style = 0; for (unsigned i=0; iColourMap.Find(c) ) { GHtmlParser::ParseColour(c, Colour); } else if (_strnicmp(c, "rgb(", 4) == 0) { char Buf[256]; strcpy_s(Buf, sizeof(Buf), c); while (!strchr(c, ')') && (c = t[++i])) { strcat(Buf, c); } GHtmlParser::ParseColour(Buf, Colour); } else if (IsDigit(*c)) { GLength::Set(c); } else if (_stricmp(c, "none") == 0) { Style = 0; } else if ( _stricmp(c, "dotted") == 0 || _stricmp(c, "dashed") == 0 || _stricmp(c, "solid") == 0 || _stricmp(c, "float") == 0 || _stricmp(c, "groove") == 0 || _stricmp(c, "ridge") == 0 || _stricmp(c, "inset") == 0 || _stricmp(c, "outse") == 0) { Style = c; } else { // ??? } } if (Style && _stricmp(Style, "dotted") == 0) { switch ((int)d) { case 2: { LineStyle = 0xcccccccc; break; } case 3: { LineStyle = 0xe38e38; LineReset = 0x800000; break; } case 4: { LineStyle = 0xf0f0f0f0; break; } case 5: { LineStyle = 0xf83e0; LineReset = 0x80000; break; } case 6: { LineStyle = 0xfc0fc0; LineReset = 0x800000; break; } case 7: { LineStyle = 0xfe03f80; LineReset = 0x8000000; break; } case 8: { LineStyle = 0xff00ff00; break; } case 9: { LineStyle = 0x3fe00; LineReset = 0x20000; break; } default: { LineStyle = 0xaaaaaaaa; break; } } } } ////////////////////////////////////////////////////////////////////// GRect GTag::GetRect(bool Client) { GRect r(Pos.x, Pos.y, Pos.x + Size.x - 1, Pos.y + Size.y - 1); if (!Client) { for (GTag *p = ToTag(Parent); p; p=ToTag(p->Parent)) { r.Offset(p->Pos.x, p->Pos.y); } } return r; } GCss::LengthType GTag::GetAlign(bool x) { for (GTag *t = this; t; t = ToTag(t->Parent)) { GCss::Len l; if (x) { if (IsTableCell(TagId) && Cell && Cell->XAlign) l.Type = Cell->XAlign; else l = t->TextAlign(); } else { l = t->VerticalAlign(); } if (l.Type != LenInherit) { return l.Type; } if (t->TagId == TAG_TABLE) break; } return LenInherit; } ////////////////////////////////////////////////////////////////////// void GFlowRegion::EndBlock(GCss::LengthType Align) { if (cx > x1) { FinishLine(Align); } } void GFlowRegion::FinishLine(GCss::LengthType Align, bool Margin) { if (Align != GCss::AlignLeft) { int Used = 0; for (auto l : Line) Used += l->X(); int Total = x2 - x1 + 1; if (Used < Total) { int Offset = 0; if (Align == GCss::AlignCenter) Offset = (Total - Used) / 2; else if (Align == GCss::AlignRight) Offset = Total - Used; if (Offset) for (auto l : Line) { if (l->Tag->Display() != GCss::DispInlineBlock) l->Offset(Offset, 0); } } } if (y2 > y1) { my = Margin ? y2 - y1 : 0; y1 = y2; } else { int fy = Html->DefFont()->GetHeight(); my = Margin ? fy : 0; y1 += fy; } cx = x1; y2 = y1; Line.Empty(); } GRect *GFlowRegion::LineBounds() { GFlowRect *Prev = Line.First(); GFlowRect *r=Prev; if (r) { GRect b; b = *r; int Ox = r->Tag->AbsX(); int Oy = r->Tag->AbsY(); b.Offset(Ox, Oy); // int Ox = 0, Oy = 0; while ((r = Line.Next())) { GRect c = *r; Ox = r->Tag->AbsX(); Oy = r->Tag->AbsY(); c.Offset(Ox, Oy); /* Ox += r->Tag->Pos.x - Prev->Tag->Pos.x; Oy += r->Tag->Pos.y - Prev->Tag->Pos.y; c.Offset(Ox, Oy); */ b.Union(&c); Prev = r; } static GRect Rgn; Rgn = b; return &Rgn; } return 0; } void GFlowRegion::Insert(GFlowRect *Tr) { if (Tr) Line.Insert(Tr); } ////////////////////////////////////////////////////////////////////// GTag::GTag(GHtml *h, GHtmlElement *p) : GHtmlElement(p), Attr(8) { Ctrl = 0; CtrlType = CtrlNone; TipId = 0; Display(DispInline); Html = h; ImageResized = false; Cursor = -1; Selection = -1; Font = 0; LineHeightCache = -1; HtmlId = NULL; // TableBorder = 0; Cell = NULL; TagId = CONTENT; Info = 0; Pos.x = Pos.y = 0; #ifdef _DEBUG Debug = false; #endif } GTag::~GTag() { if (Html->Cursor == this) { Html->Cursor = 0; } if (Html->Selection == this) { Html->Selection = 0; } DeleteObj(Ctrl); Attr.DeleteArrays(); DeleteObj(Cell); } void GTag::OnChange(PropType Prop) { } bool GTag::OnClick() { if (!Html->Environment) return false; const char *OnClick = NULL; if (Get("onclick", OnClick)) { Html->Environment->OnExecuteScript(Html, (char*)OnClick); } else { OnNotify(0); } return true; } void GTag::Set(const char *attr, const char *val) { char *existing = Attr.Find(attr); if (existing) DeleteArray(existing); if (val) Attr.Add(attr, NewStr(val)); } bool GTag::GetVariant(const char *Name, GVariant &Value, char *Array) { GDomProperty Fld = LgiStringToDomProp(Name); switch (Fld) { case ObjStyle: // Type: GCssStyle { Value = &StyleDom; return true; } case ObjTextContent: // Type: String { Value = Text(); return true; } default: { char *a = Attr.Find(Name); if (a) { Value = a; return true; } break; } } return false; } bool GTag::SetVariant(const char *Name, GVariant &Value, char *Array) { GDomProperty Fld = LgiStringToDomProp(Name); switch (Fld) { case ObjStyle: { const char *Defs = Value.Str(); if (!Defs) return false; return Parse(Defs, ParseRelaxed); } case ObjTextContent: { const char *s = Value.Str(); if (s) { GAutoWString w(CleanText(s, strlen(s), "utf-8", true, true)); Txt = w; return true; } break; } case ObjInnerHtml: // Type: String { // Clear out existing tags.. Children.DeleteObjects(); char *Doc = Value.CastString(); if (Doc) { // Create new tags... bool BackOut = false; while (Doc && *Doc) { GTag *t = new GTag(Html, this); if (t) { Doc = Html->ParseHtml(t, Doc, 1, false, &BackOut); if (!Doc) break; } else break; } } else return false; break; } default: { Set(Name, Value.CastString()); SetStyle(); break; } } Html->ViewWidth = -1; return true; } ssize_t GTag::GetTextStart() { if (PreText()) { GFlowRect *t = TextPos[1]; if (t) return t->Text - Text(); } else { GFlowRect *t = TextPos[0]; if (t) { LgiAssert(t->Text >= Text() && t->Text <= Text()+2); return t->Text - Text(); } } return 0; } static bool TextToStream(GStream &Out, char16 *Text) { if (!Text) return true; uint8_t Buf[256]; uint8_t *s = Buf; ssize_t Len = sizeof(Buf); while (*Text) { #define WriteExistingContent() \ if (s > Buf) \ Out.Write(Buf, (int)(s - Buf)); \ s = Buf; \ Len = sizeof(Buf); \ Buf[0] = 0; if (*Text == '<' || *Text == '>') { WriteExistingContent(); Out.Print("&%ct;", *Text == '<' ? 'l' : 'g'); } else if (*Text == 0xa0) { WriteExistingContent(); Out.Write((char*)" ", 6); } else { LgiUtf32To8(*Text, s, Len); if (Len < 16) { WriteExistingContent(); } } Text++; } if (s > Buf) Out.Write(Buf, s - Buf); return true; } bool GTag::CreateSource(GStringPipe &p, int Depth, bool LastWasBlock) { char *Tabs = new char[Depth+1]; memset(Tabs, '\t', Depth); Tabs[Depth] = 0; if (ValidStr(Tag)) { if (IsBlock()) { p.Print("%s%s<%s", TagId != TAG_HTML ? "\n" : "", Tabs, Tag.Get()); } else { p.Print("<%s", Tag.Get()); } if (Attr.Length()) { // const char *a; // for (char *v = Attr.First(&a); v; v = Attr.Next(&a)) for (auto v : Attr) { if (_stricmp(v.key, "style")) p.Print(" %s=\"%s\"", v.key, v.value); } } if (Props.Length()) { GCss *Css = this; GCss Tmp; #define DelProp(p) \ if (Css == this) { Tmp = *Css; Css = &Tmp; } \ Css->DeleteProp(p); // Clean out any default CSS properties where we can... GHtmlElemInfo *i = GHtmlStatic::Inst->GetTagInfo(Tag); if (i) { if (Props.Find(PropDisplay) && ( (!i->Block() && Display() == DispInline) || (i->Block() && Display() == DispBlock) )) { DelProp(PropDisplay); } switch (TagId) { default: break; case TAG_A: { GCss::ColorDef Blue(GCss::ColorRgb, Rgb32(0, 0, 255)); if (Props.Find(PropColor) && Color() == Blue) DelProp(PropColor); if (Props.Find(PropTextDecoration) && TextDecoration() == GCss::TextDecorUnderline) DelProp(PropTextDecoration) break; } case TAG_BODY: { GCss::Len FivePx(GCss::LenPx, 5.0f); if (Props.Find(PropPaddingLeft) && PaddingLeft() == FivePx) DelProp(PropPaddingLeft) if (Props.Find(PropPaddingTop) && PaddingTop() == FivePx) DelProp(PropPaddingTop) if (Props.Find(PropPaddingRight) && PaddingRight() == FivePx) DelProp(PropPaddingRight) break; } case TAG_B: { if (Props.Find(PropFontWeight) && FontWeight() == GCss::FontWeightBold) DelProp(PropFontWeight); break; } case TAG_U: { if (Props.Find(PropTextDecoration) && TextDecoration() == GCss::TextDecorUnderline) DelProp(PropTextDecoration); break; } case TAG_I: { if (Props.Find(PropFontStyle) && FontStyle() == GCss::FontStyleItalic) DelProp(PropFontStyle); break; } } } // Convert CSS props to a string and emit them... GAutoString s = Css->ToString(); if (ValidStr(s)) { // Clean off any trailing whitespace... char *e = s ? s + strlen(s) : NULL; while (e && strchr(WhiteSpace, e[-1])) *--e = 0; // Print them to the tags attributes... p.Print(" style=\"%s\"", s.Get()); } } } if (Children.Length() || TagId == TAG_STYLE) // { if (Tag) { p.Write((char*)">", 1); TextToStream(p, Text()); } bool Last = IsBlock(); for (unsigned i=0; iCreateSource(p, Parent ? Depth+1 : 0, Last); Last = c->IsBlock(); } if (Tag) { if (IsBlock()) { if (Children.Length()) p.Print("\n%s", Tabs); } p.Print("", Tag.Get()); } } else if (Tag) { if (Text()) { p.Write((char*)">", 1); TextToStream(p, Text()); p.Print("", Tag.Get()); } else { p.Print("/>\n"); } } else { TextToStream(p, Text()); } DeleteArray(Tabs); return true; } void GTag::SetTag(const char *NewTag) { Tag.Reset(NewStr(NewTag)); if (NewTag) { Info = Html->GetTagInfo(Tag); if (Info) { TagId = Info->Id; Display(Info->Flags & GHtmlElemInfo::TI_BLOCK ? GCss::DispBlock : GCss::DispInline); } } else { Info = NULL; TagId = CONTENT; } SetStyle(); } GColour GTag::_Colour(bool f) { for (GTag *t = this; t; t = ToTag(t->Parent)) { ColorDef c = f ? t->Color() : t->BackgroundColor(); if (c.Type != ColorInherit) { return GColour(c.Rgb32, 32); } #if 1 if (!f && t->TagId == TAG_TABLE) break; #else /* This implements some basic level of colour inheritance for background colours. See test case 'cisra-cqs.html'. */ if (!f && t->TagId == TAG_TABLE) break; #endif } return GColour(); } void GTag::CopyClipboard(GMemQueue &p, bool &InSelection) { ssize_t Min = -1; ssize_t Max = -1; if (Cursor >= 0 && Selection >= 0) { Min = MIN(Cursor, Selection); Max = MAX(Cursor, Selection); } else if (InSelection) { Max = MAX(Cursor, Selection); } else { Min = MAX(Cursor, Selection); } ssize_t Off = -1; ssize_t Chars = 0; if (Min >= 0 && Max >= 0) { Off = Min; Chars = Max - Min; } else if (Min >= 0) { Off = Min; Chars = StrlenW(Text()) - Min; InSelection = true; } else if (Max >= 0) { Off = 0; Chars = Max; InSelection = false; } else if (InSelection) { Off = 0; Chars = StrlenW(Text()); } if (Off >= 0 && Chars > 0) { p.Write((uchar*) (Text() + Off), Chars * sizeof(char16)); } if (InSelection) { switch (TagId) { default: break; case TAG_BR: { char16 NL[] = {'\n', 0}; p.Write((uchar*) NL, sizeof(char16)); break; } case TAG_P: { char16 NL[] = {'\n', '\n', 0}; p.Write((uchar*) NL, sizeof(char16) * 2); break; } } } for (unsigned i=0; iCopyClipboard(p, InSelection); } } static char* _DumpColour(GCss::ColorDef c) { static char Buf[4][32]; #ifdef _MSC_VER static LONG Cur = 0; LONG Idx = InterlockedIncrement(&Cur); #else static int Cur = 0; int Idx = __sync_fetch_and_add(&Cur, 1); #endif char *b = Buf[Idx % 4]; if (c.Type == GCss::ColorInherit) strcpy_s(b, 32, "Inherit"); else sprintf_s(b, 32, "%2.2x,%2.2x,%2.2x(%2.2x)", R32(c.Rgb32),G32(c.Rgb32),B32(c.Rgb32),A32(c.Rgb32)); return b; } void GTag::_Dump(GStringPipe &Buf, int Depth) { GString Tabs; Tabs.Set(NULL, Depth); memset(Tabs.Get(), '\t', Depth); const char *Empty = ""; char *ElementName = TagId == CONTENT ? (char*)"Content" : (TagId == ROOT ? (char*)"Root" : Tag); Buf.Print( "%s%s(%p)%s%s%s (%i) Pos=%i,%i Size=%i,%i Color=%s/%s", Tabs.Get(), ElementName, this, HtmlId ? "#" : Empty, HtmlId ? HtmlId : Empty, #ifdef _DEBUG Debug ? " debug" : Empty, #else Empty, #endif WasClosed, Pos.x, Pos.y, Size.x, Size.y, _DumpColour(Color()), _DumpColour(BackgroundColor())); for (unsigned i=0; iText, Tr->Len)); if (Utf8) { size_t Len = strlen(Utf8); if (Len > 40) { Utf8[40] = 0; } } else if (Tr->Text) { Utf8.Reset(NewStr("")); } Buf.Print("Tr(%i,%i %ix%i '%s') ", Tr->x1, Tr->y1, Tr->X(), Tr->Y(), Utf8.Get()); } Buf.Print("\r\n"); for (unsigned i=0; i_Dump(Buf, Depth+1); } if (Children.Length()) { Buf.Print("%s/%s\r\n", Tabs.Get(), ElementName); } } GAutoWString GTag::DumpW() { GStringPipe Buf; // Buf.Print("Html pos=%s\n", Html?Html->GetPos().GetStr():0); _Dump(Buf, 0); GAutoString a(Buf.NewStr()); GAutoWString w(Utf8ToWide(a)); return w; } GAutoString GTag::DescribeElement() { GStringPipe s(256); s.Print("%s", Tag ? Tag.Get() : "CONTENT"); if (HtmlId) s.Print("#%s", HtmlId); for (unsigned i=0; iDefFont(); } return f; } GFont *GTag::GetFont() { if (!Font) { if (PropAddress(PropFontFamily) != 0 || FontSize().Type != LenInherit || FontStyle() != FontStyleInherit || FontVariant() != FontVariantInherit || FontWeight() != FontWeightInherit || TextDecoration() != TextDecorInherit) { GCss c; GCss::PropMap Map; Map.Add(PropFontFamily, new GCss::PropArray); Map.Add(PropFontSize, new GCss::PropArray); Map.Add(PropFontStyle, new GCss::PropArray); Map.Add(PropFontVariant, new GCss::PropArray); Map.Add(PropFontWeight, new GCss::PropArray); Map.Add(PropTextDecoration, new GCss::PropArray); for (GTag *t = this; t; t = ToTag(t->Parent)) { if (!c.InheritCollect(*t, Map)) break; } c.InheritResolve(Map); Map.DeleteObjects(); if ((Font = Html->FontCache->GetFont(&c))) return Font; } else { GTag *t = this; while (!t->Font && t->Parent) { t = ToTag(t->Parent); } if (t->Font) return t->Font; } Font = Html->DefFont(); } return Font; } GTag *GTag::PrevTag() { if (Parent) { ssize_t i = Parent->Children.IndexOf(this); if (i >= 0) { return ToTag(Parent->Children[i - 1]); } } return 0; } void GTag::Invalidate() { GRect p = GetRect(); for (GTag *t=ToTag(Parent); t; t=ToTag(t->Parent)) { p.Offset(t->Pos.x, t->Pos.y); } Html->Invalidate(&p); } GTag *GTag::IsAnchor(GAutoString *Uri) { GTag *a = 0; for (GTag *t = this; t; t = ToTag(t->Parent)) { if (t->TagId == TAG_A) { a = t; break; } } if (a && Uri) { const char *u = 0; if (a->Get("href", u)) { GAutoWString w(CleanText(u, strlen(u), "utf-8")); if (w) { Uri->Reset(WideToUtf8(w)); } } } return a; } bool GTag::OnMouseClick(GMouse &m) { bool Processed = false; if (m.IsContextMenu()) { GAutoString Uri; GTag *a = IsAnchor(&Uri); if (a && ValidStr(Uri)) { GSubMenu RClick; #define IDM_COPY_LINK 100 if (Html->GetMouse(m, true)) { int Id = 0; RClick.AppendItem(LgiLoadString(L_COPY_LINK_LOCATION, "&Copy Link Location"), IDM_COPY_LINK, Uri != 0); if (Html->GetEnv()) Html->GetEnv()->AppendItems(&RClick); switch (Id = RClick.Float(Html, m.x, m.y)) { case IDM_COPY_LINK: { GClipBoard Clip(Html); Clip.Text(Uri); break; } default: { if (Html->GetEnv()) { Html->GetEnv()->OnMenu(Html, Id, a); } break; } } } Processed = true; } } else if (m.Down() && m.Left()) { #ifdef _DEBUG if (m.Ctrl()) { GAutoString Style = ToString(); GStringPipe p(256); p.Print("Tag: %s\n", Tag ? Tag.Get() : "CONTENT"); if (Class.Length()) { p.Print("Class(es): "); for (unsigned i=0; iParent; t=ToTag(t->Parent)) { GStringPipe Tmp; Tmp.Print(" %s", t->Tag ? t->Tag.Get() : "CONTENT"); if (t->HtmlId) { Tmp.Print("#%s", t->HtmlId); } for (unsigned i=0; iClass.Length(); i++) { Tmp.Print(".%s", t->Class[i].Get()); } GAutoString Txt(Tmp.NewStr()); p.Print("%s", Txt.Get()); GDisplayString Ds(SysFont, Txt); int Px = 170 - Ds.X(); int Chars = Px / Sp.X(); for (int c=0; cPos.x, t->Pos.y, t->Size.x, t->Size.y); } GAutoString a(p.NewStr()); LgiMsg( Html, "%s", Html->GetClass(), MB_OK, a.Get()); } else #endif { GAutoString Uri; if (Html && Html->Environment) { if (IsAnchor(&Uri)) { if (Uri) { if (!Html->d->LinkDoubleClick || m.Double()) { Html->Environment->OnNavigate(Html, Uri); Processed = true; } } const char *OnClk = NULL; if (!Processed && Get("onclick", OnClk)) { Html->Environment->OnExecuteScript(Html, (char*)OnClk); } } else { Processed = OnClick(); } } } } return Processed; } GTag *GTag::GetBlockParent(ssize_t *Idx) { if (IsBlock()) { if (Idx) *Idx = 0; return this; } for (GTag *t = this; t; t = ToTag(t->Parent)) { if (ToTag(t->Parent)->IsBlock()) { if (Idx) { *Idx = t->Parent->Children.IndexOf(t); } return ToTag(t->Parent); } } return 0; } GTag *GTag::GetAnchor(char *Name) { if (!Name) return 0; const char *n; if (IsAnchor(0) && Get("name", n) && n && !_stricmp(Name, n)) { return this; } for (unsigned i=0; iGetAnchor(Name); if (Result) return Result; } return 0; } GTag *GTag::GetTagByName(const char *Name) { if (Name) { if (Tag && _stricmp(Tag, Name) == 0) { return this; } for (unsigned i=0; iGetTagByName(Name); if (Result) return Result; } } return 0; } static int IsNearRect(GRect *r, int x, int y) { if (r->Overlap(x, y)) { return 0; } else if (x >= r->x1 && x <= r->x2) { if (y < r->y1) return r->y1 - y; else return y - r->y2; } else if (y >= r->y1 && y <= r->y2) { if (x < r->x1) return r->x1 - x; else return x - r->x2; } int64 dx = 0; int64 dy = 0; if (x < r->x1) { if (y < r->y1) { // top left dx = r->x1 - x; dy = r->y1 - y; } else { // bottom left dx = r->x1 - x; dy = y - r->y2; } } else { if (y < r->y1) { // top right dx = x - r->x2; dy = r->y1 - y; } else { // bottom right dx = x - r->x2; dy = y - r->y2; } } return (int) sqrt( (double) ( (dx * dx) + (dy * dy) ) ); } ssize_t GTag::NearestChar(GFlowRect *Tr, int x, int y) { GFont *f = GetFont(); if (f) { GDisplayString ds(f, Tr->Text, Tr->Len); ssize_t c = ds.CharAt(x - Tr->x1); if (Tr->Text == PreText()) { return 0; } else { char16 *t = Tr->Text + c; size_t Len = StrlenW(Text()); if (t >= Text() && t <= Text() + Len) { return (t - Text()) - GetTextStart(); } else { LgiTrace("%s:%i - Error getting char at position.\n", _FL); } } } return -1; } void GTag::GetTagByPos(GTagHit &TagHit, int x, int y, int Depth, bool InBody, bool DebugLog) { /* InBody: Originally I had this test in the code but it seems that some test cases have actual content after the body. And testing for "InBody" breaks functionality for those cases (see "spam4.html" and the unsubscribe link at the end of the doc). */ if (TagId == TAG_IMG) { GRect img(0, 0, Size.x - 1, Size.y - 1); if (/*InBody &&*/ img.Overlap(x, y)) { TagHit.Direct = this; TagHit.Block = 0; } } else if (/*InBody &&*/ TextPos.Length()) { for (unsigned i=0; i= Tr->y1 && y <= Tr->y2; int Near = IsNearRect(Tr, x, y); if (Near >= 0 && Near < 100) { if ( !TagHit.NearestText || ( SameRow && !TagHit.NearSameRow ) || ( SameRow == TagHit.NearSameRow && Near < TagHit.Near ) ) { TagHit.NearestText = this; TagHit.NearSameRow = SameRow; TagHit.Block = Tr; TagHit.Near = Near; TagHit.Index = NearestChar(Tr, x, y); if (DebugLog) { LgiTrace("%i:GetTagByPos HitText %s #%s, idx=%i, near=%i, txt='%S'\n", Depth, Tag.Get(), HtmlId, TagHit.Index, TagHit.Near, Tr->Text); } if (!TagHit.Near) { TagHit.Direct = this; TagHit.LocalCoords.x = x; TagHit.LocalCoords.y = y; } } } } } else if ( TagId != TAG_TR && Tag && x >= 0 && y >= 0 && x < Size.x && y < Size.y // && InBody ) { // Direct hit TagHit.Direct = this; TagHit.LocalCoords.x = x; TagHit.LocalCoords.y = y; if (DebugLog) { LgiTrace("%i:GetTagByPos DirectHit %s #%s, idx=%i, near=%i\n", Depth, Tag.Get(), HtmlId, TagHit.Index, TagHit.Near); } } if (TagId == TAG_BODY) InBody = true; for (unsigned i=0; iPos.x >= 0 && t->Pos.y >= 0) { t->GetTagByPos(TagHit, x - t->Pos.x, y - t->Pos.y, Depth + 1, InBody, DebugLog); } } } int GTag::OnNotify(int f) { if (!Ctrl || !Html->InThread()) return 0; switch (CtrlType) { case CtrlSubmit: { GTag *Form = this; while (Form && Form->TagId != TAG_FORM) Form = ToTag(Form->Parent); if (Form) Html->OnSubmitForm(Form); break; } default: { CtrlValue = Ctrl->Name(); break; } } return 0; } void GTag::CollectFormValues(LHashTbl,char*> &f) { if (CtrlType != CtrlNone) { const char *Name; if (Get("name", Name)) { char *Existing = f.Find(Name); if (Existing) DeleteArray(Existing); char *Val = CtrlValue.Str(); if (Val) { GStringPipe p(256); for (char *v = Val; *v; v++) { if (*v == ' ') p.Write("+", 1); else if (IsAlpha(*v) || IsDigit(*v) || *v == '_' || *v == '.') p.Write(v, 1); else p.Print("%%%02.2X", *v); } f.Add(Name, p.NewStr()); } else { f.Add(Name, NewStr("")); } } } for (unsigned i=0; iCollectFormValues(f); } } GTag *GTag::FindCtrlId(int Id) { if (Ctrl && Ctrl->GetId() == Id) return this; for (unsigned i=0; iFindCtrlId(Id); if (f) return f; } return NULL; } void GTag::Find(int TagType, GArray &Out) { if (TagId == TagType) { Out.Add(this); } for (unsigned i=0; iFind(TagType, Out); } } void GTag::SetImage(const char *Uri, GSurface *Img) { if (Img) { if (TagId != TAG_IMG) { ImageDef *Def = (ImageDef*)GCss::Props.Find(PropBackgroundImage); if (Def) { Def->Type = ImageOwn; DeleteObj(Def->Img); Def->Img = Img; } } else { Image.Reset(Img); GRect r = XSubRect(); if (r.Valid()) { GAutoPtr t(new GMemDC(r.X(), r.Y(), Image->GetColourSpace())); if (t) { t->Blt(0, 0, Image, &r); Image = t; } } } for (unsigned i=0; iCell) { t->Cell->MinContent = 0; t->Cell->MaxContent = 0; } } } else { Html->d->Loading.Add(Uri, this); } } void GTag::LoadImage(const char *Uri) { #if DOCUMENT_LOAD_IMAGES if (!Html->Environment) return; GUri u(Uri); bool LdImg = Html->GetLoadImages(); bool IsRemote = u.Protocol && ( !_stricmp(u.Protocol, "http") || !_stricmp(u.Protocol, "https") || !_stricmp(u.Protocol, "ftp") ); if (IsRemote && !LdImg) { Html->NeedsCapability("RemoteContent"); return; } GDocumentEnv::LoadJob *j = Html->Environment->NewJob(); if (j) { LgiAssert(Html != NULL); j->Uri.Reset(NewStr(Uri)); j->Env = Html->Environment; j->UserData = this; j->UserUid = Html->GetDocumentUid(); GDocumentEnv::LoadType Result = Html->Environment->GetContent(j); if (Result == GDocumentEnv::LoadImmediate) { SetImage(Uri, j->pDC.Release()); } else if (Result == GDocumentEnv::LoadDeferred) { Html->d->DeferredLoads++; } DeleteObj(j); } #endif } void GTag::LoadImages() { const char *Uri = 0; if (Html->Environment && TagId == TAG_IMG && !Image && Get("src", Uri)) { LoadImage(Uri); } for (unsigned i=0; iLoadImages(); } } void GTag::ImageLoaded(char *uri, GSurface *Img, int &Used) { const char *Uri = 0; if (!Image && Get("src", Uri)) { if (strcmp(Uri, uri) == 0) { if (Used == 0) { SetImage(Uri, Img); } else { SetImage(Uri, new GMemDC(Img)); } Used++; } } for (unsigned i=0; iImageLoaded(uri, Img, Used); } } struct GTagElementCallback : public GCss::ElementCallback { const char *Val; const char *GetElement(GTag *obj) { return obj->Tag; } const char *GetAttr(GTag *obj, const char *Attr) { if (obj->Get(Attr, Val)) return Val; return NULL; } bool GetClasses(GString::Array &Classes, GTag *obj) { Classes = obj->Class; return Classes.Length() > 0; } GTag *GetParent(GTag *obj) { return ToTag(obj->Parent); } GArray GetChildren(GTag *obj) { GArray c; for (unsigned i=0; iChildren.Length(); i++) c.Add(ToTag(obj->Children[i])); return c; } }; void GTag::RestyleAll() { Restyle(); for (unsigned i=0; iRestyleAll(); } } // After CSS has changed this function scans through the CSS and applies any rules // that match the current tag. void GTag::Restyle() { // Use the matching built into the GCss Store. GCss::SelArray Styles; GTagElementCallback Context; if (Html->CssStore.Match(Styles, &Context, this)) { for (unsigned i=0; iStyle); } } // Do the element specific styles const char *s; if (Get("style", s)) SetCssStyle(s); #if DEBUG_RESTYLE && defined(_DEBUG) if (Debug) { GAutoString Style = ToString(); LgiTrace(">>>> %s <<<<:\n%s\n\n", Tag.Get(), Style.Get()); } #endif } void GTag::SetStyle() { const static float FntMul[] = { 0.6f, // size=1 0.89f, // size=2 1.0f, // size=3 1.2f, // size=4 1.5f, // size=5 2.0f, // size=6 3.0f // size=7 }; const char *s = 0; #ifdef _DEBUG if (Get("debug", s)) { if ((Debug = atoi(s))) { LgiTrace("Debug Tag: %p '%s'\n", this, Tag ? Tag.Get() : "CONTENT"); } } #endif if (Get("Color", s)) { ColorDef Def; if (GHtmlParser::ParseColour(s, Def)) { Color(Def); } } if (Get("Background", s) || Get("bgcolor", s)) { ColorDef Def; if (GHtmlParser::ParseColour(s, Def)) { BackgroundColor(Def); } else { GCss::ImageDef Img; Img.Type = ImageUri; Img.Uri = s; BackgroundImage(Img); BackgroundRepeat(RepeatBoth); } } switch (TagId) { default: break; case TAG_LINK: { const char *Type, *Href; if (Html->Environment && Get("type", Type) && Get("href", Href) && !_stricmp(Type, "text/css") && !Html->CssHref.Find(Href)) { GDocumentEnv::LoadJob *j = Html->Environment->NewJob(); if (j) { LgiAssert(Html != NULL); GTag *t = this; j->Uri.Reset(NewStr(Href)); j->Env = Html->Environment; j->UserData = t; j->UserUid = Html->GetDocumentUid(); GDocumentEnv::LoadType Result = Html->Environment->GetContent(j); if (Result == GDocumentEnv::LoadImmediate) { GStreamI *s = j->GetStream(); if (s) { int Len = (int)s->GetSize(); if (Len > 0) { GAutoString a(new char[Len+1]); ssize_t r = s->Read(a, Len); a[r] = 0; Html->CssHref.Add(Href, true); Html->OnAddStyle("text/css", a); } } } else if (Result == GDocumentEnv::LoadDeferred) { Html->d->DeferredLoads++; } DeleteObj(j); } } break; } case TAG_BLOCKQUOTE: { MarginTop(Len("8px")); MarginBottom(Len("8px")); MarginLeft(Len("16px")); if (Get("Type", s)) { if (_stricmp(s, "cite") == 0) { BorderLeft(BorderDef(this, "1px solid blue")); PaddingLeft(Len("0.5em")); /* ColorDef Def; Def.Type = ColorRgb; Def.Rgb32 = Rgb32(0x80, 0x80, 0x80); Color(Def); */ } } break; } case TAG_P: { MarginBottom(Len("1em")); break; } case TAG_A: { const char *Href; if (Get("href", Href)) { ColorDef c; c.Type = ColorRgb; c.Rgb32 = Rgb32(0, 0, 255); Color(c); TextDecoration(TextDecorUnderline); } break; } case TAG_TABLE: { Len l; if (!Cell) Cell = new TblCell; if (Get("border", s)) { BorderDef b; if (b.Parse(this, s)) { BorderLeft(b); BorderRight(b); BorderTop(b); BorderBottom(b); } } if (Get("cellspacing", s) && l.Parse(s, PropBorderSpacing, ParseRelaxed)) { BorderSpacing(l); } else { // BorderSpacing(GCss::Len(GCss::LenPx, 2.0f)); } if (Get("cellpadding", s) && l.Parse(s, Prop_CellPadding, ParseRelaxed)) { _CellPadding(l); } if (Get("align", s)) { Len l; if (l.Parse(s)) Cell->XAlign = l.Type; } break; } case TAG_TD: case TAG_TH: { if (!Cell) Cell = new TblCell; GTag *Table = GetTable(); if (Table) { Len l = Table->_CellPadding(); if (!l.IsValid()) { l.Type = GCss::LenPx; l.Value = DefaultCellPadding; } PaddingLeft(l); PaddingRight(l); PaddingTop(l); PaddingBottom(l); } if (TagId == TAG_TH) FontWeight(GCss::FontWeightBold); break; } case TAG_BODY: { MarginLeft(Len(Get("leftmargin", s) ? s : DefaultBodyMargin)); MarginTop(Len(Get("topmargin", s) ? s : DefaultBodyMargin)); MarginRight(Len(Get("rightmargin", s) ? s : DefaultBodyMargin)); if (Get("text", s)) { ColorDef c; if (c.Parse(s)) { Color(c); } } break; } case TAG_OL: case TAG_UL: { MarginLeft(Len("16px")); break; } case TAG_STRONG: case TAG_B: { FontWeight(FontWeightBold); break; } case TAG_I: { FontStyle(FontStyleItalic); break; } case TAG_U: { TextDecoration(TextDecorUnderline); break; } } if (Get("width", s)) { Len l; if (l.Parse(s, PropWidth, ParseRelaxed)) { Width(l); } } if (Get("height", s)) { Len l; if (l.Parse(s, PropHeight, ParseRelaxed)) Height(l); } if (Get("align", s)) { if (_stricmp(s, "left") == 0) TextAlign(Len(AlignLeft)); else if (_stricmp(s, "right") == 0) TextAlign(Len(AlignRight)); else if (_stricmp(s, "center") == 0) TextAlign(Len(AlignCenter)); } if (Get("valign", s)) { if (_stricmp(s, "top") == 0) VerticalAlign(Len(VerticalTop)); else if (_stricmp(s, "middle") == 0) VerticalAlign(Len(VerticalMiddle)); else if (_stricmp(s, "bottom") == 0) VerticalAlign(Len(VerticalBottom)); } Get("id", HtmlId); if (Get("class", s)) { Class = GString(s).SplitDelimit(" \t"); } Restyle(); switch (TagId) { default: break; case TAG_BIG: { GCss::Len l; l.Type = SizeLarger; FontSize(l); break; } /* case TAG_META: { GAutoString Cs; const char *s; if (Get("http-equiv", s) && _stricmp(s, "Content-Type") == 0) { const char *ContentType; if (Get("content", ContentType)) { char *CharSet = stristr(ContentType, "charset="); if (CharSet) { char16 *cs = NULL; Html->ParsePropValue(CharSet + 8, cs); Cs.Reset(WideToUtf8(cs)); DeleteArray(cs); } } } if (Get("name", s) && _stricmp(s, "charset") == 0 && Get("content", s)) { Cs.Reset(NewStr(s)); } else if (Get("charset", s)) { Cs.Reset(NewStr(s)); } if (Cs) { if (Cs && _stricmp(Cs, "utf-16") != 0 && _stricmp(Cs, "utf-32") != 0 && LgiGetCsInfo(Cs)) { // Html->SetCharset(Cs); } } break; } */ case TAG_BODY: { GCss::ColorDef Bk = BackgroundColor(); if (Bk.Type != ColorInherit) { // Copy the background up to the GHtml wrapper Html->GetCss(true)->BackgroundColor(Bk); } /* GFont *f = GetFont(); if (FontSize().Type == LenInherit) { FontSize(Len(LenPt, (float)f->PointSize())); } */ break; } case TAG_HEAD: { Display(DispNone); break; } case TAG_PRE: { GFontType Type; if (Type.GetSystemFont("Fixed")) { LgiAssert(ValidStr(Type.GetFace())); FontFamily(StringsDef(Type.GetFace())); } break; } case TAG_TR: break; case TAG_TD: case TAG_TH: { LgiAssert(Cell != NULL); const char *s; if (Get("colspan", s)) Cell->Span.x = atoi(s); else Cell->Span.x = 1; if (Get("rowspan", s)) Cell->Span.y = atoi(s); else Cell->Span.y = 1; Cell->Span.x = MAX(Cell->Span.x, 1); Cell->Span.y = MAX(Cell->Span.y, 1); if (Display() == DispInline || Display() == DispInlineBlock) { Display(DispBlock); // Inline-block TD??? Nope. } break; } case TAG_IMG: { const char *Uri; if (Html->Environment && Get("src", Uri)) { LoadImage(Uri); } break; } case TAG_H1: { char s[32]; sprintf_s(s, sizeof(s), "%ipt", (int)((float)Html->DefFont()->PointSize() * FntMul[5])); FontSize(Len(s)); FontWeight(FontWeightBold); break; } case TAG_H2: { char s[32]; sprintf_s(s, sizeof(s), "%ipt", (int)((float)Html->DefFont()->PointSize() * FntMul[4])); FontSize(Len(s)); FontWeight(FontWeightBold); break; } case TAG_H3: { char s[32]; sprintf_s(s, sizeof(s), "%ipt", (int)((float)Html->DefFont()->PointSize() * FntMul[3])); FontSize(Len(s)); FontWeight(FontWeightBold); break; } case TAG_H4: { char s[32]; sprintf_s(s, sizeof(s), "%ipt", (int)((float)Html->DefFont()->PointSize() * FntMul[2])); FontSize(Len(s)); FontWeight(FontWeightBold); break; } case TAG_H5: { char s[32]; sprintf_s(s, sizeof(s), "%ipt", (int)((float)Html->DefFont()->PointSize() * FntMul[1])); FontSize(Len(s)); FontWeight(FontWeightBold); break; } case TAG_H6: { char s[32]; sprintf_s(s, sizeof(s), "%ipt", (int)((float)Html->DefFont()->PointSize() * FntMul[0])); FontSize(Len(s)); FontWeight(FontWeightBold); break; } case TAG_FONT: { const char *s = 0; if (Get("Face", s)) { char16 *cw = CleanText(s, strlen(s), "utf-8", true); char *c8 = WideToUtf8(cw); DeleteArray(cw); GToken Faces(c8, ","); DeleteArray(c8); char *face = TrimStr(Faces[0]); if (ValidStr(face)) { FontFamily(face); DeleteArray(face); } else { LgiTrace("%s:%i - No face for font tag.\n", __FILE__, __LINE__); } } if (Get("Size", s)) { bool Digit = false, NonW = false; for (auto *c = s; *c; c++) { if (IsDigit(*c) || *c == '-') Digit = true; else if (!IsWhiteSpace(*c)) NonW = true; } if (Digit && !NonW) { auto Sz = atoi(s); switch (Sz) { case 1: FontSize(Len(GCss::LenEm, 0.63f)); break; case 2: FontSize(Len(GCss::LenEm, 0.82f)); break; case 3: FontSize(Len(GCss::LenEm, 1.0f)); break; case 4: FontSize(Len(GCss::LenEm, 1.13f)); break; case 5: FontSize(Len(GCss::LenEm, 1.5f)); break; case 6: FontSize(Len(GCss::LenEm, 2.0f)); break; case 7: FontSize(Len(GCss::LenEm, 3.0f)); break; } } else { FontSize(Len(s)); } } break; } case TAG_SELECT: { if (!Html->InThread()) break; LgiAssert(!Ctrl); Ctrl = new GCombo(Html->d->NextCtrlId++, 0, 0, 100, SysFont->GetHeight() + 8, NULL); CtrlType = CtrlSelect; break; } case TAG_INPUT: { if (!Html->InThread()) break; LgiAssert(!Ctrl); const char *Type, *Value = NULL; Get("value", Value); GAutoWString CleanValue(Value ? CleanText(Value, strlen(Value), "utf-8", true, true) : NULL); if (CleanValue) { CtrlValue = CleanValue; } if (Get("type", Type)) { if (!_stricmp(Type, "password")) CtrlType = CtrlPassword; else if (!_stricmp(Type, "email")) CtrlType = CtrlEmail; else if (!_stricmp(Type, "text")) CtrlType = CtrlText; else if (!_stricmp(Type, "button")) CtrlType = CtrlButton; else if (!_stricmp(Type, "submit")) CtrlType = CtrlSubmit; else if (!_stricmp(Type, "hidden")) CtrlType = CtrlHidden; DeleteObj(Ctrl); if (CtrlType == CtrlEmail || CtrlType == CtrlText || CtrlType == CtrlPassword) { GEdit *Ed; GAutoString UtfCleanValue(WideToUtf8(CleanValue)); Ctrl = Ed = new GEdit(Html->d->NextCtrlId++, 0, 0, 60, SysFont->GetHeight() + 8, UtfCleanValue); if (Ctrl) { Ed->Sunken(false); Ed->Password(CtrlType == CtrlPassword); } } else if (CtrlType == CtrlButton || CtrlType == CtrlSubmit) { GAutoString UtfCleanValue(WideToUtf8(CleanValue)); if (UtfCleanValue) { Ctrl = new InputButton(this, Html->d->NextCtrlId++, UtfCleanValue); } } } break; } } if (IsBlock()) { GCss::ImageDef bk = BackgroundImage(); if (bk.Type == GCss::ImageUri && ValidStr(bk.Uri)) { LoadImage(bk.Uri); } } if (Ctrl) { GFont *f = GetFont(); if (f) Ctrl->SetFont(f, false); } if (Display() == DispBlock && Html->Environment) { GCss::ImageDef Img = BackgroundImage(); if (Img.Type == ImageUri) { LoadImage(Img.Uri); } } } void GTag::SetCssStyle(const char *Style) { if (Style) { // Strip out comments char *Comment = 0; while ((Comment = strstr((char*)Style, "/*"))) { char *End = strstr(Comment+2, "*/"); if (!End) break; for (char *c = Comment; c<=End+2; c++) *c = ' '; } // Parse CSS const char *Ptr = Style; GCss::Parse(Ptr, GCss::ParseRelaxed); } } char16 *GTag::CleanText(const char *s, ssize_t Len, const char *SourceCs, bool ConversionAllowed, bool KeepWhiteSpace) { if (!s || Len <= 0) return NULL; static const char *DefaultCs = "iso-8859-1"; char16 *t = 0; bool DocAndCsTheSame = false; if (Html->DocCharSet && Html->Charset) { DocAndCsTheSame = _stricmp(Html->DocCharSet, Html->Charset) == 0; } if (SourceCs) { t = (char16*) LgiNewConvertCp(LGI_WideCharset, s, SourceCs, Len); } else if (Html->DocCharSet && Html->Charset && !DocAndCsTheSame && !Html->OverideDocCharset) { char *DocText = (char*)LgiNewConvertCp(Html->DocCharSet, s, Html->Charset, Len); t = (char16*) LgiNewConvertCp(LGI_WideCharset, DocText, Html->DocCharSet, -1); DeleteArray(DocText); } else if (Html->DocCharSet) { t = (char16*) LgiNewConvertCp(LGI_WideCharset, s, Html->DocCharSet, Len); } else { t = (char16*) LgiNewConvertCp(LGI_WideCharset, s, Html->Charset.Get() ? Html->Charset.Get() : DefaultCs, Len); } if (t && ConversionAllowed) { char16 *o = t; for (char16 *i=t; *i; ) { switch (*i) { case '&': { i++; if (*i == '#') { // Unicode Number char n[32] = "", *p = n; i++; if (*i == 'x' || *i == 'X') { // Hex number i++; while ( *i && ( IsDigit(*i) || (*i >= 'A' && *i <= 'F') || (*i >= 'a' && *i <= 'f') ) && (p - n) < 31) { *p++ = (char)*i++; } } else { // Decimal number while (*i && IsDigit(*i) && (p - n) < 31) { *p++ = (char)*i++; } } *p++ = 0; char16 Ch = atoi(n); if (Ch) { *o++ = Ch; } if (*i && *i != ';') i--; } else { // Named Char char16 *e = i; while (*e && IsAlpha(*e) && *e != ';') { e++; } GAutoWString Var(NewStrW(i, e-i)); char16 Char = GHtmlStatic::Inst->VarMap.Find(Var); if (Char) { *o++ = Char; i = e; } else { i--; *o++ = *i; } } break; } case '\r': { break; } case ' ': case '\t': case '\n': { if (KeepWhiteSpace) { *o++ = *i; } else { *o++ = ' '; // Skip furthur whitespace while (i[1] && IsWhiteSpace(i[1])) { i++; } } break; } default: { // Normal char *o++ = *i; break; } } if (*i) i++; else break; } *o++ = 0; } if (t && !*t) { DeleteArray(t); } return t; } char *GTag::ParseText(char *Doc) { ColorDef c; c.Type = ColorRgb; c.Rgb32 = LC_WORKSPACE; BackgroundColor(c); TagId = TAG_BODY; Tag.Reset(NewStr("body")); Info = Html->GetTagInfo(Tag); char *OriginalCp = NewStr(Html->Charset); GStringPipe Utf16; char *s = Doc; while (s) { if (*s == '\r') { s++; } else if (*s == '<') { // Process tag char *e = s; e++; while (*e && *e != '>') { if (*e == '\"' || *e == '\'') { char *q = strchr(e + 1, *e); if (q) e = q + 1; else e++; } else e++; } if (*e == '>') e++; // Output tag Html->SetCharset("iso-8859-1"); char16 *t = CleanText(s, e - s, NULL, false); if (t) { Utf16.Push(t); DeleteArray(t); } s = e; } else if (!*s || *s == '\n') { // Output previous line char16 *Line = Utf16.NewStrW(); if (Line) { GTag *t = new GTag(Html, this); if (t) { t->Color(ColorDef(ColorRgb, Rgb24To32(LC_TEXT))); t->Text(Line); } } if (*s == '\n') { s++; GTag *t = new GTag(Html, this); if (t) { t->TagId = TAG_BR; t->Tag.Reset(NewStr("br")); t->Info = Html->GetTagInfo(t->Tag); } } else break; } else { // Seek end of text char *e = s; while (*e && *e != '\r' && *e != '\n' && *e != '<') e++; // Output text Html->SetCharset(OriginalCp); GAutoWString t(CleanText(s, e - s, NULL, false)); if (t) { Utf16.Push(t); } s = e; } } Html->SetCharset(OriginalCp); DeleteArray(OriginalCp); return 0; } bool GTag::ConvertToText(TextConvertState &State) { const static char *Rule = "------------------------------------------------------"; int DepthInc = 0; switch (TagId) { default: break; case TAG_P: if (State.GetPrev()) State.NewLine(); break; case TAG_UL: case TAG_OL: DepthInc = 2; break; } if (ValidStrW(Txt)) { for (int i=0; iConvertToUnicode(Txt); else u.Reset(WideToUtf8(Txt)); if (u) { size_t u_len = strlen(u); State.Write(u, u_len); } } State.Depth += DepthInc; for (unsigned i=0; iConvertToText(State); } State.Depth -= DepthInc; if (IsBlock()) { if (State.CharsOnLine) State.NewLine(); } else { switch (TagId) { case TAG_A: { // Emit the link to the anchor if it's different from the text of the span... const char *Href; if (Get("href", Href) && ValidStrW(Txt)) { if (_strnicmp(Href, "mailto:", 7) == 0) Href += 7; size_t HrefLen = strlen(Href); GAutoWString h(CleanText(Href, HrefLen, "utf-8")); if (h && StrcmpW(h, Txt) != 0) { // Href different from the text of the link State.Write(" (", 2); State.Write(Href, HrefLen); State.Write(")", 1); } } break; } case TAG_HR: { State.Write(Rule, strlen(Rule)); State.NewLine(); break; } case TAG_BR: { State.NewLine(); break; } default: break; } } return true; } char *GTag::NextTag(char *s) { while (s && *s) { char *n = strchr(s, '<'); if (n) { if (!n[1]) return NULL; if (IsAlpha(n[1]) || strchr("!/", n[1]) || n[1] == '?') { return n; } s = n + 1; } else break; } return 0; } void GHtml::CloseTag(GTag *t) { if (!t) return; OpenTags.Delete(t); } bool GTag::OnUnhandledColor(GCss::ColorDef *def, const char *&s) { const char *e = s; while (*e && (IsText(*e) || *e == '_')) e++; char tmp[256]; ssize_t len = e - s; memcpy(tmp, s, len); tmp[len] = 0; int m = GHtmlStatic::Inst->ColourMap.Find(tmp); s = e; if (m >= 0) { def->Type = GCss::ColorRgb; def->Rgb32 = Rgb24To32(m); return true; } return false; } void GTag::ZeroTableElements() { if (TagId == TAG_TABLE || TagId == TAG_TR || IsTableCell(TagId)) { Size.x = 0; Size.y = 0; if (Cell) { Cell->MinContent = 0; Cell->MaxContent = 0; } for (unsigned i=0; iZeroTableElements(); } } } void GTag::ResetCaches() { /* If during the parse process a callback causes a layout to happen then it's possible to have partial information in the GHtmlTableLayout structure, like missing TD cells. Because they haven't been parsed yet. This is called at the end of the parsing to reset all the cached info in GHtmlTableLayout. That way when the first real layout happens all the data is there. */ if (Cell) DeleteObj(Cell->Cells); for (size_t i=0; iResetCaches(); } GdcPt2 GTag::GetTableSize() { GdcPt2 s(0, 0); if (Cell && Cell->Cells) { Cell->Cells->GetSize(s.x, s.y); } return s; } GTag *GTag::GetTableCell(int x, int y) { GTag *t = this; while ( t && !t->Cell && !t->Cell->Cells && t->Parent) { t = ToTag(t->Parent); } if (t && t->Cell && t->Cell->Cells) { return t->Cell->Cells->Get(x, y); } return 0; } // This function gets the largest and smallest piece of content // in this cell and all it's children. bool GTag::GetWidthMetrics(GTag *Table, uint16 &Min, uint16 &Max) { bool Status = true; int MarginPx = 0; int LineWidth = 0; if (Display() == GCss::DispNone) return true; // Break the text into words and measure... if (Text()) { int MinContent = 0; int MaxContent = 0; GFont *f = GetFont(); if (f) { for (char16 *s = Text(); s && *s; ) { // Skip whitespace... while (*s && StrchrW(WhiteW, *s)) s++; // Find end of non-whitespace char16 *e = s; while (*e && !StrchrW(WhiteW, *e)) e++; // Find size of the word ssize_t Len = e - s; if (Len > 0) { GDisplayString ds(f, s, Len); MinContent = MAX(MinContent, ds.X()); } // Move to the next word. s = (*e) ? e + 1 : 0; } GDisplayString ds(f, Text()); LineWidth = MaxContent = ds.X(); } #ifdef _DEBUG if (Debug) { LgiTrace("GetWidthMetrics Font=%p Sz=%i,%i\n", f, MinContent, MaxContent); } #endif Min = MAX(Min, MinContent); Max = MAX(Max, MaxContent); } // Specific tag handling? switch (TagId) { default: { if (IsBlock()) { MarginPx = (int)(BorderLeft().ToPx() + BorderRight().ToPx() + PaddingLeft().ToPx() + PaddingRight().ToPx()); } break; } case TAG_IMG: { Len w = Width(); if (w.IsValid()) { int x = (int) w.Value; Min = MAX(Min, x); Max = MAX(Max, x); } else if (Image) { Min = Max = Image->X(); } else { Size.x = Size.y = DefaultImgSize; Min = MAX(Min, Size.x); Max = MAX(Max, Size.x); } break; } case TAG_TD: case TAG_TH: { Len w = Width(); if (w.IsValid()) { if (w.IsDynamic()) { Min = MAX(Min, (int)w.Value); Max = MAX(Max, (int)w.Value); } else { Max = w.ToPx(0, GetFont()); } } else { GCss::BorderDef BLeft = BorderLeft(); GCss::BorderDef BRight = BorderRight(); GCss::Len PLeft = PaddingLeft(); GCss::Len PRight = PaddingRight(); MarginPx = (int)(PLeft.ToPx() + PRight.ToPx() + BLeft.ToPx()); if (Table->BorderCollapse() == GCss::CollapseCollapse) MarginPx += BRight.ToPx(); } break; } case TAG_TABLE: { Len w = Width(); if (w.IsValid() && !w.IsDynamic()) { // Fixed width table... int CellSpacing = BorderSpacing().ToPx(Min, GetFont()); int Px = ((int)w.Value) + (CellSpacing << 1); Min = MAX(Min, Px); Max = MAX(Max, Px); return true; } else { GdcPt2 s; GHtmlTableLayout c(this); c.GetSize(s.x, s.y); // Auto layout table GArray ColMin, ColMax; for (int y=0; yGetWidthMetrics(Table, a, b)) { ColMin[x] = MAX(ColMin[x], a); ColMax[x] = MAX(ColMax[x], b); } x += t->Cell->Span.x; } else break; } } int MinSum = 0, MaxSum = 0; for (int i=0; iGetWidthMetrics(Table, Min, TagMax); LineWidth += TagMax; if (c->TagId == TAG_BR || c->TagId == TAG_LI) { Max = MAX(Max, LineWidth); LineWidth = 0; } } Max = MAX(Max, LineWidth); Min += MarginPx; Max += MarginPx; return Status; } static void DistributeSize(GArray &a, int Start, int Span, int Size, int Border) { // Calculate the current size of the cells int Cur = -Border; for (int i=0; i T Sum(GArray &a) { T s = 0; for (unsigned i=0; iCells) { #if defined(_DEBUG) && DEBUG_TABLE_LAYOUT if (Debug) { //int asd=0; } #endif Cell->Cells = new GHtmlTableLayout(this); #if defined(_DEBUG) && DEBUG_TABLE_LAYOUT if (Cell->Cells && Debug) Cell->Cells->Dump(); #endif } if (Cell->Cells) Cell->Cells->LayoutTable(f, Depth); } void GHtmlTableLayout::AllocatePx(int StartCol, int Cols, int MinPx, bool HasToFillAllAvailable) { // Get the existing total size and size of the column set int CurrentTotalX = GetTotalX(); int CurrentSpanX = GetTotalX(StartCol, Cols); int MaxAdditionalPx = AvailableX - CurrentTotalX; if (MaxAdditionalPx <= 0) return; // Calculate the maximum space we have for this column set int AvailPx = (CurrentSpanX + MaxAdditionalPx) - BorderX1 - BorderX2; // Allocate any remaining space... int RemainingPx = MaxAdditionalPx; GArray Growable, NonGrowable, SizeInherit; int GrowablePx = 0; for (int x=StartCol; x 0) { GrowablePx += DiffPx; Growable.Add(x); } else if (MinCol[x] > 0) { NonGrowable.Add(x); } else if (MinCol[x] == 0 && CurrentSpanX < AvailPx) { // Growable.Add(x); } if (SizeCol[x].Type == GCss::LenInherit) SizeInherit.Add(x); } if (GrowablePx < RemainingPx && HasToFillAllAvailable) { if (Growable.Length() == 0) { // Add any suitable non-growable columns as well for (unsigned i=0; i MinCol[Largest]) Largest = i; } Growable.Add(Largest); } } if (Growable.Length()) { // Some growable columns... int Added = 0; // Reasonably increase the size of the columns... for (unsigned i=0; i 0) { AddPx = DiffPx; } else if (DiffPx > 0) { double Ratio = (double)DiffPx / GrowablePx; AddPx = (int) (Ratio * RemainingPx); } else { AddPx = RemainingPx / Growable.Length(); } LgiAssert(AddPx >= 0); MinCol[x] += AddPx; Added += AddPx; } if (Added < RemainingPx && HasToFillAllAvailable) { // Still more to add, so if (SizeInherit.Length()) { Growable = SizeInherit; } else { int Largest = -1; for (unsigned i=0; i MinCol[Largest]) Largest = x; } Growable.Length(1); Growable[0] = Largest; } int AddPx = (RemainingPx - Added) / Growable.Length(); for (unsigned i=0; i= 0); } } } } struct ColInfo { int Large; int Growable; int Idx; int Px; }; int ColInfoCmp(ColInfo *a, ColInfo *b) { int LDiff = b->Large - a->Large; int LGrow = b->Growable - a->Growable; int LSize = b->Px - a->Px; return LDiff + LGrow + LSize; } void GHtmlTableLayout::DeallocatePx(int StartCol, int Cols, int MaxPx) { int TotalPx = GetTotalX(StartCol, Cols); if (TotalPx <= MaxPx || MaxPx == 0) return; int TrimPx = TotalPx - MaxPx; GArray Inf; int HalfMax = MaxPx >> 1; unsigned Interesting = 0; int InterestingPx = 0; for (int x=StartCol; x HalfMax; ci.Growable = MinCol[x] < MaxCol[x]; if (ci.Large || ci.Growable) { Interesting++; InterestingPx += ci.Px; } } Inf.Sort(ColInfoCmp); for (unsigned i=0; iGetFont(); Table->ZeroTableElements(); MinCol.Length(0); MaxCol.Length(0); MaxRow.Length(0); SizeCol.Length(0); GCss::Len BdrSpacing = Table->BorderSpacing(); CellSpacing = BdrSpacing.IsValid() ? (int)BdrSpacing.Value : 0; // Resolve total table width. TableWidth = Table->Width(); if (TableWidth.IsValid()) AvailableX = f->ResolveX(TableWidth, Table, false); else AvailableX = f->X(); GCss::Len MaxWidth = Table->MaxWidth(); if (MaxWidth.IsValid()) { int Px = f->ResolveX(MaxWidth, Table, false); if (Px < AvailableX) AvailableX = Px; } TableBorder = f->ResolveBorder(Table, Table); if (Table->BorderCollapse() != GCss::CollapseCollapse) TablePadding = f->ResolvePadding(Table, Table); else TablePadding.ZOff(0, 0); BorderX1 = TableBorder.x1 + TablePadding.x1; BorderX2 = TableBorder.x2 + TablePadding.x2; #if defined(_DEBUG) && DEBUG_TABLE_LAYOUT if (Table->Debug) LgiTrace("AvailableX=%i, BorderX1=%i, BorderX2=%i\n", AvailableX, BorderX1, BorderX2); #endif #ifdef _DEBUG if (Table->Debug) { printf("Table Debug\n"); } #endif // Size detection pass int y; for (y=0; yGetFont(); t->Cell->BorderPx = f->ResolveBorder(t, t); t->Cell->PaddingPx = f->ResolvePadding(t, t); if (t->Cell->Pos.x == x && t->Cell->Pos.y == y) { GCss::DisplayType Disp = t->Display(); if (Disp == GCss::DispNone) continue; GCss::Len Content = t->Width(); if (Content.IsValid() && t->Cell->Span.x == 1) { if (SizeCol[x].IsValid()) { int OldPx = f->ResolveX(SizeCol[x], t, false); int NewPx = f->ResolveX(Content, t, false); if (NewPx > OldPx) { SizeCol[x] = Content; } } else { SizeCol[x] = Content; } } if (!t->GetWidthMetrics(Table, t->Cell->MinContent, t->Cell->MaxContent)) { t->Cell->MinContent = 16; t->Cell->MaxContent = 16; } #if defined(_DEBUG) && DEBUG_TABLE_LAYOUT if (Table->Debug) LgiTrace("Content[%i,%i] MIN=%i MAX=%i\n", x, y, t->Cell->MinContent, t->Cell->MaxContent); #endif if (t->Cell->Span.x == 1) { int BoxPx = t->Cell->BorderPx.x1 + t->Cell->BorderPx.x2 + t->Cell->PaddingPx.x1 + t->Cell->PaddingPx.x2; MinCol[x] = MAX(MinCol[x], t->Cell->MinContent + BoxPx); MaxCol[x] = MAX(MaxCol[x], t->Cell->MaxContent + BoxPx); } } x += t->Cell->Span.x; } else break; } } // How much space used so far? int TotalX = GetTotalX(); if (TotalX > AvailableX) { // FIXME: // Off -> 'cisra-cqs.html' renders correctly. // On -> 'cisra_outage.html' renders correctly. #if 0 DeallocatePx(0, MinCol.Length(), AvailableX); TotalX = GetTotalX(); #endif } #if defined(_DEBUG) && DEBUG_TABLE_LAYOUT #define DumpCols(msg) \ if (Table->Debug) \ { \ LgiTrace("%s Ln%i - TotalX=%i AvailableX=%i\n", msg, __LINE__, TotalX, AvailableX); \ for (unsigned i=0; iDebug) { printf("TableDebug\n"); } #endif // Process spanned cells for (y=0; yCell->Pos.x == x && t->Cell->Pos.y == y) { if (t->Cell->Span.x > 1 || t->Cell->Span.y > 1) { int i; int ColMin = -CellSpacing; int ColMax = -CellSpacing; for (i=0; iCell->Span.x; i++) { ColMin += MinCol[x + i] + CellSpacing; ColMax += MaxCol[x + i] + CellSpacing; } GCss::Len Width = t->Width(); if (Width.IsValid()) { int Px = f->ResolveX(Width, t, false); t->Cell->MinContent = MAX(t->Cell->MinContent, Px); t->Cell->MaxContent = MAX(t->Cell->MaxContent, Px); } #if defined(_DEBUG) && DEBUG_TABLE_LAYOUT if (Table->Debug) LgiTrace("Content[%i,%i] MIN=%i MAX=%i\n", x, y, t->Cell->MinContent, t->Cell->MaxContent); #endif if (t->Cell->MinContent > ColMin) AllocatePx(t->Cell->Pos.x, t->Cell->Span.x, t->Cell->MinContent, false); if (t->Cell->MaxContent > ColMax) DistributeSize(MaxCol, t->Cell->Pos.x, t->Cell->Span.x, t->Cell->MaxContent, CellSpacing); } x += t->Cell->Span.x; } else break; } } TotalX = GetTotalX(); DumpCols("AfterSpannedCells"); // Sometimes the web page specifies too many percentages: // Scale them all. float PercentSum = 0.0f; for (int i=0; i 100.0) { float Ratio = PercentSum / 100.0f; for (int i=0; iResolveX(w, Table, false); if (w.Type == GCss::LenPercent) { MaxCol[x] = Px; } else if (Px > MinCol[x]) { int RemainingPx = AvailableX - TotalX; int AddPx = Px - MinCol[x]; AddPx = MIN(RemainingPx, AddPx); TotalX += AddPx; MinCol[x] += AddPx; } } } } TotalX = GetTotalX(); DumpCols("AfterCssNonPercentageSizes"); if (TotalX > AvailableX) { #if !ALLOW_TABLE_GROWTH // Deallocate space if overused // Take some from the largest column int Largest = 0; for (int i=0; i MinCol[Largest]) { Largest = i; } } int Take = TotalX - AvailableX; if (Take < MinCol[Largest]) { MinCol[Largest] = MinCol[Largest] - Take; TotalX -= Take; } DumpCols("AfterSpaceDealloc"); #endif } else if (TotalX < AvailableX) { AllocatePx(0, s.x, AvailableX, TableWidth.IsValid()); DumpCols("AfterRemainingAlloc"); } // Layout cell horizontally and then flow the contents to get // the height of all the cells GArray RowPad; MaxRow.Length(s.y); for (y=0; yCell->Pos.x == x && t->Cell->Pos.y == y) { t->Pos.x = XPos; t->Size.x = -CellSpacing; XPos -= CellSpacing; RowPad[y].y1 = MAX(RowPad[y].y1, t->Cell->BorderPx.y1 + t->Cell->PaddingPx.y1); RowPad[y].y2 = MAX(RowPad[y].y2, t->Cell->BorderPx.y2 + t->Cell->PaddingPx.y2); GRect Box(0, 0, -CellSpacing, 0); for (int i=0; iCell->Span.x; i++) { int ColSize = MinCol[x + i] + CellSpacing; LgiAssert(ColSize >= 0); if (ColSize < 0) break; t->Size.x += ColSize; XPos += ColSize; Box.x2 += ColSize; } GCss::Len Ht = t->Height(); GFlowRegion r(Table->Html, Box, true); t->OnFlow(&r, Depth+1); if (r.MAX.y > r.y2) { t->Size.y = MAX(r.MAX.y, t->Size.y); } if (Ht.IsValid() && Ht.Type != GCss::LenPercent) { int h = f->ResolveY(Ht, t, false); t->Size.y = MAX(h, t->Size.y); DistributeSize(MaxRow, y, t->Cell->Span.y, t->Size.y, CellSpacing); } } x += t->Cell->Span.x; } else break; } } #if defined(_DEBUG) && DEBUG_TABLE_LAYOUT if (Table->Debug) { LgiTrace("%s:%i - AfterCellFlow\n", _FL); for (unsigned i=0; iCell->Pos.x == x && t->Cell->Pos.y == y) { GCss::Len Ht = t->Height(); if (!(Ht.IsValid() && Ht.Type != GCss::LenPercent)) { DistributeSize(MaxRow, y, t->Cell->Span.y, t->Size.y, CellSpacing); } } x += t->Cell->Span.x; } else break; } } // Cell positioning int Cx = BorderX1 + CellSpacing; int Cy = TableBorder.y1 + TablePadding.y1 + CellSpacing; for (y=0; yParent); if (Row && Row->TagId == TAG_TR) { t = new GTag(Table->Html, Row); if (t) { t->TagId = TAG_TD; t->Tag.Reset(NewStr("td")); t->Info = Table->Html->GetTagInfo(t->Tag); if ((t->Cell = new GTag::TblCell)) { t->Cell->Pos.x = x; t->Cell->Pos.y = y; t->Cell->Span.x = 1; t->Cell->Span.y = 1; } t->BackgroundColor(GCss::ColorDef(GCss::ColorRgb, DefaultMissingCellColour)); Set(Table); } else break; } else break; } if (t) { if (t->Cell->Pos.x == x && t->Cell->Pos.y == y) { int RowPadOffset = RowPad[y].y1 - t->Cell->BorderPx.y1 - t->Cell->PaddingPx.y1; t->Pos.x = Cx; t->Pos.y = Cy + RowPadOffset; t->Size.x = -CellSpacing; for (int i=0; iCell->Span.x; i++) { int w = MinCol[x + i] + CellSpacing; t->Size.x += w; Cx += w; } t->Size.y = -CellSpacing; for (int n=0; nCell->Span.y; n++) { t->Size.y += MaxRow[y+n] + CellSpacing; } Table->Size.x = MAX(Cx + BorderX2, Table->Size.x); #if defined(_DEBUG) && DEBUG_TABLE_LAYOUT if (Table->Debug) { LgiTrace("cell(%i,%i) = pos(%i,%i)+size(%i,%i)\n", t->Cell->Pos.x, t->Cell->Pos.y, t->Pos.x, t->Pos.y, t->Size.x, t->Size.y); } #endif } else { Cx += t->Size.x + CellSpacing; } x += t->Cell->Span.x; } else break; Prev = t; } Cx = BorderX1 + CellSpacing; Cy += MaxRow[y] + CellSpacing; } switch (Table->Cell->XAlign ? Table->Cell->XAlign : ToTag(Table->Parent)->GetAlign(true)) { case GCss::AlignCenter: { int fx = f->X(); int Ox = (fx-Table->Size.x) >> 1; Table->Pos.x = f->x1 + MAX(Ox, 0); break; } case GCss::AlignRight: { Table->Pos.x = f->x2 - Table->Size.x; break; } default: { Table->Pos.x = f->x1; break; } } Table->Pos.y = f->y1; Table->Size.y = Cy + TablePadding.y2 + TableBorder.y2; } GRect GTag::ChildBounds() { GRect b(0, 0, -1, -1); for (unsigned i=0; iGetRect(); b.Union(&c); } else { b = t->GetRect(); } } return b; } GdcPt2 GTag::AbsolutePos() { GdcPt2 p; for (GTag *t=this; t; t=ToTag(t->Parent)) { p += t->Pos; } return p; } void GTag::SetSize(GdcPt2 &s) { Size = s; } GArea::~GArea() { DeleteObjects(); } GRect GArea::Bounds() { GRect n(0, 0, -1, -1); for (unsigned i=0; iLength(); i++) { GRect *r = (*c)[i]; if (!Top || (r && (r->y1 < Top->y1))) { Top = r; } } return Top; } void GArea::FlowText(GTag *Tag, GFlowRegion *Flow, GFont *Font, int LineHeight, char16 *Text, GCss::LengthType Align) { if (!Flow || !Text || !Font) return; char16 *Start = Text; size_t FullLen = StrlenW(Text); #if 1 if (!Tag->Html->GetReadOnly() && !*Text) { // Insert a text rect for this tag, even though it's empty. // This allows the user to place the cursor on a blank line. GFlowRect *Tr = new GFlowRect; Tr->Tag = Tag; Tr->Text = Text; Tr->x1 = Flow->cx; Tr->x2 = Tr->x1 + 1; Tr->y1 = Flow->y1; Tr->y2 = Tr->y1 + Font->GetHeight(); LgiAssert(Tr->y2 >= Tr->y1); Flow->y2 = MAX(Flow->y2, Tr->y2+1); Flow->cx = Tr->x2 + 1; Add(Tr); Flow->Insert(Tr); return; } #endif while (*Text) { GFlowRect *Tr = new GFlowRect; if (!Tr) break; Tr->Tag = Tag; Restart: Tr->x1 = Flow->cx; Tr->y1 = Flow->y1; #if 1 // I removed this at one stage but forget why. // Remove white space at start of line if not in edit mode.. if (Tag->Html->GetReadOnly() && Flow->x1 == Flow->cx && *Text == ' ') { Text++; if (!*Text) { DeleteObj(Tr); break; } } #endif Tr->Text = Text; GDisplayString ds(Font, Text, MIN(1024, FullLen - (Text-Start))); ssize_t Chars = ds.CharAt(Flow->X()); bool Wrap = false; if (Text[Chars]) { // Word wrap // Seek back to the nearest break opportunity ssize_t n = Chars; while (n > 0 && !StrchrW(WhiteW, Text[n])) n--; if (n == 0) { if (Flow->x1 == Flow->cx) { // Already started from the margin and it's too long to // fit across the entire page, just let it hang off the right edge. // Seek to the end of the word for (Tr->Len = Chars; Text[Tr->Len] && !StrchrW(WhiteW, Text[Tr->Len]); Tr->Len++) ; // Wrap... if (*Text == ' ') Text++; } else { // Not at the start of the margin Flow->FinishLine(Align); goto Restart; } } else { Tr->Len = n; LgiAssert(Tr->Len > 0); Wrap = true; } } else { // Fits.. Tr->Len = Chars; LgiAssert(Tr->Len > 0); } GDisplayString ds2(Font, Tr->Text, Tr->Len); Tr->x2 = ds2.X(); Tr->y2 = LineHeight > 0 ? LineHeight - 1 : 0; if (Wrap) { Flow->cx = Flow->x1; Flow->y1 += Tr->y2 + 1; Tr->x2 = Flow->x2 - Tag->RelX(); } else { Tr->x2 += Tr->x1 - 1; Flow->cx = Tr->x2 + 1; } Tr->y2 += Tr->y1; Flow->y2 = MAX(Flow->y2, Tr->y2 + 1); Add(Tr); Flow->Insert(Tr); Text += Tr->Len; if (Wrap) { while (*Text == ' ') Text++; } Tag->Size.x = MAX(Tag->Size.x, Tr->x2 + 1); Tag->Size.y = MAX(Tag->Size.y, Tr->y2 + 1); Flow->MAX.x = MAX(Flow->MAX.x, Tr->x2); Flow->MAX.y = MAX(Flow->MAX.y, Tr->y2); if (Tr->Len == 0) break; } } char16 htoi(char16 c) { if (c >= '0' && c <= '9') return c - '0'; if (c >= 'a' && c <= 'f') return c - 'a' + 10; if (c >= 'A' && c <= 'F') return c - 'A' + 10; LgiAssert(0); return 0; } bool GTag::Serialize(GXmlTag *t, bool Write) { GRect pos; if (Write) { // Obj -> Tag if (Tag) t->SetAttr("tag", Tag); pos.ZOff(Size.x, Size.y); pos.Offset(Pos.x, Pos.y); t->SetAttr("pos", pos.GetStr()); t->SetAttr("tagid", TagId); if (Txt) { GStringPipe p(256); for (char16 *c = Txt; *c; c++) { if (*c > ' ' && *c < 127 && !strchr("%<>\'\"", *c)) p.Print("%c", (char)*c); else p.Print("%%%.4x", *c); } GAutoString Tmp(p.NewStr()); t->SetContent(Tmp); } if (Props.Length()) { GAutoString CssStyles = ToString(); LgiAssert(!strchr(CssStyles, '\"')); t->SetAttr("style", CssStyles); } if (Html->Cursor == this) { LgiAssert(Cursor >= 0); t->SetAttr("cursor", (int64)Cursor); } else LgiAssert(Cursor < 0); if (Html->Selection == this) { LgiAssert(Selection >= 0); t->SetAttr("selection", (int64)Selection); } else LgiAssert(Selection < 0); for (unsigned i=0; iInsertTag(child); if (!tag->Serialize(child, Write)) { return false; } } } else { // Tag -> Obj Tag.Reset(NewStr(t->GetAttr("tag"))); TagId = (HtmlTag) t->GetAsInt("tagid"); pos.SetStr(t->GetAttr("pos")); if (pos.Valid()) { Pos.x = pos.x1; Pos.y = pos.y1; Size.x = pos.x2; Size.y = pos.y2; } if (ValidStr(t->GetContent())) { GStringPipe p(256); char *c = t->GetContent(); SkipWhiteSpace(c); for (; *c && *c > ' '; c++) { char16 ch; if (*c == '%') { ch = 0; for (int i=0; i<4 && *c; i++) { ch <<= 4; ch |= htoi(*++c); } } else ch = *c; p.Write(&ch, sizeof(ch)); } Txt.Reset(p.NewStrW()); } const char *s = t->GetAttr("style"); if (s) Parse(s, ParseRelaxed); s = t->GetAttr("cursor"); if (s) { LgiAssert(Html->Cursor == NULL); Html->Cursor = this; Cursor = atoi(s); LgiAssert(Cursor >= 0); } s = t->GetAttr("selection"); if (s) { LgiAssert(Html->Selection == NULL); Html->Selection = this; Selection = atoi(s); LgiAssert(Selection >= 0); } #ifdef _DEBUG s = t->GetAttr("debug"); if (s && atoi(s) != 0) Debug = true; #endif for (int i=0; iChildren.Length(); i++) { GXmlTag *child = t->Children[i]; if (child->IsTag("e")) { GTag *tag = new GTag(Html, NULL); if (!tag) { LgiAssert(0); return false; } if (!tag->Serialize(child, Write)) { return false; } Attach(tag); } } } return true; } /// This method centers the text in the area given to the tag. Used for inline block elements. void GTag::CenterText() { if (!Parent) return; // Find the size of the text elements. int ContentPx = 0; for (unsigned i=0; iX(); } GFont *f = GetFont(); int ParentPx = ToTag(Parent)->Size.x; int AvailPx = Size.x; // Remove the border and padding from the content area AvailPx -= BorderLeft().ToPx(ParentPx, f); AvailPx -= BorderRight().ToPx(ParentPx, f); AvailPx -= PaddingLeft().ToPx(ParentPx, f); AvailPx -= PaddingRight().ToPx(ParentPx, f); if (AvailPx > ContentPx) { // Now offset all the regions to the right int OffPx = (AvailPx - ContentPx) >> 1; for (unsigned i=0; iOffset(OffPx, 0); } } } void GTag::OnFlow(GFlowRegion *Flow, uint16 Depth) { if (Depth >= MAX_RECURSION_DEPTH) return; DisplayType Disp = Display(); if (Disp == DispNone) return; GFont *f = GetFont(); GFlowRegion Local(*Flow); bool Restart = true; int BlockFlowWidth = 0; const char *ImgAltText = NULL; Size.x = 0; Size.y = 0; GCssTools Tools(this, f); GRect rc(Flow->X(), Html->Y()); PadPx = Tools.GetPadding(rc); switch (TagId) { default: break; case TAG_BODY: { Flow->InBody++; break; } case TAG_IFRAME: { GFlowRegion Temp = *Flow; Flow->EndBlock(GetAlign(true)); Flow->Indent(this, MarginLeft(), MarginTop(), MarginRight(), MarginBottom(), true); // Flow children for (unsigned i=0; iOnFlow(&Temp, Depth + 1); if (TagId == TAG_TR) { Temp.x2 -= MIN(t->Size.x, Temp.X()); } } Flow->Outdent(this, MarginLeft(), MarginTop(), MarginRight(), MarginBottom(), true); BoundParents(); return; break; } case TAG_TR: { Size.x = Flow->X(); break; } case TAG_IMG: { Size.x = Size.y = 0; GCss::Len w = Width(); GCss::Len h = Height(); // GCss::Len MinX = MinWidth(); // GCss::Len MaxX = MaxWidth(); GCss::Len MinY = MinHeight(); GCss::Len MaxY = MaxHeight(); GAutoPtr a; int ImgX, ImgY; if (Image) { ImgX = Image->X(); ImgY = Image->Y(); } else if (Get("alt", ImgAltText) && ValidStr(ImgAltText)) { GDisplayString a(f, ImgAltText); ImgX = a.X() + 4; ImgY = a.Y() + 4; } else { ImgX = DefaultImgSize; ImgY = DefaultImgSize; } double AspectRatio = ImgY != 0 ? (double)ImgX / ImgY : 1.0; bool XLimit = false, YLimit = false; double Scale = 1.0; if (w.IsValid() && w.Type != LenAuto) { Size.x = Flow->ResolveX(w, this, false); XLimit = true; } else { int Fx = Flow->x2 - Flow->x1 + 1; if (ImgX > Fx) { Size.x = Fx; // * 0.8; if (Image) Scale = (double) Fx / ImgX; } else { Size.x = ImgX; } } XLimit |= Flow->LimitX(Size.x, MinWidth(), MaxWidth(), f); if (h.IsValid() && h.Type != LenAuto) { Size.y = Flow->ResolveY(h, this, false); YLimit = true; } else { Size.y = (int) (ImgY * Scale); } YLimit |= Flow->LimitY(Size.y, MinHeight(), MaxHeight(), f); if ( (XLimit ^ YLimit) && Image ) { if (XLimit) { Size.y = (int) ceil((double)Size.x / AspectRatio); } else { Size.x = (int) ceil((double)Size.y * AspectRatio); } } if (MinY.IsValid()) { int Px = Flow->ResolveY(MinY, this, false); if (Size.y < Px) Size.y = Px; } if (MaxY.IsValid()) { int Px = Flow->ResolveY(MaxY, this, false); if (Size.y > Px) Size.y = Px; } if (Disp == DispInline || Disp == DispInlineBlock) { Restart = false; if (Flow->cx > Flow->x1 && Size.x > Flow->X()) { Flow->FinishLine(GetAlign(true)); } Pos.y = Flow->y1; Flow->y2 = MAX(Flow->y1, Pos.y + Size.y - 1); GCss::LengthType a = GetAlign(true); switch (a) { case AlignCenter: { int Fx = Flow->x2 - Flow->x1; Pos.x = Flow->x1 + ((Fx - Size.x) / 2); break; } case AlignRight: { Pos.x = Flow->x2 - Size.x; break; } default: { Pos.x = Flow->cx; break; } } } break; } case TAG_HR: { Flow->FinishLine(GetAlign(true)); Pos.x = Flow->x1; Pos.y = Flow->y1 + 7; Size.x = Flow->X(); Size.y = 2; Flow->cx ++; Flow->y2 += 16; Flow->FinishLine(GetAlign(true)); return; break; } case TAG_TABLE: { Flow->EndBlock(GetAlign(true)); GCss::Len left = GetCssLen(MarginLeft, Margin); GCss::Len top = GetCssLen(MarginTop, Margin); GCss::Len right = GetCssLen(MarginRight, Margin); GCss::Len bottom = GetCssLen(MarginBottom, Margin); Flow->Indent(this, left, top, right, bottom, true); LayoutTable(Flow, Depth + 1); Flow->y1 += Size.y; Flow->y2 = Flow->y1; Flow->cx = Flow->x1; Flow->my = 0; Flow->MAX.y = MAX(Flow->MAX.y, Flow->y2); Flow->Outdent(this, left, top, right, bottom, true); BoundParents(); return; } } if (Disp == DispBlock || Disp == DispInlineBlock) { // This is a block level element, so end the previous non-block elements if (Disp == DispBlock) { Flow->EndBlock(GetAlign(true)); } /* if (Debug) LgiTrace("Before %s\n", Flow->ToString().Get()); */ BlockFlowWidth = Flow->X(); // Indent the margin... GCss::Len left = GetCssLen(MarginLeft, Margin); GCss::Len top = GetCssLen(MarginTop, Margin); GCss::Len right = GetCssLen(MarginRight, Margin); GCss::Len bottom = GetCssLen(MarginBottom, Margin); Flow->Indent(this, left, top, right, bottom, true); // Set the width if any if (Disp == DispBlock) { GCss::Len Wid = Width(); if (!IsTableCell(TagId) && Wid.IsValid()) Size.x = Flow->ResolveX(Wid, this, false); else if (TagId != TAG_IMG) { if (Flow->Inline) Size.x = 0; // block inside inline-block default to fit the content else Size.x = Flow->X(); } if (MaxWidth().IsValid()) { int Px = Flow->ResolveX(MaxWidth(), this, false); if (Size.x > Px) Size.x = Px; } Pos.x = Flow->x1; } else { Size.x = 0; // Child content should expand this to fit Pos.x = Flow->cx; } Pos.y = Flow->y1; Flow->y1 -= Pos.y; Flow->y2 -= Pos.y; if (Disp == DispBlock) { Flow->x1 -= Pos.x; Flow->x2 = Flow->x1 + Size.x; Flow->cx -= Pos.x; Flow->Indent(this, GCss::BorderLeft(), GCss::BorderTop(), GCss::BorderRight(), GCss::BorderBottom(), false); Flow->Indent(PadPx, false); } else { Flow->x2 = Flow->X(); Flow->x1 = Flow->ResolveX(BorderLeft(), this, true) + Flow->ResolveX(PaddingLeft(), this, true); Flow->cx = Flow->x1; Flow->y1 += Flow->ResolveY(BorderTop(), this, true) + Flow->ResolveY(PaddingTop(), this, true); Flow->y2 = Flow->y1; if (!IsTableTag()) Flow->Inline++; } } else { Flow->Indent(PadPx, false); } if (f) { // Clear the previous text layout... TextPos.DeleteObjects(); switch (TagId) { default: break; case TAG_LI: { // Insert the list marker if (!PreText()) { GCss::ListStyleTypes s = Parent->ListStyleType(); if (s == ListInherit) { if (Parent->TagId == TAG_OL) s = ListDecimal; else if (Parent->TagId == TAG_UL) s = ListDisc; } switch (s) { default: break; case ListDecimal: { ssize_t Index = Parent->Children.IndexOf(this); char Txt[32]; sprintf_s(Txt, sizeof(Txt), "%i. ", (int)(Index + 1)); PreText(Utf8ToWide(Txt)); break; } case ListDisc: { PreText(NewStrW(GHtmlListItem)); break; } } } if (PreText()) TextPos.FlowText(this, Flow, f, f->GetHeight(), PreText(), AlignLeft); break; } case TAG_IMG: { if (Disp == DispBlock) { Flow->cx += Size.x; Flow->y2 += Size.y; } break; } } if (Text() && Flow->InBody) { // Setup the line height cache if (LineHeightCache < 0) { GCss::PropMap Map; GCss Final; Map.Add(PropLineHeight, new GCss::PropArray); for (GTag *t = this; t; t = t->TagId == TAG_TABLE ? NULL : ToTag(t->Parent)) { if (!Final.InheritCollect(*t, Map)) break; } Final.InheritResolve(Map); Map.DeleteObjects(); GCss::Len CssLineHeight = Final.LineHeight(); if (f) { int FontPx = FontPxHeight(f); if (!CssLineHeight.IsValid() || CssLineHeight.Type == GCss::LenAuto || CssLineHeight.Type == GCss::LenNormal) { LineHeightCache = f->GetHeight(); } else { LineHeightCache = CssLineHeight.ToPx(FontPx, f); } } } // Flow in the rest of the text... char16 *Txt = Text(); GCss::LengthType Align = GetAlign(true); TextPos.FlowText(this, Flow, f, LineHeightCache, Txt, Align); } } // Flow children for (unsigned i=0; iPosition()) { case PosStatic: case PosAbsolute: case PosFixed: { GFlowRegion old = *Flow; t->OnFlow(Flow, Depth + 1); // Try and reset the flow to how it was before... Flow->x1 = old.x1; Flow->x2 = old.x2; Flow->cx = old.cx; Flow->y1 = old.y1; Flow->y2 = old.y2; Flow->MAX.x = MAX(Flow->MAX.x, old.MAX.x); Flow->MAX.y = MAX(Flow->MAX.y, old.MAX.y); break; } default: { t->OnFlow(Flow, Depth + 1); break; } } if (TagId == TAG_TR) { Flow->x2 -= MIN(t->Size.x, Flow->X()); } } GCss::LengthType XAlign = GetAlign(true); if (Disp == DispBlock || Disp == DispInlineBlock) { GCss::Len Ht = Height(); GCss::Len MaxHt = MaxHeight(); // I dunno, there should be a better way... :-( if (MarginLeft().Type == LenAuto && MarginRight().Type == LenAuto) { XAlign = GCss::AlignCenter; } bool AcceptHt = !IsTableCell(TagId) && Ht.Type != LenPercent; if (AcceptHt) { if (Ht.IsValid()) { int HtPx = Flow->ResolveY(Ht, this, false); if (HtPx > Flow->y2) Flow->y2 = HtPx; } if (MaxHt.IsValid()) { int MaxHtPx = Flow->ResolveY(MaxHt, this, false); if (MaxHtPx < Flow->y2) Flow->y2 = MaxHtPx; } } if (Disp == DispBlock) { Flow->EndBlock(XAlign); int OldFlowSize = Flow->x2 - Flow->x1 + 1; Flow->Outdent(this, PaddingLeft(), PaddingTop(), PaddingRight(), PaddingBottom(), false); Flow->Outdent(this, GCss::BorderLeft(), GCss::BorderTop(), GCss::BorderRight(), GCss::BorderBottom(), false); Size.y = Flow->y2 > 0 ? Flow->y2 : 0; Flow->Outdent(this, MarginLeft(), MarginTop(), MarginRight(), MarginBottom(), true); int NewFlowSize = Flow->x2 - Flow->x1 + 1; int Diff = NewFlowSize - OldFlowSize; if (Diff) Flow->MAX.x += Diff; Flow->y1 = Flow->y2; Flow->x2 = Flow->x1 + BlockFlowWidth; } else { GCss::Len Wid = Width(); int WidPx = Wid.IsValid() ? Flow->ResolveX(Wid, this, true) : 0; Size.x = MAX(WidPx, Size.x); Size.x += Flow->ResolveX(PaddingRight(), this, true); Size.x += Flow->ResolveX(BorderRight(), this, true); int MarginR = Flow->ResolveX(MarginRight(), this, true); int MarginB = Flow->ResolveX(MarginBottom(), this, true); Flow->x1 = Local.x1 - Pos.x; Flow->cx = Local.cx + Size.x + MarginR - Pos.x; Flow->x2 = Local.x2 - Pos.x; if (Height().IsValid()) { Size.y = Flow->ResolveY(Height(), this, false); Flow->y2 = MAX(Flow->y1 + Size.y + MarginB - 1, Flow->y2); } else { Flow->y2 += Flow->ResolveX(PaddingBottom(), this, true); Flow->y2 += Flow->ResolveX(BorderBottom(), this, true); Size.y = Flow->y2; } Flow->y1 = Local.y1; Flow->y2 = MAX(Local.y2, Local.y1 + Size.y); if (!IsTableTag()) Flow->Inline--; CenterText(); } - if (XAlign == GCss::AlignCenter) - { - int OffX = (Flow->x2 - Flow->x1 - Size.x) >> 1; - if (OffX > 0) - { - Pos.x += OffX; - } - } - else if (XAlign == GCss::AlignRight) - { - int OffX = Flow->x2 - Flow->x1 - Size.x; - if (OffX > 0) - { - Pos.x += OffX; - } - } + // Can't do alignment here because pos is used to + // restart the parents flow region... } else { Flow->Outdent(PadPx, false); switch (TagId) { default: break; case TAG_SELECT: case TAG_INPUT: { if (Html->InThread() && Ctrl) { GRect r = Ctrl->GetPos(); if (Width().IsValid()) Size.x = Flow->ResolveX(Width(), this, false); else Size.x = r.X(); if (Height().IsValid()) Size.y = Flow->ResolveY(Height(), this, false); else Size.y = r.Y(); if (Html->IsAttached() && !Ctrl->IsAttached()) Ctrl->Attach(Html); } Flow->cx += Size.x; Flow->y2 = MAX(Flow->y2, Flow->y1 + Size.y - 1); break; } case TAG_IMG: { Flow->cx += Size.x; Flow->y2 = MAX(Flow->y2, Flow->y1 + Size.y - 1); break; } case TAG_BR: { int OldFlowY2 = Flow->y2; Flow->FinishLine(GetAlign(true)); Size.y = Flow->y2 - OldFlowY2; Flow->y2 = MAX(Flow->y2, Flow->y1 + Size.y - 1); break; } case TAG_CENTER: { int Px = Flow->X(); for (GHtmlElement **e = NULL; Children.Iterate(e); ) { GTag *t = ToTag(*e); if (t->IsBlock()) { if (t->Size.x < Px) { t->Pos.x = (Px - t->Size.x) >> 1; } } } break; } } } BoundParents(); if (Restart) { Flow->x1 += Pos.x; Flow->x2 += Pos.x; Flow->cx += Pos.x; Flow->y1 += Pos.y; Flow->y2 += Pos.y; Flow->MAX.y = MAX(Flow->MAX.y, Flow->y2); } - /* - if (Debug) - LgiTrace("After %s\n", Flow->ToString().Get()); - */ + if (Disp == DispBlock || Disp == DispInlineBlock) + { + if (XAlign == GCss::AlignCenter) + { + int OffX = (Flow->x2 - Flow->x1 - Size.x) >> 1; + if (OffX > 0) + { + Pos.x += OffX; + } + } + else if (XAlign == GCss::AlignRight) + { + int OffX = Flow->x2 - Flow->x1 - Size.x; + if (OffX > 0) + { + Pos.x += OffX; + } + } + } if (TagId == TAG_BODY && Flow->InBody > 0) { Flow->InBody--; } } bool GTag::PeekTag(char *s, char *tag) { bool Status = false; if (s && tag) { if (*s == '<') { char *t = 0; Html->ParseName(++s, &t); if (t) { Status = _stricmp(t, tag) == 0; } DeleteArray(t); } } return Status; } GTag *GTag::GetTable() { GTag *t = 0; for (t=ToTag(Parent); t && t->TagId != TAG_TABLE; t = ToTag(t->Parent)) ; return t; } void GTag::BoundParents() { if (!Parent) return; GTag *np; for (GTag *n=this; n; n = np) { np = ToTag(n->Parent); if (!np || n->Parent->TagId == TAG_IFRAME) break; np->Size.x = MAX(np->Size.x, n->Pos.x + n->Size.x); np->Size.y = MAX(np->Size.y, n->Pos.y + n->Size.y); } } struct DrawBorder { GSurface *pDC; uint32_t LineStyle; uint32_t LineReset; uint32_t OldStyle; DrawBorder(GSurface *pdc, GCss::BorderDef &d) { LineStyle = 0xffffffff; LineReset = 0x80000000; if (d.Style == GCss::BorderDotted) { switch ((int)d.Value) { case 2: { LineStyle = 0xcccccccc; break; } case 3: { LineStyle = 0xe38e38; LineReset = 0x800000; break; } case 4: { LineStyle = 0xf0f0f0f0; break; } case 5: { LineStyle = 0xf83e0; LineReset = 0x80000; break; } case 6: { LineStyle = 0xfc0fc0; LineReset = 0x800000; break; } case 7: { LineStyle = 0xfe03f80; LineReset = 0x8000000; break; } case 8: { LineStyle = 0xff00ff00; break; } case 9: { LineStyle = 0x3fe00; LineReset = 0x20000; break; } default: { LineStyle = 0xaaaaaaaa; break; } } } pDC = pdc; OldStyle = pDC->LineStyle(); } ~DrawBorder() { pDC->LineStyle(OldStyle); } }; void GTag::GetInlineRegion(GRegion &rgn) { if (TagId == TAG_IMG) { GRect rc(0, 0, Size.x-1, Size.y-1); rc.Offset(Pos.x, Pos.y); rgn.Union(&rc); } else { for (unsigned i=0; iGetInlineRegion(rgn); } } class CornersImg : public GMemDC { public: int Px, Px2; CornersImg( float RadPx, GRect *BorderPx, GCss::BorderDef **defs, GColour &Back, bool DrawBackground) { Px = 0; Px2 = 0; //Radius.Type != GCss::LenInherit && if (RadPx > 0.0f) { Px = (int)ceil(RadPx); Px2 = Px << 1; if (Create(Px2, Px2, System32BitColourSpace)) { #if 1 Colour(0, 32); #else Colour(GColour(255, 0, 255)); #endif Rectangle(); GPointF ctr(Px, Px); GPointF LeftPt(0.0, Px); GPointF TopPt(Px, 0.0); GPointF RightPt(X(), Px); GPointF BottomPt(Px, Y()); int x_px[4] = {BorderPx->x1, BorderPx->x2, BorderPx->x2, BorderPx->x1}; int y_px[4] = {BorderPx->y1, BorderPx->y1, BorderPx->y2, BorderPx->y2}; GPointF *pts[4] = {&LeftPt, &TopPt, &RightPt, &BottomPt}; // Draw border parts.. for (int i=0; i<4; i++) { int k = (i + 1) % 4; // Setup the stops GBlendStop stops[2] = { {0.0, 0}, {1.0, 0} }; uint32_t iColour = defs[i]->Color.IsValid() ? defs[i]->Color.Rgb32 : Back.c32(); uint32_t kColour = defs[k]->Color.IsValid() ? defs[k]->Color.Rgb32 : Back.c32(); if (defs[i]->IsValid() && defs[k]->IsValid()) { stops[0].c32 = iColour; stops[1].c32 = kColour; } else if (defs[i]->IsValid()) { stops[0].c32 = stops[1].c32 = iColour; } else { stops[0].c32 = stops[1].c32 = kColour; } // Create a brush GLinearBlendBrush br ( *pts[i], *pts[k], 2, stops ); // Setup the clip GRect clip( (int)MIN(pts[i]->x, pts[k]->x), (int)MIN(pts[i]->y, pts[k]->y), (int)MAX(pts[i]->x, pts[k]->x)-1, (int)MAX(pts[i]->y, pts[k]->y)-1); ClipRgn(&clip); // Draw the arc... GPath p; p.Circle(ctr, Px); if (defs[i]->IsValid() || defs[k]->IsValid()) p.Fill(this, br); // Fill the background p.Empty(); p.Ellipse(ctr, Px-x_px[i], Px-y_px[i]); if (DrawBackground) { GSolidBrush br(Back); p.Fill(this, br); } else { GEraseBrush br; p.Fill(this, br); } ClipRgn(NULL); } #ifdef MAC ConvertPreMulAlpha(true); #endif #if 0 static int count = 0; GString file; file.Printf("c:\\temp\\img-%i.bmp", ++count); GdcD->Save(file, Corners); #endif } } } }; void GTag::PaintBorderAndBackground(GSurface *pDC, GColour &Back, GRect *BorderPx) { GArray r; GRect BorderPxRc; bool DrawBackground = !Back.IsTransparent(); #ifdef _DEBUG if (Debug) { //int asd=0; } #endif if (!BorderPx) BorderPx = &BorderPxRc; BorderPx->ZOff(0, 0); // Get all the border info and work out the pixel sizes. GFont *f = GetFont(); BorderDef Left = BorderLeft(); BorderPx->x1 = Left.ToPx(Size.x, f); BorderDef Top = BorderTop(); BorderPx->y1 = Top.ToPx(Size.x, f); BorderDef Right = BorderRight(); BorderPx->x2 = Right.ToPx(Size.x, f); BorderDef Bottom = BorderBottom(); BorderPx->y2 = Bottom.ToPx(Size.x, f); GCss::BorderDef *defs[4] = {&Left, &Top, &Right, &Bottom}; // Work out the rectangles switch (Display()) { case DispInlineBlock: case DispBlock: { r[0].ZOff(Size.x-1, Size.y-1); break; } case DispInline: { GRegion rgn; GetInlineRegion(rgn); // rgn.Simplify(false); for (int i=0; ix1 + PadPx.x1; rc.y1 -= BorderPx->y1 + PadPx.y1; rc.x2 += BorderPx->x2 + PadPx.x2; rc.y2 += BorderPx->y2 + PadPx.y2; } r.New() = rc; } break; } default: return; } // If we are drawing rounded corners, draw them into a memory context GAutoPtr Corners; int Px = 0, Px2 = 0; GCss::Len Radius = BorderRadius(); float RadPx = Radius.Type == GCss::LenPx ? Radius.Value : Radius.ToPx(Size.x, GetFont()); bool HasRadius = Radius.Type != GCss::LenInherit && RadPx > 0.0f; // Loop over the rectangles and draw everything int Op = pDC->Op(GDC_ALPHA); for (unsigned i=0; i rc.Y()) { Px = rc.Y() / 2; Px2 = Px << 1; } if (!Corners || Corners->Px2 != Px2) { Corners.Reset(new CornersImg((float)Px, BorderPx, defs, Back, DrawBackground)); } // top left GRect r(0, 0, Px-1, Px-1); pDC->Blt(rc.x1, rc.y1, Corners, &r); // top right r.Set(Px, 0, Corners->X()-1, Px-1); pDC->Blt(rc.x2-Px+1, rc.y1, Corners, &r); // bottom left r.Set(0, Px, Px-1, Corners->Y()-1); pDC->Blt(rc.x1, rc.y2-Px+1, Corners, &r); // bottom right r.Set(Px, Px, Corners->X()-1, Corners->Y()-1); pDC->Blt(rc.x2-Px+1, rc.y2-Px+1, Corners, &r); #if 1 pDC->Colour(Back); pDC->Rectangle(rc.x1+Px, rc.y1, rc.x2-Px, rc.y2); pDC->Rectangle(rc.x1, rc.y1+Px, rc.x1+Px-1, rc.y2-Px); pDC->Rectangle(rc.x2-Px+1, rc.y1+Px, rc.x2, rc.y2-Px); #else pDC->Colour(GColour(255, 0, 0, 0x80)); pDC->Rectangle(rc.x1+Px, rc.y1, rc.x2-Px, rc.y2); pDC->Colour(GColour(0, 255, 0, 0x80)); pDC->Rectangle(rc.x1, rc.y1+Px, rc.x1+Px-1, rc.y2-Px); pDC->Colour(GColour(0, 0, 255, 0x80)); pDC->Rectangle(rc.x2-Px+1, rc.y1+Px, rc.x2, rc.y2-Px); #endif } else if (DrawBackground) { pDC->Colour(Back); pDC->Rectangle(&rc); /* if (Debug) { pDC->Colour(GColour(255, 0, 0)); pDC->Box(&rc); } */ } GCss::BorderDef *b; if ((b = &Left)->IsValid()) { pDC->Colour(b->Color.Rgb32, 32); DrawBorder db(pDC, *b); for (int i=0; iValue; i++) { pDC->LineStyle(db.LineStyle, db.LineReset); pDC->Line(rc.x1 + i, rc.y1+Px, rc.x1+i, rc.y2-Px); } } if ((b = &Top)->IsValid()) { pDC->Colour(b->Color.Rgb32, 32); DrawBorder db(pDC, *b); for (int i=0; iValue; i++) { pDC->LineStyle(db.LineStyle, db.LineReset); pDC->Line(rc.x1+Px, rc.y1+i, rc.x2-Px, rc.y1+i); } } if ((b = &Right)->IsValid()) { pDC->Colour(b->Color.Rgb32, 32); DrawBorder db(pDC, *b); for (int i=0; iValue; i++) { pDC->LineStyle(db.LineStyle, db.LineReset); pDC->Line(rc.x2-i, rc.y1+Px, rc.x2-i, rc.y2-Px); } } if ((b = &Bottom)->IsValid()) { pDC->Colour(b->Color.Rgb32, 32); DrawBorder db(pDC, *b); for (int i=0; iValue; i++) { pDC->LineStyle(db.LineStyle, db.LineReset); pDC->Line(rc.x1+Px, rc.y2-i, rc.x2-Px, rc.y2-i); } } } pDC->Op(Op); } static void FillRectWithImage(GSurface *pDC, GRect *r, GSurface *Image, GCss::RepeatType Repeat) { int Px = 0, Py = 0; int Old = pDC->Op(GDC_ALPHA); if (!Image) return; switch (Repeat) { default: case GCss::RepeatBoth: { for (int y=0; yY(); y += Image->Y()) { for (int x=0; xX(); x += Image->X()) { pDC->Blt(Px + x, Py + y, Image); } } break; } case GCss::RepeatX: { for (int x=0; xX(); x += Image->X()) { pDC->Blt(Px + x, Py, Image); } break; } case GCss::RepeatY: { for (int y=0; yY(); y += Image->Y()) { pDC->Blt(Px, Py + y, Image); } break; } case GCss::RepeatNone: { pDC->Blt(Px, Py, Image); break; } } pDC->Op(Old); } void GTag::OnPaint(GSurface *pDC, bool &InSelection, uint16 Depth) { if (Depth >= MAX_RECURSION_DEPTH || Display() == DispNone) return; int Px, Py; pDC->GetOrigin(Px, Py); switch (TagId) { case TAG_INPUT: case TAG_SELECT: { if (Ctrl) { int Sx = 0, Sy = 0; int LineY = GetFont()->GetHeight(); Html->GetScrollPos(Sx, Sy); Sx *= LineY; Sy *= LineY; GRect r(0, 0, Size.x-1, Size.y-1), Px; GColour back = _Colour(false); PaintBorderAndBackground(pDC, back, &Px); if (!dynamic_cast(Ctrl)) { r.x1 += Px.x1; r.y1 += Px.y1; r.x2 -= Px.x2; r.y2 -= Px.y2; } r.Offset(AbsX() - Sx, AbsY() - Sy); Ctrl->SetPos(r); } if (TagId == TAG_SELECT) return; break; } case TAG_BODY: { COLOUR b = GetBack(); if (b != GT_TRANSPARENT) { pDC->Colour(b, 32); pDC->Rectangle(Pos.x, Pos.y, Pos.x+Size.x, Pos.y+Size.y); } if (Image) { GRect r; r.ZOff(Size.x-1, Size.y-1); FillRectWithImage(pDC, &r, Image, BackgroundRepeat()); } break; } case TAG_HEAD: { // Nothing under here to draw. return; } case TAG_HR: { pDC->Colour(LC_MED, 24); pDC->Rectangle(0, 0, Size.x - 1, Size.y - 1); break; } case TAG_TR: case TAG_TBODY: case TAG_META: { // Draws nothing... break; } case TAG_IMG: { GRect Clip(0, 0, Size.x-1, Size.y-1); pDC->ClipRgn(&Clip); if (Image) { #if ENABLE_IMAGE_RESIZING if ( !ImageResized && ( Size.x != Image->X() || Size.y != Image->Y() ) ) { ImageResized = true; GColourSpace Cs = Image->GetColourSpace(); if (Cs == CsIndex8 && Image->AlphaDC()) Cs = System32BitColourSpace; GAutoPtr r(new GMemDC(Size.x, Size.y, Cs)); if (r) { if (Cs == CsIndex8) r->Palette(new GPalette(Image->Palette())); ResampleDC(r, Image); Image = r; } } #endif int Old = pDC->Op(GDC_ALPHA); pDC->Blt(0, 0, Image); pDC->Op(Old); } else if (Size.x > 1 && Size.y > 1) { GRect b(0, 0, Size.x-1, Size.y-1); GColour Fill(GdcMixColour(LC_MED, LC_LIGHT, 0.2f), 24); GColour Border(LC_MED, 24); // Border pDC->Colour(Border); pDC->Box(&b); b.Size(1, 1); pDC->Box(&b); b.Size(1, 1); pDC->Colour(Fill); pDC->Rectangle(&b); const char *Alt; GColour Red(GdcMixColour(Rgb24(255, 0, 0), Fill.c24(), 0.3f), 24); if (Get("alt", Alt) && ValidStr(Alt)) { GDisplayString Ds(Html->GetFont(), Alt); Html->GetFont()->Colour(Red, Fill); Ds.Draw(pDC, 2, 2, &b); } else if (Size.x >= 16 && Size.y >= 16) { // Red 'x' int Cx = b.x1 + (b.X()/2); int Cy = b.y1 + (b.Y()/2); GRect c(Cx-4, Cy-4, Cx+4, Cy+4); pDC->Colour(Red); pDC->Line(c.x1, c.y1, c.x2, c.y2); pDC->Line(c.x1, c.y2, c.x2, c.y1); pDC->Line(c.x1, c.y1 + 1, c.x2 - 1, c.y2); pDC->Line(c.x1 + 1, c.y1, c.x2, c.y2 - 1); pDC->Line(c.x1 + 1, c.y2, c.x2, c.y1 + 1); pDC->Line(c.x1, c.y2 - 1, c.x2 - 1, c.y1); } } pDC->ClipRgn(0); break; } default: { GColour fore = _Colour(true); GColour back = _Colour(false); if (Display() == DispBlock && Html->Environment) { GCss::ImageDef Img = BackgroundImage(); if (Img.Img) { GRect Clip(0, 0, Size.x-1, Size.y-1); pDC->ClipRgn(&Clip); FillRectWithImage(pDC, &Clip, Img.Img, BackgroundRepeat()); pDC->ClipRgn(NULL); back.Empty(); } } PaintBorderAndBackground(pDC, back, NULL); GFont *f = GetFont(); #if DEBUG_TEXT_AREA bool IsEditor = Html ? !Html->GetReadOnly() : false; #else bool IsEditor = false; #endif if (f && TextPos.Length()) { // This is the non-display part of the font bounding box int LeadingPx = (int)(f->Leading() + 0.5); // This is the displayable part of the font int FontPx = f->GetHeight() - LeadingPx; // This is the pixel height we're aiming to fill int EffectiveLineHt = LineHeightCache >= 0 ? MAX(FontPx, LineHeightCache) : FontPx; // This gets added to the y coord of each peice of text int LineHtOff = ((EffectiveLineHt - FontPx + 1) >> 1) - LeadingPx; #define FontColour(InSelection) \ f->Transparent(!InSelection && !IsEditor); \ if (InSelection) \ f->Colour(LC_FOCUS_SEL_FORE, LC_FOCUS_SEL_BACK); \ else \ { \ GColour bk(back.IsTransparent() ? GColour(LC_WORKSPACE, 24) : back); \ GColour fr(fore.IsTransparent() ? GColour(DefaultTextColour, 32) : fore); \ if (IsEditor) \ bk = bk.Mix(GColour(0, 0, 0), 0.05f); \ f->Colour(fr, bk); \ } if (Html->HasSelection() && (Selection >= 0 || Cursor >= 0) && Selection != Cursor) { ssize_t Min = -1; ssize_t Max = -1; ssize_t Base = GetTextStart(); if (Cursor >= 0 && Selection >= 0) { Min = MIN(Cursor, Selection) + Base; Max = MAX(Cursor, Selection) + Base; } else if (InSelection) { Max = MAX(Cursor, Selection) + Base; } else { Min = MAX(Cursor, Selection) + Base; } GRect CursorPos; CursorPos.ZOff(-1, -1); for (unsigned i=0; iText - Text(); ssize_t Done = 0; int x = Tr->x1; if (Tr->Len == 0) { // Is this a selection edge point? if (!InSelection && Min == 0) { InSelection = !InSelection; } else if (InSelection && Max == 0) { InSelection = !InSelection; } if (Cursor >= 0) { // Is this the cursor, then draw it and save it's position if (Cursor == Start + Done - Base) { Html->d->CursorPos.Set(x, Tr->y1 + LineHtOff, x + 1, Tr->y2 - LineHtOff); if (Html->d->CursorPos.x1 > Tr->x2) Html->d->CursorPos.Offset(Tr->x2 - Html->d->CursorPos.x1, 0); CursorPos = Html->d->CursorPos; Html->d->CursorPos.Offset(AbsX(), AbsY()); } } break; } while (Done < Tr->Len) { ssize_t c = Tr->Len - Done; FontColour(InSelection); // Is this a selection edge point? if ( !InSelection && Min - Start >= Done && Min - Start < Done + Tr->Len) { InSelection = !InSelection; c = Min - Start - Done; } else if ( InSelection && Max - Start >= Done && Max - Start <= Tr->Len) { InSelection = !InSelection; c = Max - Start - Done; } // Draw the text run GDisplayString ds(f, Tr->Text + Done, c); if (IsEditor) { GRect r(x, Tr->y1, x + ds.X() - 1, Tr->y2); ds.Draw(pDC, x, Tr->y1 + LineHtOff, &r); } else { ds.Draw(pDC, x, Tr->y1 + LineHtOff); } x += ds.X(); Done += c; // Is this is end of the tag? if (Tr->Len == Done) { // Is it also a selection edge? if ( !InSelection && Min - Start == Done) { InSelection = !InSelection; } else if ( InSelection && Max - Start == Done) { InSelection = !InSelection; } } if (Cursor >= 0) { // Is this the cursor, then draw it and save it's position if (Cursor == Start + Done - Base) { Html->d->CursorPos.Set(x, Tr->y1 + LineHtOff, x + 1, Tr->y2 - LineHtOff); if (Html->d->CursorPos.x1 > Tr->x2) Html->d->CursorPos.Offset(Tr->x2 - Html->d->CursorPos.x1, 0); CursorPos = Html->d->CursorPos; Html->d->CursorPos.Offset(AbsX(), AbsY()); } } } } if (Html->d->CursorVis && CursorPos.Valid()) { pDC->Colour(LC_TEXT, 24); pDC->Rectangle(&CursorPos); } } else if (Cursor >= 0) { FontColour(InSelection); ssize_t Base = GetTextStart(); for (unsigned i=0; iText - Text()) - Base; LgiAssert(Tr->y2 >= Tr->y1); GDisplayString ds(f, Tr->Text, Tr->Len); ds.Draw(pDC, Tr->x1, Tr->y1 + LineHtOff, IsEditor ? Tr : NULL); if ( ( Tr->Text == PreText() && !ValidStrW(Text()) ) || ( Cursor >= Pos && Cursor <= Pos + Tr->Len ) ) { ssize_t Off = Tr->Text == PreText() ? StrlenW(PreText()) : Cursor - Pos; pDC->Colour(LC_TEXT, 24); GRect c; if (Off) { GDisplayString ds(f, Tr->Text, Off); int x = ds.X(); if (x >= Tr->X()) x = Tr->X()-1; c.Set(Tr->x1 + x, Tr->y1, Tr->x1 + x + 1, Tr->y1 + f->GetHeight()); } else { c.Set(Tr->x1, Tr->y1, Tr->x1 + 1, Tr->y1 + f->GetHeight()); } Html->d->CursorPos = c; if (Html->d->CursorVis) pDC->Rectangle(&c); Html->d->CursorPos.Offset(AbsX(), AbsY()); } } } else { FontColour(InSelection); for (unsigned i=0; iText, Tr->Len); ds.Draw(pDC, Tr->x1, Tr->y1 + LineHtOff, IsEditor ? Tr : NULL); } } } break; } } #if DEBUG_TABLE_LAYOUT && 0 if (IsTableCell(TagId)) { GTag *Tbl = this; while (Tbl->TagId != TAG_TABLE && Tbl->Parent) Tbl = Tbl->Parent; if (Tbl && Tbl->TagId == TAG_TABLE && Tbl->Debug) { pDC->Colour(GColour(255, 0, 0)); pDC->Box(0, 0, Size.x-1, Size.y-1); } } #endif for (unsigned i=0; iSetOrigin(Px - t->Pos.x, Py - t->Pos.y); t->OnPaint(pDC, InSelection, Depth + 1); pDC->SetOrigin(Px, Py); } #if DEBUG_DRAW_TD if (TagId == TAG_TD) { GTag *Tbl = this; while (Tbl && Tbl->TagId != TAG_TABLE) Tbl = ToTag(Tbl->Parent); if (Tbl && Tbl->Debug) { int Ls = pDC->LineStyle(GSurface::LineDot); pDC->Colour(GColour::Blue); pDC->Box(0, 0, Size.x-1, Size.y-1); pDC->LineStyle(Ls); } } #endif } ////////////////////////////////////////////////////////////////////// GHtml::GHtml(int id, int x, int y, int cx, int cy, GDocumentEnv *e) : GDocView(e), ResObject(Res_Custom), GHtmlParser(NULL) { View = this; d = new GHtmlPrivate; SetReadOnly(true); ViewWidth = -1; SetId(id); GRect r(x, y, x+cx, y+cy); SetPos(r); Cursor = 0; Selection = 0; DocumentUid = 0; _New(); } GHtml::~GHtml() { _Delete(); DeleteObj(d); if (JobSem.Lock(_FL)) { JobSem.Jobs.DeleteObjects(); JobSem.Unlock(); } } void GHtml::_New() { d->StyleDirty = false; d->IsLoaded = false; d->Content.x = d->Content.y = 0; d->DeferredLoads = 0; Tag = 0; DocCharSet.Reset(); IsHtml = true; #ifdef DefaultFont GFont *Def = new GFont; if (Def) { if (Def->CreateFromCss(DefaultFont)) SetFont(Def, true); else DeleteObj(Def); } #endif FontCache = new GFontCache(this); SetScrollBars(false, false); } void GHtml::_Delete() { LgiAssert(!d->IsParsing); CssStore.Empty(); CssHref.Empty(); OpenTags.Length(0); Source.Reset(); DeleteObj(Tag); DeleteObj(FontCache); } GFont *GHtml::DefFont() { return GetFont(); } void GHtml::OnAddStyle(const char *MimeType, const char *Styles) { if (Styles) { const char *c = Styles; bool Status = CssStore.Parse(c); if (Status) { d->StyleDirty = true; } #if 0 // def _DEBUG bool LogCss = false; if (!Status) { char p[MAX_PATH]; sprintf_s(p, sizeof(p), "c:\\temp\\css_parse_failure_%i.txt", LgiRand()); GFile f; if (f.Open(p, O_WRITE)) { f.SetSize(0); if (CssStore.Error) f.Print("Error: %s\n\n", CssStore.Error.Get()); f.Write(Styles, strlen(Styles)); f.Close(); } } if (LogCss) { GStringPipe p; CssStore.Dump(p); GAutoString a(p.NewStr()); GFile f; if (f.Open("C:\\temp\\css.txt", O_WRITE)) { f.Write(a, strlen(a)); f.Close(); } } #endif } } void GHtml::ParseDocument(const char *Doc) { if (!Tag) { Tag = new GTag(this, 0); } if (GetCss()) GetCss()->DeleteProp(GCss::PropBackgroundColor); if (Tag) { Tag->TagId = ROOT; OpenTags.Length(0); if (IsHtml) { Parse(Tag, Doc); // Add body tag if not specified... GTag *Html = Tag->GetTagByName("html"); GTag *Body = Tag->GetTagByName("body"); if (!Html && !Body) { if ((Html = new GTag(this, 0))) Html->SetTag("html"); if ((Body = new GTag(this, Html))) Body->SetTag("body"); Html->Attach(Body); if (Tag->Text()) { GTag *Content = new GTag(this, Body); if (Content) { Content->TagId = CONTENT; Content->Text(NewStrW(Tag->Text())); } } while (Tag->Children.Length()) { GTag *t = ToTag(Tag->Children.First()); Body->Attach(t, Body->Children.Length()); } DeleteObj(Tag); Tag = Html; } else if (!Body) { if ((Body = new GTag(this, Html))) Body->SetTag("body"); for (unsigned i=0; iChildren.Length(); i++) { GTag *t = ToTag(Html->Children[i]); if (t->TagId != TAG_HEAD) { Body->Attach(t); i--; } } Html->Attach(Body); } if (Html && Body) { char16 *t = Tag->Text(); if (t) { if (ValidStrW(t)) { GTag *Content = new GTag(this, 0); if (Content) { Content->Text(NewStrW(Tag->Text())); Body->Attach(Content, 0); } } Tag->Text(0); } #if 0 // Enabling this breaks the test file 'gw2.html'. for (GTag *t = Html->Tags.First(); t; ) { if (t->Tag && t->Tag[0] == '!') { Tag->Attach(t, 0); t = Html->Tags.Current(); } else if (t->TagId != TAG_HEAD && t != Body) { if (t->TagId == TAG_HTML) { GTag *c; while ((c = t->Tags.First())) { Html->Attach(c, 0); } t->Detach(); DeleteObj(t); } else { t->Detach(); Body->Attach(t); } t = Html->Tags.Current(); } else { t = Html->Tags.Next(); } } #endif if (Environment) { const char *OnLoad; if (Body->Get("onload", OnLoad)) { Environment->OnExecuteScript(this, (char*)OnLoad); } } } } else { Tag->ParseText(Source); } } ViewWidth = -1; if (Tag) Tag->ResetCaches(); Invalidate(); } bool GHtml::NameW(const char16 *s) { GAutoPtr utf(WideToUtf8(s)); return Name(utf); } char16 *GHtml::NameW() { GBase::Name(Source); return GBase::NameW(); } bool GHtml::Name(const char *s) { int Uid = -1; if (Environment) Uid = Environment->NextUid(); if (Uid < 0) Uid = GetDocumentUid() + 1; SetDocumentUid(Uid); _Delete(); _New(); IsHtml = false; // Detect HTML const char *c = s; while ((c = strchr(c, '<'))) { char *t = 0; c = ParseName((char*) ++c, &t); if (t && GetTagInfo(t)) { DeleteArray(t); IsHtml = true; break; } DeleteArray(t); } // Parse d->IsParsing = true; ParseDocument(s); d->IsParsing = false; if (Tag && d->StyleDirty) { d->StyleDirty = false; Tag->RestyleAll(); } if (d->DeferredLoads == 0) { OnLoad(); } Invalidate(); return true; } char *GHtml::Name() { #if LUIS_DEBUG LgiTrace("%s:%i html(%p).src(%p)='%30.30s'\n", _FL, this, Source, Source); #endif if (!Source && Tag) { GStringPipe s(1024); Tag->CreateSource(s); Source.Reset(s.NewStr()); } return Source; } GMessage::Result GHtml::OnEvent(GMessage *Msg) { switch (MsgCode(Msg)) { case M_COPY: { Copy(); break; } case M_JOBS_LOADED: { bool Update = false; int InitDeferredLoads = d->DeferredLoads; if (JobSem.Lock(_FL)) { for (unsigned i=0; iUserUid == MyUid && j->UserData != NULL) { Html1::GTag *r = static_cast(j->UserData); if (d->DeferredLoads > 0) d->DeferredLoads--; // Check the tag is still in our tree... if (Tag->HasChild(r)) { // Process the returned data... if (j->pDC) { r->SetImage(j->Uri, j->pDC.Release()); ViewWidth = 0; Update = true; } else if (r->TagId == TAG_LINK) { if (!CssHref.Find(j->Uri)) { GStreamI *s = j->GetStream(); if (s) { int Size = (int)s->GetSize(); GAutoString Style(new char[Size+1]); ssize_t rd = s->Read(Style, Size); if (rd > 0) { Style[rd] = 0; CssHref.Add(j->Uri, true); OnAddStyle("text/css", Style); ViewWidth = 0; Update = true; } } } } } else { Html1::GTag *p = ToTag(r->Parent); while (p->Parent) p = ToTag(p->Parent); int asd=0; } } // else it's from another (historical) HTML control, ignore } JobSem.Jobs.DeleteObjects(); JobSem.Unlock(); } if (InitDeferredLoads > 0 && d->DeferredLoads <= 0) { LgiAssert(d->DeferredLoads == 0); d->DeferredLoads = 0; OnLoad(); } if (Update) { OnPosChange(); Invalidate(); } break; } } return GDocView::OnEvent(Msg); } int GHtml::OnNotify(GViewI *c, int f) { switch (c->GetId()) { case IDC_VSCROLL: { int LineY = GetFont()->GetHeight(); if (f == GNotifyScrollBar_Create && VScroll && LineY > 0) { int y = Y(); int p = MAX(y / LineY, 1); int fy = d->Content.y / LineY; VScroll->SetPage(p); VScroll->SetLimits(0, fy); } Invalidate(); break; } default: { GTag *Ctrl = Tag ? Tag->FindCtrlId(c->GetId()) : NULL; if (Ctrl) return Ctrl->OnNotify(f); break; } } return GLayout::OnNotify(c, f); } void GHtml::OnPosChange() { GLayout::OnPosChange(); if (ViewWidth != X()) { Invalidate(); } } GdcPt2 GHtml::Layout(bool ForceLayout) { GRect Client = GetClient(); if (Tag && (ViewWidth != Client.X() || ForceLayout)) { GFlowRegion f(this, Client, false); // Flow text, width is different Tag->OnFlow(&f, 0); ViewWidth = Client.X(); d->Content.x = f.MAX.x + 1; d->Content.y = f.MAX.y + 1; // Set up scroll box bool Sy = f.y2 > Y(); int LineY = GetFont()->GetHeight(); uint64 Now = LgiCurrentTime(); if (Now - d->SetScrollTime > 100) { d->SetScrollTime = Now; SetScrollBars(false, Sy); if (Sy && VScroll && LineY > 0) { int y = Y(); int p = MAX(y / LineY, 1); int fy = f.y2 / LineY; VScroll->SetPage(p); VScroll->SetLimits(0, fy); } } else { // LgiTrace("%s - Dropping SetScroll, loop detected: %i ms\n", GetClass(), (int)(Now - d->SetScrollTime)); } } return d->Content; } void GHtml::OnPaint(GSurface *ScreenDC) { #if LGI_EXCEPTIONS try { #endif #if GHTML_USE_DOUBLE_BUFFER GRect Client = GetClient(); if (!MemDC || (MemDC->X() < Client.X() || MemDC->Y() < Client.Y())) { if (MemDC.Reset(new GMemDC)) { int Sx = Client.X() + 10; int Sy = Client.Y() + 10; if (!MemDC->Create(Sx, Sy, System32BitColourSpace)) { MemDC.Reset(); } } } if (MemDC) { MemDC->ClipRgn(NULL); #if 0//def _DEBUG MemDC->Colour(GColour(255, 0, 255)); MemDC->Rectangle(); #endif } #endif GSurface *pDC = MemDC ? MemDC : ScreenDC; GColour cBack; if (GetCss()) { GCss::ColorDef Bk = GetCss()->BackgroundColor(); if (Bk.Type == GCss::ColorRgb) cBack = Bk; } if (!cBack.IsValid()) cBack.Set(Enabled() ? LC_WORKSPACE : LC_MED, 24); pDC->Colour(cBack); pDC->Rectangle(); if (Tag) { Layout(); if (VScroll) { int LineY = GetFont()->GetHeight(); int Vs = (int)VScroll->Value(); pDC->SetOrigin(0, Vs * LineY); } bool InSelection = false; Tag->OnPaint(pDC, InSelection, 0); } #if GHTML_USE_DOUBLE_BUFFER if (MemDC) { pDC->SetOrigin(0, 0); #if 0 pDC->Colour(Rgb24(255, 0, 0), 24); pDC->Line(0, 0, X()-1, Y()-1); pDC->Line(X()-1, 0, 0, Y()-1); #endif ScreenDC->Blt(0, 0, MemDC); } #endif #if LGI_EXCEPTIONS } catch (...) { LgiTrace("GHtml paint crash\n"); } #endif if (d->OnLoadAnchor && VScroll) { GAutoString a = d->OnLoadAnchor; GotoAnchor(a); LgiAssert(d->OnLoadAnchor == 0); } } bool GHtml::HasSelection() { if (Cursor && Selection) { return Cursor->Cursor >= 0 && Selection->Selection >= 0 && !(Cursor == Selection && Cursor->Cursor == Selection->Selection); } return false; } void GHtml::UnSelectAll() { bool i = false; if (Cursor) { Cursor->Cursor = -1; Cursor = NULL; i = true; } if (Selection) { Selection->Selection = -1; Selection = NULL; i = true; } if (i) { Invalidate(); } } void GHtml::SelectAll() { } GTag *GHtml::GetLastChild(GTag *t) { if (t && t->Children.Length()) { for (GTag *i = ToTag(t->Children.Last()); i; ) { GTag *c = i->Children.Length() ? ToTag(i->Children.Last()) : NULL; if (c) i = c; else return i; } } return 0; } GTag *GHtml::PrevTag(GTag *t) { // This returns the previous tag in the tree as if all the tags were // listed via recursion using "in order". // Walk up the parent chain looking for a prev for (GTag *p = t; p; p = ToTag(p->Parent)) { // Does this tag have a parent? if (p->Parent) { // Prev? GTag *pp = ToTag(p->Parent); ssize_t Idx = pp->Children.IndexOf(p); GTag *Prev = Idx > 0 ? ToTag(pp->Children[Idx - 1]) : NULL; if (Prev) { GTag *Last = GetLastChild(Prev); return Last ? Last : Prev; } else { return ToTag(p->Parent); } } } return 0; } GTag *GHtml::NextTag(GTag *t) { // This returns the next tag in the tree as if all the tags were // listed via recursion using "in order". // Does this have a child tag? if (t->Children.Length() > 0) { return ToTag(t->Children.First()); } else { // Walk up the parent chain for (GTag *p = t; p; p = ToTag(p->Parent)) { // Does this tag have a next? if (p->Parent) { GTag *pp = ToTag(p->Parent); size_t Idx = pp->Children.IndexOf(p); GTag *Next = pp->Children.Length() > Idx + 1 ? ToTag(pp->Children[Idx + 1]) : NULL; if (Next) { return Next; } } } } return 0; } int GHtml::GetTagDepth(GTag *Tag) { // Returns the depth of the tag in the tree. int n = 0; for (GTag *t = Tag; t; t = ToTag(t->Parent)) { n++; } return n; } bool GHtml::IsCursorFirst() { if (!Cursor || !Selection) return false; return CompareTagPos(Cursor, Cursor->Cursor, Selection, Selection->Selection); } bool GHtml::CompareTagPos(GTag *a, ssize_t AIdx, GTag *b, ssize_t BIdx) { // Returns true if the 'a' is before 'b' point. if (!a || !b) return false; if (a == b) { return AIdx < BIdx; } else { GArray ATree, BTree; for (GTag *t = a; t; t = ToTag(t->Parent)) ATree.AddAt(0, t); for (GTag *t = b; t; t = ToTag(t->Parent)) BTree.AddAt(0, t); ssize_t Depth = MIN(ATree.Length(), BTree.Length()); for (int i=0; i 0); GTag *p = ATree[i-1]; LgiAssert(BTree[i-1] == p); ssize_t ai = p->Children.IndexOf(at); ssize_t bi = p->Children.IndexOf(bt); return ai < bi; } } } return false; } void GHtml::SetLoadImages(bool i) { if (i ^ GetLoadImages()) { GDocView::SetLoadImages(i); SendNotify(GNotifyShowImagesChanged); if (GetLoadImages() && Tag) { Tag->LoadImages(); } } } char *GHtml::GetSelection() { char *s = 0; if (Cursor && Selection) { GMemQueue p; bool InSelection = false; Tag->CopyClipboard(p, InSelection); int Len = (int)p.GetSize(); if (Len > 0) { char16 *t = (char16*)p.New(sizeof(char16)); if (t) { size_t Len = StrlenW(t); for (int i=0; iOnFind(Dlg); } void BuildTagList(GArray &t, GTag *Tag) { t.Add(Tag); for (unsigned i=0; iChildren.Length(); i++) { GTag *c = ToTag(Tag->Children[i]); BuildTagList(t, c); } } static void FormEncode(GStringPipe &p, const char *c) { const char *s = c; while (*c) { while (*c && *c != ' ') c++; if (c > s) { p.Write(s, c - s); c = s; } if (*c == ' ') { p.Write("+", 1); s = c; } else break; } } bool GHtml::OnSubmitForm(GTag *Form) { if (!Form || !Environment) { LgiAssert(!"Bad param"); return false; } const char *Method = NULL; const char *Action = NULL; if (!Form->Get("method", Method) || !Form->Get("action", Action)) { LgiAssert(!"Missing form action/method"); return false; } LHashTbl,char*> f; Form->CollectFormValues(f); bool Status = false; if (!_stricmp(Method, "post")) { GStringPipe p(256); bool First = true; // const char *Field; // for (char *Val = f.First(&Field); Val; Val = f.Next(&Field)) for (auto v : f) { if (First) First = false; else p.Write("&", 1); FormEncode(p, v.key); p.Write("=", 1); FormEncode(p, v.value); } GAutoPtr Data(p.NewStr()); Status = Environment->OnPostForm(this, Action, Data); } else if (!_stricmp(Method, "get")) { Status = Environment->OnNavigate(this, Action); } else { LgiAssert(!"Bad form method."); } f.DeleteArrays(); return Status; } bool GHtml::OnFind(GFindReplaceCommon *Params) { bool Status = false; if (Params) { if (!Params->Find) return Status; d->FindText.Reset(Utf8ToWide(Params->Find)); d->MatchCase = Params->MatchCase; } if (!Cursor) Cursor = Tag; if (Cursor && d->FindText) { GArray Tags; BuildTagList(Tags, Tag); ssize_t Start = Tags.IndexOf(Cursor); for (unsigned i=1; iText()) { char16 *Hit; if (d->MatchCase) Hit = StrstrW(s->Text(), d->FindText); else Hit = StristrW(s->Text(), d->FindText); if (Hit) { // found something... UnSelectAll(); Selection = Cursor = s; Cursor->Cursor = Hit - s->Text(); Selection->Selection = Cursor->Cursor + StrlenW(d->FindText); OnCursorChanged(); if (VScroll) { // Scroll the tag into view... int y = s->AbsY(); int LineY = GetFont()->GetHeight(); int Val = y / LineY; VScroll->Value(Val); } Invalidate(); Status = true; break; } } } } return Status; } bool GHtml::OnKey(GKey &k) { bool Status = false; if (k.Down()) { int Dy = 0; int LineY = GetFont()->GetHeight(); int Page = GetClient().Y() / LineY; switch (k.c16) { case 'f': case 'F': { if (k.Modifier()) { GFindDlg Dlg(this, 0, FindCallback, this); Dlg.DoModal(); Status = true; } break; } case VK_F3: { OnFind(NULL); break; } case 'c': case 'C': #ifdef WIN32 case VK_INSERT: #endif { printf("Got 'c', mod=%i\n", k.Modifier()); if (k.Modifier()) { Copy(); Status = true; } break; } case VK_UP: { Dy = -1; Status = true; break; } case VK_DOWN: { Dy = 1; Status = true; break; } case VK_PAGEUP: { Dy = -Page; Status = true; break; } case VK_PAGEDOWN: { Dy = Page; Status = true; break; } case VK_HOME: { Dy = (int) (VScroll ? -VScroll->Value() : 0); Status = true; break; } case VK_END: { if (VScroll) { int64 Low, High; VScroll->Limits(Low, High); Dy = (int) ((High - Page) - VScroll->Value()); } Status = true; break; } } if (Dy && VScroll) { VScroll->Value(VScroll->Value() + Dy); Invalidate(); } } return Status; } int GHtml::ScrollY() { return GetFont()->GetHeight() * (VScroll ? (int)VScroll->Value() : 0); } void GHtml::OnMouseClick(GMouse &m) { Capture(m.Down()); SetPulse(m.Down() ? 200 : -1); if (m.Down()) { Focus(true); int Offset = ScrollY(); bool TagProcessedClick = false; GTagHit Hit; if (Tag) { Tag->GetTagByPos(Hit, m.x, m.y + Offset, 0, false, DEBUG_TAG_BY_POS); #if DEBUG_TAG_BY_POS Hit.Dump("MouseClick"); #endif } if (m.Left() && !m.IsContextMenu()) { if (m.Double()) { d->WordSelectMode = true; if (Cursor) { // Extend the selection out to the current word's boundaries. Selection = Cursor; Selection->Selection = Cursor->Cursor; if (Cursor->Text()) { ssize_t Base = Cursor->GetTextStart(); char16 *Text = Cursor->Text() + Base; while (Text[Cursor->Cursor]) { char16 c = Text[Cursor->Cursor]; if (strchr(WordDelim, c) || StrchrW(WhiteW, c)) break; Cursor->Cursor++; } } if (Selection->Text()) { ssize_t Base = Selection->GetTextStart(); char16 *Sel = Selection->Text() + Base; while (Selection->Selection > 0) { char16 c = Sel[Selection->Selection - 1]; if (strchr(WordDelim, c) || StrchrW(WhiteW, c)) break; Selection->Selection--; } } Invalidate(); SendNotify(GNotifySelectionChanged); } } else if (Hit.NearestText) { d->WordSelectMode = false; UnSelectAll(); Cursor = Hit.NearestText; Cursor->Cursor = Hit.Index; #if DEBUG_SELECTION LgiTrace("StartSelect Near='%20S' Idx=%i\n", Hit.NearestText->Text(), Hit.Index); #endif OnCursorChanged(); SendNotify(GNotifySelectionChanged); } else { #if DEBUG_SELECTION LgiTrace("StartSelect no text hit %p, %p\n", Cursor, Selection); #endif } } if (Hit.NearestText && Hit.Near == 0) { TagProcessedClick = Hit.NearestText->OnMouseClick(m); } else if (Hit.Direct) { TagProcessedClick = Hit.Direct->OnMouseClick(m); } #ifdef _DEBUG else if (m.Left() && m.Ctrl()) { LgiMsg(this, "No tag under the cursor.", GetClass()); } #endif if (!TagProcessedClick && m.IsContextMenu()) { GSubMenu RClick; enum ContextMenuCmds { IDM_DUMP = 100, IDM_COPY_SRC, IDM_VIEW_SRC, IDM_EXTERNAL, IDM_COPY, IDM_VIEW_IMAGES, }; #define IDM_CHARSET_BASE 10000 RClick.AppendItem (LgiLoadString(L_TEXTCTRL_COPY, "Copy"), IDM_COPY, HasSelection()); GMenuItem *Vs = RClick.AppendItem (LgiLoadString(L_VIEW_SOURCE, "View Source"), IDM_VIEW_SRC, Source != 0); RClick.AppendItem (LgiLoadString(L_COPY_SOURCE, "Copy Source"), IDM_COPY_SRC, Source != 0); GMenuItem *Load = RClick.AppendItem (LgiLoadString(L_VIEW_IMAGES, "View External Images"), IDM_VIEW_IMAGES, true); if (Load) Load->Checked(GetLoadImages()); RClick.AppendItem (LgiLoadString(L_VIEW_IN_DEFAULT_BROWSER, "View in Default Browser"), IDM_EXTERNAL, Source != 0); GSubMenu *Cs = RClick.AppendSub (LgiLoadString(L_CHANGE_CHARSET, "Change Charset")); if (Cs) { int n=0; for (GCharset *c = LgiGetCsList(); c->Charset; c++, n++) { Cs->AppendItem(c->Charset, IDM_CHARSET_BASE + n, c->IsAvailable()); } } if (!GetReadOnly() || // Is editor #ifdef _DEBUG 1 #else 0 #endif ) { RClick.AppendSeparator(); RClick.AppendItem("Dump Layout", IDM_DUMP, Tag != 0); } if (Vs) { Vs->Checked(!IsHtml); } if (OnContextMenuCreate(Hit, RClick) && GetMouse(m, true)) { int Id = RClick.Float(this, m.x, m.y); switch (Id) { case IDM_COPY: { Copy(); break; } case IDM_VIEW_SRC: { if (Vs) { DeleteObj(Tag); IsHtml = !IsHtml; ParseDocument(Source); } break; } case IDM_COPY_SRC: { if (Source) { GClipBoard c(this); const char *ViewCs = GetCharset(); if (ViewCs) { GAutoWString w((char16*)LgiNewConvertCp(LGI_WideCharset, Source, ViewCs)); if (w) c.TextW(w); } else c.Text(Source); } break; } case IDM_VIEW_IMAGES: { SetLoadImages(!GetLoadImages()); break; } case IDM_DUMP: { if (Tag) { GAutoWString s = Tag->DumpW(); if (s) { GClipBoard c(this); c.TextW(s); } } break; } case IDM_EXTERNAL: { if (!Source) { LgiTrace("%s:%i - No HTML source code.\n", _FL); break; } char Path[MAX_PATH]; if (!LGetSystemPath(LSP_TEMP, Path, sizeof(Path))) { LgiTrace("%s:%i - Failed to get the system path.\n", _FL); break; } char f[32]; sprintf_s(f, sizeof(f), "_%i.html", LgiRand(1000000)); LgiMakePath(Path, sizeof(Path), Path, f); GFile F; if (!F.Open(Path, O_WRITE)) { LgiTrace("%s:%i - Failed to open '%s' for writing.\n", _FL, Path); break; } GStringPipe Ex; bool Error = false; F.SetSize(0); GAutoWString SrcMem; const char *ViewCs = GetCharset(); if (ViewCs) SrcMem.Reset((char16*)LgiNewConvertCp(LGI_WideCharset, Source, ViewCs)); else SrcMem.Reset(Utf8ToWide(Source)); for (char16 *s=SrcMem; s && *s;) { char16 *cid = StristrW(s, L"cid:"); while (cid && !strchr("\'\"", cid[-1])) { cid = StristrW(cid+1, L"cid:"); } if (cid) { char16 Delim = cid[-1]; char16 *e = StrchrW(cid, Delim); if (e) { *e = 0; if (StrchrW(cid, '\n')) { *e = Delim; Error = true; break; } else { char File[MAX_PATH] = ""; if (Environment) { GDocumentEnv::LoadJob *j = Environment->NewJob(); if (j) { j->Uri.Reset(WideToUtf8(cid)); j->Env = Environment; j->Pref = GDocumentEnv::LoadJob::FmtFilename; j->UserUid = GetDocumentUid(); GDocumentEnv::LoadType Result = Environment->GetContent(j); if (Result == GDocumentEnv::LoadImmediate) { if (j->Filename) strcpy_s(File, sizeof(File), j->Filename); } else if (Result == GDocumentEnv::LoadDeferred) { d->DeferredLoads++; } DeleteObj(j); } } *e = Delim; Ex.Push(s, cid - s); if (File[0]) { char *d; while ((d = strchr(File, '\\'))) { *d = '/'; } Ex.Push(L"file:///"); GAutoWString w(Utf8ToWide(File)); Ex.Push(w); } s = e; } } else { Error = true; break; } } else { Ex.Push(s); break; } } if (!Error) { int64 WideChars = Ex.GetSize() / sizeof(char16); GAutoWString w(Ex.NewStrW()); GAutoString u(WideToUtf8(w, WideChars)); if (u) F.Write(u, strlen(u)); F.Close(); GAutoString Err; if (!LgiExecute(Path, NULL, NULL, &Err)) { LgiMsg( this, "Failed to open '%s'\n%s", LgiApp ? LgiApp->GBase::Name() : GetClass(), MB_OK, Path, Err.Get()); } } break; } default: { if (Id >= IDM_CHARSET_BASE) { GCharset *c = LgiGetCsList() + (Id - IDM_CHARSET_BASE); if (c->Charset) { Charset = c->Charset; OverideDocCharset = true; char *Src = Source.Release(); _Delete(); _New(); Source.Reset(Src); ParseDocument(Source); Invalidate(); SendNotify(GNotifyCharsetChanged); } } else { OnContextMenuCommand(Hit, Id); } break; } } } } } else // Up Click { if (Selection && Cursor && Selection == Cursor && Selection->Selection == Cursor->Cursor) { Selection->Selection = -1; Selection = 0; SendNotify(GNotifySelectionChanged); #if DEBUG_SELECTION LgiTrace("NoSelect on release\n"); #endif } } } void GHtml::OnLoad() { d->IsLoaded = true; SendNotify(GNotifyDocLoaded); } GTag *GHtml::GetTagByPos(int x, int y, ssize_t *Index, GdcPt2 *LocalCoords, bool DebugLog) { GTag *Status = NULL; if (Tag) { if (DebugLog) LgiTrace("GetTagByPos starting...\n"); GTagHit Hit; Tag->GetTagByPos(Hit, x, y, 0, DebugLog); if (DebugLog) LgiTrace("GetTagByPos Hit=%s, %i, %i...\n\n", Hit.Direct ? Hit.Direct->Tag.Get() : 0, Hit.Index, Hit.Near); Status = Hit.NearestText && Hit.Near == 0 ? Hit.NearestText : Hit.Direct; if (Hit.NearestText && Hit.Near < 30) { if (Index) *Index = Hit.Index; if (LocalCoords) *LocalCoords = Hit.LocalCoords; } } return Status; } bool GHtml::OnMouseWheel(double Lines) { if (VScroll) { VScroll->Value(VScroll->Value() + (int)Lines); Invalidate(); } return true; } LgiCursor GHtml::GetCursor(int x, int y) { int Offset = ScrollY(); ssize_t Index = -1; GdcPt2 LocalCoords; GTag *Tag = GetTagByPos(x, y + Offset, &Index, &LocalCoords); if (Tag) { GAutoString Uri; if (LocalCoords.x >= 0 && LocalCoords.y >= 0 && Tag->IsAnchor(&Uri)) { GRect c = GetClient(); c.Offset(-c.x1, -c.y1); if (c.Overlap(x, y) && ValidStr(Uri)) { return LCUR_PointingHand; } } } return LCUR_Normal; } void GHtml::OnMouseMove(GMouse &m) { if (!Tag) return; int Offset = ScrollY(); GTagHit Hit; Tag->GetTagByPos(Hit, m.x, m.y + Offset, 0, false); if (!Hit.Direct && !Hit.NearestText) return; GAutoString Uri; GTag *HitTag = Hit.NearestText && Hit.Near == 0 ? Hit.NearestText : Hit.Direct; if (HitTag && HitTag->TipId == 0 && Hit.LocalCoords.x >= 0 && Hit.LocalCoords.y >= 0 && HitTag->IsAnchor(&Uri) && Uri) { if (!Tip.GetParent()) { Tip.Attach(this); } GRect r = HitTag->GetRect(false); r.Offset(0, -Offset); HitTag->TipId = Tip.NewTip(Uri, r); // LgiTrace("NewTip: %s @ %s, ID=%i\n", Uri.Get(), r.GetStr(), HitTag->TipId); } if (IsCapturing() && Cursor && Hit.NearestText) { if (!Selection) { Selection = Cursor; Selection->Selection = Cursor->Cursor; Cursor = Hit.NearestText; Cursor->Cursor = Hit.Index; OnCursorChanged(); Invalidate(); SendNotify(GNotifySelectionChanged); #if DEBUG_SELECTION LgiTrace("CreateSelection '%20S' %i\n", Hit.NearestText->Text(), Hit.Index); #endif } else if ((Cursor != Hit.NearestText) || (Cursor->Cursor != Hit.Index)) { // Move the cursor to track the mouse if (Cursor) { Cursor->Cursor = -1; } Cursor = Hit.NearestText; Cursor->Cursor = Hit.Index; #if DEBUG_SELECTION LgiTrace("ExtendSelection '%20S' %i\n", Hit.NearestText->Text(), Hit.Index); #endif if (d->WordSelectMode && Cursor->Text()) { ssize_t Base = Cursor->GetTextStart(); if (IsCursorFirst()) { // Extend the cursor up the document to include the whole word while (Cursor->Cursor > 0) { char16 c = Cursor->Text()[Base + Cursor->Cursor - 1]; if (strchr(WordDelim, c) || StrchrW(WhiteW, c)) break; Cursor->Cursor--; } } else { // Extend the cursor down the document to include the whole word while (Cursor->Text()[Base + Cursor->Cursor]) { char16 c = Cursor->Text()[Base + Cursor->Cursor]; if (strchr(WordDelim, c) || StrchrW(WhiteW, c)) break; Cursor->Cursor++; } } } OnCursorChanged(); Invalidate(); SendNotify(GNotifySelectionChanged); } } } void GHtml::OnPulse() { if (VScroll && IsCapturing()) { int Fy = DefFont() ? DefFont()->GetHeight() : 16; GMouse m; if (GetMouse(m, false)) { GRect c = GetClient(); int Lines = 0; if (m.y < c.y1) { // Scroll up Lines = (c.y1 - m.y + Fy - 1) / -Fy; } else if (m.y > c.y2) { // Scroll down Lines = (m.y - c.y2 + Fy - 1) / Fy; } if (Lines) { VScroll->Value(VScroll->Value() + Lines); Invalidate(); } } } } GRect *GHtml::GetCursorPos() { return &d->CursorPos; } void GHtml::SetCursorVis(bool b) { if (d->CursorVis ^ b) { d->CursorVis = b; Invalidate(); } } bool GHtml::GetCursorVis() { return d->CursorVis; } GDom *ElementById(GTag *t, char *id) { if (t && id) { const char *i; if (t->Get("id", i) && _stricmp(i, id) == 0) return t; for (unsigned i=0; iChildren.Length(); i++) { GTag *c = ToTag(t->Children[i]); GDom *n = ElementById(c, id); if (n) return n; } } return 0; } GDom *GHtml::getElementById(char *Id) { return ElementById(Tag, Id); } bool GHtml::GetLinkDoubleClick() { return d->LinkDoubleClick; } void GHtml::SetLinkDoubleClick(bool b) { d->LinkDoubleClick = b; } bool GHtml::GetFormattedContent(const char *MimeType, GString &Out, GArray *Media) { if (!MimeType) { LgiAssert(!"No MIME type for getting formatted content"); return false; } if (!_stricmp(MimeType, "text/html")) { // We can handle this type... GArray Imgs; if (Media) { // Find all the image tags... Tag->Find(TAG_IMG, Imgs); // Give them CID's if they don't already have them for (unsigned i=0; iGet("src", Src) && !Img->Get("cid", Cid)) { char id[256]; sprintf_s(id, sizeof(id), "%x.%x", (unsigned)LgiCurrentTime(), (unsigned)LgiRand()); Img->Set("cid", id); Img->Get("cid", Cid); } if (Src && Cid) { GFile *f = new GFile; if (f) { if (f->Open(Src, O_READ)) { // Add the exported image stream to the media array GDocView::ContentMedia &m = Media->New(); m.Id = Cid; m.Stream.Reset(f); } } } } } // Export the HTML, including the CID's from the first step Out = Name(); } else if (!_stricmp(MimeType, "text/plain")) { // Convert DOM tree down to text instead... GStringPipe p(512); if (Tag) { GTag::TextConvertState State(&p); Tag->ConvertToText(State); } Out = p.NewGStr(); } return false; } void GHtml::OnContent(GDocumentEnv::LoadJob *Res) { if (JobSem.Lock(_FL)) { JobSem.Jobs.Add(Res); JobSem.Unlock(); PostEvent(M_JOBS_LOADED); } } GHtmlElement *GHtml::CreateElement(GHtmlElement *Parent) { return new GTag(this, Parent); } bool GHtml::GetVariant(const char *Name, GVariant &Value, char *Array) { if (!_stricmp(Name, "supportLists")) // Type: Bool Value = false; else if (!_stricmp(Name, "vml")) // Type: Bool // Vector Markup Language Value = false; else if (!_stricmp(Name, "mso")) // Type: Bool // mso = Microsoft Office Value = false; else return false; return true; } bool GHtml::EvaluateCondition(const char *Cond) { if (!Cond) return true; // This is a really bad attempt at writing an expression evaluator. // I could of course use the scripting language but that would pull // in a fairly large dependency on the HTML control. However user // apps that already have that could reimplement this virtual function // if they feel like it. GArray Str; for (const char *c = Cond; *c; ) { if (IsAlpha(*c)) { Str.Add(LgiTokStr(c)); } else if (IsWhiteSpace(*c)) { c++; } else { const char *e = c; while (*e && !IsWhiteSpace(*e) && !IsAlpha(*e)) e++; Str.Add(NewStr(c, e - c)); LgiAssert(e > c); if (e > c) c = e; else break; } } bool Result = true; bool Not = false; for (unsigned i=0; iGetAnchor(Name); if (a) { if (VScroll) { int LineY = GetFont()->GetHeight(); int Ay = a->AbsY(); int Scr = Ay / LineY; VScroll->Value(Scr); VScroll->SendNotify(); } else d->OnLoadAnchor.Reset(NewStr(Name)); } } return false; } bool GHtml::GetEmoji() { return d->DecodeEmoji; } void GHtml::SetEmoji(bool i) { d->DecodeEmoji = i; } //////////////////////////////////////////////////////////////////////// class GHtml_Factory : public GViewFactory { GView *NewView(const char *Class, GRect *Pos, const char *Text) { if (_stricmp(Class, "GHtml") == 0) { return new GHtml(-1, 0, 0, 100, 100, new GDefaultDocumentEnv); } return 0; } } GHtml_Factory; ////////////////////////////////////////////////////////////////////// struct BuildContext { GHtmlTableLayout *Layout; GTag *Table; GTag *TBody; GTag *CurTr; GTag *CurTd; int cx, cy; BuildContext() { Layout = NULL; cx = cy = 0; Table = NULL; TBody = NULL; CurTr = NULL; CurTd = NULL; } bool Build(GTag *t, int Depth) { bool RetReattach = false; switch (t->TagId) { case TAG_TABLE: { if (!Table) Table = t; else return false; break; } case TAG_TBODY: { if (TBody) return false; TBody = t; break; } case TAG_TR: { CurTr = t; break; } case TAG_TD: { CurTd = t; if (t->Parent != CurTr) { if ( !CurTr && (Table || TBody) ) { GTag *p = TBody ? TBody : Table; CurTr = new GTag(p->Html, p); if (CurTr) { CurTr->Tag.Reset(NewStr("tr")); CurTr->TagId = TAG_TR; ssize_t Idx = t->Parent->Children.IndexOf(t); t->Parent->Attach(CurTr, Idx); } } if (CurTr) { CurTr->Attach(t); RetReattach = true; } else { LgiAssert(0); return false; } } t->Cell->Pos.x = cx; t->Cell->Pos.y = cy; Layout->Set(t); break; } default: { if (CurTd == t->Parent) return false; break; } } for (unsigned n=0; nChildren.Length(); n++) { GTag *c = ToTag(t->Children[n]); bool Reattached = Build(c, Depth+1); if (Reattached) n--; } if (t->TagId == TAG_TR) { CurTr = NULL; cy++; cx = 0; Layout->s.y = cy; } if (t->TagId == TAG_TD) { CurTd = NULL; cx += t->Cell->Span.x; Layout->s.x = MAX(cx, Layout->s.x); } return RetReattach; } }; GHtmlTableLayout::GHtmlTableLayout(GTag *table) { Table = table; if (!Table) return; #if 0 BuildContext Ctx; Ctx.Layout = this; Ctx.Build(table, 0); #else int y = 0; GTag *FakeRow = 0; GTag *FakeCell = 0; GTag *r; for (size_t i=0; iChildren.Length(); i++) { r = ToTag(Table->Children[i]); if (r->Display() == GCss::DispNone) continue; if (r->TagId == TAG_TR) { FakeRow = 0; FakeCell = 0; } else if (r->TagId == TAG_TBODY) { ssize_t Index = Table->Children.IndexOf(r); for (size_t n=0; nChildren.Length(); n++) { GTag *t = ToTag(r->Children[n]); Table->Children.AddAt(++Index, t); t->Parent = Table; /* LgiTrace("Moving '%s'(%p) from TBODY(%p) into '%s'(%p)\n", t->Tag, t, r, t->Parent->Tag, t->Parent); */ } r->Children.Length(0); } else { if (!FakeRow) { if ((FakeRow = new GTag(Table->Html, 0))) { FakeRow->Tag.Reset(NewStr("tr")); FakeRow->TagId = TAG_TR; ssize_t Idx = Table->Children.IndexOf(r); Table->Attach(FakeRow, Idx); } } if (FakeRow) { if (!IsTableCell(r->TagId) && !FakeCell) { if ((FakeCell = new GTag(Table->Html, FakeRow))) { FakeCell->Tag.Reset(NewStr("td")); FakeCell->TagId = TAG_TD; if ((FakeCell->Cell = new GTag::TblCell)) { FakeCell->Cell->Span.x = 1; FakeCell->Cell->Span.y = 1; } } } ssize_t Idx = Table->Children.IndexOf(r); r->Detach(); if (IsTableCell(r->TagId)) { FakeRow->Attach(r); } else { LgiAssert(FakeCell != NULL); FakeCell->Attach(r); } i = Idx - 1; } } } FakeCell = NULL; for (size_t n=0; nChildren.Length(); n++) { GTag *r = ToTag(Table->Children[n]); if (r->TagId == TAG_TR) { int x = 0; for (size_t i=0; iChildren.Length(); i++) { GTag *cell = ToTag(r->Children[i]); if (!IsTableCell(cell->TagId)) { if (!FakeCell) { // Make a fake TD cell FakeCell = new GTag(Table->Html, NULL); FakeCell->Tag.Reset(NewStr("td")); FakeCell->TagId = TAG_TD; if ((FakeCell->Cell = new GTag::TblCell)) { FakeCell->Cell->Span.x = 1; FakeCell->Cell->Span.y = 1; } // Join the fake TD into the TR r->Children[i] = FakeCell; FakeCell->Parent = r; } else { // Not the first non-TD tag, so delete it from the TR. Only the // fake TD will remain in the TR. r->Children.DeleteAt(i--, true); } // Insert the tag into it as a child FakeCell->Children.Add(cell); cell->Parent = FakeCell; cell = FakeCell; } else { FakeCell = NULL; } if (IsTableCell(cell->TagId)) { if (cell->Display() == GCss::DispNone) continue; while (Get(x, y)) { x++; } cell->Cell->Pos.x = x; cell->Cell->Pos.y = y; Set(cell); x += cell->Cell->Span.x; } } y++; FakeCell = NULL; } } #endif } void GHtmlTableLayout::Dump() { int Sx, Sy; GetSize(Sx, Sy); LgiTrace("Table %i x %i cells.\n", Sx, Sy); for (int x=0; xCell->Pos.x, t->Cell->Pos.y, t->Cell->Span.x, t->Cell->Span.y); LgiTrace("%-10s", s); } LgiTrace("\n"); } LgiTrace("\n"); } void GHtmlTableLayout::GetAll(List &All) { LHashTbl, bool> Added; for (size_t y=0; y= (int) c.Length()) return NULL; CellArray &a = c[y]; if (x >= (int) a.Length()) return NULL; return a[x]; } bool GHtmlTableLayout::Set(GTag *t) { if (!t) return false; for (int y=0; yCell->Span.y; y++) { for (int x=0; xCell->Span.x; x++) { // LgiAssert(!c[y][x]); c[t->Cell->Pos.y + y][t->Cell->Pos.x + x] = t; } } return true; } void GTagHit::Dump(const char *Desc) { GArray d, n; GTag *t = Direct; unsigned i; for (i=0; i<3 && t; t = ToTag(t->Parent), i++) { d.AddAt(0, t); } t = NearestText; for (i=0; i<3 && t; t = ToTag(t->Parent), i++) { n.AddAt(0, t); } LgiTrace("Hit: %s Direct: ", Desc); for (i=0; i%s", d[i]->Tag ? d[i]->Tag.Get() : "CONTENT"); LgiTrace(" Nearest: "); for (i=0; i%s", n[i]->Tag ? n[i]->Tag.Get() : "CONTENT"); LgiTrace(" Local: %ix%i Index: %i Block: %s '%.10S'\n", LocalCoords.x, LocalCoords.y, Index, Block ? Block->GetStr() : NULL, Block ? Block->Text + Index : NULL); } //////////////////////////////////////////////////////////////////////// bool GCssStyle::GetVariant(const char *Name, GVariant &Value, char *Array) { if (!Name) return false; if (!_stricmp(Name, "Display")) // Type: String { Value = Css->ToString(Css->Display()); return Value.Str() != NULL; } else LgiAssert(!"Impl me."); return false; } bool GCssStyle::SetVariant(const char *Name, GVariant &Value, char *Array) { if (!Name) return false; if (!_stricmp(Name, "display")) { const char *d = Value.Str(); if (Css->ParseDisplayType(d)) { GTag *t = dynamic_cast(Css); if (t) { t->Html->Layout(true); t->Html->Invalidate(); } return true; } } else LgiAssert(!"Impl me."); return false; } 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_t)*s) & 0x80) != 0)) { // quoted printable o.Write(i, (int)(s-i)); 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_t 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_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_t 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_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_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_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_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_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_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_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_t Ch); }; struct CtrlCap { GString Name, Param; void Set(const char *name, const char *param) { Name = name; Param = param; } }; struct ButtonState { uint8_t IsMenu : 1; uint8_t IsPress : 1; - uint8 Pressed : 1; - uint8 MouseOver : 1; + uint8_t Pressed : 1; + uint8_t MouseOver : 1; }; 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 { GNamedStyle *Style; // owned by the CSS cache public: ColourPair Colours; HtmlTag Element; GString Param; bool Emoji; StyleText(const StyleText *St); 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_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; } /// 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_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 ) { 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_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)) + GArray Tmp; + if (Utf16to32(Tmp, (const uint16_t*)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_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_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); bool Seek(SeekType To, BlockCursor &Cursor); 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_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); 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); bool Seek(SeekType To, BlockCursor &Cursor); 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_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); 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); bool Seek(SeekType To, BlockCursor &Cursor); 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_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); 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; 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/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 *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_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_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]; + Utf16to32(Utf32, (const uint16_t*) StrCache.Get(), len); + uint32_t *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 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_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) { 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_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 diff --git a/src/linux/General/GFile.cpp b/src/linux/General/GFile.cpp --- a/src/linux/General/GFile.cpp +++ b/src/linux/General/GFile.cpp @@ -1,1668 +1,1668 @@ /*hdr ** FILE: File.cpp ** AUTHOR: Matthew Allen ** DATE: 8/10/2000 ** DESCRIPTION: The new file subsystem ** ** Copyright (C) 2000, Matthew Allen ** fret@memecode.com */ /****************************** Includes **********************************/ #include #include #include #include #include #include #include #include #include #include "LgiDefs.h" #include "GFile.h" #include "GContainers.h" #include "GToken.h" #include "Gdc2.h" #include "LgiCommon.h" #include "GString.h" #include "LDateTime.h" /****************************** Defines ***********************************/ // #define FILEDEBUG #define FLOPPY_360K 0x0001 #define FLOPPY_720K 0x0002 #define FLOPPY_1_2M 0x0004 #define FLOPPY_1_4M 0x0008 #define FLOPPY_5_25 (FLOPPY_360K | FLOPPY_1_2M) #define FLOPPY_3_5 (FLOPPY_720K | FLOPPY_1_4M) /****************************** Globals ***********************************/ struct ErrorCodeType { char *Name; int Code; char *Desc; } ErrorCodes[] = { #if defined(WIN32) {"EPERM", 1, "Not owner"}, {"ENOENT", 2, "No such file"}, {"ESRCH", 3, "No such process"}, {"EINTR", 4, "Interrupted system"}, {"EIO", 5, "I/O error"}, {"ENXIO", 6, "No such device"}, {"E2BIG", 7, "Argument list too long"}, {"ENOEXEC", 8, "Exec format error"}, {"EBADF", 9, "Bad file number"}, {"ECHILD", 10, "No children"}, {"EAGAIN", 11, "No more processes"}, {"ENOMEM", 12, "Not enough core"}, {"EACCES", 13, "Permission denied"}, {"EFAULT", 14, "Bad address"}, {"ENOTBLK", 15, "Block device required"}, {"EBUSY", 16, "Mount device busy"}, {"EEXIST", 17, "File exists"}, {"EXDEV", 18, "Cross-device link"}, {"ENODEV", 19, "No such device"}, {"ENOTDIR", 20, "Not a directory"}, {"EISDIR", 21, "Is a directory"}, {"EINVAL", 22, "Invalid argument"}, {"ENFILE", 23, "File table overflow"}, {"EMFILE", 24, "Too many open file"}, {"ENOTTY", 25, "Not a typewriter"}, {"ETXTBSY", 26, "Text file busy"}, {"EFBIG", 27, "File too large"}, {"ENOSPC", 28, "No space left on"}, {"ESPIPE", 29, "Illegal seek"}, {"EROFS", 30, "Read-only file system"}, {"EMLINK", 31, "Too many links"}, {"EPIPE", 32, "Broken pipe"}, {"EWOULDBLOCK", 35, "Operation would block"}, {"EINPROGRESS", 36, "Operation now in progress"}, {"EALREADY", 37, "Operation already in progress"}, {"ENOTSOCK", 38, "Socket operation on"}, {"EDESTADDRREQ", 39, "Destination address required"}, {"EMSGSIZE", 40, "Message too long"}, {"EPROTOTYPE", 41, "Protocol wrong type"}, {"ENOPROTOOPT", 42, "Protocol not available"}, {"EPROTONOSUPPORT", 43, "Protocol not supported"}, {"ESOCKTNOSUPPORT", 44, "Socket type not supported"}, {"EOPNOTSUPP", 45, "Operation not supported"}, {"EPFNOSUPPORT", 46, "Protocol family not supported"}, {"EAFNOSUPPORT", 47, "Address family not supported"}, {"EADDRINUSE", 48, "Address already in use"}, {"EADDRNOTAVAIL", 49, "Can't assign requested address"}, {"ENETDOWN", 50, "Network is down"}, {"ENETUNREACH", 51, "Network is unreachable"}, {"ENETRESET", 52, "Network dropped connection"}, {"ECONNABORTED", 53, "Software caused connection"}, {"ECONNRESET", 54, "Connection reset by peer"}, {"ENOBUFS", 55, "No buffer space available"}, {"EISCONN", 56, "Socket is already connected"}, {"ENOTCONN", 57, "Socket is not connected" }, {"ESHUTDOWN", 58, "Can't send after shutdown"}, {"ETOOMANYREFS", 59, "Too many references"}, {"ETIMEDOUT", 60, "Connection timed out"}, {"ECONNREFUSED", 61, "Connection refused"}, {"ELOOP", 62, "Too many levels of nesting"}, {"ENAMETOOLONG", 63, "File name too long" }, {"EHOSTDOWN", 64, "Host is down"}, {"EHOSTUNREACH", 65, "No route to host"}, {"ENOTEMPTY", 66, "Directory not empty"}, {"EPROCLIM", 67, "Too many processes"}, {"EUSERS", 68, "Too many users"}, {"EDQUOT", 69, "Disc quota exceeded"}, {"ESTALE", 70, "Stale NFS file handle"}, {"EREMOTE", 71, "Too many levels of remote in the path"}, {"ENOSTR", 72, "Device is not a stream"}, {"ETIME", 73, "Timer expired"}, {"ENOSR", 74, "Out of streams resources"}, {"ENOMSG", 75, "No message"}, {"EBADMSG", 76, "Trying to read unreadable message"}, {"EIDRM", 77, "Identifier removed"}, {"EDEADLK", 78, "Deadlock condition"}, {"ENOLCK", 79, "No record locks available"}, {"ENONET", 80, "Machine is not on network"}, {"ERREMOTE", 81, "Object is remote"}, {"ENOLINK", 82, "The link has been severed"}, {"EADV", 83, "ADVERTISE error"}, {"ESRMNT", 84, "SRMOUNT error"}, {"ECOMM", 85, "Communication error"}, {"EPROTO", 86, "Protocol error"}, {"EMULTIHOP", 87, "Multihop attempted"}, {"EDOTDOT", 88, "Cross mount point"}, {"EREMCHG", 89, "Remote address change"}, #elif defined(LINUX) {"EPERM", EPERM, "Operation not permitted"}, {"ENOENT", ENOENT, "No such file or directory"}, {"ESRCH", ESRCH, "No such process"}, {"EINTR", EINTR, "Interrupted system call"}, {"EIO", EIO, "I/O error"}, {"ENXIO", ENXIO, "No such device or address"}, {"E2BIG", E2BIG, "Argument list too long"}, {"ENOEXEC", ENOEXEC, "Exec format error"}, {"EBADF", EBADF, "Bad file number"}, {"ECHILD", ECHILD, "No child processes"}, {"EAGAIN", EAGAIN, "Try again"}, {"ENOMEM", ENOMEM, "Out of memory"}, {"EACCES", EACCES, "Permission denied"}, {"EFAULT", EFAULT, "Bad address"}, {"ENOTBLK", ENOTBLK, "Block device required"}, {"EBUSY", EBUSY, "Device or resource busy"}, {"EEXIST", EEXIST, "File exists"}, {"EXDEV", EXDEV, "Cross-device link"}, {"ENODEV", ENODEV, "No such device"}, {"ENOTDIR", ENOTDIR, "Not a directory"}, {"EISDIR", EISDIR, "Is a directory"}, {"EINVAL", EINVAL, "Invalid argument"}, {"ENFILE", ENFILE, "File table overflow"}, {"EMFILE", EMFILE, "Too many open files"}, {"ENOTTY", ENOTTY, "Not a typewriter"}, {"ETXTBSY", ETXTBSY, "Text file busy"}, {"EFBIG", EFBIG, "File too large"}, {"ENOSPC", ENOSPC, "No space left on device"}, {"ESPIPE", ESPIPE, "Illegal seek"}, {"EROFS", EROFS, "Read-only file system"}, {"EMLINK", EMLINK, "Too many links"}, {"EPIPE", EPIPE, "Broken pipe"}, {"EDOM", EDOM, "Math argument out of domain of func"}, {"ERANGE", ERANGE, "Math result not representable"}, {"EDEADLK", EDEADLK, "Resource deadlock would occur"}, {"ENAMETOOLONG", ENAMETOOLONG, "File name too long"}, {"ENOLCK", ENOLCK, "No record locks available"}, {"ENOSYS", ENOSYS, "Function not implemented"}, {"ENOTEMPTY", ENOTEMPTY, "Directory not empty"}, {"ELOOP", ELOOP, "Too many symbolic links encountered"}, {"EWOULDBLOCK", EWOULDBLOCK, "Operation would block"}, {"ENOMSG", ENOMSG, "No message of desired type"}, {"EIDRM", EIDRM, "Identifier removed"}, {"ECHRNG", ECHRNG, "Channel number out of range"}, {"EL2NSYNC", EL2NSYNC, "Level 2 not synchronized"}, {"EL3HLT", EL3HLT, "Level 3 halted"}, {"EL3RST", EL3RST, "Level 3 reset"}, {"ELNRNG", ELNRNG, "Link number out of range"}, {"EUNATCH", EUNATCH, "Protocol driver not attached"}, {"ENOCSI", ENOCSI, "No CSI structure available"}, {"EL2HLT", EL2HLT, "Level 2 halted"}, {"EBADE", EBADE, "Invalid exchange"}, {"EBADR", EBADR, "Invalid request descriptor"}, {"EXFULL", EXFULL, "Exchange full"}, {"ENOANO", ENOANO, "No anode"}, {"EBADRQC", EBADRQC, "Invalid request code"}, {"EBADSLT", EBADSLT, "Invalid slot"}, {"EBFONT", EBFONT, "Bad font file format"}, {"ENOSTR", ENOSTR, "Device not a stream"}, {"ENODATA", ENODATA, "No data available"}, {"ETIME", ETIME, "Timer expired"}, {"ENOSR", ENOSR, "Out of streams resources"}, {"ENONET", ENONET, "Machine is not on the network"}, {"ENOPKG", ENOPKG, "Package not installed"}, {"EREMOTE", EREMOTE, "Object is remote"}, {"ENOLINK", ENOLINK, "Link has been severed"}, {"EADV", EADV, "Advertise error"}, {"ESRMNT", ESRMNT, "Srmount error"}, {"ECOMM", ECOMM, "Communication error on send"}, {"EPROTO", EPROTO, "Protocol error"}, {"EMULTIHOP", EMULTIHOP, "Multihop attempted"}, {"EDOTDOT", EDOTDOT, "RFS specific error"}, {"EBADMSG", EBADMSG, "Not a data message"}, {"EOVERFLOW", EOVERFLOW, "Value too large for defined data type"}, {"ENOTUNIQ", ENOTUNIQ, "Name not unique on network"}, {"EBADFD", EBADFD, "File descriptor in bad state"}, {"EREMCHG", EREMCHG, "Remote address changed"}, {"ELIBACC", ELIBACC, "Can not access a needed shared library"}, {"ELIBBAD", ELIBBAD, "Accessing a corrupted shared library"}, {"ELIBSCN", ELIBSCN, ".lib section in a.out corrupted"}, {"ELIBMAX", ELIBMAX, "Attempting to link in too many shared libraries"}, {"ELIBEXEC", ELIBEXEC, "Cannot exec a shared library directly"}, {"EILSEQ", EILSEQ, "Illegal byte sequence"}, {"ERESTART", ERESTART, "Interrupted system call should be restarted"}, {"ESTRPIPE", ESTRPIPE, "Streams pipe error"}, {"EUSERS", EUSERS, "Too many users"}, {"ENOTSOCK", ENOTSOCK, "Socket operation on non-socket"}, {"EDESTADDRREQ", EDESTADDRREQ, "Destination address required"}, {"EMSGSIZE", EMSGSIZE, "Message too long"}, {"EPROTOTYPE", EPROTOTYPE, "Protocol wrong type for socket"}, {"ENOPROTOOPT", ENOPROTOOPT, "Protocol not available"}, {"EPROTONOSUPPORT", EPROTONOSUPPORT, "Protocol not supported"}, {"ESOCKTNOSUPPORT", ESOCKTNOSUPPORT, "Socket type not supported"}, {"EOPNOTSUPP", EOPNOTSUPP, "Operation not supported on transport endpoint"}, {"EPFNOSUPPORT", EPFNOSUPPORT, "Protocol family not supported"}, {"EAFNOSUPPORT", EAFNOSUPPORT, "Address family not supported by protocol"}, {"EADDRINUSE", EADDRINUSE, "Address already in use"}, {"EADDRNOTAVAIL", EADDRNOTAVAIL, "Cannot assign requested address"}, {"ENETDOWN", ENETDOWN, "Network is down"}, {"ENETUNREACH", ENETUNREACH, "Network is unreachable"}, {"ENETRESET", ENETRESET, "Network dropped connection because of reset"}, {"ECONNABORTED", ECONNABORTED, "Software caused connection abort"}, {"ECONNRESET", ECONNRESET, "Connection reset by peer"}, {"ENOBUFS", ENOBUFS, "No buffer space available"}, {"EISCONN", EISCONN, "Transport endpoint is already connected"}, {"ENOTCONN", ENOTCONN, "Transport endpoint is not connected"}, {"ESHUTDOWN", ESHUTDOWN, "Cannot send after transport endpoint shutdown"}, {"ETOOMANYREFS", ETOOMANYREFS, "Too many references: cannot splice"}, {"ETIMEDOUT", ETIMEDOUT, "Connection timed out"}, {"ECONNREFUSED", ECONNREFUSED, "Connection refused"}, {"EHOSTDOWN", EHOSTDOWN, "Host is down"}, {"EHOSTUNREACH", EHOSTUNREACH, "No route to host"}, {"EALREADY", EALREADY, "Operation already in progress"}, {"EINPROGRESS", EINPROGRESS, "Operation now in progress"}, {"ESTALE", ESTALE, "Stale NFS file handle"}, {"EUCLEAN", EUCLEAN, "Structure needs cleaning"}, {"ENOTNAM", ENOTNAM, "Not a XENIX named type file"}, {"ENAVAIL", ENAVAIL, "No XENIX semaphores available"}, {"EISNAM", EISNAM, "Is a named type file"}, {"EREMOTEIO", EREMOTEIO, "Remote I/O error"}, {"EDQUOT", EDQUOT, "Quota exceeded"}, {"ENOMEDIUM", ENOMEDIUM, "No medium found"}, {"EMEDIUMTYPE", EMEDIUMTYPE, "Wrong medium type"}, #else #error impl me #endif {"NONE", 0, "No error"}, }; const char *GetErrorName(int e) { for (ErrorCodeType *c=ErrorCodes; c->Code; c++) { if (e == c->Code) { return c->Name; } } static char s[32]; sprintf(s, "Unknown(%i)", e); return s; } char *GetErrorDesc(int e) { for (ErrorCodeType *c=ErrorCodes; c->Code; c++) { if (e == c->Code) { return c->Desc; } } return 0; } /****************************** Helper Functions **************************/ char *ReadTextFile(const char *File) { char *s = 0; GFile f; if (File && f.Open(File, O_READ)) { int Len = f.GetSize(); s = new char[Len+1]; if (s) { int Read = f.Read(s, Len); s[Read] = 0; } } return s; } int64 LgiFileSize(const char *FileName) { struct stat64 s; if (FileName && stat64(FileName, &s) == 0) { return s.st_size; } return 0; } bool DirExists(const char *FileName, char *CorrectCase) { bool Status = false; if (FileName) { struct stat s; // Check for exact match... int r = lstat(FileName, &s); if (r == 0) { Status = S_ISDIR(s.st_mode) || S_ISLNK(s.st_mode); // printf("DirStatus(%s) lstat = %i, %i\n", FileName, Status, s.st_mode); } else { r = stat(FileName, &s); if (r == 0) { Status = S_ISDIR(s.st_mode) || S_ISLNK(s.st_mode); // printf("DirStatus(%s) stat ok = %i, %i\n", FileName, Status, s.st_mode); } else { // printf("DirStatus(%s) lstat and stat failed, r=%i, errno=%i\n", FileName, r, errno); } } } return Status; } bool FileExists(const char *FileName, char *CorrectCase) { bool Status = false; if (FileName) { struct stat s; // Check for exact match... if (stat(FileName, &s) == 0) { Status = !S_ISDIR(s.st_mode); } else if (CorrectCase) { // Look for altenate case by enumerating the directory char d[256]; strcpy(d, FileName); char *e = strrchr(d, DIR_CHAR); if (e) { *e++ = 0; DIR *Dir = opendir(d); if (Dir) { dirent *De; while (De = readdir(Dir)) { if (De->d_type != DT_DIR && stricmp(De->d_name, e) == 0) { try { // Tell the calling program the actual case of the file... e = strrchr(FileName, DIR_CHAR); // If this crashes because the argument is read only then we get caught by the try catch strcpy(e+1, De->d_name); // It worked! Status = true; } catch (...) { // It didn't work :( #ifdef _DEBUG printf("%s,%i - FileExists(%s) found an alternate case version but couldn't return it to the caller.\n", __FILE__, __LINE__, FileName); #endif } break; } } closedir(Dir); } } } } return Status; } bool ResolveShortcut(const char *LinkFile, char *Path, ssize_t Len) { bool Status = false; return Status; } void WriteStr(GFile &f, const char *s) { ulong Len = (s) ? strlen(s) : 0; f << Len; if (Len > 0) { f.Write(s, Len); } } char *ReadStr(GFile &f DeclDebugArgs) { char *s = 0; // read the strings length... - uint32 Len; + uint32_t Len; f >> Len; if (Len > 0) { // 16mb sanity check.... anything over this // is _probably_ an error if (Len >= (16 << 20)) { // LgiAssert(0); return 0; } // allocate the memory buffer #if defined(_DEBUG) && defined MEMORY_DEBUG s = new(_file, _line) char[Len+1]; #else s = new char[Len+1]; #endif if (s) { // read the bytes from disk f.Read(s, Len); s[Len] = 0; } else { // memory allocation error, skip the data // on disk so the caller is where they think // they are in the file. f.Seek(Len, SEEK_CUR); } } return s; } ssize_t SizeofStr(const char *s) { return sizeof(ulong) + ((s) ? strlen(s) : 0); } bool LgiGetDriveInfo ( char *Path, uint64 *Free, uint64 *Size, uint64 *Available ) { bool Status = false; if (Path) { struct stat s; if (lstat(Path, &s) == 0) { // printf("LgiGetDriveInfo dev=%i\n", s.st_dev); } } return Status; } /****************************** Classes *************************************************************************************/ GVolume::GVolume() { _Type = VT_NONE; _Flags = 0; _Size = 0; _Free = 0; } ///////////////////////////////////////////////////////////////////////// #include #include class GLinuxVolume : public GVolume { int Which; List _Sub; public: GLinuxVolume(int w) { Which = w; _Type = VT_NONE; _Flags = 0; _Size = 0; _Free = 0; if (Which < 0) { _Name = "Desktop"; _Type = VT_DESKTOP; _Path = LGetSystemPath(LSP_DESKTOP); } } ~GLinuxVolume() { _Sub.DeleteObjects(); } bool IsMounted() { return false; } bool SetMounted(bool Mount) { return Mount; } void Insert(GAutoPtr v) { LgiAssert(0); } GVolume *First() { if (Which < 0 && !_Sub.Length()) { // Get various shortcuts to points of interest GLinuxVolume *v = new GLinuxVolume(0); if (v) { v->_Path = "/"; v->_Name = "Root"; v->_Type = VT_HARDDISK; _Sub.Insert(v); } struct passwd *pw = getpwuid(getuid()); if (pw) { v = new GLinuxVolume(0); if (v) { v->_Path = pw->pw_dir; v->_Name = "Home"; v->_Type = VT_HARDDISK; _Sub.Insert(v); } } // Get mount list // this is just a hack at this stage to establish some base // functionality. I would appreciate someone telling me how // to do this properly. Till then... GFile f; if (f.Open("/etc/fstab", O_READ)) { int Len = f.GetSize(); GAutoString Buf(new char[Len+1]); if (Buf) { f.Read(Buf, Len); Buf[Len] = 0; f.Close(); GToken L(Buf, "\r\n"); for (int l=0; l 2) { char *Mount = M[1]; if (Mount && strnicmp(M[0], "/dev/", 5) == 0 && strlen(M[1]) > 1 && stricmp(M[2], "swap") != 0) { v = new GLinuxVolume(0); if (v) { char *MountName = strrchr(Mount, '/'); v->_Name = (MountName ? MountName + 1 : Mount); v->_Path = Mount; v->_Type = VT_HARDDISK; char *Device = M[0]; char *FileSys = M[2]; if (stristr(Device, "fd")) { v->_Type = VT_3_5FLOPPY; } else if (stristr(Device, "cdrom")) { v->_Type = VT_CDROM; } _Sub.Insert(v); } } } } } } } return _Sub.First(); } GVolume *Next() { return _Sub.Next(); } GDirectory *GetContents() { GDirectory *Dir = 0; if (Which >= 0 && _Path) { Dir = new GDirectory; if (Dir) { if (!Dir->First(_Path)) { DeleteObj(Dir); } } } return Dir; } }; /////////////////////////////////////////////////////////////////////////////// GFileSystem *GFileSystem::Instance = 0; GFileSystem::GFileSystem() { Instance = this; Root = 0; } GFileSystem::~GFileSystem() { DeleteObj(Root); } void GFileSystem::OnDeviceChange(char *Reserved) { } GVolume *GFileSystem::GetRootVolume() { if (!Root) { Root = new GLinuxVolume(-1); } return Root; } int FloppyType(int Letter) { uchar MaxTrack; uchar SecPerTrack; /* _asm { mov eax, 0800h mov edx, Letter int 13h mov MaxTrack, ch mov SecPerTrack, cl } if (MaxTrack > 39) { switch (SecPerTrack) { case 9: { return FLOPPY_720K; } case 15: { return FLOPPY_1_2M; } case 18: { return FLOPPY_1_4M; } } } else { return FLOPPY_360K; } */ return 0; } bool GFileSystem::Copy(const char *From, const char *To, LError *Status, CopyFileCallback Callback, void *Token) { GArray Buf; if (Status) *Status = 0; if (Buf.Length(2 << 20)) { GFile In, Out; if (!In.Open(From, O_READ)) { if (Status) *Status = In.GetError(); return false; } if (!Out.Open(To, O_WRITE)) { if (Status) *Status = Out.GetError(); return false; } int64 Size = In.GetSize(), Done = 0; for (int64 i=0; i 0) { int w = Out.Write(&Buf[0], r); if (w <= 0) break; r -= w; Done += w; if (Callback) Callback(Token, Done, Size); } if (r > 0) break; } return Done == Size; } return false; } bool GFileSystem::Delete(GArray &Files, GArray *Status, bool ToTrash) { bool Error = false; if (ToTrash) { char p[MAX_PATH]; if (LGetSystemPath(LSP_TRASH, p, sizeof(p))) { for (int i=0; i f; f.Add(FileName); return Delete(f, 0, ToTrash); } return false; } bool GFileSystem::CreateFolder(const char *PathName, bool CreateParentTree, LError *ErrorCode) { int r = mkdir(PathName, S_IRWXU | S_IXGRP | S_IXOTH); if (r) { if (ErrorCode) *ErrorCode = errno; printf("%s:%i - mkdir('%s') failed with %i, errno=%i\n", _FL, PathName, r, errno); } return r == 0; } bool GFileSystem::RemoveFolder(const char *PathName, bool Recurse) { if (Recurse) { GDirectory *Dir = new GDirectory; if (Dir && Dir->First(PathName)) { do { char Str[256]; Dir->Path(Str, sizeof(Str)); if (Dir->IsDir()) { RemoveFolder(Str, Recurse); } else { Delete(Str, false); } } while (Dir->Next()); } DeleteObj(Dir); } return rmdir(PathName) == 0; } bool GFileSystem::Move(const char *OldName, const char *NewName, LError *Err) { if (rename(OldName, NewName)) { printf("%s:%i - rename failed, error: %s(%i)\n", _FL, GetErrorName(errno), errno); return false; } return true; } /* bool Match(char *Name, char *Mask) { strupr(Name); strupr(Mask); while (*Name && *Mask) { if (*Mask == '*') { if (*Name == *(Mask+1)) { Mask++; } else { Name++; } } else if (*Mask == '?' || *Mask == *Name) { Mask++; Name++; } else { return false; } } while (*Mask && ((*Mask == '*') || (*Mask == '.'))) Mask++; return (*Name == 0 && *Mask == 0); } */ short DaysInMonth[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; int LeapYear(int year) { if (year & 3) { return 0; } if ((year % 100 == 0) && !(year % 400 == 0)) { return 0; } return 1; } ///////////////////////////////////////////////////////////////////////////////// bool GDirectory::ConvertToTime(char *Str, int SLen, uint64 Time) const { time_t k = Time; struct tm *t = localtime(&k); if (t) { strftime(Str, SLen, "%I:%M:%S", t); return true; } return false; } bool GDirectory::ConvertToDate(char *Str, int SLen, uint64 Time) const { time_t k = Time; struct tm *t = localtime(&k); if (t) { strftime(Str, SLen, "%d/%m/%y", t); return true; } return false; } ///////////////////////////////////////////////////////////////////////////////// //////////////////////////// Directory ////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////// class GDirectoryPriv { public: char BasePath[MAX_PATH]; DIR *Dir; struct dirent *De; struct stat Stat; char *Pattern; GDirectoryPriv() { Dir = 0; De = 0; BasePath[0] = 0; Pattern = 0; } ~GDirectoryPriv() { DeleteArray(Pattern); } bool Ignore() { return De && ( strcmp(De->d_name, ".") == 0 || strcmp(De->d_name, "..") == 0 || ( Pattern && !MatchStr(Pattern, De->d_name) ) ); } }; GDirectory::GDirectory() { d = new GDirectoryPriv; } GDirectory::~GDirectory() { Close(); DeleteObj(d); } GDirectory *GDirectory::Clone() { return new GDirectory; } int GDirectory::First(const char *Name, const char *Pattern) { Close(); if (Name) { strcpy(d->BasePath, Name); if (!Pattern || stricmp(Pattern, LGI_ALL_FILES) == 0) { struct stat S; if (lstat(Name, &S) == 0) { if (S_ISREG(S.st_mode)) { char *Dir = strrchr(d->BasePath, DIR_CHAR); if (Dir) { *Dir++ = 0; d->Pattern = NewStr(Dir); } } } } else { d->Pattern = NewStr(Pattern); } d->Dir = opendir(d->BasePath); if (d->Dir) { d->De = readdir(d->Dir); if (d->De) { char s[256]; LgiMakePath(s, sizeof(s), d->BasePath, GetName()); lstat(s, &d->Stat); if (d->Ignore()) { if (!Next()) { return false; } } } } } return d->Dir != 0 && d->De != 0; } int GDirectory::Next() { int Status = false; while (d->Dir && d->De) { if (d->De = readdir(d->Dir)) { char s[256]; LgiMakePath(s, sizeof(s), d->BasePath, GetName()); lstat(s, &d->Stat); if (!d->Ignore()) { Status = true; break; } } } return Status; } int GDirectory::Close() { if (d->Dir) { closedir(d->Dir); d->Dir = 0; } d->De = 0; return true; } const char *GDirectory::FullPath() { static char s[MAX_PATH]; #warning this should really be optimized, and thread safe... Path(s, sizeof(s)); return s; } GString GDirectory::FileName() const { return GetName(); } bool GDirectory::Path(char *s, int BufLen) const { if (!s) { return false; } return LgiMakePath(s, BufLen, d->BasePath, GetName()); } int GDirectory::GetType() const { return IsDir() ? VT_FOLDER : VT_FILE; } int GDirectory::GetUser(bool Group) const { if (Group) { return d->Stat.st_gid; } else { return d->Stat.st_uid; } } bool GDirectory::IsReadOnly() const { if (getuid() == d->Stat.st_uid) { // Check user perms return !TestFlag(GetAttributes(), S_IWUSR); } else if (getgid() == d->Stat.st_gid) { // Check group perms return !TestFlag(GetAttributes(), S_IWGRP); } // Check global perms return !TestFlag(GetAttributes(), S_IWOTH); } bool GDirectory::IsHidden() const { return GetName() && GetName()[0] == '.'; } bool GDirectory::IsDir() const { int a = GetAttributes(); return !S_ISLNK(a) && S_ISDIR(a); } bool GDirectory::IsSymLink() const { int a = GetAttributes(); return S_ISLNK(a); } long GDirectory::GetAttributes() const { return d->Stat.st_mode; } char *GDirectory::GetName() const { return (d->De) ? d->De->d_name : 0; } uint64 GDirectory::GetCreationTime() const { return (uint64) d->Stat.st_ctime * LDateTime::Second64Bit; } uint64 GDirectory::GetLastAccessTime() const { return (uint64) d->Stat.st_atime * LDateTime::Second64Bit; } uint64 GDirectory::GetLastWriteTime() const { return (uint64) d->Stat.st_mtime * LDateTime::Second64Bit; } uint64 GDirectory::GetSize() const { - return (uint32)d->Stat.st_size; + return (uint32_t)d->Stat.st_size; } int64 GDirectory::GetSizeOnDisk() { - return (uint32)d->Stat.st_size; + return (uint32_t)d->Stat.st_size; } ///////////////////////////////////////////////////////////////////////////////// //////////////////////////// File /////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////// class GFilePrivate { public: int hFile; char *Name; bool Swap; int Status; int Attributes; int ErrorCode; GFilePrivate() { hFile = INVALID_HANDLE; Name = 0; Swap = false; Status = true; Attributes = 0; ErrorCode = 0; } ~GFilePrivate() { DeleteArray(Name); } }; GFile::GFile(const char *Path, int Mode) { d = new GFilePrivate; if (Path) Open(Path, Mode); } GFile::~GFile() { if (d && ValidHandle(d->hFile)) { Close(); } DeleteObj(d); } OsFile GFile::Handle() { return d->hFile; } int GFile::GetError() { return d->ErrorCode; } bool GFile::IsOpen() { return ValidHandle(d->hFile); } #define DEBUG_OPEN_FILES 0 #if DEBUG_OPEN_FILES LMutex Lck; GArray OpenFiles; #endif int GFile::Open(const char *File, int Mode) { int Status = false; if (File) { if (TestFlag(Mode, O_WRITE) || TestFlag(Mode, O_READWRITE)) { Mode |= O_CREAT; } Close(); d->hFile = open(File, Mode | O_LARGEFILE, S_IRUSR | S_IWUSR); if (ValidHandle(d->hFile)) { d->Attributes = Mode; d->Name = new char[strlen(File)+1]; if (d->Name) { strcpy(d->Name, File); } Status = true; d->Status = true; #if DEBUG_OPEN_FILES if (Lck.Lock(_FL)) { if (!OpenFiles.HasItem(this)) OpenFiles.Add(this); Lck.Unlock(); } #endif } else { d->ErrorCode = errno; #if DEBUG_OPEN_FILES if (Lck.Lock(_FL)) { for (unsigned i=0; iGetName()); Lck.Unlock(); } #endif printf("GFile::Open failed\n\topen(%s,%08.8x) = %i\n\terrno=%s (%s)\n", File, Mode, d->hFile, GetErrorName(d->ErrorCode), GetErrorDesc(d->ErrorCode)); } } return Status; } int GFile::Close() { if (ValidHandle(d->hFile)) { close(d->hFile); d->hFile = INVALID_HANDLE; DeleteArray(d->Name); #if DEBUG_OPEN_FILES if (Lck.Lock(_FL)) { OpenFiles.Delete(this); Lck.Unlock(); } #endif } return true; } /* int GFile::Print(char *Format, ...) { int Chars = 0; if (Format) { va_list Arg; va_start(Arg, Format); int Size = vsnprintf(0, 0, Format, Arg); char *Buffer = new char[Size+1]; if (Buffer) { vsprintf(Buffer, Format, Arg); } va_end(Arg); if (Size > 0) { Write(Buffer, Size); } DeleteArray(Buffer); } return Chars; } */ #define CHUNK 0xFFF0 ssize_t GFile::Read(void *Buffer, ssize_t Size, int Flags) { int Red = 0; if (Buffer && Size > 0) { Red = read(d->hFile, Buffer, Size); } d->Status = Red == Size; return MAX(Red, 0); } ssize_t GFile::Write(const void *Buffer, ssize_t Size, int Flags) { int Written = 0; if (Buffer && Size > 0) { Written = write(d->hFile, Buffer, Size); } d->Status = Written == Size; return MAX(Written, 0); } #define LINUX64 1 int64 GFile::Seek(int64 To, int Whence) { #if LINUX64 return lseek64(d->hFile, To, Whence); // If this doesn't compile, switch off LINUX64 #else return lseek(d->hFile, To, Whence); #endif } int64 GFile::SetPos(int64 Pos) { #if LINUX64 int64 p = lseek64(d->hFile, Pos, SEEK_SET); if (p < 0) { int e = errno; printf("%s:%i - lseek64(%Lx) failed (error %i: %s).\n", __FILE__, __LINE__, Pos, e, GetErrorName(e)); } #else return lseek(d->hFile, Pos, SEEK_SET); #endif } int64 GFile::GetPos() { #if LINUX64 int64 p = lseek64(d->hFile, 0, SEEK_CUR); if (p < 0) { int e = errno; printf("%s:%i - lseek64 failed (error %i: %s).\n", __FILE__, __LINE__, e, GetErrorName(e)); } return p; #else return lseek(d->hFile, 0, SEEK_CUR); #endif } int64 GFile::GetSize() { int64 Here = GetPos(); #if LINUX64 int64 Ret = lseek64(d->hFile, 0, SEEK_END); #else int64 Ret = lseek(d->hFile, 0, SEEK_END); #endif SetPos(Here); return Ret; } int64 GFile::SetSize(int64 Size) { if (ValidHandle(d->hFile)) { int64 Pos = GetPos(); /* close(d->hFile); if (d->Name) { #if LINUX64 truncate64(Name, Size); #else truncate(Name, Size); #endif } d->hFile = open(Name, Attributes, 0); */ #if LINUX64 ftruncate64(d->hFile, Size); #else ftruncate(d->hFile, Size); #endif if (d->hFile) { SetPos(Pos); } } return GetSize(); } bool GFile::Eof() { return GetPos() >= GetSize(); } ssize_t GFile::SwapRead(uchar *Buf, ssize_t Size) { ssize_t i = 0; ssize_t r = 0; Buf = &Buf[Size-1]; while (Size--) { r = read(d->hFile, Buf--, 1); i += r; } return i; } ssize_t GFile::SwapWrite(uchar *Buf, ssize_t Size) { ssize_t i = 0; ssize_t w = 0; Buf = &Buf[Size-1]; while (Size--) { w = write(d->hFile, Buf--, 1); i += w; } return i; } ssize_t GFile::ReadStr(char *Buf, ssize_t Size) { ssize_t i = 0; ssize_t r = 0; if (Buf && Size > 0) { char c; Size--; do { r = read(d->hFile, &c, 1); if (Eof()) { break; } *Buf++ = c; i++; } while (i < Size - 1 && c != '\n'); *Buf = 0; } return i; } ssize_t GFile::WriteStr(char *Buf, ssize_t Size) { ssize_t i = 0; ssize_t w; while (i <= Size) { w = write(d->hFile, Buf, 1); Buf++; i++; if (*Buf == '\n') break; } return i; } void GFile::SetStatus(bool s) { d->Status = s; } bool GFile::GetStatus() { return d->Status; } void GFile::SetSwap(bool s) { d->Swap = s; } bool GFile::GetSwap() { return d->Swap; } int GFile::GetOpenMode() { return d->Attributes; } char *GFile::GetName() { return d->Name; } #define RdIO { d->Status |= ((d->Swap) ? SwapRead((uchar*) &i, sizeof(i)) : Read(&i, sizeof(i))) != sizeof(i); return *this; } #define WrIO { d->Status |= ((d->Swap) ? SwapWrite((uchar*) &i, sizeof(i)) : Write(&i, sizeof(i))) != sizeof(i); return *this; } #define GFilePre GFile &GFile::operator >> ( #define GFilePost &i) RdIO GFileOps(); #undef GFilePre #undef GFilePost #define GFilePre GFile &GFile::operator << ( #define GFilePost i) WrIO GFileOps(); #undef GFilePre #undef GFilePost diff --git a/src/linux/Gtk/GPrintDC.cpp b/src/linux/Gtk/GPrintDC.cpp --- a/src/linux/Gtk/GPrintDC.cpp +++ b/src/linux/Gtk/GPrintDC.cpp @@ -1,317 +1,317 @@ #include "Lgi.h" // #include // #include #define PS_SCALE 10 /////////////////////////////////////////////////////////////////////////////////////// class GPrintDCPrivate // : public GCups { public: class PrintPainter *p; Gtk::GtkPrintContext *Handle; GString PrintJobName; GString PrinterName; int Pages; GColour c; GRect Clip; GPrintDCPrivate(Gtk::GtkPrintContext *handle) { p = 0; Pages = 0; Handle = handle; } ~GPrintDCPrivate() { } bool IsOk() { return this != 0; } }; ///////////////////////////////////////////////////////////////////////////////////// GPrintDC::GPrintDC(void *Handle, const char *PrintJobName, const char *PrinterName) { d = new GPrintDCPrivate((Gtk::GtkPrintContext*)Handle); d->PrintJobName = PrintJobName; d->PrinterName = PrinterName; Cairo = gtk_print_context_get_cairo_context(d->Handle); ColourSpace = CsRgb24; d->Clip = Bounds(); } GPrintDC::~GPrintDC() { Cairo = NULL; DeleteObj(d); } -Gtk::GtkPrintContext *GPrintDC::GetPrintContext() -{ - return d->Handle; -} - +Gtk::GtkPrintContext *GPrintDC::GetPrintContext() +{ + return d->Handle; +} + int GPrintDC::X() { return gtk_print_context_get_width(d->Handle); } int GPrintDC::Y() { return gtk_print_context_get_height(d->Handle); } int GPrintDC::GetBits() { return 24; -} - -int GPrintDC::DpiX() -{ - return Gtk::gtk_print_context_get_dpi_x(d->Handle); -} - -int GPrintDC::DpiY() -{ - return Gtk::gtk_print_context_get_dpi_y(d->Handle); -} - -GRect GPrintDC::ClipRgn(GRect *Rgn) -{ - GRect Prev = d->Clip; - if (Rgn) - d->Clip = *Rgn; - else - d->Clip = Bounds(); - return Prev; -} - -GRect GPrintDC::ClipRgn() -{ - return d->Clip; -} - -COLOUR GPrintDC::Colour() -{ - return d->c.c24(); -} - -COLOUR GPrintDC::Colour(COLOUR c, int Bits) -{ - GColour col(c, Bits); - return Colour(col).c24(); -} - -GColour GPrintDC::Colour(GColour c) -{ - GColour Prev = d->c; - d->c = c; - if (Cairo) - cairo_set_source_rgb(Cairo, - (double)d->c.r() / 255.0, - (double)d->c.g() / 255.0, - (double)d->c.b() / 255.0); - return Prev; -} - -void GPrintDC::Set(int x, int y) -{ - if (Cairo) - { - cairo_new_path(Cairo); - cairo_rectangle(Cairo, x, y, x+1, y+1); - cairo_fill(Cairo); - } -} - -void GPrintDC::HLine(int x1, int x2, int y) -{ - Line(x1, y, x2, y); -} - -void GPrintDC::VLine(int x, int y1, int y2) -{ - Line(x, y1, x, y2); -} - -void GPrintDC::Line(int x1, int y1, int x2, int y2) -{ - if (Cairo) - { +} + +int GPrintDC::DpiX() +{ + return Gtk::gtk_print_context_get_dpi_x(d->Handle); +} + +int GPrintDC::DpiY() +{ + return Gtk::gtk_print_context_get_dpi_y(d->Handle); +} + +GRect GPrintDC::ClipRgn(GRect *Rgn) +{ + GRect Prev = d->Clip; + if (Rgn) + d->Clip = *Rgn; + else + d->Clip = Bounds(); + return Prev; +} + +GRect GPrintDC::ClipRgn() +{ + return d->Clip; +} + +COLOUR GPrintDC::Colour() +{ + return d->c.c24(); +} + +COLOUR GPrintDC::Colour(COLOUR c, int Bits) +{ + GColour col(c, Bits); + return Colour(col).c24(); +} + +GColour GPrintDC::Colour(GColour c) +{ + GColour Prev = d->c; + d->c = c; + if (Cairo) + cairo_set_source_rgb(Cairo, + (double)d->c.r() / 255.0, + (double)d->c.g() / 255.0, + (double)d->c.b() / 255.0); + return Prev; +} + +void GPrintDC::Set(int x, int y) +{ + if (Cairo) + { + cairo_new_path(Cairo); + cairo_rectangle(Cairo, x, y, x+1, y+1); + cairo_fill(Cairo); + } +} + +void GPrintDC::HLine(int x1, int x2, int y) +{ + Line(x1, y, x2, y); +} + +void GPrintDC::VLine(int x, int y1, int y2) +{ + Line(x, y1, x, y2); +} + +void GPrintDC::Line(int x1, int y1, int x2, int y2) +{ + if (Cairo) + { cairo_set_line_width(Cairo, 0.5); - cairo_new_path(Cairo); + cairo_new_path(Cairo); cairo_move_to(Cairo, x1, y1); cairo_line_to(Cairo, x2, y2); - cairo_stroke(Cairo); - } -} - -void GPrintDC::Circle(double cx, double cy, double radius) -{ - LgiAssert(!"Not impl."); -} - -void GPrintDC::FilledCircle(double cx, double cy, double radius) -{ - LgiAssert(!"Not impl."); -} - -void GPrintDC::Arc(double cx, double cy, double radius, double start, double end) -{ - LgiAssert(!"Not impl."); -} - -void GPrintDC::FilledArc(double cx, double cy, double radius, double start, double end) -{ - LgiAssert(!"Not impl."); -} - -void GPrintDC::Ellipse(double cx, double cy, double x, double y) -{ - LgiAssert(!"Not impl."); -} - -void GPrintDC::FilledEllipse(double cx, double cy, double x, double y) -{ - LgiAssert(!"Not impl."); -} - -void GPrintDC::Box(int x1, int y1, int x2, int y2) -{ - GRect r(x1, y1, x2, y2); - Box(&r); -} - -void GPrintDC::Box(GRect *a) -{ - GRect r; - if (a) - r = *a; - else - r = Bounds(); - if (Cairo) - { - double Half = 0.5; + cairo_stroke(Cairo); + } +} + +void GPrintDC::Circle(double cx, double cy, double radius) +{ + LgiAssert(!"Not impl."); +} + +void GPrintDC::FilledCircle(double cx, double cy, double radius) +{ + LgiAssert(!"Not impl."); +} + +void GPrintDC::Arc(double cx, double cy, double radius, double start, double end) +{ + LgiAssert(!"Not impl."); +} + +void GPrintDC::FilledArc(double cx, double cy, double radius, double start, double end) +{ + LgiAssert(!"Not impl."); +} + +void GPrintDC::Ellipse(double cx, double cy, double x, double y) +{ + LgiAssert(!"Not impl."); +} + +void GPrintDC::FilledEllipse(double cx, double cy, double x, double y) +{ + LgiAssert(!"Not impl."); +} + +void GPrintDC::Box(int x1, int y1, int x2, int y2) +{ + GRect r(x1, y1, x2, y2); + Box(&r); +} + +void GPrintDC::Box(GRect *a) +{ + GRect r; + if (a) + r = *a; + else + r = Bounds(); + if (Cairo) + { + double Half = 0.5; cairo_set_line_width(Cairo, Half); - cairo_new_path(Cairo); - cairo_rectangle(Cairo, - Half + r.x1, - Half + r.y1, - -Half + r.X(), - -Half + r.Y()); - cairo_stroke(Cairo); - } -} - -void GPrintDC::Rectangle(int x1, int y1, int x2, int y2) -{ - GRect r(x1, y1, x2, y2); - Rectangle(&r); -} - -void GPrintDC::Rectangle(GRect *a) -{ - GRect r; - if (a) - r = *a; - else - r = Bounds(); - if (Cairo) - { - cairo_new_path(Cairo); - cairo_rectangle(Cairo, r.x1, r.y1, r.X(), r.Y()); - cairo_fill(Cairo); - } -} - -void GPrintDC::Blt(int x, int y, GSurface *Src, GRect *SrcClip) -{ - GRect s = SrcClip ? *SrcClip : Src->Bounds(); - GRect d = s; - d.ZOff(x, y); - StretchBlt(&d, Src, &s); -} - -void GPrintDC::StretchBlt(GRect *d, GSurface *Src, GRect *s) -{ - if (!Cairo) - { - LgiAssert(0); - return; - } - - uint8 *Scan0 = (*Src)[0]; - if (!Scan0) - { - LgiAssert(0); - return; - } - - Gtk::cairo_format_t Fmt = Gtk::CAIRO_FORMAT_INVALID; - switch (Src->GetBits()) - { - case 16: - Fmt = Gtk::CAIRO_FORMAT_RGB16_565; - break; - case 24: - Fmt = Gtk::CAIRO_FORMAT_RGB24; - break; - case 32: - Fmt = Gtk::CAIRO_FORMAT_ARGB32; - break; - } - if (Fmt == Gtk::CAIRO_FORMAT_INVALID) - { - LgiAssert(0); - return; - } - - Gtk::cairo_surface_t *Img = cairo_image_surface_create_for_data(Scan0, - Fmt, - Src->X(), - Src->Y(), - Src->GetRowStep()); - if (!Img) - { - LgiAssert(0); - return; - } - - Gtk::cairo_pattern_t *Pat = cairo_pattern_create_for_surface(Img); - if (Pat) - { - Gtk::cairo_matrix_t m; - double Sx = (double) s->X() / d->X(); - double Sy = (double) s->Y() / d->Y(); + cairo_new_path(Cairo); + cairo_rectangle(Cairo, + Half + r.x1, + Half + r.y1, + -Half + r.X(), + -Half + r.Y()); + cairo_stroke(Cairo); + } +} + +void GPrintDC::Rectangle(int x1, int y1, int x2, int y2) +{ + GRect r(x1, y1, x2, y2); + Rectangle(&r); +} + +void GPrintDC::Rectangle(GRect *a) +{ + GRect r; + if (a) + r = *a; + else + r = Bounds(); + if (Cairo) + { + cairo_new_path(Cairo); + cairo_rectangle(Cairo, r.x1, r.y1, r.X(), r.Y()); + cairo_fill(Cairo); + } +} + +void GPrintDC::Blt(int x, int y, GSurface *Src, GRect *SrcClip) +{ + GRect s = SrcClip ? *SrcClip : Src->Bounds(); + GRect d = s; + d.ZOff(x, y); + StretchBlt(&d, Src, &s); +} + +void GPrintDC::StretchBlt(GRect *d, GSurface *Src, GRect *s) +{ + if (!Cairo) + { + LgiAssert(0); + return; + } + + uint8_t *Scan0 = (*Src)[0]; + if (!Scan0) + { + LgiAssert(0); + return; + } + + Gtk::cairo_format_t Fmt = Gtk::CAIRO_FORMAT_INVALID; + switch (Src->GetBits()) + { + case 16: + Fmt = Gtk::CAIRO_FORMAT_RGB16_565; + break; + case 24: + Fmt = Gtk::CAIRO_FORMAT_RGB24; + break; + case 32: + Fmt = Gtk::CAIRO_FORMAT_ARGB32; + break; + } + if (Fmt == Gtk::CAIRO_FORMAT_INVALID) + { + LgiAssert(0); + return; + } + + Gtk::cairo_surface_t *Img = cairo_image_surface_create_for_data(Scan0, + Fmt, + Src->X(), + Src->Y(), + Src->GetRowStep()); + if (!Img) + { + LgiAssert(0); + return; + } + + Gtk::cairo_pattern_t *Pat = cairo_pattern_create_for_surface(Img); + if (Pat) + { + Gtk::cairo_matrix_t m; + double Sx = (double) s->X() / d->X(); + double Sy = (double) s->Y() / d->Y(); cairo_matrix_init_scale(&m, Sx, Sy); cairo_matrix_translate(&m, -d->x1,-d->y1); - cairo_pattern_set_matrix(Pat, &m); - - cairo_save(Cairo); - cairo_set_source(Cairo, Pat); - - cairo_new_path(Cairo); - cairo_rectangle(Cairo, d->x1, d->y1, d->X(), d->Y()); - cairo_fill(Cairo); - - cairo_restore(Cairo); - cairo_pattern_destroy(Pat); - } - - cairo_surface_destroy(Img); -} - -void GPrintDC::Polygon(int Points, GdcPt2 *Data) -{ - LgiAssert(!"Not impl."); -} - -void GPrintDC::Bezier(int Threshold, GdcPt2 *Pt) -{ - LgiAssert(!"Not impl."); -} - + cairo_pattern_set_matrix(Pat, &m); + + cairo_save(Cairo); + cairo_set_source(Cairo, Pat); + + cairo_new_path(Cairo); + cairo_rectangle(Cairo, d->x1, d->y1, d->X(), d->Y()); + cairo_fill(Cairo); + + cairo_restore(Cairo); + cairo_pattern_destroy(Pat); + } + + cairo_surface_destroy(Img); +} + +void GPrintDC::Polygon(int Points, GdcPt2 *Data) +{ + LgiAssert(!"Not impl."); +} + +void GPrintDC::Bezier(int Threshold, GdcPt2 *Pt) +{ + LgiAssert(!"Not impl."); +} + diff --git a/src/linux/Gtk/GScreenDC.cpp b/src/linux/Gtk/GScreenDC.cpp --- a/src/linux/Gtk/GScreenDC.cpp +++ b/src/linux/Gtk/GScreenDC.cpp @@ -1,693 +1,693 @@ /*hdr ** FILE: GScreenDC.cpp ** AUTHOR: Matthew Allen ** DATE: 14/10/2000 ** DESCRIPTION: GDC v2.xx header ** ** Copyright (C) 2000, Matthew Allen ** fret@memecode.com */ #include #include #include "Lgi.h" using namespace Gtk; class GScreenPrivate { public: int x, y, Bits; bool Own; GColour Col; GRect Client; GView *View; OsView v; GdkDrawable *d; GdkGC *gc; GScreenPrivate() { View = NULL; x = y = Bits = 0; Own = false; v = 0; d = NULL; gc = NULL; Client.ZOff(-1, -1); } ~GScreenPrivate() { if (gc) g_object_unref((Gtk::GObject*)g_type_check_instance_cast((Gtk::GTypeInstance*)gc, G_TYPE_OBJECT)); } }; // Translates are cumulative... so we undo the last one before resetting it. #define UnTranslate() // d->p.translate(OriginX, OriginY); #define Translate() // d->p.translate(-OriginX, -OriginY); ///////////////////////////////////////////////////////////////////////////////////////////////////// GScreenDC::GScreenDC() { d = new GScreenPrivate; d->x = GdcD->X(); d->y = GdcD->Y(); } /* GScreenDC::GScreenDC(OsView View) { d = new GScreenPrivate; d->v = View; d->d = View->window; d->x = View->allocation.width; d->y = View->allocation.height; if (d->gc = gdk_gc_new(View->window)) { GdkScreen *s = gdk_gc_get_screen(d->gc); if (s) { GdkVisual *v = gdk_screen_get_system_visual(s); if (v) { d->Bits = v->depth; ColourSpace = GdkVisualToColourSpace(v, v->depth); } } } // printf("%s:%i %p, %ix%i, %i\n", _FL, View, d->x, d->y, d->Bits); } */ GScreenDC::GScreenDC(int x, int y, int bits) { d = new GScreenPrivate; d->x = x; d->y = y; d->Bits = bits; } GScreenDC::GScreenDC(Gtk::GdkDrawable *Drawable) { d = new GScreenPrivate; d->Own = false; d->d = Drawable; if (d->gc = gdk_gc_new(Drawable)) { GdkScreen *s = gdk_gc_get_screen(d->gc); if (s) { GdkVisual *v = gdk_screen_get_system_visual(s); if (v) { d->Bits = v->depth; ColourSpace = GdkVisualToColourSpace(v, v->depth); } } } } GScreenDC::GScreenDC(GView *view, void *param) { d = new GScreenPrivate; d->View = view; if (view) { OsView v = view->Handle(); if (v) { d->v = v; d->d = v->window; d->x = v->allocation.width; d->y = v->allocation.height; if (d->gc = gdk_gc_new(v->window)) { GdkScreen *s = gdk_gc_get_screen(d->gc); if (s) { GdkVisual *v = gdk_screen_get_system_visual(s); if (v) { d->Bits = v->depth; ColourSpace = GdkVisualToColourSpace(v, v->depth); } } } /* d->d = v->window; if (d->gc = gdk_gc_new(v->window)) { GdkScreen *s = gdk_gc_get_screen(d->gc); if (s) { GdkVisual *v = gdk_screen_get_system_visual(s); if (v) { d->Bits = v->depth; ColourSpace = GdkVisualToColourSpace(v, v->depth); } } } */ } else { d->x = view->X(); d->y = view->Y(); d->Bits = 0; d->Own = false; GdkScreen *s = gdk_display_get_default_screen(gdk_display_get_default()); if (s) { GdkVisual *v = gdk_screen_get_system_visual(s); if (v) { d->Bits = v->depth; ColourSpace = GdkVisualToColourSpace(v, v->depth); } } } } else { printf("%s:%i - No view?\n", _FL); } } GScreenDC::~GScreenDC() { UnTranslate(); DeleteObj(d); } OsPainter GScreenDC::Handle() { if (!Cairo) { Cairo = gdk_cairo_create(d->d); if (Cairo) { // cairo_reset_clip(Cairo); double x1, y1, x2, y2; cairo_clip_extents (Cairo, &x1, &y1, &x2, &y2); #ifdef _DEBUG int x = (int) (x2 - x1); int y = (int) (y2 - y1); if (d->View && d->View->_Debug) { int width, height; gdk_drawable_get_size (d->d, &width, &height); printf("%s:%i %s %g,%g,%g,%g %i,%i %i,%i %i,%i\n", _FL, d->View ? d->View->GetClass() : NULL, x1,y1,x2,y2, x,y, d->x, d->y, width, height); } #endif } } return Cairo; } bool GScreenDC::SupportsAlphaCompositing() { // GTK/X11 doesn't seem to support alpha compositing. return false; } GdcPt2 GScreenDC::GetSize() { return GdcPt2(d->x, d->y); } bool GScreenDC::GetClient(GRect *c) { if (!c) return false; *c = d->Client; return true; } void GScreenDC::SetClient(GRect *c) { if (c) { d->Client = *c; GdkRectangle r = {c->x1, c->y1, c->X(), c->Y()}; gdk_gc_set_clip_rectangle(d->gc, &r); OriginX = -c->x1; OriginY = -c->y1; } else { OriginX = 0; OriginY = 0; d->Client.ZOff(-1, -1); GdkRectangle r = {0, 0, X(), Y()}; gdk_gc_set_clip_rectangle(d->gc, &r); } } GRect *GScreenDC::GetClient() { return &d->Client; } uint GScreenDC::LineStyle() { return GSurface::LineStyle(); } -uint GScreenDC::LineStyle(uint32 Bits, uint32 Reset) +uint GScreenDC::LineStyle(uint32_t Bits, uint32_t Reset) { return GSurface::LineStyle(Bits); } int GScreenDC::GetFlags() { return 0; } void GScreenDC::GetOrigin(int &x, int &y) { return GSurface::GetOrigin(x, y); } void GScreenDC::SetOrigin(int x, int y) { UnTranslate(); GSurface::SetOrigin(x, y); Translate(); } GRect GScreenDC::ClipRgn() { return Clip; } GRect GScreenDC::ClipRgn(GRect *c) { GRect Prev = Clip; UnTranslate(); if (c) { Clip = *c; // LgiTrace("Setting clip %s client=%s\n", Clip.GetStr(), d->Client.GetStr()); GdkRectangle r = {c->x1+d->Client.x1, c->y1+d->Client.y1, c->X(), c->Y()}; gdk_gc_set_clip_rectangle(d->gc, &r); } else { Clip.ZOff(-1, -1); // LgiTrace("Removing clip\n"); GdkRectangle r = {d->Client.x1, d->Client.y1, X(), Y()}; gdk_gc_set_clip_rectangle(d->gc, &r); } Translate(); return Prev; } COLOUR GScreenDC::Colour() { return d->Col.Get(GetBits()); } COLOUR GScreenDC::Colour(COLOUR c, int Bits) { GColour col(c, Bits ? Bits : GetBits()); return Colour(col).Get(GetBits()); } GColour GScreenDC::Colour(GColour c) { GColour Prev = d->Col; d->Col = c; if (d->gc) { GdkColor col; col.pixel = 0; col.red = c.r(); col.red |= col.red << 8; col.green = c.g(); col.green |= col.green << 8; col.blue = c.b(); col.blue |= col.blue << 8; // printf("Setting Col %x, %x, %x\n", col.red, col.green, col.blue); gdk_gc_set_rgb_fg_color(d->gc, &col); gdk_gc_set_rgb_bg_color(d->gc, &col); } return Prev; } int GScreenDC::Op(int ROP, NativeInt Param) { int Prev = Op(); switch (ROP) { case GDC_SET: { //d->p.setRasterOp(XPainter::CopyROP); break; } case GDC_OR: { //d->p.setRasterOp(XPainter::OrROP); break; } case GDC_AND: { //d->p.setRasterOp(XPainter::AndROP); break; } case GDC_XOR: { //d->p.setRasterOp(XPainter::XorROP); break; } } return Prev; } int GScreenDC::Op() { /* switch (d->p.rasterOp()) { case XPainter::CopyROP: { return GDC_SET; break; } case XPainter::OrROP: { return GDC_OR; break; } case XPainter::AndROP: { return GDC_AND; break; } case XPainter::XorROP: { return GDC_XOR; break; } } */ return GDC_SET; } int GScreenDC::X() { return d->Client.Valid() ? d->Client.X() : d->x; } int GScreenDC::Y() { return d->Client.Valid() ? d->Client.Y() : d->y; } int GScreenDC::GetBits() { return d->Bits; } GPalette *GScreenDC::Palette() { return 0; } void GScreenDC::Palette(GPalette *pPal, bool bOwnIt) { } void GScreenDC::Set(int x, int y) { gdk_draw_point(d->d, d->gc, x-OriginX, y-OriginY); } COLOUR GScreenDC::Get(int x, int y) { return 0; } void GScreenDC::HLine(int x1, int x2, int y) { gdk_draw_line(d->d, d->gc, x1-OriginX, y-OriginY, x2-OriginX, y-OriginY); } void GScreenDC::VLine(int x, int y1, int y2) { gdk_draw_line(d->d, d->gc, x-OriginX, y1-OriginY, x-OriginX, y2-OriginY); } void GScreenDC::Line(int x1, int y1, int x2, int y2) { gdk_draw_line(d->d, d->gc, x1-OriginX, y1-OriginY, x2-OriginX, y2-OriginY); } void GScreenDC::Circle(double cx, double cy, double radius) { gdk_draw_arc(d->d, d->gc, false, cx - radius, cy - radius, radius * 2.0, radius * 2.0, 0, 360 * 64); } void GScreenDC::FilledCircle(double cx, double cy, double radius) { gdk_draw_arc(d->d, d->gc, true, cx - radius, cy - radius, radius * 2.0, radius * 2.0, 0, 360 * 64); } void GScreenDC::Arc(double cx, double cy, double radius, double start, double end) { gdk_draw_arc(d->d, d->gc, false, cx - radius, cy - radius, radius * 2.0, radius * 2.0, start * 64.0, end * 64.0); } void GScreenDC::FilledArc(double cx, double cy, double radius, double start, double end) { gdk_draw_arc(d->d, d->gc, true, cx - radius, cy - radius, radius * 2.0, radius * 2.0, start * 64.0, end * 64.0); } void GScreenDC::Ellipse(double cx, double cy, double x, double y) { gdk_draw_arc(d->d, d->gc, false, cx - (x / 2), cy - (y / 2), x, y, 0, 360 * 64); } void GScreenDC::FilledEllipse(double cx, double cy, double x, double y) { gdk_draw_arc(d->d, d->gc, true, cx - (x / 2), cy - (y / 2), x, y, 0, 360 * 64); } void GScreenDC::Box(int x1, int y1, int x2, int y2) { gdk_draw_rectangle(d->d, d->gc, false, x1-OriginX, y1-OriginY, x2-x1, y2-y1); } void GScreenDC::Box(GRect *a) { if (a) { Box(a->x1, a->y1, a->x2, a->y2); } else { Box(0, 0, X()-1, Y()-1); } } void GScreenDC::Rectangle(int x1, int y1, int x2, int y2) { if (x2 >= x1 && y2 >= y1) { gdk_draw_rectangle(d->d, d->gc, true, x1-OriginX, y1-OriginY, x2-x1+1, y2-y1+1); } } void GScreenDC::Rectangle(GRect *a) { if (a) { if (a->X() > 0 && a->Y() > 0) { gdk_draw_rectangle(d->d, d->gc, true, a->x1-OriginX, a->y1-OriginY, a->X(), a->Y()); } } else { gdk_draw_rectangle(d->d, d->gc, true, -OriginX, -OriginY, X(), Y()); } } void GScreenDC::Polygon(int Points, GdcPt2 *Data) { if (Data) { ::GArray pt; for (int p=0; pd, d->gc, true, &pt.First(), pt.Length()); } } void GScreenDC::Blt(int x, int y, GSurface *Src, GRect *a) { if (!Src) { LgiTrace("%s:%i - No source.\n", _FL); return; } if (Src->IsScreen()) { LgiTrace("%s:%i - Can't do screen->screen blt.\n", _FL); return; } // memory -> screen blt GRect RealClient = d->Client; int Dx, Dy; Dx = x - OriginX; Dy = y - OriginY; d->Client.ZOff(-1, -1); // Clear this so the blit rgn calculation uses the // full context size rather than just the client. GBlitRegions br(this, Dx, Dy, Src, a); d->Client = RealClient; if (!br.Valid()) { return; } GMemDC *Mem; if (Mem = dynamic_cast(Src)) { GMemDC Tmp; if (Mem->GetCreateCs() != GetColourSpace() && Mem->GetBits() > 16 && GetBits() <= 16) { // Do an on the fly colour space conversion... this is slow though if (Tmp.Create(br.SrcClip.X(), br.SrcClip.Y(), GetColourSpace())) { Tmp.Blt(0, 0, Mem, &br.SrcClip); printf("On the fly Mem->Scr conversion: %s->%s\n", GColourSpaceToString(Mem->GetColourSpace()), GColourSpaceToString(GetColourSpace())); Mem = &Tmp; br.SrcClip = Tmp.Bounds(); } else { printf("Failed to Mem->Scr Blt: %s->%s\n", GColourSpaceToString(Mem->GetColourSpace()), GColourSpaceToString(GetColourSpace())); return; } } if (d->d && d->gc && Mem->GetImage()) { gdk_draw_image( d->d, d->gc, Mem->GetImage(), br.SrcClip.x1, br.SrcClip.y1, Dx, Dy, br.SrcClip.X(), br.SrcClip.Y()); } else { LgiTrace("%s:%i - Error missing d=%p, gc=%p, img=%p\n", _FL, d->d, d->gc, Mem->GetImage()); } } } void GScreenDC::StretchBlt(GRect *d, GSurface *Src, GRect *s) { LgiAssert(0); } void GScreenDC::Bezier(int Threshold, GdcPt2 *Pt) { LgiAssert(0); } void GScreenDC::FloodFill(int x, int y, int Mode, COLOUR Border, GRect *Bounds) { LgiAssert(0); } diff --git a/src/linux/Gtk/Gdc2.cpp b/src/linux/Gtk/Gdc2.cpp --- a/src/linux/Gtk/Gdc2.cpp +++ b/src/linux/Gtk/Gdc2.cpp @@ -1,999 +1,999 @@ /// \file /// \author Matthew Allen, fret@memecode.com #include #include #include #include #include "Lgi.h" #include "GPalette.h" #define LGI_PI 3.141592654 #define LGI_RAD (360/(2*LGI_PI)) /****************************** Classes *************************************************************************************/ GPalette::GPalette() { Data = 0; Size = 0; } GPalette::GPalette(GPalette *pPal) { // printf("%s:%i - %p::new(%p) %i\n", _FL, this, pPal, pPal?pPal->GetSize():0); Size = 0; Data = 0; Set(pPal); } GPalette::GPalette(uchar *pPal, int s) { // printf("%s:%i - %p::new(%p) %i\n", _FL, this, pPal, s); Size = 0; Data = 0; Set(pPal, s); } GPalette::~GPalette() { DeleteArray(Data); Size = 0; } void GPalette::Set(GPalette *pPal) { if (pPal == this) return; // printf("%s:%i - %p::Set(%p) %i\n", _FL, this, pPal, pPal?pPal->GetSize():0); DeleteArray(Data); Size = 0; if (pPal) { LgiAssert(pPal->GetSize() > 0); if (pPal->Data) { Data = new GdcRGB[pPal->Size]; if (Data) { memcpy(Data, pPal->Data, sizeof(GdcRGB) * pPal->Size); } } Size = pPal->Size; } } void GPalette::Set(int Index, int r, int g, int b) { GdcRGB *rgb = (*this)[Index]; if (rgb) { rgb->r = r; rgb->g = g; rgb->b = b; } } void GPalette::Set(uchar *pPal, int s) { // printf("%s:%i - SetPal %p %i\n", _FL, pPal, s); DeleteArray(Data); Size = 0; Data = new GdcRGB[s]; if (Data) { if (pPal) { for (int i=0; ir; (*this)[i]->r = (*this)[i]->b; (*this)[i]->b = n; } } Update(); } uchar *GPalette::MakeLut(int Bits) { uchar *Lut = 0; GdcRGB *p = (*this)[0]; int Size = 1 << Bits; switch (Bits) { case 15: { Lut = new uchar[Size]; if (Lut) { for (int i=0; i> 2); int g = (G15(i) << 3) | (G15(i) >> 2); int b = (B15(i) << 3) | (B15(i) >> 2); Lut[i] = MatchRgb(Rgb24(r, g, b)); } } break; } case 16: { Lut = new uchar[Size]; if (Lut) { for (int i=0; i> 2); int g = (G16(i) << 2) | (G16(i) >> 3); int b = (B16(i) << 3) | (B16(i) >> 2); Lut[i] = MatchRgb(Rgb24(r, g, b)); } } break; } } return Lut; } int GPalette::MatchRgb(COLOUR Rgb) { if (Data) { GdcRGB *Entry = (*this)[0]; int r = (R24(Rgb) & 0xF8) + 4; int g = (G24(Rgb) & 0xF8) + 4; int b = (B24(Rgb) & 0xF8) + 4; ulong *squares = GdcD->GetCharSquares(); ulong mindist = 200000; ulong bestcolor; ulong curdist; long rdist; long gdist; long bdist; for (int i = 0; i < Size; i++) { rdist = Entry[i].r - r; gdist = Entry[i].g - g; bdist = Entry[i].b - b; curdist = squares[rdist] + squares[gdist] + squares[bdist]; if (curdist < mindist) { mindist = curdist; bestcolor = i; } } return bestcolor; } return 0; } void GPalette::CreateGreyScale() { SetSize(256); GdcRGB *p = (*this)[0]; for (int i=0; i<256; i++) { p->r = i; p->g = i; p->b = i; p->a = 0; p++; } } void GPalette::CreateCube() { SetSize(256); GdcRGB *p = (*this)[0]; for (int r=0; r<6; r++) { for (int g=0; g<6; g++) { for (int b=0; b<6; b++) { p->r = r * 51; p->g = g * 51; p->b = b * 51; p->a = 0; p++; } } } for (int i=216; i<256; i++) { (*this)[i]->r = 0; (*this)[i]->g = 0; (*this)[i]->b = 0; } } void TrimWhite(char *s) { char *White = " \r\n\t"; char *c = s; while (*c && strchr(White, *c)) c++; if (c != s) { strcpy(s, c); } c = s + strlen(s) - 1; while (c > s && strchr(White, *c)) { *c = 0; c--; } } bool GPalette::Load(GFile &F) { bool Status = false; char Buf[256]; F.ReadStr(Buf, sizeof(Buf)); TrimWhite(Buf); if (strcmp(Buf, "JASC-PAL") == 0) { // is JASC palette // skip hex length F.ReadStr(Buf, sizeof(Buf)); // read decimal length F.ReadStr(Buf, sizeof(Buf)); SetSize(atoi(Buf)); for (int i=0; ir = atoi(strtok(Buf, " ")); p->g = atoi(strtok(NULL, " ")); p->b = atoi(strtok(NULL, " ")); } Status = true; } } else { // check for microsoft format } return Status; } bool GPalette::Save(GFile &F, int Format) { bool Status = false; switch (Format) { case GDCPAL_JASC: { char Buf[256]; sprintf(Buf, "JASC-PAL\r\n%04.4X\r\n%i\r\n", GetSize(), GetSize()); F.Write(Buf, strlen(Buf)); for (int i=0; ir, p->g, p->b); F.Write(Buf, strlen(Buf)); } } Status = true; break; } } return Status; } bool GPalette::operator ==(GPalette &p) { if (GetSize() == p.GetSize()) { GdcRGB *a = (*this)[0]; GdcRGB *b = p[0]; for (int i=0; ir != b->r || a->g != b->g || a->b != b->b) { return false; } a++; b++; } return true; } return false; } bool GPalette::operator !=(GPalette &p) { return !((*this) == p); } //////////////////////////////////////////////////////////////////////////////////////////////////// GBmpMem::GBmpMem() { Base = 0; Flags = 0; } GBmpMem::~GBmpMem() { if (Base && (Flags & BmpOwnMemory)) { delete [] Base; } } //////////////////////////////////////////////////////////////////////////////////////////// class GdcDevicePrivate { public: GdcDevice *Device; // Current mode info int ScrX; int ScrY; int ScrBits; GColourSpace ScrColourSpace; // Palette double GammaCorrection; uchar GammaTable[256]; GPalette *pSysPal; GGlobalColour *GlobalColour; // Data ulong *CharSquareData; uchar *Div255; // Options int OptVal[GDC_MAX_OPTION]; GdcDevicePrivate(GdcDevice *d) { Device = d; GlobalColour = 0; ZeroObj(OptVal); ScrX = ScrY = 0; ScrBits = 0; // Palette information GammaCorrection = 1.0; // Get mode stuff Gtk::GdkDisplay *Dsp = Gtk::gdk_display_get_default(); Gtk::gint Screens = Gtk::gdk_display_get_n_screens(Dsp); for (Gtk::gint i=0; idepth; ScrColourSpace = GdkVisualToColourSpace(Vis, Vis->depth); } } } printf("Screen: %i x %i @ %i bpp (%s)\n", ScrX, ScrY, ScrBits, GColourSpaceToString(ScrColourSpace)); #if !LGI_RPI OptVal[GDC_PROMOTE_ON_LOAD] = ScrBits; #endif // Calcuate lookups CharSquareData = new ulong[255+255+1]; if (CharSquareData) { for (int i = -255; i <= 255; i++) { CharSquareData[i+255] = i*i; } } // Divide by 255 lookup, real handy for alpha blending 8 bit components int Size = (255 * 255) * 2; Div255 = new uchar[Size]; if (Div255) { for (int i=0; i= 0 && Opt < GDC_MAX_OPTION) { return d->OptVal[Opt]; } LgiAssert(0); return 0; } int GdcDevice::SetOption(int Opt, int Value) { int Prev = d->OptVal[Opt]; if (Opt >= 0 && Opt < GDC_MAX_OPTION) { d->OptVal[Opt] = Value; } else { LgiAssert(0); } return Prev; } ulong *GdcDevice::GetCharSquares() { return (d->CharSquareData) ? d->CharSquareData + 255 : 0; } uchar *GdcDevice::GetDiv255() { return d->Div255; } GGlobalColour *GdcDevice::GetGlobalColour() { return d->GlobalColour; } GColourSpace GdcDevice::GetColourSpace() { return d->ScrColourSpace; } int GdcDevice::GetBits() { return d->ScrBits; } int GdcDevice::X() { return d->ScrX; } int GdcDevice::Y() { return d->ScrY; } void GdcDevice::SetGamma(double Gamma) { d->GammaCorrection = Gamma; for (int i=0; i<256; i++) { d->GammaTable[i] = (uchar) (pow(((double)i)/256, Gamma) * 256); } } double GdcDevice::GetGamma() { return d->GammaCorrection; } void GdcDevice::SetSystemPalette(int Start, int Size, GPalette *pPal) { /* if (pPal) { uchar Pal[768]; uchar *Temp = Pal; uchar *System = Palette + (Start * 3); GdcRGB *P = (*pPal)[Start]; for (int i=0; iR] >> PalShift; *Temp++ = GammaTable[*System++ = P->G] >> PalShift; *Temp++ = GammaTable[*System++ = P->B] >> PalShift; } SetPaletteBlockDirect(Pal, Start, Size * 3); } */ } GPalette *GdcDevice::GetSystemPalette() { return d->pSysPal; } void GdcDevice::SetColourPaletteType(int Type) { bool SetOpt = true; /* switch (Type) { case PALTYPE_ALLOC: { SetPalIndex(0, 0, 0, 0); SetPalIndex(255, 255, 255, 255); break; } case PALTYPE_RGB_CUBE: { uchar Pal[648]; uchar *p = Pal; for (int r=0; r<6; r++) { for (int g=0; g<6; g++) { for (int b=0; b<6; b++) { *p++ = r * 51; *p++ = g * 51; *p++ = b * 51; } } } SetPalBlock(0, 216, Pal); SetPalIndex(255, 0xFF, 0xFF, 0xFF); break; } case PALTYPE_HSL: { for (int h = 0; h<16; h++) { } break; } default: { SetOpt = false; } } if (SetOpt) { SetOption(GDC_PALETTE_TYPE, Type); } */ } COLOUR GdcDevice::GetColour(COLOUR Rgb24, GSurface *pDC) { int Bits = (pDC) ? pDC->GetBits() : GetBits(); COLOUR C; switch (Bits) { case 8: { switch (GetOption(GDC_PALETTE_TYPE)) { case PALTYPE_ALLOC: { static uchar Current = 1; Rgb24 &= 0xFFFFFF; if (Rgb24 == 0xFFFFFF) { C = 0xFF; } else if (Rgb24 == 0) { C = 0; } else { GdcRGB *p = (*d->pSysPal)[Current]; p->r = R24(Rgb24); p->g = G24(Rgb24); p->b = B24(Rgb24); C = Current++; if (Current == 255) Current = 1; } break; } case PALTYPE_RGB_CUBE: { uchar r = (R24(Rgb24) + 25) / 51; uchar g = (G24(Rgb24) + 25) / 51; uchar b = (B24(Rgb24) + 25) / 51; C = (r*36) + (g*6) + b; break; } case PALTYPE_HSL: { C = 0; break; } } break; } case 16: { C = Rgb24To16(Rgb24); break; } case 24: { C = Rgb24; break; } case 32: { C = Rgb24To32(Rgb24); break; } } return C; } ////////////////////////////////////////////////////////////////////////////////////////////// static int _Factories; static GApplicatorFactory *_Factory[16]; GApp8 Factory8; GApp15 Factory15; GApp16 Factory16; GApp24 Factory24; GApp32 Factory32; GAlphaFactory FactoryAlpha; GApplicatorFactory::GApplicatorFactory() { LgiAssert(_Factories >= 0 && _Factories < CountOf(_Factory)); if (_Factories < CountOf(_Factory) - 1) { _Factory[_Factories++] = this; } } GApplicatorFactory::~GApplicatorFactory() { LgiAssert(_Factories >= 0 && _Factories < CountOf(_Factory)); for (int i=0; i<_Factories; i++) { if (_Factory[i] == this) { _Factory[i] = _Factory[_Factories-1]; _Factories--; break; } } } GApplicator *GApplicatorFactory::NewApp(GColourSpace Cs, int Op) { LgiAssert(_Factories >= 0 && _Factories < CountOf(_Factory)); for (int i=0; i<_Factories; i++) { GApplicator *a = _Factory[i]->Create(Cs, Op); if (a) return a; } return 0; } /////////////////////////////////////////////////////////////////////////////////////////////// class GlobalColourEntry { public: COLOUR c24; bool Fixed; bool Used; GlobalColourEntry() { c24 = 0; Fixed = false; Used = false; } }; class GGlobalColourPrivate { public: GlobalColourEntry c[256]; GPalette *Global; List Cache; int FirstUnused; int FreeColours() { int f = 0; for (int i=0; i<256; i++) { if (!c[i].Used) { f++; } } return f; } GGlobalColourPrivate() { } ~GGlobalColourPrivate() { Cache.DeleteObjects(); } }; GGlobalColour::GGlobalColour() { d = new GGlobalColourPrivate; } GGlobalColour::~GGlobalColour() { DeleteObj(d); } COLOUR GGlobalColour::AddColour(COLOUR c24) { return c24; } bool GGlobalColour::AddBitmap(GSurface *pDC) { return false; } bool GGlobalColour::AddBitmap(GImageList *il) { return false; } bool GGlobalColour::MakeGlobalPalette() { return 0; } GPalette *GGlobalColour::GetPalette() { return 0; } COLOUR GGlobalColour::GetColour(COLOUR c24) { return c24; } bool GGlobalColour::RemapBitmap(GSurface *pDC) { return false; } //////////////////////////////////////////////////////////////////////////////////////////// union EndianTest { char b[2]; short s; }; #define VisualToColourSpaceDebug 0 GColourSpace GdkVisualToColourSpace(Gtk::GdkVisual *v, int output_bits) { - uint32 c = CsNone; + uint32_t c = CsNone; if (v) { EndianTest Test; Test.b[0] = 1; Test.b[1] = 0; bool LittleEndian = Test.s == 1; #if VisualToColourSpaceDebug printf("GdkVisualToColourSpace, Type: %i, LittleEndian=%i\n", v->type, LittleEndian); #endif switch (v->type) { default: { LgiAssert(!"impl me"); c = GBitsToColourSpace(v->depth); break; } case Gtk::GDK_VISUAL_PSEUDO_COLOR: case Gtk::GDK_VISUAL_STATIC_COLOR: { LgiAssert(v->depth <= 16); c = (CtIndex << 4) | (v->depth != 16 ? v->depth : 0); break; } case Gtk::GDK_VISUAL_TRUE_COLOR: case Gtk::GDK_VISUAL_DIRECT_COLOR: { int red = (CtRed << 4) | v->red_prec; int green = (CtGreen << 4) | v->green_prec; int blue = (CtBlue << 4) | v->blue_prec; #ifdef __arm__ if ( (v->depth == 16 && v->red_shift < v->blue_shift) || (v->depth != 16 && v->red_shift > v->blue_shift) ) #else if (v->red_shift > v->blue_shift) #endif { c = (red << 16) | (green << 8) | blue; } else { c = (blue << 16) | (green << 8) | red; } int bits = GColourSpaceToBits((GColourSpace) c); #if VisualToColourSpaceDebug printf("GdkVisualToColourSpace, rgb: %i/%i, %i/%i, %i/%i bits: %i output_bits: %i\n", v->red_prec, v->red_shift, v->green_prec, v->green_shift, v->blue_prec, v->blue_shift, bits, output_bits ); #endif if (bits != output_bits) { int remaining_bits = output_bits - bits; LgiAssert(remaining_bits <= 16); if (remaining_bits <= 16) { c |= ( ( (CtAlpha << 4) | (remaining_bits < 16 ? remaining_bits : 0) ) ) << 24; } } break; } } } GColourSpace Cs; if (v->depth != 16) { if (v->byte_order == Gtk::GDK_LSB_FIRST) { #if VisualToColourSpaceDebug printf("GdkVisualToColourSpace swapping\n"); #endif c = LgiSwap32(c); while (!(c & 0xff)) c >>= 8; } } Cs = (GColourSpace)c; #if VisualToColourSpaceDebug printf("GdkVisualToColourSpace %x %s\n", Cs, GColourSpaceToString(Cs)); #endif return Cs; } diff --git a/src/linux/Gtk/LgiWidget.cpp b/src/linux/Gtk/LgiWidget.cpp --- a/src/linux/Gtk/LgiWidget.cpp +++ b/src/linux/Gtk/LgiWidget.cpp @@ -1,959 +1,959 @@ #include "Lgi.h" #include "GDragAndDrop.h" #define DEBUG_KEY_EVENT 0 using namespace Gtk; #include "LgiWidget.h" #include "gdk/gdkkeysyms.h" static void lgi_widget_class_init(LgiWidgetClass *klass); static void lgi_widget_init(LgiWidget *w); static void lgi_widget_forall( GtkContainer *container, gboolean include_internals, GtkCallback callback, gpointer callback_data) { LgiWidget *w = LGI_WIDGET(container); for (int i=0; ichild.Length(); i++) (*callback)(w->child[i].w, callback_data); } static GType lgi_widget_child_type(GtkContainer *container) { return GTK_TYPE_WIDGET; } GtkType lgi_widget_get_type(void) { static GtkType lgi_widget_type = 0; if (!lgi_widget_type) { static const GtkTypeInfo lgi_widget_info = { "LgiWidget", sizeof(LgiWidget), sizeof(LgiWidgetClass), (GtkClassInitFunc) lgi_widget_class_init, (GtkObjectInitFunc) lgi_widget_init, NULL, NULL, (GtkClassInitFunc) NULL }; lgi_widget_type = gtk_type_unique(GTK_TYPE_CONTAINER, &lgi_widget_info); } return lgi_widget_type; } GtkWidget *lgi_widget_new(GViewI *target, int w, int h, bool pour_largest) { LgiWidget *p = LGI_WIDGET(gtk_type_new(lgi_widget_get_type())); if (p) { // printf("Created %p for %s:%p\n", p, target->GetClass(), target); p->target = target; p->w = w; p->h = h; p->pour_largest = pour_largest; g_object_set_data(G_OBJECT(p), "GViewI", target); if (target->GetTabStop()) { #if GtkVer(2, 18) gtk_widget_set_can_focus(GTK_WIDGET(p), TRUE); #else GTK_OBJECT_FLAGS(GTK_WIDGET(p)) |= GTK_CAN_FOCUS; #endif } gtk_widget_add_events(GTK_WIDGET(p), GDK_ALL_EVENTS_MASK); } return GTK_WIDGET(p); } static void lgi_widget_remove(GtkContainer *wid, GtkWidget *child) { LgiWidget *p = LGI_WIDGET(wid); if (p) { for (int i=0; ichild.Length(); i++) { _LgiWidget::ChildInfo &c = p->child[i]; if (c.w == child) { bool widget_was_visible = GTK_WIDGET_VISIBLE(child); LgiWidget *cw = LGI_WIDGET(c.w); // if (cw) // printf("Unparenting %p, %s.%p\n", cw, cw->target->GetClass(), cw->target); gtk_widget_unparent(child); p->child.DeleteAt(i, true); if (widget_was_visible) gtk_widget_queue_resize(GTK_WIDGET(wid)); break; } } } } static gboolean lgi_widget_click(GtkWidget *widget, GdkEventButton *ev) { bool BtnDown = ev->type == GDK_BUTTON_PRESS || ev->type == GDK_2BUTTON_PRESS || ev->type == GDK_3BUTTON_PRESS; LgiWidget *p = LGI_WIDGET(widget); GView *v = dynamic_cast(p->target); if (v) { GMouse m; m.Target = v; m.x = ev->x; m.y = ev->y; m.Double(ev->type == GDK_2BUTTON_PRESS || ev->type == GDK_3BUTTON_PRESS); m.Down( ev->type == GDK_BUTTON_PRESS || ev->type == GDK_2BUTTON_PRESS || ev->type == GDK_3BUTTON_PRESS); m.Left(ev->button == 1); m.Middle(ev->button == 2); m.Right(ev->button == 3); m.Alt((ev->state & GDK_MOD1_MASK) != 0); m.Shift((ev->state & GDK_SHIFT_MASK) != 0); m.Ctrl((ev->state & GDK_CONTROL_MASK) != 0); #if 0 char s[256]; sprintf_s(s, sizeof(s), "%s::MouseClick", v->GetClass()); m.Trace(s); #endif v->_Mouse(m, false); } return TRUE; } static gboolean lgi_widget_motion(GtkWidget *widget, GdkEventMotion *ev) { LgiWidget *p = LGI_WIDGET(widget); GView *v = dynamic_cast(p->target); if (v) { GMouse m; m.Target = v; m.x = ev->x; m.y = ev->y; m.Flags |= LGI_EF_MOVE; m.Down((ev->state & GDK_BUTTON_PRESS_MASK) != 0); m.Left(ev->state & GDK_BUTTON1_MASK); m.Middle(ev->state & GDK_BUTTON2_MASK); m.Right(ev->state & GDK_BUTTON3_MASK); #if 0 char s[256]; sprintf_s(s, sizeof(s), "%s::MouseMove", v->GetClass()); m.Trace(s); #endif v->_Mouse(m, true); } return TRUE; } static gboolean lgi_widget_scroll(GtkWidget *widget, GdkEventScroll *ev) { LgiWidget *p = LGI_WIDGET(widget); GView *v = dynamic_cast(p->target); if (v) { double Lines = ev->direction == GDK_SCROLL_DOWN ? 3 : -3; // LgiTrace("%s::OnMouseWheel %g\n", v->GetClass(), Lines); v->OnMouseWheel(Lines); } return TRUE; } static gboolean lgi_widget_mouse_enter_leave(GtkWidget *widget, GdkEventCrossing *ev) { LgiWidget *p = LGI_WIDGET(widget); GView *v = dynamic_cast(p->target); if (v) { GMouse m; m.Target = v; m.x = ev->x; m.y = ev->y; if (ev->type == GDK_LEAVE_NOTIFY) { // LgiTrace("%s::OnMouseExit %i,%i\n", v->GetClass(), m.x, m.y); v->OnMouseExit(m); } else { // LgiTrace("%s::OnMouseEnter %i,%i\n", v->GetClass(), m.x, m.y); v->OnMouseEnter(m); } } return TRUE; } static gboolean lgi_widget_client_event(GtkWidget *wid, GdkEventClient *ev) { LgiWidget *p = LGI_WIDGET(wid); GView *v = dynamic_cast(p->target); if (v) { GMessage m(ev->data.l[0], ev->data.l[1], ev->data.l[2]); v->OnEvent(&m); } return TRUE; } static gboolean lgi_widget_focus_event(GtkWidget *wid, GdkEventFocus *e) { LgiWidget *p = LGI_WIDGET(wid); GView *v = dynamic_cast(p->target); if (v) v->OnFocus(e->in); else LgiAssert(0); return TRUE; } void BuildTabStops(GViewI *v, ::GArray &a) { if (v->Enabled() && v->Visible() && v->GetTabStop()) a.Add(v); GAutoPtr it(v->IterateViews()); for (GViewI *c = it->First(); c; c = it->Next()) { if (c->Enabled() && c->Visible()) BuildTabStops(c, a); } } static gboolean lgi_widget_key_event(GtkWidget *wid, GdkEventKey *e) { #if 0 // This is useful for debugging... if (e->keyval == GDK_Shift_L || e->keyval == GDK_Shift_R || e->keyval == GDK_Control_L || e->keyval == GDK_Control_R || e->keyval == GDK_Alt_L || e->keyval == GDK_Alt_R) { return TRUE; } #endif LgiWidget *p = LGI_WIDGET(wid); GView *v = dynamic_cast(p->target); if (!v) printf("%s:%i - No target??\n", _FL); else { GKey k; k.Down(e->type == GDK_KEY_PRESS); k.c16 = k.vkey = e->keyval; k.Shift((e->state & 1) != 0); k.Ctrl((e->state & 4) != 0); k.Alt((e->state & 8) != 0); // k.IsChar = !k.Ctrl() && (k.c16 >= ' ' && k.c16 <= 0x7f); k.IsChar = !k.Ctrl() && !k.Alt() && (k.c16 >= ' ') && (k.c16 >> 8 != 0xff); if (e->keyval > 0xff && e->string != NULL) { // Convert string to unicode char - uint8 *i = e->string; + uint8_t *i = e->string; ptrdiff_t len = strlen(i); k.c16 = LgiUtf8To32(i, len); } switch (k.vkey) { case GDK_ISO_Left_Tab: case GDK_Tab: k.IsChar = true; k.c16 = k.vkey = VK_TAB; break; case GDK_Return: case GDK_KP_Enter: k.IsChar = true; k.c16 = k.vkey = VK_RETURN; break; case GDK_BackSpace: k.c16 = k.vkey = VK_BACKSPACE; k.IsChar = !k.Ctrl() && !k.Alt(); break; case GDK_Left: k.vkey = k.c16 = VK_LEFT; break; case GDK_Right: k.vkey = k.c16 = VK_RIGHT; break; case GDK_Up: k.vkey = k.c16 = VK_UP; break; case GDK_Down: k.vkey = k.c16 = VK_DOWN; break; case GDK_Home: k.vkey = k.c16 = VK_HOME; break; case GDK_End: k.vkey = k.c16 = VK_END; break; #define KeyPadMap(gdksym, ch, is) \ case gdksym: k.c16 = ch; k.IsChar = is; break; KeyPadMap(GDK_KP_0, '0', true) KeyPadMap(GDK_KP_1, '1', true) KeyPadMap(GDK_KP_2, '2', true) KeyPadMap(GDK_KP_3, '3', true) KeyPadMap(GDK_KP_4, '4', true) KeyPadMap(GDK_KP_5, '5', true) KeyPadMap(GDK_KP_6, '6', true) KeyPadMap(GDK_KP_7, '7', true) KeyPadMap(GDK_KP_8, '8', true) KeyPadMap(GDK_KP_9, '9', true) KeyPadMap(GDK_KP_Space, ' ', true) KeyPadMap(GDK_KP_Tab, '\t', true) KeyPadMap(GDK_KP_F1, VK_F1, false) KeyPadMap(GDK_KP_F2, VK_F2, false) KeyPadMap(GDK_KP_F3, VK_F3, false) KeyPadMap(GDK_KP_F4, VK_F4, false) KeyPadMap(GDK_KP_Home, VK_HOME, false) KeyPadMap(GDK_KP_Left, VK_LEFT, false) KeyPadMap(GDK_KP_Up, VK_UP, false) KeyPadMap(GDK_KP_Right, VK_RIGHT, false) KeyPadMap(GDK_KP_Down, VK_DOWN, false) KeyPadMap(GDK_KP_Page_Up, VK_PAGEUP, false) KeyPadMap(GDK_KP_Page_Down, VK_PAGEDOWN, false) KeyPadMap(GDK_KP_End, VK_END, false) KeyPadMap(GDK_KP_Begin, VK_HOME, false) KeyPadMap(GDK_KP_Insert, VK_INSERT, false) KeyPadMap(GDK_KP_Delete, VK_DELETE, false) KeyPadMap(GDK_KP_Equal, '=', true) KeyPadMap(GDK_KP_Multiply, '*', true) KeyPadMap(GDK_KP_Add, '+', true) KeyPadMap(GDK_KP_Separator, '|', true) // is this right? KeyPadMap(GDK_KP_Subtract, '-', true) KeyPadMap(GDK_KP_Decimal, '.', true) KeyPadMap(GDK_KP_Divide, '/', true) } #if DEBUG_KEY_EVENT k.Trace("lgi_widget_key_event"); #endif GWindow *w = v->GetWindow(); if (w) { if (!w->HandleViewKey(v, k) && (k.vkey == VK_TAB || k.vkey == GDK_ISO_Left_Tab) && k.Down()) { // Do tab between controls ::GArray a; BuildTabStops(w, a); int idx = a.IndexOf((GViewI*)v); if (idx >= 0) { idx += k.Shift() ? -1 : 1; int next_idx = idx == 0 ? a.Length() -1 : idx % a.Length(); GViewI *next = a[next_idx]; if (next) { // LgiTrace("Setting focus to %i of %i: %s, %s, %i\n", next_idx, a.Length(), next->GetClass(), next->GetPos().GetStr(), next->GetId()); next->Focus(true); } } } } else v->OnKey(k); } return true; } static void lgi_widget_drag_begin(GtkWidget *widget, GdkDragContext *context) { LgiTrace("lgi_widget_drag_begin\n"); } static void lgi_widget_drag_end(GtkWidget *widget, GdkDragContext *drag_context) { LgiTrace("lgi_widget_drag_end\n"); } static void lgi_widget_drag_data_get(GtkWidget *widget, GdkDragContext *context, GtkSelectionData *selection_data, guint info, guint time_) { LgiTrace("lgi_widget_drag_data_get\n"); } static void lgi_widget_drag_data_delete(GtkWidget *widget, GdkDragContext *context) { LgiTrace("lgi_widget_drag_data_delete\n"); } static void lgi_widget_drag_leave(GtkWidget *widget, GdkDragContext *context, guint time) { LgiWidget *v = LGI_WIDGET(widget); if (!v || !v->target) { // printf("%s:%i - LGI_WIDGET failed.\n", _FL); return; } GDragDropTarget *Target = v->target->DropTarget(); if (!Target) { // printf("%s:%i - View '%s' doesn't have drop target.\n", _FL, v->target->GetClass()); return; } v->drag_over_widget = false; Target->OnDragExit(); } static gboolean lgi_widget_drag_motion(GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time_) { LgiWidget *v = LGI_WIDGET(widget); if (!v || !v->target) { printf("%s:%i - LGI_WIDGET failed.\n", _FL); return false; } GViewI *view = v->target; #if DEBUG_DND printf("%s:%i - DragMotion %s\n", _FL, view->GetClass()); #endif GDragDropTarget *Target = view->DropTarget(); while (view && !Target) { view = view->GetParent(); if (!view) break; Target = view->DropTarget(); #if DEBUG_DND printf("\t%s = %p\n", view->GetClass(), Target); #endif } if (!Target) { #if DEBUG_DND printf("%s:%i - View '%s' doesn't have drop target.\n", _FL, v->target->GetClass()); #endif return false; } #if DEBUG_DND // printf("%s:%i - DragMotion(%s): ", _FL, v->target->GetClass()); #endif List Formats; for (Gtk::GList *Types = gdk_drag_context_list_targets(context); Types; Types = Types->next) { gchar *Type = gdk_atom_name((GdkAtom)Types->data); if (Type) { Formats.Insert(NewStr(Type)); #if DEBUG_DND // printf("%s, ", Type); #endif } } #if DEBUG_DND // printf("\n"); #endif if (!v->drag_over_widget) { v->drag_over_widget = true; Target->OnDragEnter(); } GdcPt2 p(x, y); int Result = Target->WillAccept(Formats, p, 0); Formats.DeleteArrays(); if (Result != DROPEFFECT_NONE) { GdkDragAction action = DropEffectToAction(Result); gdk_drag_status(context, action, time_); } return Result != DROPEFFECT_NONE; } static gboolean lgi_widget_drag_drop(GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time_) { LgiWidget *v = LGI_WIDGET(widget); if (!v || !v->target) { printf("%s:%i - LGI_WIDGET failed.\n", _FL); return false; } GViewI *view = v->target; GDragDropTarget *Target = view->DropTarget(); while (view && !Target) { view = view->GetParent(); if (view) Target = view->DropTarget(); } if (!Target) { #if DEBUG_DND printf("%s:%i - View '%s' doesn't have drop target.\n", _FL, v->target->GetClass()); #endif return false; } // Convert the GTK list of formats to our own List List Formats; for (Gtk::GList *Types = gdk_drag_context_list_targets(context); Types; Types = Types->next) { gchar *Type = gdk_atom_name((GdkAtom)Types->data); if (Type) Formats.Insert(NewStr(Type)); } // Select a format from the supplied types GdcPt2 p(x, y); int Result = Target->WillAccept(Formats, p, 0); if (Result == DROPEFFECT_NONE) return false; char *drop_format = Formats.First(); Formats.Delete(drop_format); Formats.DeleteArrays(); #if DEBUG_DND LgiTrace("lgi_widget_drag_drop, fmt=%s\n", drop_format); #endif // Request the data... gtk_drag_get_data ( widget, context, gdk_atom_intern(drop_format, false), time_ ); DeleteArray(drop_format); return true; } static void lgi_widget_drag_data_received( GtkWidget *widget, GdkDragContext *context, gint x, gint y, GtkSelectionData *data, guint info, guint time) { // LgiTrace("lgi_widget_drag_data_received\n"); LgiWidget *v = LGI_WIDGET(widget); if (!v || !v->target) { // printf("%s:%i - LGI_WIDGET failed.\n", _FL); return; } GViewI *view = v->target; GDragDropTarget *Target = view->DropTarget(); while (view && !Target) { view = view->GetParent(); if (view) Target = view->DropTarget(); } if (!Target) { #if DEBUG_DND printf("%s:%i - View '%s' doesn't have drop target.\n", _FL, v->target->GetClass()); #endif return; } gchar *Type = gdk_atom_name(gtk_selection_data_get_data_type(data)); #if DEBUG_DND printf("%s:%i - Type=%s, Target=%s\n", _FL, Type, v->target->GetClass()); #endif GdcPt2 p(x, y); gint Len = gtk_selection_data_get_length(data); #if DEBUG_DND printf("%s:%i - Len=%i\n", _FL, Len); #endif const guchar *Ptr = gtk_selection_data_get_data(data); #if DEBUG_DND printf("%s:%i - Ptr=%p\n", _FL, Ptr); #endif if (!Ptr || Len <= 0) { #if DEBUG_DND printf("%s:%i - gtk_selection_data_get_[data/len] failed.\n", _FL); #endif return; } ::GArray dd; dd[0].Format = Type; dd[0].Data[0].SetBinary(Len, (void*)Ptr); // Give the data to the App Target->OnDrop(dd, p, 0); } static void lgi_widget_destroy(GtkObject *object) { g_return_if_fail(object != NULL); g_return_if_fail(LGI_IS_WIDGET(object)); LgiWidget *p = LGI_WIDGET(object); void *klass = gtk_type_class(gtk_widget_get_type()); if (GTK_OBJECT_CLASS(klass)->destroy) { (* GTK_OBJECT_CLASS(klass)->destroy)(object); } } static void lgi_widget_size_allocate(GtkWidget *widget, GtkAllocation *allocation) { g_return_if_fail(widget != NULL); g_return_if_fail(LGI_IS_WIDGET(widget)); g_return_if_fail(allocation != NULL); widget->allocation = *allocation; if (GTK_WIDGET_REALIZED(widget)) { gdk_window_move_resize( widget->window, allocation->x, allocation->y, allocation->width, allocation->height); LgiWidget *w = LGI_WIDGET(widget); GtkAllocation child_allocation; GtkRequisition child_requisition; guint16 border_width = GTK_CONTAINER(w)->border_width; for (int i=0; ichild.Length(); i++) { _LgiWidget::ChildInfo &c = w->child[i]; if (GTK_WIDGET_VISIBLE(c.w)) { gtk_widget_size_request(c.w, &child_requisition); child_allocation.x = c.x + border_width; child_allocation.y = c.y + border_width; if (GTK_WIDGET_NO_WINDOW(widget)) { child_allocation.x += widget->allocation.x; child_allocation.y += widget->allocation.y; } child_allocation.width = MAX(child_requisition.width, 1); child_allocation.height = MAX(child_requisition.height, 1); gtk_widget_size_allocate(c.w, &child_allocation); } } } } static void lgi_widget_realize(GtkWidget *widget) { GdkWindowAttr attributes; guint attributes_mask; g_return_if_fail(widget != NULL); g_return_if_fail(LGI_IS_WIDGET(widget)); GTK_WIDGET_SET_FLAGS(widget, GTK_REALIZED); LgiWidget *w = LGI_WIDGET(widget); attributes.window_type = GDK_WINDOW_CHILD; attributes.x = widget->allocation.x; attributes.y = widget->allocation.y; attributes.width = w->w; attributes.height = w->h; attributes.wclass = GDK_INPUT_OUTPUT; attributes.event_mask = gtk_widget_get_events(widget) | GDK_EXPOSURE_MASK; attributes_mask = GDK_WA_X | GDK_WA_Y; widget->window = gdk_window_new(gtk_widget_get_parent_window(widget), &attributes, attributes_mask); gdk_window_set_user_data(widget->window, widget); widget->style = gtk_style_attach(widget->style, widget->window); gtk_style_set_background(widget->style, widget->window, GTK_STATE_NORMAL); lgi_widget_size_allocate(widget, &widget->allocation); GView *v = dynamic_cast(w->target); if (v) v->OnGtkRealize(); else LgiTrace("%s:%i - Failed to cast target to GView.\n", _FL); } static gboolean lgi_widget_expose(GtkWidget *widget, GdkEventExpose *event) { g_return_val_if_fail(widget != NULL, FALSE); g_return_val_if_fail(LGI_IS_WIDGET(widget), FALSE); g_return_val_if_fail(event != NULL, FALSE); LgiWidget *p = LGI_WIDGET(widget); if (GTK_WIDGET_DRAWABLE(widget)) { if (p && p->target) { GScreenDC Dc(p->target->GetGView()); GView *v = dynamic_cast(p->target); if (v) v->_Paint(&Dc); else p->target->OnPaint(&Dc); } else printf("%s:%i - No view to paint widget.\n", _FL); } return FALSE; } void lgi_widget_setsize(GtkWidget *wid, int width, int height) { LgiWidget *p = LGI_WIDGET(wid); if (p) { if (p->w != width || p->h != height) { p->w = width; p->h = height; wid->requisition.width = width; wid->requisition.height = height; } } } void lgi_widget_setchildpos(GtkWidget *parent, GtkWidget *child, int x, int y) { if (!LGI_IS_WIDGET(parent)) { LgiAssert(0); // should this ever happen? return; } LgiWidget *p = LGI_WIDGET(parent); if (p) { for (int i=0; ichild.Length(); i++) { _LgiWidget::ChildInfo &c = p->child[i]; if (c.w == child) { c.x = x; c.y = y; if (GTK_WIDGET_REALIZED(c.w)) { LgiWidget *child_wid = LGI_WIDGET(c.w); GtkAllocation a; a.x = c.x; a.y = c.y; a.width = MAX(1, child_wid->w); a.height = MAX(1, child_wid->h); gtk_widget_size_allocate(c.w, &a); gdk_window_invalidate_rect( #if GtkVer(2, 14) gtk_widget_get_window(c.w) #else c.w->window #endif , &a, FALSE); } return; } } } } void lgi_widget_add(GtkContainer *wid, GtkWidget *child) { LgiWidget *p = LGI_WIDGET(wid); if (p) { for (int i=0; ichild.Length(); i++) { _LgiWidget::ChildInfo &c = p->child[i]; if (c.w == child) { printf("%s:%i - Already a child.\n", _FL); return; } } _LgiWidget::ChildInfo &c = p->child.New(); c.w = child; c.x = 0; c.y = 0; gtk_widget_set_parent(child, GTK_WIDGET(wid)); } } static void lgi_widget_size_request(GtkWidget *widget, GtkRequisition *requisition) { g_return_if_fail(widget != NULL); g_return_if_fail(LGI_IS_WIDGET(widget)); g_return_if_fail(requisition != NULL); LgiWidget *p = LGI_WIDGET(widget); if (p->pour_largest) { requisition->width = 10; requisition->height = 10; } else { requisition->width = p->w; requisition->height = p->h; } // LgiTrace("%s::req %i,%i\n", p->target->GetClass(), requisition->width, requisition->height); } gboolean lgi_widget_configure(GtkWidget *widget, GdkEventConfigure *ev) { LgiWidget *p = LGI_WIDGET(widget); printf("lgi_widget_configure %p\n", p); if (p) { p->target->OnPosChange(); } return TRUE; } static void lgi_widget_class_init(LgiWidgetClass *klass) { GtkObjectClass *object_class = (GtkObjectClass *)klass; object_class->destroy = lgi_widget_destroy; GtkWidgetClass *widget_class = (GtkWidgetClass *)klass; widget_class->realize = lgi_widget_realize; // widget_class->configure_event = lgi_widget_configure; widget_class->size_request = lgi_widget_size_request; widget_class->size_allocate = lgi_widget_size_allocate; widget_class->expose_event = lgi_widget_expose; widget_class->button_press_event = lgi_widget_click; widget_class->button_release_event = lgi_widget_click; widget_class->motion_notify_event = lgi_widget_motion; widget_class->scroll_event = lgi_widget_scroll; widget_class->enter_notify_event = lgi_widget_mouse_enter_leave; widget_class->leave_notify_event = lgi_widget_mouse_enter_leave; widget_class->client_event = lgi_widget_client_event; widget_class->focus_in_event = lgi_widget_focus_event; widget_class->focus_out_event = lgi_widget_focus_event; widget_class->key_press_event = lgi_widget_key_event; widget_class->key_release_event = lgi_widget_key_event; widget_class->drag_begin = lgi_widget_drag_begin; widget_class->drag_end = lgi_widget_drag_end; widget_class->drag_data_get = lgi_widget_drag_data_get; widget_class->drag_data_delete = lgi_widget_drag_data_delete; widget_class->drag_leave = lgi_widget_drag_leave; widget_class->drag_motion = lgi_widget_drag_motion; widget_class->drag_drop = lgi_widget_drag_drop; widget_class->drag_data_received = lgi_widget_drag_data_received; GtkContainerClass *container_class = (GtkContainerClass*)klass; container_class->add = lgi_widget_add; container_class->remove = lgi_widget_remove; container_class->forall = lgi_widget_forall; container_class->child_type = lgi_widget_child_type; } static void lgi_widget_init(LgiWidget *w) { GTK_WIDGET_UNSET_FLAGS(w, GTK_NO_WINDOW); w->target = 0; w->w = 0; w->h = 0; w->pour_largest = false; w->drag_over_widget = false; w->drop_format = NULL; w->debug = false; } diff --git a/src/linux/Lgi/GClipBoard.cpp b/src/linux/Lgi/GClipBoard.cpp --- a/src/linux/Lgi/GClipBoard.cpp +++ b/src/linux/Lgi/GClipBoard.cpp @@ -1,317 +1,317 @@ // Clipboard Implementation #include "Lgi.h" #include "GVariant.h" #include "GClipBoard.h" #define DEBUG_CLIPBOARD 0 #define VAR_COUNT 16 #define LGI_CLIP_BINARY "lgi.binary" #define LGI_RECEIVE_CLIPBOARD_TIMEOUT 4000 using namespace Gtk; struct ClipData : public LMutex { ::GVariant v[VAR_COUNT]; ClipData() : LMutex("ClipData") { } } Data; class GClipBoardPriv { public: GtkClipboard *c; }; /////////////////////////////////////////////////////////////////////////////////////////////// GClipBoard::GClipBoard(GView *o) { d = new GClipBoardPriv; Owner = o; Open = false; pDC = 0; d->c = gtk_clipboard_get(GDK_NONE); // gdk_atom_intern("CLIPBOARD", false) if (d->c) Open = true; #if DEBUG_CLIPBOARD printf("d->c = %i\n", d->c); #endif } GClipBoard::~GClipBoard() { d->c = 0; DeleteObj(d); } bool GClipBoard::Empty() { if (d->c) { gtk_clipboard_clear(d->c); #if DEBUG_CLIPBOARD printf("gtk_clipboard_clear(%i)\n", d->c); #endif return true; } return false; } bool GClipBoard::EnumFormats(::GArray &Formats) { return false; } bool GClipBoard::Html(const char *doc, bool AutoEmpty) { return false; } ::GString GClipBoard::Html() { return ::GString(); } bool GClipBoard::Text(char *Str, bool AutoEmpty) { bool Status = false; if (AutoEmpty) { Empty(); } if (Str && d->c) { gtk_clipboard_set_text(d->c, Str, strlen(Str)); #if DEBUG_CLIPBOARD printf("gtk_clipboard_set_text(%i,%s,%i)\n", d->c, Str, strlen(Str)); #endif Status = true; } return Status; } char *GClipBoard::Text() { char *t = 0; if (d->c) { gchar *txt = gtk_clipboard_wait_for_text(d->c); #if DEBUG_CLIPBOARD printf("gtk_clipboard_wait_for_text(%i)='%s'\n", d->c, txt); #endif if (txt) { t = NewStr(txt); g_free(txt); } } return t; } bool GClipBoard::TextW(char16 *Str, bool AutoEmpty) { GAutoString u(WideToUtf8(Str)); return Text(u, AutoEmpty); } char16 *GClipBoard::TextW() { GAutoString u(Text()); return Utf8ToWide(u); } bool GClipBoard::Bitmap(GSurface *pDC, bool AutoEmpty) { bool Status = false; if (pDC && d->c) { GMemDC *Mem = dynamic_cast(pDC); if (Mem) { /* GdkPixbuf *pb = gdk_pixbuf_new_from_data ( const guchar *data, GDK_COLORSPACE_RGB, gboolean has_alpha, int bits_per_sample, int width, int height, int rowstride, GdkPixbufDestroyNotify destroy_fn, gpointer destroy_fn_data); // gtk_clipboard_set_image(d->c, pb); */ } } return Status; } GSurface *GClipBoard::Bitmap() { return pDC; } void LgiClipboardGetFunc(GtkClipboard *clipboard, GtkSelectionData *data, guint info, gpointer user_data) { if (Data.Lock(_FL)) { ::GVariant *p = (::GVariant*)user_data; #if DEBUG_CLIPBOARD printf("%s:%i - LgiClipboardGetFunc: %p, %i\n", _FL, p, info); #endif switch (info) { case GV_BINARY: { if (p->Type == info) { data->data = p->Value.Binary.Data; data->length = p->Value.Binary.Length; } else LgiTrace("%s:%i - Variant is the wrong type: %i\n", _FL, p->Type); break; } default: { LgiTrace("%s:%i - Undefined data type: %i\n", _FL, info); break; } } Data.Unlock(); } } void LgiClipboardClearFunc(GtkClipboard *clipboard, gpointer user_data) { if (Data.Lock(_FL)) { ::GVariant *p = (::GVariant*)user_data; #if DEBUG_CLIPBOARD printf("%s:%i - LgiClipboardClearFunc: %i\n", _FL, p->Type); #endif p->Empty(); Data.Unlock(); } } bool GClipBoard::Binary(FormatType Format, uchar *Ptr, ssize_t Len, bool AutoEmpty) { if (!Ptr || Len <= 0) return false; ::GVariant *p = NULL; if (Data.Lock(_FL)) { for (int i=0; iSetBinary(Len, Ptr); break; } } Data.Unlock(); } GtkTargetEntry te; te.target = LGI_CLIP_BINARY; te.flags = 0; // GTK_TARGET_SAME_APP? te.info = GV_BINARY; // App defined data type ID Gtk::gboolean r = gtk_clipboard_set_with_data(d->c, &te, 1, LgiClipboardGetFunc, LgiClipboardClearFunc, p); #if DEBUG_CLIPBOARD printf("%s:%i - gtk_clipboard_set_with_data = %i\n", _FL, r); #endif return r; } ::GString::Array GClipBoard::Files() { ::GString::Array a; return a; } bool GClipBoard::Files(::GString::Array &a, bool AutoEmpty) { return false; } struct ReceiveData { - GAutoPtr *Ptr; + GAutoPtr *Ptr; ssize_t *Len; }; void LgiClipboardReceivedFunc(GtkClipboard *clipboard, GtkSelectionData *data, gpointer user_data) { ReceiveData *r = (ReceiveData*) user_data; if (data && r) { - uint8 *d = new uint8[data->length]; + uint8_t *d = new uint8_t[data->length]; if (d) { memcpy(d, data->data, data->length); if (r->Len) *r->Len = data->length; r->Ptr->Reset(d); #if DEBUG_CLIPBOARD printf("%s:%i - LgiClipboardReceivedFunc\n", _FL); #endif } else LgiTrace("%s:%i - Alloc failed %i\n", _FL, data->length); } else LgiTrace("%s:%i - Missing ptr: %p %p\n", _FL, data, r); } -bool GClipBoard::Binary(FormatType Format, GAutoPtr &Ptr, ssize_t *Len) +bool GClipBoard::Binary(FormatType Format, GAutoPtr &Ptr, ssize_t *Len) { ReceiveData r = {&Ptr, Len}; gtk_clipboard_request_contents( d->c, gdk_atom_intern(LGI_CLIP_BINARY, false), LgiClipboardReceivedFunc, &r); uint64 Start = LgiCurrentTime(); do { if (r.Ptr->Get()) break; LgiYield(); LgiSleep(1); } while (LgiCurrentTime() - Start > LGI_RECEIVE_CLIPBOARD_TIMEOUT); #if DEBUG_CLIPBOARD printf("%s:%i - GClipBoard::Binary %p, %i\n", _FL, r.Ptr->Get(), Len ? *Len : -1); #endif return r.Ptr->Get() != NULL; } diff --git a/src/linux/Lgi/GGeneral.cpp b/src/linux/Lgi/GGeneral.cpp --- a/src/linux/Lgi/GGeneral.cpp +++ b/src/linux/Lgi/GGeneral.cpp @@ -1,653 +1,653 @@ // Linux Implementation of General LGI functions #include #include #include #include #include #define _POSIX_TIMERS #include #include "Lgi.h" #include "GProcess.h" #include "GTextLabel.h" #include "GButton.h" #ifndef LGI_SDL #include "LgiWinManGlue.h" #endif #include "GToken.h" #include #include #define DEBUG_GET_APPS_FOR_MIMETYPE 0 //////////////////////////////////////////////////////////////// // Local helper functions bool _lgi_check_file(char *Path) { if (Path) { if (FileExists(Path)) { // file is there return true; } else { // shortcut? char *e = Path + strlen(Path); strcpy(e, ".lnk"); if (FileExists(Path)) { // resolve shortcut char Link[256]; if (ResolveShortcut(Path, Link, sizeof(Link))) { // check destination of link if (FileExists(Link)) { strcpy(Path, Link); return true; } } } *e = 0; } } return false; } GString LCurrentUserName() { struct passwd *pw = getpwuid(geteuid()); if (pw) return pw->pw_name; return ""; } -void LgiSleep(uint32 i) +void LgiSleep(uint32_t i) { struct timespec request, remain; ZeroObj(request); ZeroObj(remain); request.tv_sec = i / 1000; request.tv_nsec = (i % 1000) * 1000000; //printf("%i LgiSleep(%i)\n", LgiGetCurrentThread(), i); while (nanosleep(&request, &remain) == -1) { request = remain; //printf("\t%i Resleeping=%i\n", LgiGetCurrentThread(), request.tv_sec*1000 + request.tv_nsec/1000); } } int GtkAssertDlg(const char *File, int Line, const char *Msg) { Gtk::GtkWidget *dialog = Gtk::gtk_message_dialog_new ( NULL, Gtk::GTK_DIALOG_DESTROY_WITH_PARENT, Gtk::GTK_MESSAGE_ERROR, Gtk::GTK_BUTTONS_NONE, "%s:%i - Assert failed:\n%s", File, Line, Msg ); Gtk::GtkDialog *dlg = GtkCast(dialog, gtk_dialog, GtkDialog); Gtk::gtk_dialog_add_buttons(dlg, "Break", 1, "Quit", 2, "Ignore", 3, NULL); int Result = Gtk::gtk_dialog_run(dlg); Gtk::gtk_widget_destroy(dialog); return Result; } void _lgi_assert(bool b, const char *test, const char *file, int line) { static bool Asserting = false; if (!b && !Asserting) { Asserting = true; printf("%s:%i - Assert failed:\n%s\n", file, line, test); #ifdef LGI_SDL exit(-1); #else Gtk::gint Result = Gtk::GTK_RESPONSE_NO; #if 1 if (LgiApp->InThread()) Result = GtkAssertDlg(file, line, test); else Result = 1; #endif switch (Result) { case 1: { int *i = NULL; *i = 0; break; } case 2: { exit(-1); break; } } #endif Asserting = false; } } //////////////////////////////////////////////////////////////////////// // Implementations GMessage CreateMsg(int m, int a, int b) { static GMessage Msg(0); Msg.Set(m, a, b); return Msg; } bool LgiGetMimeTypeExtensions(const char *Mime, GArray &Ext) { int Start = Ext.Length(); char *e; #define HardCodeExtention(Mime, Ext1, Ext2) \ else if (!stricmp(Mime, Mime)) \ { if (Ext1) Ext.Add(Ext1); \ if (Ext2) Ext.Add(Ext2); } if (!Mime); HardCodeExtention("text/calendar", "ics", 0) HardCodeExtention("text/x-vcard", "vcf", 0) HardCodeExtention("text/mbox", "mbx", "mbox"); return Ext.Length() > Start; } GString LGetFileMimeType(const char *File) { GAutoString s = LgiApp->GetFileMimeType(File); return GString(s.Get()); } bool _GetSystemFont(char *FontType, char *Font, int FontBufSize, int &PointSize) { bool Status = false; #ifndef LGI_SDL GLibrary *WmLib = LgiApp->GetWindowManagerLib(); if (WmLib) { Proc_LgiWmGetSysFont GetSysFont = (Proc_LgiWmGetSysFont) WmLib->GetAddress("LgiWmGetSysFont"); if (GetSysFont) { Status = GetSysFont(FontType, Font, FontBufSize, PointSize); if (!Status) { printf("%s:%i - GetSysFont failed\n", _FL); } } else { printf("%s:%i - Entry point doesn't exist\n", _FL); } } else #endif { static bool Warn = true; if (Warn) { printf("%s:%i - GetWindowManagerLib failed\n", _FL); Warn = false; } } return Status; } bool LgiGetAppsForMimeType(const char *Mime, GArray &Apps, int Limit) { bool Status = false; char Args[MAX_PATH]; sprintf(Args, "query default %s", Mime); GProcess p; GStringPipe Output; GLanguage *CurLang = LgiGetLanguageId(); char LangName[64]; sprintf_s(LangName, sizeof(LangName), "Name[%s]", CurLang ? CurLang->Id : "en"); #if DEBUG_GET_APPS_FOR_MIMETYPE printf("LgiGetAppsForMimeType('%s', ..., %i)\nRunning 'xdg-mime %s'\n", Mime, Limit, Args); #endif if (p.Run("xdg-mime", Args, 0, true, 0, &Output)) { GAutoString o(Output.NewStr()); #if DEBUG_GET_APPS_FOR_MIMETYPE printf("Output:\n%s\n", o.Get()); #endif if (o) { char *e = o + strlen(o); while (e > o && strchr(" \t\r\n", e[-1])) *(--e) = 0; char p[MAX_PATH]; if (LgiMakePath(p, sizeof(p), "/usr/share/applications", o)) { if (FileExists(p)) { GAutoString txt(ReadTextFile(p)); GAutoString Section; #if DEBUG_GET_APPS_FOR_MIMETYPE printf("Reading '%s', got %i bytes\n", p, strlen(txt)); #endif if (txt) { GAppInfo *ai = new GAppInfo; Apps.Add(ai); GToken t(txt, "\n"); for (int i=0; iPath.Reset(NewStr(exe, sp-exe)); else ai->Path = exe; Status = true; } else if (!stricmp(Var, "Name") || !stricmp(Var, LangName)) { ai->Name.Reset(TrimStr(Value)); } } } } #if DEBUG_GET_APPS_FOR_MIMETYPE printf(" ai='%s' '%s'\n", ai->Name.Get(), ai->Path.Get()); #endif } else LgiTrace("%s:%i - Can't read from '%s'\n", _FL, p); } else LgiTrace("%s:%i - '%s' doesn't exist.", _FL, p); } else LgiTrace("%s:%i - Failed to create path.\n", _FL); } else LgiTrace("%s:%i - No output from xdg-mime\n", _FL); } else LgiTrace("%s:%i - Failed to execute xdg-mime\n", _FL); return Status; } GString LGetAppForMimeType(const char *Mime) { GString App; GArray Apps; if (LgiGetAppsForMimeType(Mime, Apps, 1)) App = Apps[0]->Path.Get(); Apps.DeleteObjects(); return App; } int LgiRand(int Limit) { return rand() % Limit; } -GAutoString LgiErrorCodeToString(uint32 ErrorCode) +GAutoString LgiErrorCodeToString(uint32_t ErrorCode) { GAutoString e; char *s = strerror(ErrorCode); if (s) { e.Reset(NewStr(s)); } else { char buf[256]; sprintf_s(buf, sizeof(buf), "UnknownError(%i)", ErrorCode); e.Reset(NewStr(buf)); } return e; } bool LgiExecute(const char *File, const char *Args, const char *Dir, GAutoString *Error) { if (File) { bool IsUrl = false; char App[MAX_PATH] = ""; if (strnicmp(File, "http://", 7) == 0 || strnicmp(File, "https://", 8) == 0) { IsUrl = true; LGetAppForMimeType("text/html", App, sizeof(App)); } else { struct stat f; char Path[MAX_PATH]; // see if the file is executable bool InPath = false; bool Ok = stat(File, &f) == 0; if (Ok) { strcpy_s(Path, sizeof(Path), File); } else { // look in the path InPath = true; GToken p(getenv("PATH"), LGI_PATH_SEPARATOR); for (int i=0; iReset(NewStr(m)); } } if (App[0]) { bool FileAdded = false; GString AppPath; GString EscFile = GString::Escape(File, -1, " &"); GString a = App; int Pos; while ((Pos = a.Find("%")) >= 0) { char *s = a.Get() + Pos; printf("a='%s'\n", a.Get()); switch (s[1]) { case 'f': case 'F': { a = a(0,Pos) + EscFile + a(Pos+2,-1); FileAdded = true; break; } case 'u': case 'U': { if (IsUrl) a = a(0,Pos) + EscFile + a(Pos+2,-1); else a = a(0,Pos) + GString("file:") + EscFile + a(Pos+2,-1); FileAdded = true; break; } default: { // we don't understand this command a = a(0,Pos) + a(Pos+2,-1); break; } } printf("a='%s'\n", a.Get()); } if (!FileAdded) { a += " "; a += EscFile; } a += " > /dev/null 2> /dev/null &"; int e; if (Dir) chdir(Dir); if (e = system(a)) { if (Error) *Error = LgiErrorCodeToString(errno); return false; } return true; } } return false; } ////////////////////////////////////////////////////////////////////////// WindowManager LgiGetWindowManager() { static WindowManager Status = WM_Unknown; if (Status == WM_Unknown) { GDirectory d; for (bool b=d.First("/proc"); b && Status == WM_Unknown; b=d.Next()) { if (d.IsDir() && isdigit(d.GetName()[0])) { char Path[256]; d.Path(Path, sizeof(Path)); LgiMakePath(Path, sizeof(Path), Path, "status"); GFile s; if (s.Open(Path, O_READ)) { char Buf[256]; Buf[sizeof(Buf)-1] = 0; s.Read(Buf, sizeof(Buf)-1); char *n = strchr(Buf, '\n'); if (n) { *n = 0; if (stristr(Buf, "gnome-settings") != 0 || stristr(Buf, "gnome-session") != 0 || stristr(Buf, "gnome-panel") != 0) { Status = WM_Gnome; } else if (stristr(Buf, "startkde") != 0 || stristr(Buf, "kdesktop") != 0) { Status = WM_Kde; } } } else printf("%s:%i - error\n", __FILE__, __LINE__); } } } return Status; } void LgiFinishXWindowsStartup(GViewI *Wnd) { // Get the startup ID const char *EnvStartId = "DESKTOP_STARTUP_ID"; char *DesktopStartupId = getenv(EnvStartId); if (ValidStr(DesktopStartupId)) { GStringPipe oss; // Create remove string oss.Push("remove: ID="); // Quote the id string for (char *c = DesktopStartupId; *c; c++) { if (*c == ' ' || *c == '"' || *c == '\\') { oss.Write((char*)"\\", 1); } oss.Write(c, 1); } char *Str = oss.NewStr(); // Get the window and display /* XWidget *View = Wnd->Handle(); if (!View) return; Display *display = View->XDisplay(); Window xroot_window = DefaultRootWindow(display); XSetWindowAttributes attrs; attrs.override_redirect = True; attrs.event_mask = PropertyChangeMask | StructureNotifyMask; // Get the atoms Atom type_atom = XInternAtom(display, "_NET_STARTUP_INFO", false); Atom type_atom_begin = XInternAtom(display, "_NET_STARTUP_INFO_BEGIN", false); // Create the event we will send XEvent xevent; xevent.xclient.type = ClientMessage; xevent.xclient.message_type = type_atom_begin; xevent.xclient.display = display; xevent.xclient.window = View->handle(); xevent.xclient.format = 8; const char* src = Str; const char* src_end = src + strlen(Str) + 1; // Include trailing NUL. // Loop over the string and send it. while (src != src_end) { char* dest = &xevent.xclient.data.b[0]; char* dest_end = dest + 20; while (dest != dest_end && src != src_end) { *dest++ = *src++; } while (dest != dest_end) { *dest++ = 0; } printf("%s:%i - XSendEvent\n", __FILE__, __LINE__); XSendEvent(display, xroot_window, False, PropertyChangeMask, &xevent); xevent.xclient.message_type = type_atom; } // Clear the event ID so it's not inherited by child processes. unsetenv(EnvStartId); */ } } #if HAS_GSTREAMER // sudo apt-get install libgstreamer1.0-dev using namespace Gtk; #include #endif bool LgiPlaySound(const char *FileName, int ASync) { #if HAS_GSTREAMER #else return LgiExecute(FileName); #endif } diff --git a/src/linux/Lgi/GView.cpp b/src/linux/Lgi/GView.cpp --- a/src/linux/Lgi/GView.cpp +++ b/src/linux/Lgi/GView.cpp @@ -1,1041 +1,1041 @@ /*hdr ** FILE: GView.cpp ** AUTHOR: Matthew Allen ** DATE: 23/4/98 ** DESCRIPTION: Linux GView Implementation ** ** Copyright (C) 1998-2003, Matthew Allen ** fret@memecode.com */ #include #include #include "Lgi.h" #include "GDragAndDrop.h" #include "GEdit.h" #include "GViewPriv.h" #include "GPopup.h" #include "GCss.h" using namespace Gtk; #include "LgiWidget.h" #define DEBUG_MOUSE_EVENTS 0 #define ADJ_LEFT 1 #define ADJ_RIGHT 2 #define ADJ_UP 3 #define ADJ_DOWN 4 #if GtkVer(2, 14) #else #define gtk_widget_get_window(widget) ((widget)->window) #endif struct CursorInfo { public: GRect Pos; GdcPt2 HotSpot; } CursorMetrics[] = { // up arrow { GRect(0, 0, 8, 15), GdcPt2(4, 0) }, // cross hair { GRect(20, 0, 38, 18), GdcPt2(29, 9) }, // hourglass { GRect(40, 0, 51, 15), GdcPt2(45, 8) }, // I beam { GRect(60, 0, 66, 17), GdcPt2(63, 8) }, // N-S arrow { GRect(80, 0, 91, 16), GdcPt2(85, 8) }, // E-W arrow { GRect(100, 0, 116, 11), GdcPt2(108, 5) }, // NW-SE arrow { GRect(120, 0, 132, 12), GdcPt2(126, 6) }, // NE-SW arrow { GRect(140, 0, 152, 12), GdcPt2(146, 6) }, // 4 way arrow { GRect(160, 0, 178, 18), GdcPt2(169, 9) }, // Blank { GRect(0, 0, 0, 0), GdcPt2(0, 0) }, // Vertical split { GRect(180, 0, 197, 16), GdcPt2(188, 8) }, // Horizontal split { GRect(200, 0, 216, 17), GdcPt2(208, 8) }, // Hand { GRect(220, 0, 233, 13), GdcPt2(225, 0) }, // No drop { GRect(240, 0, 258, 18), GdcPt2(249, 9) }, // Copy drop { GRect(260, 0, 279, 19), GdcPt2(260, 0) }, // Move drop { GRect(280, 0, 299, 19), GdcPt2(280, 0) }, }; // CursorData is a bitmap in an array of uint32's. This is generated from a graphics file: // ./Code/cursors.png // // The pixel values are turned into C code by a program called i.Mage: // http://www.memecode.com/image.php // // Load the graphic into i.Mage and then go Edit->CopyAsCode // Then paste the text into the CursorData variable at the bottom of this file. // // This saves a lot of time finding and loading an external resouce, and even having to // bundle extra files with your application. Which have a tendancy to get lost along the // way etc. -extern uint32 CursorData[]; +extern uint32_t CursorData[]; GInlineBmp Cursors = { 300, 20, 8, CursorData }; //////////////////////////////////////////////////////////////////////////// void _lgi_yield() { LgiApp->Run(false); } bool LgiIsKeyDown(int Key) { LgiAssert(0); return false; } GKey::GKey(int vkey, int flags) { } //////////////////////////////////////////////////////////////////////////////////////////////////// GViewPrivate::GViewPrivate() { Parent = 0; ParentI = 0; Notify = 0; CtrlId = -1; DropTarget = 0; Font = 0; FontOwnType = GV_FontPtr; Popup = 0; TabStop = 0; Pulse = 0; InPaint = false; GotOnCreate = false; WantsFocus = false; SinkHnd = -1; } GViewPrivate::~GViewPrivate() { LgiAssert(Pulse == 0); } void GView::OnGtkRealize() { // printf("%s::OnGtkRealize %i\n", GetClass(), d->GotOnCreate); if (!d->GotOnCreate) { d->GotOnCreate = true; if (d->WantsFocus && _View) { d->WantsFocus = false; gtk_widget_grab_focus(_View); } OnCreate(); } } void GView::_Focus(bool f) { ThreadCheck(); if (f) SetFlag(WndFlags, GWF_FOCUS); else ClearFlag(WndFlags, GWF_FOCUS); OnFocus(f); Invalidate(); if (f) { if (_View) { // printf("%s:%i - grabbing focus on %s.%p\n", _FL, GetClass(), _View); gtk_widget_grab_focus(_View); } else d->WantsFocus = f; } } void GView::_Delete() { ThreadCheck(); SetPulse(); // Remove static references to myself if (_Over == this) _Over = 0; if (_Capturing == this) _Capturing = 0; GWindow *Wnd = GetWindow(); if (Wnd && Wnd->GetFocus() == static_cast(this)) Wnd->SetFocus(this, GWindow::ViewDelete); if (LgiApp && LgiApp->AppWnd == this) { LgiApp->AppWnd = 0; } // Misc Pos.ZOff(-1, -1); // Heirarchy GViewI *c; while (c = Children.First()) { if (c->GetParent() != (GViewI*)this) { printf("%s:%i - ~GView error, %s not attached correctly: %p(%s) Parent: %p(%s)\n", _FL, c->GetClass(), c, c->Name(), c->GetParent(), c->GetParent() ? c->GetParent()->Name() : ""); Children.Delete(c); } DeleteObj(c); } Detach(); LgiAssert(_View == NULL); } GView *&GView::PopupChild() { return d->Popup; } void LgiToGtkCursor(GViewI *v, LgiCursor c) { static LgiCursor CurrentCursor = LCUR_Normal; if (!v || c == CurrentCursor) return; CurrentCursor = c; GdkCursorType type = GDK_ARROW; switch (c) { // No cursor #ifdef GDK_BLANK_CURSOR case LCUR_Blank: type = GDK_BLANK_CURSOR; break; #endif /// Normal arrow case LCUR_Normal: type = GDK_ARROW; break; /// Upwards arrow case LCUR_UpArrow: type = GDK_SB_UP_ARROW; break; /// Downwards arrow case LCUR_DownArrow: type = GDK_SB_DOWN_ARROW; break; /// Left arrow case LCUR_LeftArrow: type = GDK_SB_LEFT_ARROW; break; /// Right arrow case LCUR_RightArrow: type = GDK_SB_RIGHT_ARROW; break; /// Crosshair case LCUR_Cross: type = GDK_CROSSHAIR; break; /// Hourglass/watch case LCUR_Wait: type = GDK_WATCH; break; /// Ibeam/text entry case LCUR_Ibeam: type = GDK_XTERM; break; /// Vertical resize (|) case LCUR_SizeVer: type = GDK_DOUBLE_ARROW; break; /// Horizontal resize (-) case LCUR_SizeHor: type = GDK_SB_H_DOUBLE_ARROW; break; /// Diagonal resize (/) case LCUR_SizeBDiag: type = GDK_BOTTOM_LEFT_CORNER; break; /// Diagonal resize (\) case LCUR_SizeFDiag: type = GDK_BOTTOM_RIGHT_CORNER; break; /// All directions resize case LCUR_SizeAll: type = GDK_FLEUR; break; /// A pointing hand case LCUR_PointingHand: type = GDK_HAND2; break; /// A slashed circle case LCUR_Forbidden: type = GDK_X_CURSOR; break; default: type = GDK_ARROW; break; /* case LCUR_SplitV: case LCUR_SplitH: case LCUR_DropCopy: case LCUR_DropMove: LgiAssert(0); break; */ } GWindow *Wnd = v->GetWindow(); OsView h = Wnd ? Wnd->Handle() : v->Handle(); LgiAssert(v->InThread()); LgiAssert(h->window); if (type == GDK_ARROW) { gdk_window_set_cursor(h->window, NULL); // printf("gdk_window_set_cursor(%s, NULL)\n", v->GetClass()); } else { GdkCursor *cursor = gdk_cursor_new_for_display(gdk_display_get_default(), type); if (cursor) { gdk_window_set_cursor(h->window, cursor); // printf("gdk_window_set_cursor(%s, cursor)\n", v->GetClass()); } else { gdk_window_set_cursor(h->window, NULL); // printf("gdk_window_set_cursor(%s, gdk_cursor_new_for_display fail)\n", v->GetClass()); } } } bool GView::_Mouse(GMouse &m, bool Move) { ThreadCheck(); #if 0 if (!Move) { m.Trace("_Mouse"); ::GArray _m; for (GViewI *i=this; i; i=i->GetParent()) { _m.Add(i); } for (int n=0; n<_m.Length(); n++) { GViewI *i=_m[_m.Length()-1-n]; char s[256]; ZeroObj(s); memset(s, ' ', (n+1)*2); LgiTrace("%s%s %s\n", s, i->GetClass(), i->GetPos().GetStr()); } } #endif #if DEBUG_MOUSE_EVENTS LgiTrace("%s:%i - _Mouse([%i,%i], %i)\n", _FL, m.x, m.y, Move); #endif if ( !_View || ( GetWindow() && !GetWindow()->HandleViewMouse(this, m) ) ) { #if DEBUG_MOUSE_EVENTS LgiTrace("%s:%i - HandleViewMouse consumed event, _View=%p\n", _FL, _View); #endif return false; } GViewI *cap = _Capturing; #if DEBUG_MOUSE_EVENTS LgiTrace("%s:%i - _Capturing=%p/%s\n", _FL, _Capturing, _Capturing ? _Capturing->GetClass() : NULL); #endif if (_Capturing) { if (Move) { GMouse Local = lgi_adjust_click(m, _Capturing); LgiToGtkCursor(_Capturing, _Capturing->GetCursor(Local.x, Local.y)); #if DEBUG_MOUSE_EVENTS LgiTrace("%s:%i - Local=%i,%i\n", _FL, Local.x, Local.y); #endif _Capturing->OnMouseMove(Local); // This can set _Capturing to NULL } else { _Capturing->OnMouseClick(lgi_adjust_click(m, _Capturing)); } } else { if (Move) { bool Change = false; GViewI *o = WindowFromPoint(m.x, m.y); if (_Over != o) { #if DEBUG_MOUSE_EVENTS LgiTrace("%s:%i - _Over changing from %p/%s to %p/%s\n", _FL, _Over, _Over ? _Over->GetClass() : NULL, o, o ? o->GetClass() : NULL); #endif if (_Over) _Over->OnMouseExit(lgi_adjust_click(m, _Over)); _Over = o; if (_Over) _Over->OnMouseEnter(lgi_adjust_click(m, _Over)); } } GView *Target = dynamic_cast(_Over ? _Over : this); GRect Client = Target->GView::GetClient(false); m = lgi_adjust_click(m, Target, !Move); if (!Client.Valid() || Client.Overlap(m.x, m.y)) { LgiToGtkCursor(Target, Target->GetCursor(m.x, m.y)); if (Move) { Target->OnMouseMove(m); } else { #if 0 if (!Move) { char Msg[256]; sprintf(Msg, "_Mouse Target %s", Target->GetClass()); m.Trace(Msg); } #endif Target->OnMouseClick(m); } } else if (!Move) { #if DEBUG_MOUSE_EVENTS LgiTrace("%s:%i - Click outside %s %s %i,%i\n", _FL, Target->GetClass(), Client.GetStr(), m.x, m.y); #endif } } return true; } gboolean GtkViewCallback(GtkWidget *widget, GdkEvent *event, GView *This) { #if 0 printf("GtkViewCallback, widget=%p, event=%p, event=%x, This=%p(%s\"%s\")\n", widget, event, event->type, This, (NativeInt)This > 0x1000 ? This->GetClass() : 0, (NativeInt)This > 0x1000 ? This->Name() : 0); #endif if (event->type < 0 || event->type > 1000) { printf("%s:%i - CORRUPT EVENT %i\n", _FL, event->type); return false; } return This->OnGtkEvent(widget, event); } gboolean GView::OnGtkEvent(GtkWidget *widget, GdkEvent *event) { printf("GView::OnGtkEvent ?????\n"); return false; } GRect &GView::GetClient(bool ClientSpace) { int Edge = (Sunken() || Raised()) ? _BorderSize : 0; static GRect c; if (ClientSpace) { c.ZOff(Pos.X() - 1 - (Edge<<1), Pos.Y() - 1 - (Edge<<1)); } else { c.ZOff(Pos.X()-1, Pos.Y()-1); c.Size(Edge, Edge); } return c; } void GView::Quit(bool DontDelete) { ThreadCheck(); if (DontDelete) { Visible(false); } else { delete this; } } bool GView::SetPos(GRect &p, bool Repaint) { ThreadCheck(); Pos = p; if (_View) { int o = 0; GView *Par = d->GetParent(); if (Par && (Par->Sunken() || Par->Raised())) { o = Par->_BorderSize; } GtkWidget *GtkPar; if (GTK_IS_WINDOW(_View)) { gtk_window_move(GTK_WINDOW(_View), Pos.x1, Pos.y1); gtk_window_resize(GTK_WINDOW(_View), Pos.X(), Pos.Y()); } else if (GtkPar = gtk_widget_get_parent(_View)) { if (LGI_IS_WIDGET(GtkPar)) { lgi_widget_setsize(_View, Pos.X(), Pos.Y()); lgi_widget_setchildpos( GtkPar, _View, Pos.x1 + o, Pos.y1 + o); } else { // LgiTrace("%s:%i - Error: Can't set object position, parent is: %s\n", _FL, G_OBJECT_TYPE_NAME(GtkPar)); } } } OnPosChange(); return true; } bool GView::Invalidate(GRect *r, bool Repaint, bool Frame) { if (IsAttached()) { if (InThread() && !d->InPaint) { GRect Client; if (Frame) Client.ZOff(Pos.X()-1, Pos.Y()-1); else Client = GView::GetClient(false); static bool Repainting = false; if (!Repainting) { Repainting = true; GdkWindow *hnd = gtk_widget_get_window(_View); if (hnd) { if (r) { GRect cr = *r; cr.Offset(Client.x1, Client.y1); Gtk::GdkRectangle gr = cr; gdk_window_invalidate_rect(hnd, &gr, FALSE); } else { Gtk::GdkRectangle r = {0, 0, Pos.X(), Pos.Y()}; gdk_window_invalidate_rect(hnd, &r, FALSE); } } Repainting = false; } } else { PostEvent( M_INVALIDATE, (GMessage::Param)(r ? new GRect(r) : NULL), (GMessage::Param)(GView*)this); } return true; } else { GRect Up; GViewI *p = this; if (r) { Up = *r; } else { Up.Set(0, 0, Pos.X()-1, Pos.Y()-1); } while (p && !p->IsAttached()) { GRect pos = p->GetPos(); Up.Offset(pos.x1, pos.y1); p = p->GetParent(); } if (p && p->IsAttached()) { return p->Invalidate(&Up, Repaint); } } return false; } void GView::SetPulse(int Length) { ThreadCheck(); if (d->Pulse) { DeleteObj(d->Pulse); } if (Length > 0) { d->Pulse = new GPulseThread(this, Length); } } GMessage::Param GView::OnEvent(GMessage *Msg) { ThreadCheck(); int Id; switch (Id = Msg->Msg()) { case M_INVALIDATE: { if ((GView*)this == (GView*)Msg->B()) { GAutoPtr r((GRect*)Msg->A()); Invalidate(r); } break; } case M_PULSE: { OnPulse(); break; } case M_CHANGE: { GViewI *Ctrl; if (GetViewById(MsgA(Msg), Ctrl)) return OnNotify(Ctrl, MsgB(Msg)); break; } case M_COMMAND: { return OnCommand(MsgA(Msg), 0, (OsView) MsgB(Msg)); } } return 0; } void GView::PointToScreen(GdcPt2 &p) { ThreadCheck(); GViewI *c = this; // Find real parent while (c->GetParent() && !dynamic_cast(c)) { GRect n = c->GetPos(); p.x += n.x1; p.y += n.y1; c = c->GetParent(); } if (c && c->WindowHandle()) { gint x = 0, y = 0; // GdkRectangle rect; Gtk::GtkWindow *wnd = c->WindowHandle(); Gtk::GtkWidget *w = GTK_WIDGET(wnd); // Gtk::GdkWindow *gdk_wnd = gtk_widget_get_window(w); // gdk_window_get_frame_extents(gdk_wnd, &rect); gdk_window_get_origin(w->window, &x, &y); // int DecorX = x - rect.x; // int DecorY = y - rect.y; // printf("%s:%i - rect=%i,%i-%i,%i origin=%i,%i\n", _FL, rect.x, rect.y, rect.width, rect.height, x, y); p.x += x; p.y += y; } else { printf("%s:%i - No real view to map to screen.\n", _FL); } } void GView::PointToView(GdcPt2 &p) { ThreadCheck(); if (_View) { gint x = 0, y = 0; gdk_window_get_origin(GetWindow()->Handle()->window, &x, &y); p.x -= x; p.y -= y; GViewI *w = GetWindow(); for (GViewI *i = this; i && i != w; i = i->GetParent()) { GRect pos = i->GetPos(); const char *cls = i->GetClass(); p.x -= pos.x1; p.y -= pos.y1; } GRect cli = GetClient(false); p.x -= cli.x1; p.y -= cli.y1; } else if (GetParent()) { // Virtual window int Sx = 0, Sy = 0; GViewI *v; // Work out the virtual offset for (v = this; v && !v->Handle(); v = v->GetParent()) { Sx += v->GetPos().x1; Sy += v->GetPos().y1; } if (v && v->Handle()) { // Get the point relative to the first real parent v->PointToView(p); // Move point back into virtual space p.x -= Sx; p.y -= Sy; } else LgiTrace("%s:%i - No Real view for %s\n", _FL, GetClass()); } } bool GView::GetMouse(GMouse &m, bool ScreenCoords) { ThreadCheck(); if (_View) { gint x = 0, y = 0; GdkModifierType mask; GdkScreen *wnd_scr = gtk_window_get_screen(GTK_WINDOW(WindowHandle())); GdkDisplay *wnd_dsp = wnd_scr ? gdk_screen_get_display(wnd_scr) : NULL; gdk_display_get_pointer(wnd_dsp, &wnd_scr, &x, &y, &mask); if (!ScreenCoords) { GdcPt2 p(x, y); PointToView(p); m.x = p.x; m.y = p.y; } else { m.x = x; m.y = y; } m.SetModifer(mask); m.Left((mask & GDK_BUTTON1_MASK) != 0); m.Middle((mask & GDK_BUTTON2_MASK) != 0); m.Right((mask & GDK_BUTTON3_MASK) != 0); return true; } else if (GetParent()) { bool s = GetParent()->GetMouse(m, ScreenCoords); if (s) { if (!ScreenCoords) { m.x -= Pos.x1; m.y -= Pos.y1; } return true; } } return false; } bool GView::IsAttached() { return _View && _View->parent; } const char *GView::GetClass() { return "GView"; } bool GView::Attach(GViewI *parent) { ThreadCheck(); bool Status = false; SetParent(parent); GView *Parent = d->GetParent(); _Window = Parent ? Parent->_Window : this; if (!_View) { _View = lgi_widget_new(this, Pos.X(), Pos.Y(), false); } if (_View) { int o = 0; { GView *Par = d->GetParent(); if (Par && (Par->Sunken() || Par->Raised())) { o = Par->_BorderSize; } } if (parent) { GtkWidget *p = parent->Handle(); GWindow *w; if (w = dynamic_cast(parent)) p = w->_Root; if (p && gtk_widget_get_parent(_View) != p) { lgi_widget_add(GTK_CONTAINER(p), _View); lgi_widget_setchildpos(p, _View, Pos.x1 + o, Pos.y1 + o); // printf("%s:%i - Attach %s @ %i,%i - %i,%i\n", _FL, GetClass(), Pos.x1 + o, Pos.y1 + o, Pos.X(), Pos.Y()); } } if (Visible()) { gtk_widget_show(_View); } if (DropTarget()) { DropTarget(true); } if (TestFlag(WndFlags, GWF_FOCUS)) { // LgiTrace("OnCreate Focus %s\n", GetClass()); gtk_widget_grab_focus(_View); } OnAttach(); Status = true; } if (d->Parent && !d->Parent->HasView(this)) { if (!d->Parent->HasView(this)) d->Parent->AddView(this); d->Parent->OnChildrenChanged(this, true); } return Status; } bool GView::Detach() { ThreadCheck(); // Detach view if (_Window) { GWindow *Wnd = dynamic_cast(_Window); if (Wnd) Wnd->SetFocus(this, GWindow::ViewDelete); _Window = NULL; } GViewI *Par = GetParent(); if (Par) { // Events Par->DelView(this); Par->OnChildrenChanged(this, false); } d->Parent = 0; d->ParentI = 0; { int Count = Children.Length(); if (Count) { int Detached = 0; GViewI *c, *prev = NULL; while (c = Children.First()) { LgiAssert(!prev || c != prev); if (c->GetParent()) c->Detach(); else Children.Delete(c); Detached++; prev = c; } LgiAssert(Count == Detached); } } if (_View) { LgiAssert(_View->object.parent_instance.g_type_instance.g_class); LgiApp->OnDetach(this); gtk_widget_destroy(_View); _View = 0; } return true; } GViewI *GView::FindControl(OsView hCtrl) { ThreadCheck(); if (Handle() == hCtrl) { return this; } List::I it = Children.begin(); for (GViewI *c = *it; c; c = *++it) { GViewI *Ctrl = c->FindControl(hCtrl); if (Ctrl) { return Ctrl; } } return 0; } LgiCursor GView::GetCursor(int x, int y) { return LCUR_Normal; } void GView::OnGtkDelete() { _View = NULL; List::I it = Children.begin(); for (GViewI *c = *it; c; c = *++it) { GView *v = c->GetGView(); if (v) v->OnGtkDelete(); } } /////////////////////////////////////////////////////////////////// bool LgiIsMounted(char *Name) { return false; } bool LgiMountVolume(char *Name) { return false; } //////////////////////////////// -uint32 CursorData[] = { +uint32_t CursorData[] = { 0x02020202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020102, 0x02020202, 0x02020202, 0x01010101, 0x01010101, 0x01010101, 0x02020202, 0x02020202, 0x02010101, 0x02010101, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010102, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020102, 0x01020202, 0x02020202, 0x02020202, 0x02020202, 0x01010101, 0x01010101, 0x02020201, 0x02020202, 0x01010101, 0x01010101, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020102, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x02010101, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02010102, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x01010101, 0x02020202, 0x02020202, 0x02020101, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020101, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x01000000, 0x02020202, 0x02020202, 0x01000001, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020101, 0x01020202, 0x02020201, 0x02020202, 0x02020202, 0x00000102, 0x00000000, 0x02020201, 0x02020202, 0x00000001, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x00010102, 0x00000000, 0x02020101, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x00010001, 0x00010001, 0x01000001, 0x02020202, 0x02020202, 0x00010102, 0x02020101, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x00010202, 0x00000000, 0x02020201, 0x02020202, 0x00000001, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x02010000, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x01000102, 0x00000100, 0x02010000, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x00000000, 0x02020100, 0x02020202, 0x02020202, 0x00010202, 0x02020100, 0x01020202, 0x02010000, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x02020201, 0x02020202, 0x00000001, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x01020202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x00000102, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x00010101, 0x01000000, 0x02020202, 0x00000001, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x00010202, 0x00000001, 0x02020100, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x00000102, 0x00000000, 0x02010000, 0x02020202, 0x02020202, 0x00000102, 0x01010100, 0x01010101, 0x01000000, 0x02020202, 0x02020201, 0x00010202, 0x00000000, 0x02020201, 0x02020202, 0x00000001, 0x02010000, 0x02020202, 0x02020201, 0x02020202, 0x02020202, 0x01010102, 0x01010001, 0x02020101, 0x02020202, 0x02020202, 0x01020201, 0x02010000, 0x02020102, 0x02020202, 0x02020202, 0x01010101, 0x01010100, 0x02020201, 0x02020202, 0x02020202, 0x01000001, 0x02020101, 0x02020202, 0x02020202, 0x00010202, 0x01010000, 0x01020202, 0x00000001, 0x02020201, 0x00000001, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x00000102, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x01010101, 0x01000001, 0x01010101, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x02020201, 0x02020101, 0x00000102, 0x00000100, 0x02020201, 0x02020202, 0x01000001, 0x01000000, 0x01020202, 0x02020201, 0x02020202, 0x02020202, 0x02020201, 0x02010001, 0x02010202, 0x02020202, 0x01020202, 0x01020201, 0x02010000, 0x02010102, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x01010000, 0x02020101, 0x02020202, 0x00000102, 0x01000000, 0x02020202, 0x00000102, 0x02020100, 0x00000001, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x00000102, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x02020201, 0x02010001, 0x00000001, 0x00010201, 0x02020201, 0x02020202, 0x02010001, 0x00000001, 0x00010201, 0x02020201, 0x02020202, 0x01020202, 0x02020201, 0x02010001, 0x01010202, 0x02020202, 0x00010202, 0x01020201, 0x02010000, 0x01000102, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02010102, 0x00000001, 0x00000000, 0x02010000, 0x02020202, 0x00000102, 0x00000001, 0x02020201, 0x00010202, 0x02020100, 0x00000001, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x00000102, 0x01010100, 0x01010101, 0x01000000, 0x02020202, 0x01000001, 0x01000000, 0x01020202, 0x02020201, 0x02020202, 0x02020101, 0x00000102, 0x00000100, 0x02020201, 0x02020202, 0x00010202, 0x02020201, 0x02010001, 0x00010202, 0x02020201, 0x00000102, 0x01010101, 0x01010000, 0x00000101, 0x02020201, 0x01010101, 0x01010101, 0x01010100, 0x01010101, 0x02020201, 0x01000001, 0x00000001, 0x00000000, 0x02010000, 0x02020202, 0x00000001, 0x00000101, 0x02020100, 0x00010202, 0x02010000, 0x00000001, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x01010102, 0x01010101, 0x01010001, 0x01010101, 0x02020101, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x02020100, 0x01020202, 0x02010000, 0x02020202, 0x00000001, 0x02010000, 0x02020202, 0x02020201, 0x02020202, 0x02020201, 0x00010202, 0x00000000, 0x02020201, 0x02020202, 0x00000102, 0x01010101, 0x01010001, 0x00010101, 0x02020100, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x02020100, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x02020201, 0x00000001, 0x00000000, 0x00000000, 0x02010000, 0x02020202, 0x01000001, 0x00010202, 0x02010000, 0x01020202, 0x02010000, 0x00000001, 0x00000000, 0x02020100, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x02020100, 0x02020202, 0x02020202, 0x01010101, 0x01010100, 0x02020201, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x02010000, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x01020202, 0x02020100, 0x02020202, 0x00000001, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x02020201, 0x02020202, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x02010000, 0x00000102, 0x01010101, 0x01010000, 0x00000101, 0x02020201, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x02020201, 0x00000102, 0x00000000, 0x00000000, 0x02010000, 0x02020202, 0x01000001, 0x01020202, 0x01000000, 0x01020202, 0x02010000, 0x00000001, 0x00000000, 0x02010000, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x02010000, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x01010102, 0x01010101, 0x01010001, 0x01010101, 0x02020101, 0x01020202, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020101, 0x01020202, 0x02020201, 0x02020202, 0x00000001, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x00000000, 0x02020201, 0x02020202, 0x00000102, 0x01010101, 0x01010001, 0x00010101, 0x02020100, 0x00010202, 0x01020201, 0x02010000, 0x01000102, 0x02020202, 0x01010101, 0x01010101, 0x01010100, 0x01010101, 0x02020201, 0x00010202, 0x00000000, 0x00000000, 0x02010000, 0x02020202, 0x01000001, 0x02020202, 0x00000001, 0x01020201, 0x02010000, 0x00000001, 0x01000000, 0x02010101, 0x02020202, 0x02020202, 0x00000001, 0x01000000, 0x02010101, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x00010202, 0x01000001, 0x02020100, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x01010101, 0x01000001, 0x01010101, 0x02020202, 0x02020202, 0x02020202, 0x02020102, 0x01020202, 0x02020202, 0x02020202, 0x00000001, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x00000102, 0x00000000, 0x02020201, 0x02020202, 0x00010202, 0x02020201, 0x02010001, 0x00010202, 0x02020201, 0x01020202, 0x01020201, 0x02010000, 0x02010102, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x00000000, 0x02010000, 0x02020202, 0x00000001, 0x02020201, 0x00000102, 0x00010100, 0x02010000, 0x00000001, 0x01000001, 0x01020202, 0x01010101, 0x01010101, 0x00000001, 0x01000001, 0x01020202, 0x01010101, 0x01010101, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x01000102, 0x01000001, 0x02010001, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x00000102, 0x00000000, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01010101, 0x01010101, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x01010101, 0x01010101, 0x02020201, 0x02020202, 0x01020202, 0x02020201, 0x02010001, 0x01010202, 0x02020202, 0x02020202, 0x01020201, 0x02010000, 0x02020102, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x02020100, 0x02020202, 0x00000102, 0x02020201, 0x00010202, 0x00010000, 0x02020100, 0x01000001, 0x01000001, 0x01020202, 0x00000000, 0x01000000, 0x01000001, 0x01000001, 0x01020202, 0x00000000, 0x01000000, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x00010001, 0x00000000, 0x01000100, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x00000000, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020201, 0x02010001, 0x02010202, 0x02020202, 0x02020202, 0x01020202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x01010101, 0x01010100, 0x02020201, 0x02020202, 0x02020202, 0x00000102, 0x00000000, 0x02020201, 0x02020202, 0x00000102, 0x02020100, 0x01020202, 0x00000000, 0x02020100, 0x02010001, 0x00000102, 0x01020201, 0x01010000, 0x01000001, 0x02010001, 0x00000102, 0x01020201, 0x01010100, 0x01000001, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x01000000, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01010102, 0x01010001, 0x02020101, 0x02020202, 0x02020202, 0x01020202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x00000102, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x01010202, 0x01010101, 0x02020202, 0x02020202, 0x00010202, 0x01010000, 0x01020202, 0x00000001, 0x02020201, 0x02020101, 0x00000102, 0x01020201, 0x00000100, 0x01000100, 0x02020101, 0x00000102, 0x01020201, 0x01000100, 0x01000100, 0x01020202, 0x02020101, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x01010101, 0x01010101, 0x01010101, 0x02020202, 0x02020202, 0x00010102, 0x02020101, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x01020202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x00010101, 0x01000000, 0x02020202, 0x02020202, 0x00010202, 0x01020100, 0x00000100, 0x01000000, 0x02020202, 0x00010202, 0x01020100, 0x01000100, 0x01000100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010102, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x02010101, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x02010000, 0x02020202, 0x02020202, 0x00010202, 0x01020100, 0x00000100, 0x01000100, 0x02020202, 0x00010202, 0x01020100, 0x01000100, 0x01000100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010101, 0x02010101, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x00010102, 0x00000000, 0x02020101, 0x02020202, 0x02020202, 0x01020202, 0x01020201, 0x01010000, 0x01000001, 0x02020202, 0x01020202, 0x01020201, 0x01000100, 0x01000100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020102, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020102, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x01010101, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x01000000, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x01010101, 0x01010101, 0x02020202, 0x02020202, 0x01020202, 0x01010101, 0x01010101, };