diff --git a/Code/AddressSelect.cpp b/Code/AddressSelect.cpp --- a/Code/AddressSelect.cpp +++ b/Code/AddressSelect.cpp @@ -1,643 +1,643 @@ #include "Scribe.h" #include "lgi/common/Edit.h" #include "resdefs.h" #include "lgi/common/ClipBoard.h" #include "lgi/common/DisplayString.h" #include "ScribeListAddr.h" #include "lgi/common/LgiRes.h" #include "AddressSelect.h" /////////////////////////////////////////////////////////////////////////////////////// static const char *Empty = ""; AddressList::AddressList(ScribeWnd *app, int id, int x, int y, int cx, int cy, const char *name) : LList(id, x, y, cx, cy, name) { App = app; _ObjName = Res_Custom; ColumnHeaders = false; } int AddressList::WillAccept(LDragFormats &Formats, LPoint Pt, int KeyState) { if (Formats.HasFormat(ScribeThingList)) Formats.Supports(ScribeThingList); else Formats.SupportsFileDrops(); return Formats.GetSupported().Length() ? DROPEFFECT_COPY : DROPEFFECT_NONE; } int AddressList::OnDrop(LArray &Data, LPoint Pt, int KeyState) { int Status = DROPEFFECT_NONE; for (unsigned idx=0; idx 0 && dd.Data[0].Type == GV_BINARY && ScribeClipboardFmt::IsThing(dd.Data[0].Value.Binary.Data, dd.Data[0].Value.Binary.Length)) { ScribeClipboardFmt *tl = (ScribeClipboardFmt*)dd.Data[0].Value.Binary.Data; for (uint32_t i=0; iLength(); i++) { Contact *c = tl->ThingAt(i)->IsContact(); if (c) { ListAddr *New = new ListAddr(c); if (New) Insert(New, -1, false); } } Invalidate(); Status = DROPEFFECT_COPY; } } else if (_stricmp(dd.Format, LGI_FileDropFormat) == 0) { } } return DROPEFFECT_NONE; } void AddressList::OnCreate() { LDisplayString ds(LSysFont, "Bcc:"); AddColumn("To", ds.X() + 2); AddColumn("Info", 1000); SetWindow(this); } void AddressList::OnInit(GDataIt l) { Empty(); for (LDataPropI *a = l->First(); a; a = l->Next()) { ListAddr *New = new ListAddr(App, a); if (New) { New->begin(); Insert(New, -1, false); } } Invalidate(); } void AddressList::OnSave(LDataStoreI *store, GDataIt l) { l->DeleteObjects(); List a; GetAll(a); for (auto i: a) { LDataPropI *n = l->Create(store); if (n) { n->CopyProps(*i); l->Insert(n); } } } void AddressList::OnItemClick(LListItem *Item, LMouse &m) { LList::OnItemClick(Item, m); // Do the right click menu if (!Item && m.Right()) { LSubMenu *RClick = new LSubMenu; if (RClick) { RClick->AppendItem(LLoadString(IDS_ADD), IDM_NEW_CONTACT, true); if (GetMouse(m, true)) { switch (RClick->Float(this, m.x, m.y)) { case IDM_NEW_CONTACT: { LViewI *v = GetWindow()->FindControl(IDC_ADD); if (v) { LNotification note(LNotifyValueChanged); GetWindow()->OnNotify(v, note); } break; } } } DeleteObj(RClick); } } } void AddressList::Copy() { List Sel; if (GetSelection(Sel)) { LStringPipe p; for (auto i: Sel) { ListAddr *La = dynamic_cast(i); if (La) { char *s = La->Copy(); if (s) { p.Print("%s\r\n", s); DeleteArray(s); } } } char *t = p.NewStr(); if (t) { LClipBoard Clip(this); Clip.Text(t); char16 *w = Utf8ToWide(t); if (w) { Clip.TextW(w, false); DeleteArray(w); } DeleteArray(t); } } } void AddressList::Paste() { LClipBoard Clip(this); char *Txt = Clip.Text(); if (Txt) { LToken t(Txt, "\r\n"); for (unsigned i=0; iPaste(t[i]); Insert(La); } } } } bool AddressList::OnKey(LKey &k) { bool Status = false; if (k.Down()) { switch (k.vkey) { case LK_DELETE: { if (!k.IsChar) { List Sel; LList::GetSelection(Sel); for (auto i: Sel) { Delete(i); } Status = true; } break; } default: { switch (k.c16) { case 'c': case 'C': { Copy(); Status = true; break; } case 'v': case 'V': { Paste(); Status = true; break; } } break; } } } return LList::OnKey(k) || Status; } //////////////////////////////////////////////////////////////////////////// int AddrBrItemCmp(BrowseItem **a, BrowseItem **b) { if ((*a)->Score != (*b)->Score) { return (*b)->Score - (*a)->Score; } else { char *A = (*a)->First, *B = (*b)->First; if (A && B) { return _stricmp(A, B); } } return 0; } class AddressBrowsePluginResults : public LDom, public LMutex { LArray Results; LAutoString Msg; public: bool Dirty; AddressBrowsePluginResults() : LMutex("AddressBrowsePluginResults") { Dirty = false; } bool CallMethod(const char *MethodName, LVariant *ReturnValue, LArray &Args) { for (unsigned i=0; iType == GV_DOM) { Results.Add(v->Value.Dom); Dirty = true; } else if (v->Type == GV_STRING) { Msg.Reset(v->ReleaseStr()); Dirty = true; } } return true; } } PluginResults; class AddressBrowsePrivate { public: AddressBrowse *Ad; ScribeWnd *App; LEdit *Target; LList *Recip; LViewI *SetTo; LArray Items; LHashTbl, BrowseItem*> Has; ssize_t WordStart, WordEnd; AddressBrowsePrivate(AddressBrowse *ad) { Ad = ad; App = NULL; Target = NULL; Recip = NULL; SetTo = NULL; Recip = NULL; WordStart = WordEnd = 0; } void Test(LArray &Txt, const char *First, const char *Last, LString::Array &Email, const char *Nick) { if (Txt.Length() < 1) return; LAutoString TxtEmail; int i, EmailIdx = -1; for (i=0; i<(int)Txt.Length(); i++) { if (strchr(Txt[i], '@')) { TxtEmail.Reset(TrimStr(Txt[i], "{}[]<>()")); EmailIdx = i; break; } } #define ScoreText(Str, Search, Complete, First, Nth) \ if (Str) { char *s = Search; \ if (s) { \ size_t Len = strlen(s); \ if (_stricmp(Str, s) == 0) Score += Complete; \ if (_strnicmp(Str, s, Len) == 0) Score += First; \ else if (stristr(Str, s)) Score += Nth; \ } } for (i=0; i<(int)Email.Length(); i++) { char *Match = 0; int Score = 0; if (Txt.Length() == 1) { if (EmailIdx != 0) { ScoreText(First, Txt[0], 6, 5, 4); ScoreText(Last, Txt[0], 5, 4, 3); } } else if (Txt.Length() == 2) { if (EmailIdx != 0) ScoreText(First, Txt[0], 6, 5, 4); if (EmailIdx != 1) ScoreText(Last, Txt[1], 6, 5, 4); } // Full email match? if (TxtEmail) { if (stristr(Email[i], TxtEmail)) { Match = Email[i]; Score += 7; } } else { // Partial email match? if (stristr(Email[i], Txt[0])) { Match = Email[i]; Score += 2; } } if (TxtEmail && !Match) { // if they specify an email address and it not // found at all then this can't be the contact // they are looking for return; } Score += stristr(Nick, Txt[0]) ? 1 : 0; if (Score) { char *Addr = Match ? Match : Email[i].Get(); BrowseItem *i; if (!Has.Find(Addr)) { Items.Add(i = new BrowseItem(First, Last, Addr, Score)); if (i) { Has.Add(Addr, i); } } } } } void Update() { Items.DeleteObjects(); Has.Empty(); char *Txt = 0; char *RawTxt = (char*)Target->Name(); if (RawTxt) { ssize_t CharPos = Target->GetCaret(); ssize_t BytePos = LSeekUtf8(RawTxt, CharPos) - RawTxt; Txt = RawTxt + BytePos; if (Txt) { char *s = Txt; // Seek to the start of the name while (Txt > RawTxt && !strchr(MailAddressDelimiters, Txt[-1])) { Txt = LSeekUtf8(Txt, -1, RawTxt); } WordStart = Txt - RawTxt; // Seek to the end of the name Txt = s; while (Txt[0] && !strchr(MailAddressDelimiters, Txt[0])) { Txt = LSeekUtf8(Txt, 1); } WordEnd = Txt - RawTxt; // Store the sub-string Txt = NewStr(RawTxt + WordStart, WordEnd - WordStart); } } // printf("%s:%i - onupdate '%s'\n", _FL, Txt); if (ValidStr(Txt)) { Scan(Txt); // Put in the UI if (Ad) { // printf("Items=%i\n", (int)Items.Length()); Ad->SetItems(Items); } DeleteArray(Txt); } } bool Scan(LString Txt) { if (!App) return false; LToken t(Txt, " "); LHashTbl,Contact*> Contacts; App->HashContacts(Contacts); // Poll the contact sources LArray Srcs = App->GetThingSources(MAGIC_CONTACT); for (auto Src: Srcs) { for (auto t: Src->Items) { Contact *c = t->IsContact(); if (!c) continue; auto emails = c->GetEmails(); for (auto e: emails) if (Contacts.Find(e)) Contacts.Add(e, c); } } for (auto c : Contacts) { const char *First = 0, *Last = 0, *Nick = 0; c.value->Get(OPT_First, First); c.value->Get(OPT_Last, Last); c.value->Get(OPT_Nick, Nick); auto Emails = c.value->GetEmails(); Test(t, First, Last, Emails, Nick); } auto GrpSrcs = App->GetThingSources(MAGIC_GROUP); for (auto Groups: GrpSrcs) { if (!Groups->IsLoaded()) - Groups->LoadThings(); + Groups->LoadThings(); // FIXME... this should refresh the browse list after load for (auto t : Groups->Items) { ContactGroup *Grp = t->IsGroup(); if (Grp) { LVariant Name; LArray Empty; if (Grp->GetVariant("Name", Name) && Name.Str()) { if (stristr(Name.Str(), Txt) && !Has.Find(Name.Str())) { BrowseItem *i; Items.Add(i = new BrowseItem(Name.Str(), 0, Name.Str(), 1)); if (i) { Has.Add(Name.Str(), i); } } } } } } // Sort Items.Sort(AddrBrItemCmp); return true; } }; bool AddressBrowseLookup(ScribeWnd *App, LArray &Items, LString s) { AddressBrowsePrivate d(NULL); d.App = App; if (!d.Scan(s)) return false; d.Items.Swap(Items); return true; } AddressBrowse::AddressBrowse(ScribeWnd *app, LView *target, LList *recip, LViewI *setto) : LPopupList(target, PopupBelow, target->X(), 150) { d = new AddressBrowsePrivate(this); d->App = app; d->Recip = recip; d->SetTo = setto; d->Target = dynamic_cast(target); Name("AddressBrowse"); } AddressBrowse::~AddressBrowse() { DeleteObj(d); } LString AddressBrowse::ToString(BrowseItem *Obj) { LString s; s.Printf("%s %s <%s>", Obj->First ? Obj->First.Get() : Empty, Obj->Last ? Obj->Last.Get() : Empty, Obj->Email.Get()); return s; } void AddressBrowse::OnSelect(BrowseItem *a) { if (a && a->Email) { LStringPipe p; const char *Cur = d->Target->Name(); if (Cur) { p.Push(Cur, d->WordStart); p.Push(Cur + d->WordEnd); } char *New = p.NewStr(); d->Target->Name(New); d->Target->SetCaret(d->WordStart); if (d->Recip) { char FullName[300], *Name = 0; if (a->First && a->Last) sprintf_s(Name = FullName, sizeof(FullName), "%s %s", a->First.Get(), a->Last.Get()); else if (a->First) Name = a->First; else if (a->Last) Name = a->Last; if (Name || a->Email) { ListAddr *n = new ListAddr(d->App, a->Email, Name); if (n) { if (d->SetTo) n->CC = (EmailAddressType) d->SetTo->Value(); d->Recip->Insert(n); } } } Visible(false); DeleteArray(New); } } int AddressBrowse::OnNotify(LViewI *c, LNotification n) { if (c == d->Target && n.Type == LNotifyValueChanged) { d->Update(); } else { // printf("%s:%i - no update %i %i\n", _FL, c == d->Target, !f); } return LPopupList::OnNotify(c, n); } diff --git a/Code/BayesDlg.cpp b/Code/BayesDlg.cpp --- a/Code/BayesDlg.cpp +++ b/Code/BayesDlg.cpp @@ -1,99 +1,107 @@ #include "Scribe.h" #include "resdefs.h" /////////////////////////////////////////////////////////////////////// class BayesDlgPrivate { public: ScribeWnd *App; }; /////////////////////////////////////////////////////////////////////// BayesDlg::BayesDlg(ScribeWnd *app) // : TabDialog(IDC_TABS, IDC_LAUNCH_HELP) { d = new BayesDlgPrivate; d->App = app; SetParent(d->App); Map(OPT_BayesFilterMode, IDC_BAYES_MODE, GV_INT32); Map(OPT_BayesMoveTo, IDC_SUSPECT_FOLDER, GV_STRING); Map(OPT_BayesDeleteAttachments, IDC_BAYES_DELETE_ATTACHMENTS, GV_BOOL); Map(OPT_BayesDeleteOnServer, IDC_BAYES_DELETE_ON_SERVER, GV_BOOL); Map(OPT_BayesUserWhiteList, IDC_WHITELIST, GV_STRING); Map(OPT_BayesThreshold, IDC_BAYES_THRESHOLD, GV_STRING); Map(OPT_BayesIncremental, IDC_BAYES_INCREMENTAL, GV_BOOL); Map(OPT_BayesDebug, IDC_BAYES_DEBUG, GV_BOOL); Map(OPT_BayesHam, IDC_HAM); Map(OPT_BayesSpam, IDC_SPAM); Map(OPT_BayesFalsePositives, IDC_FALSE_POS); Map(OPT_BayesSetRead, IDC_BAYES_READ); Map(OPT_SpamFolder, IDC_SPAM_FOLDER); if (LoadFromResource(IDD_BAYES_SETTINGS)) { MoveToCenter(); Convert(app->GetOptions(), this, true); int Spam = (int) GetCtrlValue(IDC_SPAM); int FalseNeg = (int) GetCtrlValue(IDC_FALSE_NEG); char s[256]; int Total = Spam + FalseNeg; if (Total) { sprintf_s(s, sizeof(s), "%.1f%%", (double)Spam*100/Total); SetCtrlName(IDC_EFFICIENCY, s); } else { SetCtrlName(IDC_EFFICIENCY, "n/a"); } } } BayesDlg::~BayesDlg() { DeleteObj(d); } int BayesDlg::OnNotify(LViewI *c, LNotification n) { switch (c->GetId()) { case IDC_LAUNCH_HELP: { d->App->LaunchHelp("filters.html#bayes"); break; } case IDC_SET_SUSPECT_FOLDER: { - FolderDlg fd(this, d->App, MAGIC_MAIL); - if (fd.DoModal()) - SetCtrlName(IDC_SUSPECT_FOLDER, fd.Get()); + auto fd = new FolderDlg(this, d->App, MAGIC_MAIL); + fd->DoModal([this, fd](auto dlg, auto ctrlId) + { + if (ctrlId) + SetCtrlName(IDC_SUSPECT_FOLDER, fd->Get()); + delete dlg; + }); break; } case IDC_SET_SPAM_FOLDER: { - FolderDlg fd(this, d->App, MAGIC_MAIL); - if (fd.DoModal()) - SetCtrlName(IDC_SPAM_FOLDER, fd.Get()); + auto fd = new FolderDlg(this, d->App, MAGIC_MAIL); + fd->DoModal([this, fd](auto dlg, auto ctrlId) + { + if (ctrlId) + SetCtrlName(IDC_SPAM_FOLDER, fd->Get()); + delete dlg; + }); break; } case IDOK: { Convert(d->App->GetOptions(), this, false); // fall thru } case IDCANCEL: { EndModal(c->GetId() == IDOK); break; } } return 0; } diff --git a/Code/BayesianFilter.cpp b/Code/BayesianFilter.cpp --- a/Code/BayesianFilter.cpp +++ b/Code/BayesianFilter.cpp @@ -1,1860 +1,1854 @@ #include #include "Scribe.h" #include "resdefs.h" #include "BayesianFilter.h" #include "lgi/common/LgiRes.h" #include "lgi/common/SpellCheck.h" #define SECONDS(n) ((n) * 1000) #define TIMEOUT_BAYES_LOAD SECONDS(10) #define TIMEOUT_SPELL_CHECK SECONDS(3) #define TIMEOUT_UPDATE_REBUILD SECONDS(2) #define TIMEOUT_BAYES_IDLE (50) // ms, out of 100ms idle timer. #define WHITELIST_MY_EMAIL 0 #define WHITELIST_CONTACTS 0 #define STORE_SIZE 16 #define WORD_INTEREST_CENTER 0.5 static const char HamWordsFile[] = "hamwords.idx"; static const char SpamWordsFile[] = "spamwords.idx"; static const char WhiteListFile[] = "whitelist.idx"; void ProcessWords(LString Words, std::function Callback) { char *end = Words.Get() + Words.Length(); if (!Callback) { LAssert(0); return; } for (char *s = Words; s < end; ) { char *e = s; while (*e && *e != ' ' && e < end) e++; if (*e == ' ') *e = 0; Callback(s); s = e + 1; } } double WordInterest(double d) { d -= WORD_INTEREST_CENTER; if (d < 0) d *= -1; return d; } class Token { public: LString Word; double Prob = 0.0; double Interest = 0.0; }; class TokenStore { public: constexpr static int StoreSize = STORE_SIZE; int Used; Token *Tok[StoreSize]; TokenStore() { Used = 0; ZeroObj(Tok); } ~TokenStore() { for (int i=0; i 0) return Tok[Used-1]->Interest; return 0; } double Prob(LStream *Log) { int i; if (Log) { Log->Print("%s\n", LLoadString(IDS_SPAM_PROB)); for (i=0; iPrint("\t[%i] '%s'=%f\n", i, Tok[i]->Word.Get(), Tok[i]->Prob); } } // ab //------------------- //ab + (1 - a)(1 - b) if (Used == 0) return 0.5; double top, bottom; top = Tok[0]->Prob; bottom = 1 - Tok[0]->Prob; for (i=1; iProb; bottom *= 1 - Tok[i]->Prob; } double Result = top / (top + bottom); if (Log) Log->Print("\nResult=%f\n", Result); return Result; } bool CanInsert(double i) { return Used < CountOf(Tok) || i >= Min(); } bool Insert(Token *t) { bool Status = false; if (t) { for (int i=0; iWord, t->Word) == 0) { Status = true; DeleteObj(t); goto End; } else if (t->Interest > Tok[i]->Interest) { if (Used >= CountOf(Tok)) { DeleteObj(Tok[Used-1]); memmove(Tok + i + 1, Tok + i, sizeof(Tok[0]) * (Used - i - 1)); } else { memmove(Tok + i + 1, Tok + i, sizeof(Tok[0]) * (Used - i)); Used++; } Tok[i] = t; Status = true; goto End; } } if (Used < CountOf(Tok)) { Tok[Used++] = t; Status = true; goto End; } else { DeleteObj(t); } } End: return Status; } }; class BayesianThread : public LThread, public LMutex, public LEventTargetI, public LMappedEventSink { public: enum ThreadState { BayesLoading, BayesReady, BayesExiting, }; struct Build { /// Empty all the word databases first... otherwise append new words on top of old ones bool ResetDb; LHashTbl,int> WhiteList; int HamEmailCount; LHashTbl,int> HamWords; int SpamEmailCount; LHashTbl,int> SpamWords; Build(bool Reset) : WhiteList(0, -1), HamWords(0, -1), SpamWords(0, -1) { ResetDb = Reset; HamEmailCount = 0; SpamEmailCount = 0; HamWords.SetMaxSize(HamWords.Unlimited); SpamWords.SetMaxSize(SpamWords.Unlimited); } void InsertWhiteList(const char *Word) { int c = WhiteList.Find(Word); WhiteList.Add(Word, c < 0 ? 1 : c + 1); } void InsertHamWords(const char *Word) { int c = HamWords.Find(Word); HamWords.Add(Word, c < 0 ? 1 : c + 1); } void InsertSpamWords(const char *Word) { int c = SpamWords.Find(Word); SpamWords.Add(Word, c < 0 ? 1 : c + 1); } }; /// This is used when the GUI thread requests an email to be /// tested by the Bayesian Classifier. Initially the GUI thread /// fills out this structure and passes it over to the worker /// thread and it then does the calculation and passes it back /// to the GUI thread for actioning. struct Test { bool Analyse = false; LStringPipe Log; LString FromAddr; LString MsgRef; LString Words; double Score = 0.0; bool WhiteListed = false; }; /// This is a change of classification container for /// when the email changes from ham to spam or the reverse. struct Change { LString Words; LString Str; ScribeMailType OldType = BayesMailUnknown; ScribeMailType NewType = BayesMailUnknown; bool RemoveWhite = false; bool IncrementWhite = false; }; private: ScribeWnd *App; LAutoPtr WhiteList; LAutoPtr Ham; LAutoPtr Spam; ThreadState State; LArray< LAutoPtr > Work; LArray< LAutoPtr > Tests; LArray< LAutoPtr > Results; LArray< LAutoPtr > Changes; LAutoPtr Tokens; uint64_t CheckTextTs = 0; LAutoPtr CheckText; void Empty() { WhiteList.Reset(new LWordStore); Spam.Reset(new LWordStore); Ham.Reset(new LWordStore); #define SetListFile(list, file) \ if (!list->GetFile()) \ { \ if (auto s = FindWordDb(file)) \ { \ list->SetFile(s); \ } \ } SetListFile(WhiteList, WhiteListFile); SetListFile(Spam, SpamWordsFile); SetListFile(Ham, HamWordsFile); // empty the word lists WhiteList->Empty(); WhiteList->Serialize(0, true); Spam->Empty(); Spam->Serialize(0, true); Ham->Empty(); Ham->Serialize(0, true); } public: BayesianThread(ScribeWnd *app) : LThread("BayesianThread.Thread"), LMutex ("BayesianThread.Mutex") { App = app; State = BayesLoading; Run(); } ~BayesianThread() { State = BayesExiting; int64 Start = LCurrentTime(); while (!IsExited()) { LSleep(20); if (LCurrentTime()-Start > 5000) { Terminate(); break; } } } LMessage::Result OnEvent(LMessage *Msg) { switch (Msg->Msg()) { case M_CHECK_TEXT: CheckTextTs = LCurrentTime(); CheckText.Reset((LSpellCheck::CheckText*)Msg->A()); break; } return 0; } - bool PostEvent(int Cmd, LMessage::Param a = 0, LMessage::Param b = 0) + bool PostEvent(int Cmd, LMessage::Param a = 0, LMessage::Param b = 0, int64_t TimeoutMs = -1) override { LMessage m(Cmd, a, b); OnEvent(&m); return true; } void Add(LAutoPtr b) { if (Lock(_FL)) { Work.New() = b; Unlock(); } } void Add(LAutoPtr t) { if (Lock(_FL)) { Tests.New() = t; Unlock(); } } void Add(LAutoPtr c) { if (Lock(_FL)) { Changes.New() = c; Unlock(); } } bool GetResults(LArray< LAutoPtr > &results) { if (Lock(_FL)) { results = Results; Unlock(); } Results.Length(0); return results.Length() > 0; } ThreadState GetState() { return State; } void SetStore(LAutoPtr &s, LWordStore *ws) { if (ws && Lock(_FL)) { s.Reset(ws); Unlock(); } } LString FindWordDb(const char *Name) { LString OptPath; auto Opts = App->GetOptions(); // Look in the same folder as the options file: if (Opts && Opts->GetFile()) { LFile::Path p(Opts->GetFile()); p--; OptPath = p.GetFull(); p += Name; if (p.IsFile()) return p.GetFull(); } // Check the install folder too: LFile::Path p(LSP_APP_INSTALL); p += Name; if (p.IsFile()) return p.GetFull(); // No existing file found, so create a path using the options location: p = OptPath; p += Name; return p.GetFull(); } void OnCheckText(LSpellCheck::CheckText *Ct) { auto p = Ct->Errors.Length() ? 0.3 : 0.7; auto i = WordInterest(p); if (Tokens->CanInsert(i)) { Token *t = new Token; if (t) { t->Word = Ct->Text; t->Prob = p; t->Interest = i; Tokens->Insert(t); } } } double IsSpam(Test *t) { // Check the auto white list if (WhiteList) { long Count = WhiteList->GetWordCount(t->FromAddr); if (Count > 0) { // It's from someone we've accepted mail from before if (t->Analyse) t->Log.Print("%s\n", LLoadString(IDS_IS_WHITELIST)); t->WhiteListed = true; return 0.0; } } bool Analyse = t->Analyse; LAutoPtr Spell(App->CreateSpellObject()); Tokens.Reset(new TokenStore); if (!Ham || !Spam) { LgiTrace("%s:%i - No Ham/Spam DB loaded?\n", _FL); return 0.0; } ssize_t HamItems = Ham->Length(); ssize_t SpamItems = Spam->Length(); if (Analyse) LgiTrace("HamItems=" LPrintfSSizeT " SpamItems=" LPrintfSSizeT "\n"); if (Spell) { Spell->Check(GetHandle(), t->Words, 0, t->Words.Length()); auto Start = LCurrentTime(); while (!CheckText) // Wait for the spell check to complete { if (LCurrentTime() - Start > TIMEOUT_SPELL_CHECK) { LgiTrace("%s:%i - Bayesian filter didn't get response from spell check, continuing without.\n", _FL); break; } LSleep(10); } if (CheckText) LgiTrace("%s:%i - SpellCheck took %ims\n", _FL, (int)(LCurrentTime()-CheckTextTs)); } struct Entry { LString word; ssize_t spam = 0, ham = 0; bool spellErr = false; int Compare(Entry *e) { auto i = (spam+ham) - (e->spam+e->ham); if (i < 0) return -1; return i > 0 ? 1 : 0; } }; LArray entries; size_t nextErr = 0; ProcessWords(t->Words, [&](auto w) { Entry &e = entries.New(); e.word = w; e.spam = Spam->GetWordCount(w); e.ham = Ham->GetWordCount(w); size_t Cur = w - t->Words.Get(); // bool hasAt = Strchr(w, '@') != NULL; if (CheckText) { while (nextErr < CheckText->Errors.Length()) { auto &errs = CheckText->Errors[nextErr]; if (errs.Overlap(Cur)) { e.spellErr = true; nextErr++; break; } if (errs.Start < (ssize_t)Cur) nextErr++; else break; } } }); entries.Sort([](auto a, auto b) { return a->Compare(b); }); for (auto &e: entries) { double s, p, i; if (e.spam && e.ham) { s = (double) e.spam / SpamItems; p = s / ( ((double) e.ham / HamItems) + s); } else if (e.spam) p = 0.99; else if (e.ham) p = e.spellErr ? 0.6 : 0.01; else p = e.spellErr ? 0.88 : 0.4; i = WordInterest(p); if (Tokens->CanInsert(i)) { Token *t = new Token; if (t) { t->Word = e.word; t->Prob = p; t->Interest = i; Tokens->Insert(t); } } if (Analyse) LgiTrace(" %s, " LPrintfSSizeT ", " LPrintfSSizeT ", %i, %g\n", e.word.Get(), e.spam, e.ham, e.spellErr, p); } auto result = Tokens->Prob(&t->Log); if (Analyse) LgiTrace(" result=%g\n", result); return result; } void ConvertHashToBtree(LWordStore *Ws, LHashTbl,int> &Hash, int EmailCount, bool Append, LStream *Debug = NULL) { auto Items = Hash.Length(); int64 Start = LCurrentTime(); if (Debug) Debug->Print("ConvertHashToBtree(%s, %i words, %i emails)\n", Ws->GetFile(), Items, EmailCount); Ws->Empty(); for (auto i: Hash) { ssize_t Result; if (Append) { ssize_t Old = Ws->GetWordCount(i.key); Result = Ws->SetWordCount(i.key, Old + i.value); } else { Result = Ws->SetWordCount(i.key, i.value); } if (!Result) { if (Debug) Debug->Print("SetWordCount(%s, %i) failed\n", i.key, i.value); LAssert(0); return; } } if (EmailCount) { Ws->SetItems(EmailCount); } Hash.Empty(); LgiTrace("ConvertHashToBtree(%s) took %.1f sec, for " LPrintfInt64 " items.\n", Ws->GetFile(), ((double)((int64)LCurrentTime()-Start))/1000.0, Items); } void ApplyChange(Change *c, LWordStore *Ws, bool Add) { bool status = false; if (!Ws || !c) { LgiTrace("%s:%i - Invalid param: %p, %p\n", _FL, c, Ws); return; } ProcessWords(c->Words, [&](auto w) { ssize_t c = Ws->GetWordCount(w); if (Add) status = Ws->SetWordCount(w, c + 1) > 0; else status = Ws->SetWordCount(w, c > 0 ? c - 1 : 0) > 0; LAssert(status); }); ssize_t Total = Ws->GetItems(); status = Ws->SetItems((int)Total + (Add ? 1 : -1)); LAssert(status); } int Main() { if (auto s = FindWordDb(HamWordsFile)) SetStore(Ham, new LWordStore(s)); if (auto s = FindWordDb(SpamWordsFile)) SetStore(Spam, new LWordStore(s)); if (auto s = FindWordDb(WhiteListFile)) SetStore(WhiteList, new LWordStore(s)); State = BayesReady; bool Notify = false; while (State == BayesReady) { LAutoPtr b; LAutoPtr t; LAutoPtr c; if (Lock(_FL)) { if (Work.Length()) { b = Work[0]; Work.DeleteAt(0, true); } else if (Tests.Length()) { t = Tests[0]; Tests.DeleteAt(0, true); } else if (Changes.Length()) { c = Changes[0]; Changes.DeleteAt(0, true); } Unlock(); } if (b) { if (b->ResetDb) Empty(); ConvertHashToBtree(Spam, b->SpamWords, b->SpamEmailCount, b->ResetDb == false); ConvertHashToBtree(Ham, b->HamWords, b->HamEmailCount, b->ResetDb == false); ConvertHashToBtree(WhiteList, b->WhiteList, 0, b->ResetDb == false); } else if (t) { t->Score = IsSpam(t); if (Lock(_FL)) { Results.New() = t; Notify = true; Unlock(); } } else if (c) { switch (c->OldType) { default: break; case BayesMailHam: ApplyChange(c, Ham, false); break; case BayesMailSpam: ApplyChange(c, Spam, false); break; } switch (c->NewType) { default: break; case BayesMailHam: ApplyChange(c, Ham, true); break; case BayesMailSpam: ApplyChange(c, Spam, true); break; } if (c->Str) { if (!WhiteList) { LgiTrace("Missing whitelist obj.\n"); } else if (c->NewType == BayesMailSpam || c->RemoveWhite) { // Make sure the email address is not in the white list... WhiteList->DeleteWord(c->Str); LAssert(WhiteList->GetWordCount(c->Str) == 0); } else if (c->IncrementWhite) { auto i = WhiteList->GetWordCount(c->Str); WhiteList->SetWordCount(c->Str, i + 1); } } } else { // No work to do... if (Notify) { App->PostEvent(M_SCRIBE_BAYES_RESULT); Notify = false; } LSleep(20); } } return 0; } }; struct BayesEvent { Mail *m; bool Loading, Done; ScribeMailType OldType, NewType; }; class BuildSpamDB { int FolderLoads = 0; int MailLoads = 0; public: ScribeWnd *App; BayesianFilter *Filter; LAutoPtr Prog; LAutoPtr Debug; // Processing part 1... load the folders: LArray Folders; // Processing part 2... convert the mail to words: struct BuildItem { Mail *m = NULL; bool loading = false; ScribeMailType type = BayesMailUnknown; void Set(Mail *mail, ScribeMailType Type) { m = mail; m->IncRef(); type = Type; loading = false; } }; LArray Items; int HamCount = 0; int SpamCount = 0; int FalsePositives = 0; int FalseNegatives = 0; int LoadFailures = 0; LAutoPtr b; BuildSpamDB(ScribeWnd *app); ~BuildSpamDB(); /// \returns true when the processing is finished bool Process(); void ProcessMail(Mail *m, ScribeMailType type); void AbortProcess(); void AddFolder(ScribeFolder *f) { Folders.Add(f); } bool IsCancelled() { return Prog ? Prog->IsCancelled() : true; } }; class BayesianFilterPriv : public LMutex { LAutoPtr Thread; public: ScribeWnd *App; uint64_t Ts; uint64_t WorkStartTs = 0; LArray Work; LAutoPtr Prog; LAutoPtr Build; BayesianFilterPriv(ScribeWnd *app) : LMutex("BayesianFilterPriv") { Ts = 0; App = app; } BayesianThread *GetThread() { if (!Thread) Thread.Reset(new BayesianThread(App)); return Thread; } bool IsCancelled() { return Build ? Build->IsCancelled() : true; } }; BuildSpamDB::BuildSpamDB(ScribeWnd *app) : App(app), Filter(app) { App->OnFolderTask(Filter->d->GetThread(), true); b.Reset(new BayesianThread::Build(true)); if (Prog.Reset(new LProgressDlg(App))) Prog->SetDescription("Scanning folders..."); LVariant i; if (App->GetOptions()->GetValue(OPT_BayesDebug, i)) { if (Debug.Reset(new LFile)) { char s[MAX_PATH_LEN]; LMakePath(s, sizeof(s), LGetExePath(), "Bayes.txt"); if (Debug->Open(s, O_WRITE)) Debug->SetSize(0); else LgiTrace("%s:%i - Couldn't open '%s'\n", _FL, s); } } } BuildSpamDB::~BuildSpamDB() { // Save stats... LVariant i; auto opts = App->GetOptions(); opts->SetValue(OPT_BayesHam, i = HamCount); opts->SetValue(OPT_BayesSpam, i = SpamCount); opts->SetValue(OPT_BayesFalsePositives, i = FalsePositives); opts->SetValue(OPT_BayesFalseNegitives, i = FalseNegatives); App->OnFolderTask(Filter->d->GetThread(), false); } void BuildSpamDB::AbortProcess() { Folders.Length(0); Items.Length(0); } bool BuildSpamDB::Process() { if (IsCancelled()) AbortProcess(); // This should execute for only a small time slice... if (Folders.Length() || FolderLoads) { if (!Folders.Length()) return false; // Just wait for them... auto f = Folders[0]; Folders.DeleteAt(0); if (f) { auto Path = f->GetPath(); ScribeMailType Type = Filter->BayesTypeFromPath(Path); if (Debug) Debug->Print("%s:%i - add folder '%s', Type=%i\n", _FL, Path.Get(), Type); auto Parent = f->GetParent(); if (Type != BayesMailUnknown && Parent) { FolderLoads++; f->WhenLoaded(_FL, [this, f, Type, Path]() { LgiTrace("%s:%i - Scanning '%s' got %i items, FolderLoads=%i\n", _FL, Path.Get(), (int)f->Items.Length(), FolderLoads); for (auto i: f->Items) { auto m = i->IsMail(); if (!m) continue; // Add item to the work queue... Items.New().Set(m, Type); } FolderLoads--; (*Prog)++; }); + + // FIXME: does this need to do something on callback? f->LoadThings(); } else { // LgiTrace("%s:%i - Unknown folder '%s'\n", _FL, Path.Get()); (*Prog)++; } } if (Folders.Length() == 0 && FolderLoads == 0) { Prog->SetDescription("Processing mail..."); Prog->SetRange(Items.Length()); Prog->Value(0); } return false; } if (Items.Length() || MailLoads) { if (Items.Length() && MailLoads < 100) { auto Start = LCurrentTime(); while ( (LCurrentTime() - Start) < TIMEOUT_BAYES_IDLE && Items.Length()) { // Process mail items... auto &i = Items[0]; // Operate on read mail only... auto flags = i.m->GetFlags(); if (TestFlag(flags, MAIL_READ)) { MailLoads++; // auto loaded = i.m->GetLoaded(); i.m->WhenLoaded(_FL, [this, mail = i.m, type = i.type] { ProcessMail(mail, type); MailLoads--; (*Prog)++; }); i.m->SetLoaded(); } Items.DeleteAt(0); } } return false; } // We're done... return true; } void BuildSpamDB::ProcessMail(Mail *m, ScribeMailType Type) { auto LoadState = m->GetLoaded(); if (LoadState != Store3Loaded) { LAssert(!"Should only be called on loaded email..."); return; } const char *email; if (Type == BayesMailHam && (email = m->GetFromStr(FIELD_EMAIL))) { b->InsertWhiteList(email); } LString Words; auto Status = Filter->MakeMailWordList(m, Words); if (Status != Store3Success) { LAssert(!"MakeMailWordList failed."); return; } ProcessWords(Words, [&](auto w) { if (Type == BayesMailSpam) b->InsertSpamWords(w); else if (Type == BayesMailHam) b->InsertHamWords(w); // else do nothing }); auto flags = m->GetFlags(); // Remove the bayes DB flags... flags &= ~(MAIL_HAM_DB|MAIL_SPAM_DB); if (Type == BayesMailSpam) { b->SpamEmailCount++; flags |= MAIL_SPAM_DB; } else if (Type == BayesMailHam) { b->HamEmailCount++; flags |= MAIL_HAM_DB; } if (flags & MAIL_BAYES_HAM) // originally classified as ham.. { if (flags & MAIL_SPAM_DB) // but now is spam... FalseNegatives++; else if (Type == BayesMailHam) HamCount++; } else if (flags & MAIL_BAYES_SPAM) // originally classified as spam.. { if (flags & MAIL_SPAM_DB) SpamCount++; else if (Type == BayesMailHam) // but now is ham... FalsePositives++; } // Update the object.. m->SetFlags(flags); // Delete our reference... m->DecRef(); } #ifdef _DEBUG bool HashSerialize(LHashTbl,uint32_t> &h, char *file, bool write) { bool Status = false; struct Field { uint32_t Value; uint16_t Len; char Str[1]; }; LFile f; if (f.Open(file, write?O_WRITE:O_READ)) { Field *fld = (Field*)malloc(64<<10); if (fld) { int header = 6; if (write) { f.SetSize(0); // char *key; // for (int i=h.First(&key); i; i=h.Next(&key)) for (auto i : h) { fld->Value = (uint32_t)i.value; fld->Len = (uint16_t)strlen(i.key); memcpy(fld->Str, i.key, fld->Len + 1); Status = f.Write(fld, header + fld->Len) == (header + fld->Len); } } else { h.Empty(); while (!f.Eof()) { if (f.Read(fld, header) == header) { if (f.Read(fld->Str, fld->Len) == fld->Len) { fld->Str[fld->Len] = 0; Status = h.Add(fld->Str, fld->Value); } else break; } else break; } } free(fld); } } return Status; } #endif BayesianFilter::BayesianFilter(ScribeWnd *app) { d = new BayesianFilterPriv(app); App = app; } BayesianFilter::~BayesianFilter() { DeleteObj(d); } void BayesianFilter::AddFolderToSpamDb(ScribeFolder *f) { if (d->IsCancelled()) return; d->Build->AddFolder(f); for (auto c = f->GetChildFolder(); c; c = c->GetNextFolder()) AddFolderToSpamDb(c); } /* static size_t FolderCount(ScribeFolder *f) { ssize_t len = f->Length(); auto items = f->GetItems(); auto n = MAX(len, items); for (auto c = f->GetChildFolder(); c; c = c->GetNextFolder()) n += FolderCount(c); return n; } */ void BayesianFilter::BuildStats() { auto prob = App->GetFolder("/Mail3/Spam/Probably"); auto inbox = App->GetFolder("/IMAP/INBOX"); if (!prob || !inbox) return; prob->LoadThings(); inbox->LoadThings(); - - for (auto t: prob->Items) - { - auto m = t->IsMail(); - if (m) - { - } - } } bool BayesianFilter::BuildSpamDb() { if (!d->GetThread()) return false; // scan folders LVariant MoveTo; if (!App->GetOptions()->GetValue(OPT_BayesMoveTo, MoveTo)) MoveTo = "/Spam/Probably"; if (!d->Build.Reset(new BuildSpamDB(App))) return false; // Recurse over the folders for (auto &s: App->GetStorageFolders()) { if (s.Root) AddFolderToSpamDb(s.Root); } for (auto a : *App->GetAccounts()) { if (a->Receive.GetRootFolder()) AddFolderToSpamDb(a->Receive.GetRootFolder()); } d->Build->Prog->SetRange(d->Build->Folders.Length()); return true; } #define IsCJK(c) \ ( \ ((c)>=0x4E00 && (c)<=0x9FFF) || \ ((c)>=0x3400 && (c)<=0x4DFF) || \ ((c)>=0x20000 && (c)<=0x2A6DF)|| \ ((c)>=0xF900 && (c)<=0xFAFF) || \ ((c)>=0x2F800 && (c)<=0x2FA1F) \ ) bool IsUriChar(int32 ch) { if (!ch) return false; if (IsDigit(ch) || IsAlpha(ch)) return true; if (strchr("-._~:/?#[]@!$&\'()*+,;%=", ch)) return true; return false; } typedef LHashTbl,bool> TokenMap; void TokeniseText(const char *Source, bool *Lut, LString::Array &Blocks, TokenMap *Ignore = NULL) { if (!Source || !Lut) return; char buf[16 << 10]; ssize_t used = 0; auto oldWarn = LUtf8Ptr::Warn; LUtf8Ptr::Warn = false; auto emitBuf = [&]() { if (used > 0) Blocks.New().Set(buf, used); used = 0; }; for (auto *s = Source; *s;) { int32 ch; LUtf8Ptr start(s); // Skip non-word while ( (ch = start) && ch < 256 && !Lut[ch] ) { start++; } // Seek end of word LUtf8Ptr end(start); while ( (ch = end) && ( ch >= 256 || Lut[ch] ) ) { if (end > start && IsCJK(ch)) break; end++; } auto len = end - start; // Is the word something that looks like a URI? bool isUrl = (len == 4 && !Strnicmp((const char*)start.GetPtr(), "http", 4)) || (len == 5 && !Strnicmp((const char*)start.GetPtr(), "https", 5)); if (isUrl) { // Seek to the end of the URI while ((ch = end) && IsUriChar(ch) && (end - start) < sizeof(buf) - 10) end++; isUrl = true; len = end - start; } if (len > 0) { ch = start; if (ch != '*' || len != 10 || Strnicmp((const char*)start.GetPtr(), "***SPAM***", len)) { if (used + len >= sizeof(buf) - 2) emitBuf(); auto word = buf + used; if (isUrl) { LUri uri(LString((char*)start.GetPtr(), len)); if (uri.sHost) { memcpy(word, uri.sHost.Get(), uri.sHost.Length()); used += uri.sHost.Length(); buf[used++] = ' '; } } else { memcpy(word, start.GetPtr(), len); used += len; if (Ignore) { buf[used] = 0; if (Ignore->Find(buf+used-len)) used -= len; // undo adding word else buf[used++] = ' '; // convert to space delimit } else { buf[used++] = ' '; } } } } else { if (ch) LgiTrace("%s:%i - Invalid utf-8, aborting parse...\n", _FL); break; } s = (const char*)end.GetPtr(); } emitBuf(); LUtf8Ptr::Warn = oldWarn; } Store3Status BayesianFilter::MakeMailWordList(Mail *m, LString &out) { LString::Array Blocks; if (!m || !m->GetObject()) return Store3Error; static bool Processing = false; if (!Processing) { Processing = true; // create word lut for deciding whether a char is part of a word or not bool Lut[256]; ZeroObj(Lut); memset(Lut + 'a', true, 'z'-'a'+1); memset(Lut + 'A', true, 'Z'-'A'+1); Lut[(int)'-'] = true; // Lut[(int)'!'] = true; Lut[(int)'$'] = true; bool Email[256]; memcpy(Email, Lut, sizeof(Email)); Email[(int)'@'] = true; Email[(int)'.'] = true; memset(Lut + 0x80, true, 128); // create ignored words list LString::Array Temp; TokenMap Ignore; for (auto a: *App->GetAccounts()) { auto s = a->Identity.Name(); if (ValidStr(s.Str())) TokeniseText(s.Str(), Lut, Temp); s = a->Identity.Email(); if (ValidStr(s.Str())) TokeniseText(s.Str(), Email, Temp); } ProcessWords(LString("").Join(Temp), [&](auto w) { Ignore.Add(w, true); }); // process various parts of the email TokeniseText(m->GetSubject(), Lut, Blocks, &Ignore); TokeniseText(m->GetFromStr(FIELD_EMAIL), Email, Blocks, &Ignore); TokeniseText(m->GetFromStr(FIELD_NAME), Lut, Blocks, &Ignore); LVariant Body; Store3State Loaded = (Store3State)m->GetObject()->GetInt(FIELD_LOADED); if (Loaded < Store3Loaded) { m->GetBody(); Loaded = (Store3State)m->GetObject()->GetInt(FIELD_LOADED); if (Loaded < Store3Loaded) { Processing = false; return Store3Delayed; } // else continue... } auto Req = m->GetValue("BodyAsText", Body); if (Req) { // auto id = m->GetMessageId(); TokeniseText(Body.Str(), Lut, Blocks, &Ignore); } else { LString path = "(nullFolder)"; auto fld = m->GetFolder(); if (fld) path = fld->GetPath(); LgiTrace("%s:%i - couldn't get body for %s/%s\n", _FL, path.Get(), m->GetMessageId()); /* Technically not an error... body can be blank. Processing = false; return Store3Error; */ } out = LString("").Join(Blocks); Processing = false; return Store3Success; } else { LAssert(!"Recursion."); return Store3Error; } } Store3Status BayesianFilter::IsSpam(double &Result, Mail *m, bool Analyse) { if (!m) { Result = 0.0; return Store3Error; } Store3Status Status = Store3NotImpl; LAutoPtr t(new BayesianThread::Test); const char *FromAddr; if ((FromAddr = m->GetFromStr(FIELD_EMAIL))) { #if WHITELIST_MY_EMAIL // Check if from yourself... if (App->IsMyEmail(FromAddr)) goto OnWhiteListed; #endif // Check the user white list LVariant Wl; if (App->GetOptions()->GetValue(OPT_BayesUserWhiteList, Wl)) { LToken w(Wl.Str(), "\r\n\t "); bool IsWhite = false; for (unsigned i=0; i Contacts; App->GetContacts(Contacts); for (auto c: Contacts) { if (c->HasEmail(FromAddr)) goto OnWhiteListed; } #endif t->FromAddr = FromAddr; } // Tokenise the mail... Status = MakeMailWordList(m, t->Words); if (Status == Store3Error) return Status; if (Status == Store3Delayed) { // Not loaded yet, retry it later... m->WhenLoaded(_FL, [this, Analyse, m]() { double r; IsSpam(r, m, Analyse); }); return Store3Delayed; } // Pass it over to the thread t->Analyse = Analyse; t->MsgRef = m->GetMailRef(); d->GetThread()->Add(t); return Store3Delayed; OnWhiteListed: if (Analyse) OnBayesAnalyse(LLoadString(IDS_IS_WHITELIST), FromAddr); Result = 0.0; return Store3Success; } ScribeMailType BayesianFilter::BayesTypeFromPath(LString Path) { if (!Path) return BayesMailUnknown; auto spam = App->GetFolder(FOLDER_SPAM); auto folder = App->GetFolder(Path); if (spam && folder) { if (folder == spam) return BayesMailSpam; // If folder is a child of the spam folder, it's NOT ham but "unknown". // Typically a staging ground for "possible" spam. So it should not contribute to // bayes word counts. for (auto p = folder->GetParent(); p; p = p->GetParent()) if (p == spam) return BayesMailUnknown; } else { auto t = Path.SplitDelimit("/"); ssize_t spamIdx = -1; for (size_t i=0; i spamIdx + 1 ? BayesMailUnknown : BayesMailSpam; } return BayesMailHam; } ScribeMailType BayesianFilter::BayesTypeFromPath(Mail *m) { ScribeMailType Status = BayesMailUnknown; if (m) { ScribeFolder *f = m->GetFolder(); if (f) Status = BayesTypeFromPath(f->GetPath()); } return Status; } void BayesianFilter::WhiteListIncrement(const char *Word) { if (!Word) return; LAutoPtr c(new BayesianThread::Change); c->IncrementWhite = true; c->Str = Word; d->GetThread()->Add(c); } bool BayesianFilter::RemoveFromWhitelist(const char *Email) { if (!Email) return false; LAutoPtr c(new BayesianThread::Change); c->Str = Email; c->RemoveWhite = true; d->GetThread()->Add(c); return true; } Store3Status BayesianFilter::OnBayesianMailEvent(Mail *m, ScribeMailType OldType, ScribeMailType NewType) { if (!m) return Store3Error; auto flags = m->GetFlags(); if (NewType == BayesMailHam) { if (TestFlag(flags, MAIL_HAM_DB)) return Store3Success; if (!TestFlag(flags, MAIL_BAYES_HAM|MAIL_BAYES_SPAM)) flags |= MAIL_BAYES_HAM; } if (NewType == BayesMailSpam) { if (TestFlag(flags, MAIL_SPAM_DB)) return Store3Success; if (!TestFlag(flags, MAIL_BAYES_HAM|MAIL_BAYES_SPAM)) flags |= MAIL_BAYES_SPAM; } m->SetFlags(flags); if (d->Work.Length() == 0) d->WorkStartTs = LCurrentTime(); auto &w = d->Work.New(); w.m = m; w.m->IncRef(); w.Loading = false; w.Done = false; w.OldType = OldType; w.NewType = NewType; return Store3Delayed; } void BayesianFilter::OnEvent(LMessage *Msg) { switch (Msg->Msg()) { case M_SCRIBE_IDLE: { // LProfile prof("M_SCRIBE_IDLE", 15); if (d->Build) { if (d->Build->Process()) { // Give the hash tables to the worker thread to convert to disk: d->GetThread()->Add(d->Build->b); // And finish doing the build processing: d->Build.Reset(); } break; } auto EmptyWorkQueue = [this]() { for (auto j: d->Work) { if (!j.Done && j.m) { j.m->DecRef(); j.m = NULL; } } d->Work.Length(0); }; // Look for something we can do... auto StartTs = LCurrentTime(); size_t i, processedCount = 0; for (i=0; iWork.Length(); i++) { auto &job = d->Work[i]; if (job.Done) continue; if (!job.m->GetObject()) { // Why? LgiTrace("%s:%i - Error: object not found.\n", _FL); job.Done = true; // We can't continue anyway continue; } if (job.Loading) { // prof.Add("loading"); auto loaded = job.m->GetLoaded(); if (loaded < Store3Loaded) continue; // Still hasn't loaded.. } LString words; // prof.Add("MakeMailWordList"); auto Status = MakeMailWordList(job.m, words); if (Status == Store3Error) { // Remove the work from the list... it's probably going to keep failing d->Work.DeleteAt(i--); continue; } else if (Status == Store3Delayed) { if (job.Loading) LAssert(!"Already waiting for load."); else job.Loading = true; continue; } // prof.Add("change"); LAutoPtr c(new BayesianThread::Change); c->Str = job.m->GetFromStr(FIELD_EMAIL); c->Words = words; c->OldType = job.OldType; c->NewType = job.NewType; d->GetThread()->Add(c); // Update the spam/ham flags... auto flags = job.m->GetFlags(); flags &= ~(MAIL_BAYES_SPAM|MAIL_BAYES_HAM); if (job.NewType == BayesMailHam) flags |= MAIL_BAYES_HAM; else if (job.NewType == BayesMailSpam) flags |= MAIL_BAYES_SPAM; job.m->SetFlags(flags); job.m->DecRef(); job.m = NULL; job.Done = true; processedCount++; if (LCurrentTime()-StartTs >= TIMEOUT_BAYES_IDLE) break; } // prof.Add("post"); if (d->Work.Length() > 0) { if (!processedCount) { EmptyWorkQueue(); } else { auto Now = LCurrentTime(); LAssert(d->WorkStartTs); if (Now - d->WorkStartTs > 300 && !d->Prog) { d->Prog.Reset(new LProgressDlg(App, 500)); d->Prog->SetDescription("Bayesian filtering..."); } if (d->Prog) { d->Prog->SetRange(d->Work.Length()); d->Prog->Value(i); if (d->Prog->IsCancelled()) { // Dump the work queue EmptyWorkQueue(); } } } } else { d->Prog.Reset(); d->WorkStartTs = 0; } break; } case M_SCRIBE_BAYES_RESULT: { LArray< LAutoPtr > Results; if (!d->GetThread()->GetResults(Results)) break; for (auto t: Results) { if (!t) { LAssert(!"Null ptr in Results"); continue; } if (t->Analyse) { LArray a; int Size = (int) t->Log.GetSize(); a.Length(Size+1); t->Log.Read(&a[0], Size); a[Size] = 0; OnBayesAnalyse(&a[0], t->WhiteListed ? t->FromAddr : NULL); } else if (t->MsgRef) { OnBayesResult(t->MsgRef, t->Score); } else LAssert(!"There should always be a msg ref"); } break; } } } diff --git a/Code/Calendar.cpp b/Code/Calendar.cpp --- a/Code/Calendar.cpp +++ b/Code/Calendar.cpp @@ -1,3297 +1,3314 @@ /*hdr ** FILE: Calendar.cpp ** AUTHOR: Matthew Allen ** DATE: 23/11/2001 ** DESCRIPTION: Scribe calender support ** ** Copyright (C) 2001 Matthew Allen ** fret@memecode.com */ #include "Scribe.h" #include "lgi/common/vCard-vCal.h" #include "lgi/common/Combo.h" #include "lgi/common/DateTimeCtrls.h" #include "lgi/common/TabView.h" #include "lgi/common/DisplayString.h" #include "lgi/common/Edit.h" #include "lgi/common/ColourSelect.h" #include "lgi/common/Button.h" #include "lgi/common/LgiRes.h" #include "lgi/common/Json.h" #include "CalendarView.h" #include "PrintContext.h" #include "../Resources/resdefs.h" #include "resource.h" #include "AddressSelect.h" #include "ObjectInspector.h" #define MAX_RECUR 1024 #define DEBUG_REMINDER 0 #define DEBUG_DATES 0 ////////////////////////////////////////////////////////////////////////////// ItemFieldDef CalendarFields[] = { {"Start", SdStart, GV_DATETIME, FIELD_CAL_START_UTC, IDC_START_DATE, 0, true}, {"End", SdEnd, GV_DATETIME, FIELD_CAL_END_UTC, IDC_END_DATE, 0, true}, {"Subject", SdSubject, GV_STRING, FIELD_CAL_SUBJECT, IDC_SUBJECT}, {"Location", SdLocation, GV_STRING, FIELD_CAL_LOCATION, IDC_LOCATION}, {"Show Time As", SdShowTimeAs, GV_INT32, FIELD_CAL_SHOW_TIME_AS, IDC_AVAILABLE_TYPE}, {"All Day", SdAllDay, GV_BOOL, FIELD_CAL_ALL_DAY, IDC_ALL_DAY}, // TRUE if the calendar event recurs {"Recur", SdRecur, GV_INT32, FIELD_CAL_RECUR, -1}, // Base time unit of recurring event. See enum CalRecurFreq: days, weeks, months, years. {"Recur Freq", SdRecurFreq, GV_INT32, FIELD_CAL_RECUR_FREQ, -1}, // Number of FIELD_CAL_RECUR_FREQ units of time between recurring events. (Minimum is '1') {"Recur Interval", SdRecurInterval, GV_INT32, FIELD_CAL_RECUR_INTERVAL, -1}, // Bitfield of days, Bit 0 = Sunday, Bit 1 = Monday, Bit 2 = Teusday etc. {"Filter Days", SdFilterDays, GV_INT32, FIELD_CAL_RECUR_FILTER_DAYS, -1}, // Bitfield of months, Bit 0 = Janurary, Bit 1 = feburary, Bit 2 = March etc. {"Filter Months", SdFilterMonths, GV_INT32, FIELD_CAL_RECUR_FILTER_MONTHS, -1}, // String of year numbers separated by commas: "2010,2014,2018" {"Filter Years", SdFilterYears, GV_STRING, FIELD_CAL_RECUR_FILTER_YEARS, -1}, // Position in month, "1" means the first matching day of the month. Multiple indexes can be joined with ',' // like: "1,3" {"Filter Pos", SdFilterPos, GV_STRING, FIELD_CAL_RECUR_FILTER_POS, -1}, // See the CalRecurEndType enumeration {"Recur End Type", SdRecurEndType, GV_INT32, FIELD_CAL_RECUR_END_TYPE, -1}, // If FIELD_CAL_RECUR_END_TYPE==CalEndOnDate then this specifies the date to end {"Recur End Date", SdRecurEndDate, GV_DATETIME, FIELD_CAL_RECUR_END_DATE, -1}, // If FIELD_CAL_RECUR_END_TYPE==CalEndOnCount then this specifies the count of events {"Recur End Count", SdRecurEndCount, GV_INT32, FIELD_CAL_RECUR_END_COUNT, -1}, // The timezone the start and end are referencing {"Timezone", SdTimeZone, GV_STRING, FIELD_CAL_TIMEZONE, IDC_TIMEZONE}, // User defined notes for the object {"Notes", SdNotes, GV_STRING, FIELD_CAL_NOTES, IDC_DESCRIPTION}, // See the CalendarType enumeration {"Type", SdType, GV_INT32, FIELD_CAL_TYPE, -1}, {"Reminders", SdReminders, GV_STRING, FIELD_CAL_REMINDERS, -1}, {"LastCheck", SdLastCheck, GV_DATETIME, FIELD_CAL_LAST_CHECK, -1}, {0} }; ////////////////////////////////////////////////////////////////////////////// const char *sReminderType[] = { "Email", "Popup", "ScriptCallback" }; const char *sReminderUnits[] = { "Minutes", "Hours", "Days", "Weeks" }; class ReminderItem : public LListItem { CalendarReminderType Type; float Value; CalendarReminderUnits Units; LString Param; public: ReminderItem(CalendarReminderType type, float value, CalendarReminderUnits units, LString param) { Type = type; Value = value; Units = units; Param = param; Update(); SetText("x", 1); } ReminderItem(LString s) { if (!SetString(s)) { // Default Type = CalPopup; Value = 1.0; Units = CalMinutes; Param.Empty(); } Update(); SetText("x", 1); } CalendarReminderType GetType() { return Type; } float GetValue() { return Value; } CalendarReminderUnits GetUnits() { return Units; } LString GetParam() { return Param; } LString GetString() { // See also FIELD_CAL_REMINDERS LString s; s.Printf("%g,%i,%i,%s", Value, Units, Type, Param?LUrlEncode(Param, ",\r\n").Get():""); return s; } bool SetString(LString s) { // See also FIELD_CAL_REMINDERS LString::Array a = s.Split(","); if (a.Length() < 3) return false; Value = (float) a[0].Float(); Units = (CalendarReminderUnits) a[1].Int(); Type = (CalendarReminderType) a[2].Int(); if (a.Length() > 3) Param = LUrlDecode(a[3]); return true; } void Update() { char s[300]; if (Param) sprintf_s(s, sizeof(s), "%s '%s' @ %g %s", sReminderType[Type], Param.Get(), Value, sReminderUnits[Units]); else sprintf_s(s, sizeof(s), "%s @ %g %s", sReminderType[Type], Value, sReminderUnits[Units]); SetText(s); } void OnPaintColumn(LItem::ItemPaintCtx &Ctx, int i, LItemColumn *c) { LColour old = Ctx.Fore; if (i == 1) Ctx.Fore.Rgb(255, 0, 0); LListItem::OnPaintColumn(Ctx, i, c); Ctx.Fore = old; } void OnMouseClick(LMouse &m) { if (m.Down() && m.Left()) { if (GetList()->ColumnAtX(m.x) == 1) { delete this; return; } } LListItem::OnMouseClick(m); } }; //////////////////////////////////////////////////////////////////////////////////// static int DefaultCalenderFields[] = { FIELD_CAL_SUBJECT, FIELD_CAL_START_UTC, FIELD_CAL_END_UTC, 0, }; #define MINUTE 1 // we're in minutes... #define HOUR (60 * MINUTE) #define DAY (24 * HOUR) int ReminderOffsets[] = { 0, 15 * MINUTE, 30 * MINUTE, 1 * HOUR, 2 * HOUR, 3 * HOUR, 4 * HOUR, 5 * HOUR, 6 * HOUR, 7 * HOUR, 8 * HOUR, 9 * HOUR, 10 * HOUR, 11 * HOUR, 12 * HOUR, 1 * DAY, 2 * DAY }; ////////////////////////////////////////////////////////////////////////////// List Calendar::Reminders; int Calendar::DayStart = -1; int Calendar::DayEnd = -1; int Calendar::WorkDayStart = -1; int Calendar::WorkDayEnd = -1; int Calendar::WorkWeekStart = -1; int Calendar::WorkWeekEnd = -1; void InitCalendarView() { Calendar::DayStart = 6; Calendar::DayEnd = 23; Calendar::WorkDayStart = -1; Calendar::WorkDayEnd = -1; Calendar::WorkWeekStart = -1; Calendar::WorkWeekEnd = -1; auto s = LAppInst->GetConfig("Scribe.Calendar.WorkDayStart"); if (s) Calendar::WorkDayStart = (int)s.Int(); s = LAppInst->GetConfig("Scribe.Calendar.WorkDayEnd"); if (s) Calendar::WorkDayEnd = (int)s.Int(); s = LAppInst->GetConfig("Scribe.Calendar.WorkWeekStart"); if (s) Calendar::WorkWeekStart = (int)s.Int() + 1; s = LAppInst->GetConfig("Scribe.Calendar.WorkWeekEnd"); if (s) Calendar::WorkWeekEnd = (int)s.Int() + 1; if (Calendar::WorkDayStart < 0) Calendar::WorkDayStart = 9; if (Calendar::WorkDayEnd < 0) Calendar::WorkDayEnd = 18; if (Calendar::WorkWeekStart < 0) Calendar::WorkWeekStart = 1; if (Calendar::WorkWeekEnd < 0) Calendar::WorkWeekEnd = 5; } void Calendar::CheckReminders() { LDateTime Now; Now.SetNow(); LDateTime Then; Then = Now; Then.AddDays(1); #if DEBUG_REMINDER LgiTrace("%s:%i - Reminders.Len=%i, Now=%s, Then=%s\n", _FL, Reminders.Length(), Now.Get().Get(), Then.Get().Get()); #endif for (auto c: Reminders) { auto App = c->App; LHashTbl,bool> Added; Thing *ReminderThing = NULL; Mail *ReminderEmail = NULL; // Is a reminder on the entry? if (!c->GetObject()) continue; LString Rem = c->GetObject()->GetStr(FIELD_CAL_REMINDERS); if (!Rem) continue; const char *Subj = NULL, *Notes = NULL; c->GetField(FIELD_CAL_SUBJECT, Subj); c->GetField(FIELD_CAL_NOTES, Notes); LArray Times; if (!c->GetTimes(Now, Then, Times)) { #if DEBUG_REMINDER LgiTrace(" No times for '%s'\n", Subj); #endif continue; } LDataI *Obj = c->GetObject(); LDateTime LastCheck = *Obj->GetDate(FIELD_CAL_LAST_CHECK); if (LastCheck.IsValid()) LastCheck.ToLocal(); LString::Array r = Rem.SplitDelimit("\n"); for (unsigned i=0; i LastCheck; bool b = ts <= Now; LgiTrace(" last check = %s\n", LastCheck.Get().Get()); LgiTrace(" now = %s\n", Now.Get().Get()); LgiTrace(" %i %i\n", a, b); #endif if ( ( !LastCheck.IsValid() || ts > LastCheck ) && ts <= Now ) { // Save the last check field now auto NowUtc = Now.Utc(); Obj->SetDate(FIELD_CAL_LAST_CHECK, &NowUtc); c->SetDirty(); // Fire the event switch (ri.GetType()) { case CalEmail: { ScribeAccount *Acc = App->GetCurrentAccount(); if (!Acc || !Acc->Identity.IsValid()) { LView *Ui = c->GetUI(); LgiMsg(Ui ? Ui : c->App, "No from account to use for sending event notifications.", AppName, MB_OK); break; } auto Email = Acc->Identity.Email(); auto Name = Acc->Identity.Name(); LgiTrace("%s:%i - Using account ('%s' '%s') for the event reminder 'from' address.\n", _FL, Email.Str(), Name.Str()); if (!ReminderThing) ReminderThing = App->CreateThingOfType(MAGIC_MAIL); if (!ReminderEmail) { ReminderEmail = ReminderThing ? ReminderThing->IsMail() : NULL; if (ReminderEmail) { LString s; s.Printf("Calendar Notification: %s", Subj); ReminderEmail->SetSubject(s); s.Printf("The event '%s' is due at: %s\n" "\n" "%s\n" "\n" "(This email was generated by Scribe)", Subj, t.s.Get().Get(), Notes ? Notes : ""); ReminderEmail->SetBody(s); auto Frm = ReminderEmail->GetFrom(); Frm->SetStr(FIELD_EMAIL, Email.Str()); Frm->SetStr(FIELD_NAME, Name.Str()); } } if (ReminderEmail) { auto To = ReminderEmail->GetTo(); // Add any custom parameter email address: LString Param = ri.GetParam(); if (LIsValidEmail(Param)) { auto Recip = To->Create(c->GetObject()->GetStore()); if (Recip) { Added.Add(Param, true); Recip->SetStr(FIELD_EMAIL, Param); To->Insert(Recip); } } // Add all the guests LString sGuests = c->GetObject()->GetStr(FIELD_TO); LString::Array Guests = sGuests.SplitDelimit(","); for (auto Guest: Guests) { LAutoString e, n; DecodeAddrName(Guest, n, e, NULL); #if DEBUG_REMINDER LgiTrace("Attendee=%s,%s\n", n.Get(), e.Get()); #endif if (LIsValidEmail(e.Get()) && !Added.Find(e)) { auto Recip = To->Create(c->GetObject()->GetStore()); if (Recip) { if (n) Recip->SetStr(FIELD_NAME, n); Recip->SetStr(FIELD_EMAIL, e); To->Insert(Recip); Added.Add(e, true); } } else { #if DEBUG_REMINDER LgiTrace("Attendee not valid or added already: %s\n", e.Get()); #endif } } } break; } case CalPopup: { if (LgiMsg( 0, // this causes the dialog to be ontop of everything else LLoadString(IDS_EVENT_DUE), AppName, MB_YESNO | MB_SYSTEMMODAL, Subj) == IDYES) { // Open the calendar entry c->DoUI(); } break; } case CalScriptCallback: { break; } default: { LgiTrace("%s:%i - Unknown reminder type.\n", _FL); break; } } } } } } if (ReminderEmail) { ReminderEmail->CreateMailHeaders(); ReminderEmail->Update(); ReminderEmail->Send(true); } } } #define MIN_1 ((int64)LDateTime::Second64Bit * 60) #define HOUR_1 (MIN_1 * 60) #define DAY_1 (HOUR_1 * 24) #define YEAR_1 (DAY_1 * 365) const char *RelativeTime(LDateTime &Then) { static char s[256]; static const int Id[] = { IDS_CAL_LDAY_SUN, IDS_CAL_LDAY_MON, IDS_CAL_LDAY_TUE, IDS_CAL_LDAY_WED, IDS_CAL_LDAY_THU, IDS_CAL_LDAY_FRI, IDS_CAL_LDAY_SAT, }; LDateTime Now; Now.SetNow(); char Val[64]; uint64 n, t; Now.Get(n); Then.Get(t); int64_t Diff = (int64)t - (int64)n; int Yrs = 0; int Months = 0; int Days = 0; int Hrs = 0; int Mins = 0; LDateTime i = Now; int Inc = Then > Now ? 1 : -1; char DirIndcator = Then > Now ? '+' : '-'; while (ABS(Diff) >= YEAR_1) { Yrs++; i.Year(i.Year()+Inc); i.Get(n); Diff = (int64)t - (int64)n; } int TotalDays = 0; if (ABS(Diff) > DAY_1) { TotalDays = Days = (int) (Diff / DAY_1); while (true) { LDateTime first = i; first.AddMonths(Inc); if ( (Inc < 0 && first > Then) // Tracking back in time.. || (Inc > 0 && Then > first) // Forward in time.. ) { Months += Inc; i = first; } else break; } if (Months) { uint64 remaining; i.Get(remaining); Diff = (int64_t)t - (int64_t)remaining; Days = (int) (Diff / DAY_1); } Diff -= (int64) Days * DAY_1; } if (ABS(Diff) > HOUR_1) { Hrs = (int) (Diff / HOUR_1); Diff -= (int64) Hrs * HOUR_1; } if (ABS(Diff) > MIN_1) { Mins = (int) (Diff / MIN_1); Diff -= (int64) Mins * MIN_1; } if (Yrs) { // Years + months sprintf_s(Val, sizeof(Val), "%c%iy %im", DirIndcator, abs(Yrs), abs(Months)); } else if (Months) { // Months + days sprintf_s(Val, sizeof(Val), "%c%im %id", DirIndcator, abs(Months), abs(Days)); } else if (Days) { if (abs(Days) >= 7) { // Weeks + days... sprintf_s(Val, sizeof(Val), "%c%iw %id", DirIndcator, abs(Days)/7, abs(Days)%7); } else { // Days + hours... sprintf_s(Val, sizeof(Val), "%c%id %ih", DirIndcator, abs(Days), abs(Hrs)); } } else if (Hrs) { // Hours + min sprintf_s(Val, sizeof(Val), "%c%ih %im", DirIndcator, abs(Hrs), abs(Mins)); } else { // Mins sprintf_s(Val, sizeof(Val), "%c%im", DirIndcator, abs(Mins)); } if (Yrs != 0 || Months != 0) { sprintf_s(s, sizeof(s), "%s", Val); return s; } auto NowDay = n / DAY_1; auto ThenDay = t / DAY_1; auto DaysDiff = (int64_t)ThenDay - (int64_t)NowDay; int Ch = 0; if (NowDay == ThenDay) Ch = sprintf_s(s, sizeof(s), "%s", LLoadString(IDS_TODAY)); else if (DaysDiff == -1) Ch = sprintf_s(s, sizeof(s), "%s", LLoadString(IDS_YESTERDAY)); else if (DaysDiff == 1) Ch = sprintf_s(s, sizeof(s), "%s", LLoadString(IDS_TOMORROW)); else if (DaysDiff > 1 && DaysDiff < 7) Ch = sprintf_s(s, sizeof(s), LLoadString(IDS_THIS_WEEK), LLoadString(Id[Then.DayOfWeek()])); else if (DaysDiff >= 7 && DaysDiff < 14) Ch = sprintf_s(s, sizeof(s), LLoadString(IDS_NEXT_WEEK), LLoadString(Id[Then.DayOfWeek()])); else { sprintf_s(s, sizeof(s), "%s", Val); return s; } sprintf_s(s+Ch, sizeof(s)-Ch, ", %s", Val); return s; } int CalSorter(TimePeriod *a, TimePeriod *b) { return a->s.Compare(&b->s); } bool Calendar::SummaryOfToday(ScribeWnd *App, LVariant &v) { bool Status = false; LArray Sources; if (App && App->GetCalendarSources(Sources)) { LDateTime Now; Now.SetNow(); LDateTime Next = Now; Next.AddMonths(1); LArray e; for (unsigned i=0; iGetEvents(Now, Next, e); } if (e.Length()) { e.Sort(CalSorter); LStringPipe p; p.Print("\n"); for (unsigned n=0; nGetField(FIELD_CAL_SUBJECT, Subject); LColour Base32 = c->GetColour(); auto Edge = Base32.Mix(LColour(L_WORKSPACE), 0.85f); sprintf_s(Back, sizeof(Back), "#%2.2x%2.2x%2.2x", Edge.r(), Edge.g(), Edge.b()); sprintf_s(Fore, sizeof(Fore), "#%2.2x%2.2x%2.2x", Base32.r(), Base32.g(), Base32.b()); p.Print("\t
\n" "\t\t%s\n", Fore, Back, Fore, Fore, (Thing*)c, Subject); char Str[256]; const char *Rel = RelativeTime(tp.s); if (Rel) { p.Print("\t\t
%s\n", Rel); } tp.s.Get(Str, sizeof(Str)); p.Print("\t\t
%s\n", Str); tp.e.Get(Str, sizeof(Str)); p.Print("\t\t-
%s\n", Str); } p.Print("
\n"); char *Str = p.NewStr(); if (Str) { v = Str; DeleteArray(Str); Status = true; } } else { char s[256]; sprintf_s(s, sizeof(s), "%s", LLoadString(IDS_NO_EVENTS)); v = s; Status = true; } } return Status; } void Calendar::OnSerialize(bool Write) { // Update reminder list.. Reminders.Delete(this); const char *Rem = NULL; if (GetField(FIELD_CAL_REMINDERS, Rem) && ValidStr(Rem)) { Reminders.Insert(this); } SetImage(GetCalType() == CalTodo ? ICON_TODO : ICON_CALENDAR); if (Write && TodoView) { TodoView->Update(); TodoView->Resort(); } } ////////////////////////////////////////////////////////////////////////////// Calendar::Calendar(ScribeWnd *app, LDataI *object) : Thing(app, object) { DefaultObject(object); Ui = 0; TodoView = 0; Source = 0; SetImage(ICON_CALENDAR); } Calendar::~Calendar() { DeleteObj(TodoView); Reminders.Delete(this); } bool Calendar::GetTimes(LDateTime StartLocal, LDateTime EndLocal, LArray &Times) { if (Calendar::DayStart < 0) InitCalendarView(); ssize_t StartLen = Times.Length(); LDateTime StartUtc = StartLocal; LDateTime EndUtc = EndLocal; StartUtc.ToUtc(); EndUtc.ToUtc(); // int StartTz = StartLocal.GetTimeZone(); TimePeriod w; w.s = StartUtc; w.e = EndUtc; const char *Subj = NULL; GetField(FIELD_CAL_SUBJECT, Subj); TimePeriod BaseUtc; LArray Periods; if (GetField(FIELD_CAL_START_UTC, BaseUtc.s)) { if (!GetField(FIELD_CAL_END_UTC, BaseUtc.e)) { BaseUtc.e = BaseUtc.s; BaseUtc.e.AddHours(1); } LArray Dst; LDateTime::GetDaylightSavingsInfo(Dst, BaseUtc.s, &EndUtc); LAssert(Dst.Length() > 0); Periods.Add(BaseUtc); LDateTime BaseS = BaseUtc.s; LDateTime::DstToLocal(Dst, BaseS); auto BaseTz = BaseS.GetTimeZone(); // Process recur rules int Recur = 0; if (GetField(FIELD_CAL_RECUR, Recur) && Recur) { LDateTime Diff = BaseUtc.e - BaseUtc.s; int FilterFreq = -1; int FilterInterval = 0; int FilterDay = 0; int FilterMonth = 0; const char *FilterYear = 0; const char *FilterPos = 0; int EndType = 0; LDateTime EndDate; int EndCount = 0; GetField(FIELD_CAL_RECUR_FREQ, FilterFreq); GetField(FIELD_CAL_RECUR_INTERVAL, FilterInterval); GetField(FIELD_CAL_RECUR_FILTER_DAYS, FilterDay); GetField(FIELD_CAL_RECUR_FILTER_MONTHS, FilterMonth); GetField(FIELD_CAL_RECUR_FILTER_YEARS, FilterYear); GetField(FIELD_CAL_RECUR_FILTER_POS, FilterPos); GetField(FIELD_CAL_RECUR_END_TYPE, EndType); GetField(FIELD_CAL_RECUR_END_DATE, EndDate); GetField(FIELD_CAL_RECUR_END_COUNT, EndCount); LDateTime CurUtc = BaseUtc.s; const char *Error = 0; int Count = 0; while (!Error) { // Advance the current date by interval * freq switch (FilterFreq) { case CalFreqDays: CurUtc.AddDays(FilterInterval); break; case CalFreqWeeks: CurUtc.AddDays(FilterInterval * 7); break; case CalFreqMonths: CurUtc.AddMonths(FilterInterval); break; case CalFreqYears: CurUtc.Year(CurUtc.Year() + FilterInterval); break; default: Error = "Invalid freq."; break; } if (Error || CurUtc > EndUtc) break; // Check against end conditions bool IsEnded = CurUtc > EndUtc; switch (EndType) { case CalEndNever: break; case CalEndOnCount: // count IsEnded = Count >= EndCount - 1; break; case CalEndOnDate: // date IsEnded = CurUtc > EndDate; break; default: // error IsEnded = true; break; } if (IsEnded) break; // Check against filters LDateTime CurLocal = CurUtc; LDateTime::DstToLocal(Dst, CurLocal); // This fixes the current time when it's in a different daylight saves zone. // Otherwise you get events one hour or whatever out of position after DST starts // or ends during the recurring set. int DiffMins = BaseTz - CurLocal.GetTimeZone(); CurLocal.AddMinutes(DiffMins); bool Show = true; if (FilterDay) { int Day = CurLocal.DayOfWeek(); for (int i=0; i<7; i++) { int Bit = 1 << i; if (Day == i && (FilterDay & Bit) == 0) { Show = false; break; } } } if (Show && FilterMonth) { for (int i=0; i<12; i++) { int Bit = 1 << i; if ((CurLocal.Month() == i + 1) && (FilterMonth & Bit) == 0) { Show = false; break; } } } if (Show && ValidStr(FilterYear)) { LToken t(FilterYear, " ,;:"); Show = false; for (unsigned i=0; i= MAX_RECUR) break; TimePeriod &p = Periods.New(); p.s = CurLocal; p.e = CurLocal; p.e.AddHours(Diff.Hours()); p.e.AddMinutes(Diff.Minutes()); } Count++; } } // Now process periods into multiday segments if needed for (unsigned k=0; k Calendar::WorkDayEnd) { t.e.Hours(23); t.e.Minutes(59); t.e.Seconds(59); } else { t.e.Hours(Calendar::WorkDayEnd); } if (t.Overlap(w)) { t.c = this; Times.Add(t); } } else if (i.IsSameDay(n.e)) { // End day TimePeriod t; t.s = n.e; t.e = n.e; int h = t.s.Hours(); if (h < Calendar::WorkDayStart) { t.s.Hours(0); t.s.Minutes(0); t.s.Seconds(0); } else { t.s.Hours(Calendar::WorkDayStart); } if (t.Overlap(w)) { t.c = this; Times.Add(t); } break; } else { // Middle day TimePeriod t; t.s = i; t.s.Hours(Calendar::WorkDayStart); t.s.Minutes(0); t.s.Seconds(0); t.e = i; t.e.Hours(Calendar::WorkDayEnd); t.e.Minutes(0); t.e.Seconds(0); if (t.Overlap(w)) { t.c = this; Times.Add(t); } } } } else if (n.Overlap(w)) { n.c = this; Times.Add(n); } } } return (int)Times.Length() > StartLen; } CalendarType Calendar::GetCalType() { CalendarType Type = CalEvent; GetField(FIELD_CAL_TYPE, (int&)Type); return Type; } void Calendar::SetCalType(CalendarType Type) { SetField(FIELD_CAL_TYPE, (int)Type); SetImage(Type == CalTodo ? ICON_TODO : ICON_CALENDAR); } LColour Calendar::GetColour() { if (GetObject()) { int64 c = GetObject()->GetInt(FIELD_COLOUR); if (c >= 0) { return LColour((uint32_t)c, 32); } } if (Source) return Source->GetColour(); return LColour(L_LOW); } void Calendar::OnPaintView(LSurface *pDC, LFont *Font, LRect *Pos, TimePeriod *Period) { LRect p = *Pos; const char *Title = "..."; LDateTime Now; float Sx = 1.0; float Sy = 1.0; if (pDC->IsPrint()) { auto DcDpi = pDC->GetDpi(); auto SrcDpi = LScreenDpi(); Sx = (float)DcDpi.x / SrcDpi.x; Sy = (float)DcDpi.y / SrcDpi.y; } float Scale = Sx < Sy ? Sx : Sy; Now.SetNow(); GetField(FIELD_CAL_SUBJECT, Title); bool Delayed = GetObject() ? GetObject()->GetInt(FIELD_STATUS) == Store3Delayed : false; LColour Grey(192, 192, 192); auto View = GetView(); if (View && View->Selection.HasItem(this)) { // selected LColour f = L_FOCUS_SEL_FORE; LColour b = L_FOCUS_SEL_BACK; if (Delayed) { f = f.Mix(Grey); b = b.Mix(Grey); } pDC->Colour(f); pDC->Box(&p); p.Inset(1, 1); pDC->Colour(b); Font->Colour(f, b); } else { LColour Text(0x80, 0x80, 0x80); LColour Base = GetColour(); LColour Qtr = GdcMixColour(Base, LColour(L_WORKSPACE), 0.1f); LColour Half = GdcMixColour(Base, LColour(L_WORKSPACE), 0.4f); // not selected LColour f, b; if (Period && Now < Period->s) { // future entry (full colour) f = Base; b = Half; } else { // historical entry (half strength colour) f = Half; b = Qtr; } if (Delayed) { f = f.Mix(Grey); b = b.Mix(Grey); } pDC->Colour(f); pDC->Box(&p); p.Inset(1, 1); pDC->Colour(b); Font->Colour(Text, b); } pDC->Rectangle(&p); p.Inset((int)SX(1), (int)SY(1)); Font->Transparent(false); LDisplayString ds(Font, Title); float Ht = p.Y() > 0 ? (float)ds.Y() / p.Y() : 1.0f; if (Ht < 0.75f) p.Inset((int)SX(3), (int)SY(3)); else if (Ht < 0.95f) p.Inset((int)SX(1), (int)SY(1)); ds.Draw(pDC, p.x1, p.y1, &p); ViewPos.Union(Pos); } Thing &Calendar::operator =(Thing &Obj) { if (Obj.GetObject() && GetObject()) { GetObject()->CopyProps(*Obj.GetObject()); } return *this; } bool Calendar::operator ==(Thing &t) { Calendar *c = t.IsCalendar(); if (!c) return false; { LDateTime a, b; if (GetField(FIELD_CAL_START_UTC, a) && c->GetField(FIELD_CAL_START_UTC, b) && a != b) return false; if (GetField(FIELD_CAL_END_UTC, a) && c->GetField(FIELD_CAL_END_UTC, b) && a != b) return false; } { const char *a, *b; if (GetField(FIELD_CAL_SUBJECT, a) && c->GetField(FIELD_CAL_SUBJECT, b) && Stricmp(a, b)) return false; if (GetField(FIELD_CAL_LOCATION, a) && c->GetField(FIELD_CAL_LOCATION, b) && Stricmp(a, b)) return false; } return true; } int Calendar::Compare(LListItem *Arg, ssize_t FieldId) { Calendar *c1 = this; Calendar *c2 = dynamic_cast(Arg); if (c1 && c2) { switch (FieldId) { case FIELD_CAL_START_UTC: case FIELD_CAL_END_UTC: { LDateTime d1, d2; if (!c1->GetField((int)FieldId, d1)) d1.SetNow(); if (!c2->GetField((int)FieldId, d2)) d2.SetNow(); return d1.Compare(&d2); break; } case FIELD_CAL_SUBJECT: case FIELD_CAL_LOCATION: { const char *s1 = "", *s2 = ""; if (c1->GetField((int)FieldId, s1) && c2->GetField((int)FieldId, s2)) { return _stricmp(s1, s2); } break; } } } return 0; } uint32_t Calendar::GetFlags() { return 0; } ThingUi *Calendar::DoUI(MailContainer *c) { if (!Ui) { Ui = new CalendarUi(this); } return Ui; } ThingUi *Calendar::GetUI() { return Ui; } bool Calendar::SetUI(ThingUi *ui) { if (Ui) Ui->Item = NULL; Ui = dynamic_cast(ui); if (Ui) Ui->Item = this; return true; } void Calendar::OnMouseClick(LMouse &m) { if (m.Down()) { if (m.Left()) { if (m.Double()) { DoUI(); } } else if (m.Right()) { auto View = GetView(); DoContextMenu(m, View ? (LView*)View : (LView*)App); } } } void Calendar::OnCreate() { for (auto v: CalendarView::CalendarViews) v->OnContentsChanged(); } bool Calendar::OnDelete() { bool IsTodo = GetCalType() == CalTodo; bool Status = Thing::OnDelete(); if (IsTodo) DeleteObj(TodoView); for (auto v: CalendarView::CalendarViews) v->OnContentsChanged(); return Status; } bool Calendar::GetParentSelection(LList *Lst, List &s) { if (Lst) { return Lst->GetSelection(s); } if (auto View = GetView()) { LArray &a = View->GetSelection(); for (unsigned i=0; i(a[i])); } return true; } return false; } #define IDM_MOVE_TO 4000 void Calendar::DoContextMenu(LMouse &m, LView *Parent) { LSubMenu Sub; LScriptUi s(&Sub); if (s.Sub) { s.Sub->AppendItem(LLoadString(IDS_OPEN), IDM_OPEN); s.Sub->AppendItem(LLoadString(IDS_DELETE), IDM_DELETE); s.Sub->AppendItem(LLoadString(IDS_EXPORT), IDM_EXPORT); s.Sub->AppendSeparator(); s.Sub->AppendItem(LLoadString(IDS_INSPECT), IDM_INSPECT); CalendarView *Cv = dynamic_cast(Parent); if (Cv) { s.Sub->AppendSeparator(); for (unsigned n = 0; nGetName()); s.Sub->AppendItem(m, IDM_MOVE_TO + n++, Source != Cs); } } LArray Callbacks; if (App->GetScriptCallbacks(LThingContextMenu, Callbacks)) { LScriptArguments Args(NULL); Args[0] = new LVariant(App); Args[1] = new LVariant(this); Args[2] = new LVariant(&s); for (unsigned i=0; iExecuteScriptCallback(*Callbacks[i], Args); Args.DeleteObjects(); } m.ToScreen(); int Result = s.Sub->Float(App, m.x, m.y); switch (Result) { default: { if (Cv) { int Idx = Result - IDM_MOVE_TO; if (Idx >= 0 && Idx < (int)CalendarSource::GetSources().Length()) { CalendarSource *Dst = CalendarSource::GetSources().ItemAt(Idx); if (Dst) { FolderCalendarSource *Fsrc = dynamic_cast(Dst); if (Fsrc) { const char *Path = Fsrc->GetPath(); ScribeFolder *DstFolder = App->GetFolder(Path); if (DstFolder) { LArray Items; Items.Add(this); DstFolder->MoveTo(Items); if (Items[0] != (Thing*)this) return; Source = Dst; Cv->Invalidate(); } } else LAssert(!"Impl me."); } } } // Handle any installed callbacks for menu items for (unsigned i=0; iExecuteScriptCallback(Cb, Args); } } break; } case IDM_OPEN: { DoUI(); break; } case IDM_DELETE: { LVariant ConfirmDelete; App->GetOptions()->GetValue(OPT_ConfirmDelete, ConfirmDelete); if (!ConfirmDelete.CastInt32() || LgiMsg(Parent, LLoadString(IDS_DELETE_ASK), AppName, MB_YESNO) == IDYES) { List Del; LList *PList = dynamic_cast(Parent); if (GetParentSelection(PList ? PList : GetList(), Del)) { for (auto i: Del) { Thing *t = dynamic_cast(i); if (t) { t->OnDelete(); } else { CalendarTodoItem *Todo = dynamic_cast(i); if (Todo) { Calendar *c = Todo->GetTodo(); c->OnDelete(); } } } } else { Thing *t = this; t->OnDelete(); } } break; } case IDM_EXPORT: { - ExportAll(GetList(), sMimeVCalendar); + ExportAll(GetList(), sMimeVCalendar, NULL); break; } case IDM_INSPECT: { new ObjectInspector(App, this); break; } } } } const char *Calendar::GetText(int i) { static char s[64]; if (!GetObject()) { LAssert(!"No storage object"); return 0; } int Field = 0; if (FieldArray.Length()) { if (i >= 0 && i < (int) FieldArray.Length()) Field = FieldArray[i]; } else if (i >= 0 && i < CountOf(DefaultCalenderFields)) { Field = DefaultCalenderFields[i]; } if (!Field) return 0; ItemFieldDef *Def = GetFieldDefById(Field); if (!Def) { LAssert(!"Where is the field def?"); return 0; } switch (Def->Type) { case GV_STRING: { return GetObject()->GetStr(Field); break; } case GV_INT32: { sprintf_s(s, sizeof(s), LPrintfInt64, GetObject()->GetInt(Field)); return s; break; } case GV_DATETIME: { // Get any effective timezone for this event. LString Tz = GetObject()->GetStr(FIELD_CAL_TIMEZONE); auto dt = GetObject()->GetDate(Field); if (dt && dt->IsValid()) { LDateTime tmp = *dt; #if DEBUG_DATES printf("GetText.UTC %i = %s\n", i, tmp.Get().Get()); #endif bool UseLocal = true; tmp.SetTimeZone(0, false); if (Tz.Get()) { bool HasPt = false, HasDigit = false; char *e = Tz.Get(); while (strchr(" \t\r\n-.+", *e) || IsDigit(*e)) { if (*e == '.') HasPt = true; if (IsDigit(*e)) HasDigit = true; e++; } if (HasDigit) { if (HasPt) { double TzHrs = Tz.Float(); double i, f = modf(TzHrs, &i); int Mins = (int) ((i * 60) + (f * 60)); tmp.AddMinutes(Mins); } else { int64 i = Tz.Int(); int a = (int)ABS(i); int Mins = (int) (((a / 100) * 60) + (a % 100)); tmp.AddMinutes(i < 0 ? -Mins : Mins); } UseLocal = false; } } if (UseLocal) tmp.ToLocal(); #if DEBUG_DATES printf("GetText.Local %i = %s\n", i, tmp.Get().Get()); #endif tmp.Get(s, sizeof(s)); return s; } break; } default: { LAssert(0); break; } } return 0; } int *Calendar::GetDefaultFields() { return DefaultCalenderFields; } const char *Calendar::GetFieldText(int Field) { return 0; } bool Calendar::Overlap(Calendar *c) { if (c) { LDateTime Ts, Te, Cs, Ce; if (GetField(FIELD_CAL_START_UTC, Ts) && c->GetField(FIELD_CAL_START_UTC, Cs)) { if (!GetField(FIELD_CAL_END_UTC, Te)) { Te = Ts; Te.AddHours(1); } if (!c->GetField(FIELD_CAL_END_UTC, Ce)) { Ce = Cs; Ce.AddHours(1); } if ((Ce <= Ts) || (Cs >= Te)) { return false; } return true; } } return false; } bool Calendar::Save(ScribeFolder *Folder) { bool Status = false; // Check the dates are the right way around LDateTime Start, End; if (GetField(FIELD_CAL_START_UTC, Start) && GetField(FIELD_CAL_END_UTC, End)) { if (End < Start) { SetField(FIELD_CAL_START_UTC, End); SetField(FIELD_CAL_END_UTC, Start); } } + auto ChangeEvent = [&]() + { + auto View = GetView(); + if (View && Status) + { + View->OnContentsChanged(Source); + OnSerialize(true); + } + }; + if (GetObject() && GetObject()->GetInt(FIELD_STORE_TYPE) == Store3Webdav) { auto ParentObj = Folder ? Folder->GetObject() : NULL; Store3Status s = GetObject()->Save(ParentObj); Status = s > Store3Error; if (Status) SetDirty(false); + + ChangeEvent(); } else { if (!Folder) { Folder = GetFolder(); } if (!Folder && App) { Folder = App->GetFolder(FOLDER_CALENDAR); } + // FIXME: This can't wait for WriteThing to finish it's call back... + Status = true; if (Folder) { - if ((Status = (Folder->WriteThing(this) != Store3Error))) - SetDirty(false); + Folder->WriteThing(this, [&](auto Status) + { + if (Status > Store3Error) + SetDirty(false); + ChangeEvent(); + }); } - } - - auto View = GetView(); - if (View && Status) - { - View->OnContentsChanged(Source); - OnSerialize(true); + else ChangeEvent(); } return Status; } // Import/Export bool Calendar::GetFormats(bool Export, LString::Array &MimeTypes) { MimeTypes.Add(sMimeVCalendar); return MimeTypes.Length() > 0; } Thing::IoProgress Calendar::Import(IoProgressImplArgs) { if (Stricmp(mimeType, sMimeVCalendar) && Stricmp(mimeType, sMimeICalendar)) IoProgressNotImpl(); VCal vCal; if (!vCal.Import(GetObject(), stream)) IoProgressError("vCal import failed."); IoProgressSuccess(); } Thing::IoProgress Calendar::Export(IoProgressImplArgs) { if (Stricmp(mimeType, sMimeVCalendar)) IoProgressNotImpl(); VCal vCal; if (!vCal.Export(GetObject(), stream)) IoProgressError("vCal export failed."); IoProgressSuccess(); } char *Calendar::GetDropFileName() { if (!DropFileName) { const char *Name = 0; GetField(FIELD_CAL_SUBJECT, Name); DropFileName.Reset(MakeFileName(Name ? Name : "Cal", "ics")); } return DropFileName; } bool Calendar::GetDropFiles(LString::Array &Files) { bool Status = false; if (GetDropFileName()) { if (!LFileExists(DropFileName)) { LAutoPtr F(new LFile); if (F->Open(DropFileName, O_WRITE)) { F->SetSize(0); Export(AutoCast(F), sMimeVCalendar); } } if (LFileExists(DropFileName)) { Files.Add(DropFileName.Get()); Status = true; } } return Status; } void Calendar::OnPrintHeaders(ScribePrintContext &Context) { LDisplayString *ds = Context.Text(LLoadString(IDS_CAL_EVENT)); LRect &r = Context.MarginPx; int Line = ds->Y(); LDrawListSurface *Page = Context.Pages.Last(); Page->Rectangle(r.x1, Context.CurrentY + (Line * 5 / 10), r.x2, Context.CurrentY + (Line * 6 / 10)); Context.CurrentY += Line; } void Calendar::OnPrintText(ScribePrintContext &Context, LPrintPageRanges &Pages) { // Print document for (ItemFieldDef *Fld = CalendarFields; Fld->FieldId; Fld++) { LString Value; switch (Fld->Type) { case GV_STRING: { Value = GetObject()->GetStr(Fld->FieldId); break; } case GV_DATETIME: { auto Dt = GetObject()->GetDate(Fld->FieldId); if (Dt) { char s[64] = ""; Dt->Get(s, sizeof(s)); Value = s; } break; } default: break; } if (Value) { LString f; const char *Name = LLoadString(Fld->FieldId); f.Printf("%s: %s", Name ? Name : Fld->DisplayText, Value.Get()); // LDisplayString *ds = Context.Text(f); } } } bool Calendar::GetVariant(const char *Name, LVariant &Value, const char *Array) { ScribeDomType Fld = StrToDom(Name); switch (Fld) { // String variables case SdSubject: // Type: String Value = GetObject()->GetStr(FIELD_CAL_SUBJECT); break; case SdTo: // Type: String Value = GetObject()->GetStr(FIELD_TO); break; case SdLocation: // Type: String Value = GetObject()->GetStr(FIELD_CAL_LOCATION); break; case SdUid: // Type: String Value = GetObject()->GetStr(FIELD_UID); break; case SdReminders: // Type: String Value = GetObject()->GetStr(FIELD_CAL_REMINDERS); break; case SdNotes: // Type: String Value = GetObject()->GetStr(FIELD_CAL_NOTES); break; case SdStatus: // Type: String Value = GetObject()->GetStr(FIELD_CAL_STATUS); break; // Int variables case SdType: // Type: Int32 Value = GetObject()->GetInt(FIELD_CAL_TYPE); break; case SdCompleted: // Type: Int32 Value = GetObject()->GetInt(FIELD_CAL_COMPLETED); break; case SdShowTimeAs: // Type: Int32 Value = GetObject()->GetInt(FIELD_CAL_SHOW_TIME_AS); break; case SdRecur: // Type: Int32 Value = GetObject()->GetInt(FIELD_CAL_RECUR); break; case SdRecurFreq: // Type: Int32 Value = GetObject()->GetInt(FIELD_CAL_RECUR_FREQ); break; case SdRecurInterval: // Type: Int32 Value = GetObject()->GetInt(FIELD_CAL_RECUR_INTERVAL); break; case SdRecurEndCount: // Type: Int32 Value = GetObject()->GetInt(FIELD_CAL_RECUR_END_COUNT); break; case SdRecurEndType: // Type: Int32 Value = GetObject()->GetInt(FIELD_CAL_RECUR_END_TYPE); break; case SdRecurFilterDays: // Type: Int32 Value = GetObject()->GetInt(FIELD_CAL_RECUR_FILTER_DAYS); break; case SdRecurFilterMonths: // Type: Int32 Value = GetObject()->GetInt(FIELD_CAL_RECUR_FILTER_MONTHS); break; case SdPrivacy: // Type: Int32 Value = GetObject()->GetInt(FIELD_CAL_PRIVACY); break; case SdAllDay: // Type: Int32 Value = GetObject()->GetInt(FIELD_CAL_ALL_DAY); break; case SdColour: // Type: Int64 Value = GetObject()->GetInt(FIELD_COLOUR); break; // Date time fields case SdStart: // Type: DateTime Value = GetObject()->GetDate(FIELD_CAL_START_UTC); break; case SdEnd: // Type: DateTime Value = GetObject()->GetDate(FIELD_CAL_END_UTC); break; default: return false; } return true; } bool Calendar::SetVariant(const char *Name, LVariant &Value, const char *Array) { ScribeDomType Fld = StrToDom(Name); switch (Fld) { // String variables case SdSubject: Value = GetObject()->SetStr(FIELD_CAL_SUBJECT, Value.Str()); break; case SdTo: Value = GetObject()->SetStr(FIELD_TO, Value.Str()); break; case SdLocation: Value = GetObject()->SetStr(FIELD_CAL_LOCATION, Value.Str()); break; case SdUid: Value = GetObject()->SetStr(FIELD_UID, Value.Str()); break; case SdReminders: Value = GetObject()->SetStr(FIELD_CAL_REMINDERS, Value.Str()); break; case SdNotes: Value = GetObject()->SetStr(FIELD_CAL_NOTES, Value.Str()); break; case SdStatus: Value = GetObject()->SetStr(FIELD_CAL_STATUS, Value.Str()); break; // Int variables case SdType: Value = GetObject()->SetInt(FIELD_CAL_TYPE, Value.CastInt32()); break; case SdCompleted: Value = GetObject()->SetInt(FIELD_CAL_COMPLETED, Value.CastInt32()); break; case SdShowTimeAs: Value = GetObject()->SetInt(FIELD_CAL_SHOW_TIME_AS, Value.CastInt32()); break; case SdRecur: Value = GetObject()->SetInt(FIELD_CAL_RECUR, Value.CastInt32()); break; case SdRecurFreq: Value = GetObject()->SetInt(FIELD_CAL_RECUR_FREQ, Value.CastInt32()); break; case SdRecurInterval: Value = GetObject()->SetInt(FIELD_CAL_RECUR_INTERVAL, Value.CastInt32()); break; case SdRecurEndCount: Value = GetObject()->SetInt(FIELD_CAL_RECUR_END_COUNT, Value.CastInt32()); break; case SdRecurEndType: Value = GetObject()->SetInt(FIELD_CAL_RECUR_END_TYPE, Value.CastInt32()); break; case SdRecurFilterDays: Value = GetObject()->SetInt(FIELD_CAL_RECUR_FILTER_DAYS, Value.CastInt32()); break; case SdRecurFilterMonths: Value = GetObject()->SetInt(FIELD_CAL_RECUR_FILTER_MONTHS, Value.CastInt32()); break; case SdPrivacy: Value = GetObject()->SetInt(FIELD_CAL_PRIVACY, Value.CastInt32()); break; case SdColour: Value = GetObject()->SetInt(FIELD_COLOUR, Value.CastInt32()); break; case SdAllDay: Value = GetObject()->SetInt(FIELD_CAL_ALL_DAY, Value.CastInt32()); break; // Date time fields case SdStart: if (Value.Type == GV_DATETIME) Value = GetObject()->SetDate(FIELD_CAL_START_UTC, Value.Value.Date); else if (Value.Str()) { LDateTime dt; dt.Set(Value.Str()); GetObject()->SetDate(FIELD_CAL_START_UTC, &dt); } break; case SdEnd: if (Value.Type == GV_DATETIME) Value = GetObject()->SetDate(FIELD_CAL_END_UTC, Value.Value.Date); else if (Value.Str()) { LDateTime dt; dt.Set(Value.Str()); GetObject()->SetDate(FIELD_CAL_END_UTC, &dt); } break; default: return false; } return true; } bool Calendar::CallMethod(const char *MethodName, LVariant *ReturnValue, LArray &Args) { // ScribeDomType Fld = StrToDom(MethodName); return Thing::CallMethod(MethodName, ReturnValue, Args); } ////////////////////////////////////////////////////////////////////////////// bool SerializeUi(ItemFieldDef *Defs, LDataI *Object, LViewI *View, bool ToUi) { if (!Object || !Defs || !View) return false; if (ToUi) { for (ItemFieldDef *d = Defs; d->FieldId; d++) { if (d->CtrlId <= 0) continue; switch (d->Type) { case GV_STRING: { auto s = Object->GetStr(d->FieldId); View->SetCtrlName(d->CtrlId, s); break; } case GV_INT32: { int64 i = Object->GetInt(d->FieldId); View->SetCtrlValue(d->CtrlId, i); break; } case GV_DATETIME: { char s[64] = ""; auto dt = Object->GetDate(d->FieldId); if (dt && dt->Year()) dt->Get(s, sizeof(s)); View->SetCtrlName(d->CtrlId, s); break; } default: { LAssert(0); break; } } } } else { for (ItemFieldDef *d = Defs; d->FieldId; d++) { if (d->CtrlId <= 0) continue; switch (d->Type) { case GV_STRING: { const char *s = View->GetCtrlName(d->CtrlId); Object->SetStr(d->FieldId, s); break; } case GV_INT32: { int i = (int)View->GetCtrlValue(d->CtrlId); Object->SetInt(d->FieldId, i); break; } case GV_DATETIME: { const char *s = View->GetCtrlName(d->CtrlId); LDateTime dt; dt.Set(s); Object->SetDate(d->FieldId, &dt); break; } default: { LAssert(0); break; } } } } return true; } ////////////////////////////////////////////////////////////////////////////// class LEditDropDown : public LEdit { public: enum DropType { DropNone, DropDate, DropTime, }; protected: DropType Type; LAutoPtr Popup; public: LEditDropDown(int id) : LEdit(id, 0, 0, 60, 20, NULL) { Type = DropNone; SetObjectName(Res_Custom); } void SetType(DropType type) { Type = type; } const char *GetClass() { return "LEditDropDown"; } void OnMouseClick(LMouse &m) { LEdit::OnMouseClick(m); if (Focus() && Popup && !Popup->Visible()) { Popup->Visible(true); } } void OnFocus(bool f) { if (f) { if (!Popup) { if (Type == DropDate) Popup.Reset(new LDatePopup(this)); else if (Type == DropTime) Popup.Reset(new LTimePopup(this)); if (Popup) { Popup->TakeFocus(false); LPoint p(0, Y()); PointToScreen(p); LRect r = Popup->GetPos(); r.Offset(p.x - r.x1, p.y - r.y1); Popup->SetPos(r); } } if (Popup) Popup->Visible(true); } } int OnNotify(LViewI *Wnd, LNotification n) { /* LgiTrace("OnNotify %s, %i\n", Wnd->GetClass(), Flags); if (Wnd == (LViewI*)Popup) { GDatePopup *Date; if ((Date = dynamic_cast(Popup.Get()))) { LDateTime Ts = Date->Get(); char s[256]; Ts.GetDate(s, sizeof(s)); Name(s); } else LgiTrace("%s:%i - Incorrect pop up type.\n", _FL); } */ return 0; } void OnChildrenChanged(LViewI *Wnd, bool Attaching) { if (Wnd == (LViewI*)Popup.Get() && !Attaching) { // This gets called in the destructor of the Popup, so we should // lose the pointer to it. Popup.Release(); } } }; struct LEditDropDownFactory : public LViewFactory { LView *NewView(const char *Class, LRect *Pos, const char *Text) { if (!_stricmp(Class, "LEditDropDown")) { return new LEditDropDown(-1); } return NULL; } } EditDropDownFactory; ////////////////////////////////////////////////////////////////////////////// class LRecurDlg : public LDialog { CalendarUi *Ui; LEditDropDown *EndOnDate; LCombo *Repeats; bool AcceptNotify; public: LRecurDlg(CalendarUi *ui) { EndOnDate = NULL; Repeats = NULL; AcceptNotify = true; Ui = ui; SetParent(ui); if (LoadFromResource(IDD_CAL_RECUR)) { if (GetViewById(IDC_ON_DATE, EndOnDate)) { EndOnDate->SetType(LEditDropDown::DropDate); LDateTime dt; dt.SetNow(); char s[64]; dt.GetDate(s, sizeof(s)); EndOnDate->Name(s); } if (GetViewById(IDC_REPEATS, Repeats)) { Repeats->Insert(LLoadString(IDS_DAY)); Repeats->Insert(LLoadString(IDS_WEEK)); Repeats->Insert(LLoadString(IDS_MONTH)); Repeats->Insert(LLoadString(IDS_YEAR)); Repeats->Value(1); } Radio(IDC_NEVER); SetCtrlValue(IDC_EVERY, 1); Serialize(false); } } ~LRecurDlg() { } void Serialize(bool Write) { int DayCtrls[] = { IDC_SUNDAY, IDC_MONDAY, IDC_TUESDAY, IDC_WEDNESDAY, IDC_THURSDAY, IDC_FRIDAY, IDC_SATURDAY }; Calendar *c = Ui->GetCal(); LDataI *o = c->GetObject(); if (Write) { // Dlg -> Object int64 v = Repeats->Value(); o->SetInt(FIELD_CAL_RECUR_FREQ, v); v = GetCtrlValue(IDC_EVERY); o->SetInt(FIELD_CAL_RECUR_INTERVAL, v); int DayFilter = 0; for (int i=0; i<7; i++) { if (GetCtrlValue(DayCtrls[i])) DayFilter |= 1 << i; } o->SetInt(FIELD_CAL_RECUR_FILTER_DAYS, DayFilter); if (GetCtrlValue(IDC_NEVER)) { o->SetInt(FIELD_CAL_RECUR_END_TYPE, CalEndNever); } else if (GetCtrlValue(IDC_AFTER)) { o->SetInt(FIELD_CAL_RECUR_END_TYPE, CalEndOnCount); o->SetInt(FIELD_CAL_RECUR_END_COUNT, GetCtrlValue(IDC_AFTER_COUNT)); } else if (GetCtrlValue(IDC_ON)) { o->SetInt(FIELD_CAL_RECUR_END_TYPE, CalEndOnDate); LDateTime e; e.SetDate(GetCtrlName(IDC_ON_DATE)); e.SetTime("11:59:59"); o->SetDate(FIELD_CAL_RECUR_END_DATE, &e); } else LAssert(0); } else { // Object -> Dlg int64 v = o->GetInt(FIELD_CAL_RECUR_FREQ); Repeats->Value(v); OnRepeat(v); v = o->GetInt(FIELD_CAL_RECUR_INTERVAL); SetCtrlValue(IDC_EVERY, MAX(v, 1)); int DayFilter = (int)o->GetInt(FIELD_CAL_RECUR_FILTER_DAYS); for (int i=0; i<7; i++) { SetCtrlValue(DayCtrls[i], (DayFilter & (1 << i)) ? 1 : 0); } CalRecurEndType EndType = (CalRecurEndType) o->GetInt(FIELD_CAL_RECUR_END_TYPE); if (EndType == CalEndOnCount) { Radio(IDC_AFTER); SetCtrlValue(IDC_AFTER_COUNT, o->GetInt(FIELD_CAL_RECUR_END_COUNT)); } else if (EndType == CalEndOnDate) { Radio(IDC_ON); auto e = o->GetDate(FIELD_CAL_RECUR_END_DATE); if (e) SetCtrlName(IDC_ON_DATE, e->GetDate()); } else // Default to never... { Radio(IDC_NEVER); } } } void Radio(int Id) { AcceptNotify = false; SetCtrlValue(IDC_NEVER, Id == IDC_NEVER); SetCtrlValue(IDC_AFTER, Id == IDC_AFTER); SetCtrlEnabled(IDC_AFTER_COUNT, Id == IDC_AFTER); SetCtrlEnabled(IDC_OCCURRENCES, Id == IDC_AFTER); SetCtrlValue(IDC_ON, Id == IDC_ON); SetCtrlEnabled(IDC_ON_DATE, Id == IDC_ON); AcceptNotify = true; } void OnRepeat(int64 Val) { LViewI *v; if (!GetViewById(IDC_TIME_TYPE, v)) return; switch (Val) { case 0: v->Name(LLoadString(IDS_DAYS)); break; case 1: v->Name(LLoadString(IDS_WEEKS)); break; case 2: v->Name(LLoadString(IDS_MONTHS)); break; case 3: v->Name(LLoadString(IDS_YEARS)); break; } v->SendNotify(LNotifyTableLayoutRefresh); } int OnNotify(LViewI *Ctrl, LNotification n) { if (!AcceptNotify) return 0; switch (Ctrl->GetId()) { case IDC_NEVER: case IDC_AFTER: case IDC_ON: Radio(Ctrl->GetId()); break; case IDC_REPEATS: OnRepeat(Ctrl->Value()); break; case IDOK: Serialize(true); // Fall through case IDCANCEL: EndModal(Ctrl->GetId() == IDOK); break; } return 0; } }; /////////////////////////////////////////////////////////////////////////////// struct CalendarUiPriv { CalendarUi *Ui; LEditDropDown *StartDate, *StartTime; LEditDropDown *EndDate, *EndTime; LEdit *Entry; AddressBrowse *Browse; LList *Guests; LList *Reminders; LCombo *ReminderType; LCombo *ReminderUnit; LCombo *CalSelect; LColourSelect *Colour; CalendarUiPriv(CalendarUi *ui) { Ui = ui; StartDate = StartTime = NULL; EndDate = EndTime = NULL; CalSelect = NULL; Entry = NULL; Browse = NULL; Guests = NULL; Colour = NULL; Reminders = NULL; ReminderType = NULL; ReminderUnit = NULL; } void OnGuest() { const char *g = Ui->GetCtrlName(IDC_GUEST_ENTRY); if (!g) return; Mailto mt(Ui->App, g); for (auto a: mt.To) { ListAddr *la = new ListAddr(Ui->App, a); if (la) { Guests->Insert(la); } } Ui->SetCtrlName(IDC_GUEST_ENTRY, ""); } }; CalendarUi::CalendarUi(Calendar *item) : ThingUi(item, LLoadString(IDS_CAL_EVENT)) { NotifyOn = false; Item = item; d = new CalendarUiPriv(this); #if WINNATIVE CreateClassW32("Event", LoadIcon(LProcessInst(), MAKEINTRESOURCE(IDI_EVENT))); #endif if (!Attach(NULL)) LAssert(0); else { LoadFromResource(IDD_CAL, this); AttachChildren(); if (!SerializeState(Item->App->GetOptions(), OPT_CalendarEventPos, true)) { LRect p(100, 100, 900, 700); SetPos(p); MoveToCenter(); } if (GetViewById(IDC_START_DATE, d->StartDate)) { d->StartDate->SetType(LEditDropDown::DropDate); } if (GetViewById(IDC_START_TIME, d->StartTime)) { d->StartTime->SetType(LEditDropDown::DropTime); } if (GetViewById(IDC_END_DATE, d->EndDate)) { d->EndDate->SetType(LEditDropDown::DropDate); } if (GetViewById(IDC_END_TIME, d->EndTime)) { d->EndTime->SetType(LEditDropDown::DropTime); } GetViewById(IDC_GUEST_ENTRY, d->Entry); if (GetViewById(IDC_GUESTS, d->Guests)) { d->Guests->AddColumn(LLoadString(IDS_ADDRESS), 120); d->Guests->AddColumn(LLoadString(IDS_NAME), 120); d->Guests->ShowColumnHeader(false); } if (GetViewById(IDC_REMINDERS, d->Reminders)) { d->Reminders->AddColumn(LLoadString(FIELD_CAL_REMINDER_TIME), 200); d->Reminders->AddColumn(LLoadString(IDC_DELETE), 20); d->Reminders->ShowColumnHeader(false); } if (GetViewById(IDC_REMINDER_TYPE, d->ReminderType)) { d->ReminderType->Insert(LLoadString(IDS_EMAIL)); d->ReminderType->Insert(LLoadString(IDS_POPUP)); d->ReminderType->Insert("Script"); d->ReminderType->Value(1); } SetCtrlValue(IDC_REMINDER_VALUE, 10); if (GetViewById(IDC_REMINDER_UNIT, d->ReminderUnit)) { d->ReminderUnit->Insert(LLoadString(IDS_MINUTES)); d->ReminderUnit->Insert(LLoadString(IDS_HOURS)); d->ReminderUnit->Insert(LLoadString(IDS_DAYS)); d->ReminderUnit->Insert(LLoadString(IDS_WEEKS)); d->ReminderUnit->Value(0); } SetCtrlValue(IDC_AVAILABLE_TYPE, 1); if (GetViewById(IDC_COLOUR, d->Colour)) { LArray Colours; Colours.Add(LColour(84, 132, 237, 255)); Colours.Add(LColour(164, 189, 252, 255)); Colours.Add(LColour(70, 214, 219, 255)); Colours.Add(LColour(122, 231, 191, 255)); Colours.Add(LColour(81, 183, 73, 255)); Colours.Add(LColour(251, 215, 91, 255)); Colours.Add(LColour(255, 184, 120, 255)); Colours.Add(LColour(255, 136, 124, 255)); Colours.Add(LColour(220, 33, 39, 255)); Colours.Add(LColour(219, 173, 255, 255)); Colours.Add(LColour(225, 225, 225, 255)); d->Colour->SetColourList(&Colours); d->Colour->Value(0); } if (GetViewById(IDC_CALENDAR, d->CalSelect)) { LArray Sources; if (Item->App->GetCalendarSources(Sources)) { for (unsigned i=0; iCalSelect->Insert(cs->GetName()); } } } LView *s; if (GetViewById(IDC_SUBJECT, s)) s->Focus(true); LButton *btn; if (GetViewById(IDC_SAVE, btn)) btn->Default(true); OnLoad(); Visible(true); NotifyOn = true; } LViewI *v; if (GetViewById(IDC_REMINDER_TYPE, v)) { LNotification note; OnNotify(v, note); } RegisterHook(this, LKeyEvents); } CalendarUi::~CalendarUi() { if (Item) { if (Item->App) SerializeState(Item->App->GetOptions(), OPT_CalendarEventPos, false); Item->SetUI(NULL); } } bool CalendarUi::OnViewKey(LView *v, LKey &k) { if (k.CtrlCmd()) { switch (k.c16) { case 's': case 'S': { if (k.Down()) OnSave(); return true; } case 'w': case 'W': { if (k.Down()) { OnSave(); Quit(); } return true; } } if (k.vkey == LK_RETURN) { if (!k.Down()) { OnSave(); Quit(); } return true; } } return false; } int CalendarUi::OnCommand(int Cmd, int Event, OsView Window) { return 0; } void CalendarUi::OnPosChange() { LWindow::OnPosChange(); } void CalendarUi::CheckConsistancy() { auto St = CurrentStart(); auto En = CurrentEnd(); if (En < St) { En = St; En.AddMinutes(30); SetCtrlName(IDC_END_DATE, En.GetDate().Get()); SetCtrlName(IDC_END_TIME, En.GetTime().Get()); } } LDateTime CalendarUi::CurrentStart() { LDateTime dt; dt.SetDate(GetCtrlName(IDC_START_DATE)); dt.SetTime(GetCtrlName(IDC_START_TIME)); return dt; } LDateTime CalendarUi::CurrentEnd() { LDateTime dt; dt.SetDate(GetCtrlName(IDC_END_DATE)); dt.SetTime(GetCtrlName(IDC_END_TIME)); return dt; } void CalendarUi::UpdateStartRelative() { LDateTime dt = CurrentStart(); if (dt.IsValid()) { const char *s = RelativeTime(dt); SetCtrlName(IDC_START_REL, s); } } void CalendarUi::UpdateEndRelative() { LDateTime dt = CurrentEnd(); if (dt.IsValid()) { const char *s = RelativeTime(dt); SetCtrlName(IDC_END_REL, s); } } void CalendarUi::UpdateRelative() { CheckConsistancy(); UpdateStartRelative(); UpdateEndRelative(); } int CalendarUi::OnNotify(LViewI *Ctrl, LNotification n) { if (!NotifyOn) return false; switch (Ctrl->GetId()) { case IDC_START_DATE: case IDC_START_TIME: { CheckConsistancy(); UpdateStartRelative(); break; } case IDC_END_DATE: case IDC_END_TIME: { CheckConsistancy(); UpdateEndRelative(); break; } case IDC_REMINDER_TYPE: { LViewI *Label, *Param; if (GetViewById(IDC_PARAM_LABEL, Label) && GetViewById(IDC_REMINDER_PARAM, Param)) { Param->Name(NULL); switch (Ctrl->Value()) { case CalEmail: Label->Name("optional email:"); Param->Enabled(true); break; default: case CalPopup: Label->Name(NULL); Param->Enabled(false); break; case CalScriptCallback: Label->Name("callback fn:"); Param->Enabled(true); break; } } break; } case IDC_ALL_DAY: { int64 AllDay = Ctrl->Value(); LDataI *o = Item->GetObject(); char s[256] = ""; auto dt = o->GetDate(FIELD_CAL_START_UTC); if (dt) { LDateTime tmp = *dt; tmp.ToLocal(true); tmp.GetTime(s, sizeof(s)); SetCtrlName(IDC_START_TIME, AllDay ? "" : s); SetCtrlEnabled(IDC_START_TIME, !AllDay); } dt = o->GetDate(FIELD_CAL_END_UTC); if (dt) { LDateTime tmp = *dt; tmp.ToLocal(true); tmp.GetTime(s, sizeof(s)); SetCtrlName(IDC_END_TIME, AllDay ? "" : s); SetCtrlEnabled(IDC_END_TIME, !AllDay); } break; } case IDC_SUBJECT: { SetCtrlEnabled(IDC_SAVE, ValidStr(Ctrl->Name())); break; } case IDC_SHOW_LOCATION: { LString Loc = GetCtrlName(IDC_LOCATION); if (ValidStr(Loc)) { for (char *c = Loc; *c; c++) { if (Strchr(WhiteSpace, *c)) *c = '+'; } LString Uri; Uri.Printf("http://maps.google.com/?q=%s", Loc.Get()); LExecute(Uri); } break; } case IDC_GUESTS: { if (n.Type == LNotifyItemInsert) { d->Guests->ResizeColumnsToContent(); } else if (n.Type == LNotifyDeleteKey) { List la; d->Guests->GetSelection(la); la.DeleteObjects(); } break; } case IDC_GUEST_ENTRY: { if (d->Entry) { if (ValidStr(d->Entry->Name())) { if (!d->Browse) { d->Browse = new AddressBrowse(Item->App, d->Entry, d->Guests, NULL); } } if (d->Browse) { d->Browse->OnNotify(d->Entry, n); } if (n.Type == LNotifyReturnKey) { d->OnGuest(); } } break; } case IDC_REPEAT: { if (!Ctrl->Value()) break; - LRecurDlg Dlg(this); - if (!Dlg.DoModal()) + auto Dlg = new LRecurDlg(this); + Dlg->DoModal([this, Dlg](auto dlg, auto ctrlId) { - SetCtrlValue(IDC_REPEAT, 0); - } + if (ctrlId) + SetCtrlValue(IDC_REPEAT, 0); + delete dlg; + }); break; } case IDC_TIMEZONE: { auto Tz = Item->GetObject()->GetStr(FIELD_CAL_TIMEZONE); - LInput Dlg(this, Tz, "Time zone:", "Calendar Event Timezone"); - int Result = Dlg.DoModal(); - if (Result) + auto Dlg = new LInput(this, Tz, "Time zone:", "Calendar Event Timezone"); + Dlg->DoModal([this, Dlg](auto dlg, auto Result) { - Item->GetObject()->SetStr(FIELD_CAL_TIMEZONE, Dlg.GetStr()); - Item->SetDirty(); - } + if (Result) + { + Item->GetObject()->SetStr(FIELD_CAL_TIMEZONE, Dlg->GetStr()); + Item->SetDirty(); + } + delete dlg; + }); break; } case IDC_REMINDER_ADD: { const char *v = GetCtrlName(IDC_REMINDER_VALUE); const char *param = GetCtrlName(IDC_REMINDER_PARAM); d->Reminders->Insert ( new ReminderItem ( (CalendarReminderType) d->ReminderType->Value(), v ? (float)atof(v) : 1.0f, (CalendarReminderUnits) d->ReminderUnit->Value(), param ) ); d->Reminders->ResizeColumnsToContent(); break; } case IDC_ADD_GUEST: { d->OnGuest(); break; } case IDC_SAVE: { LViewI *v; if (GetViewById(IDC_GUEST_ENTRY, v) && v->Focus()) { d->OnGuest(); break; } else { OnSave(); // Fall through } } case IDCANCEL: { Quit(); break; } } return 0; } void CalendarUi::OnLoad() { // Sanity check if (!Item) { LAssert(!"No item."); return; } LDataI *o = Item->GetObject(); if (!o) { LAssert(!"No object."); return; } // Copy object values into UI int64 AllDay = false; auto CalSubject = o->GetStr(FIELD_CAL_SUBJECT); SetCtrlName(IDC_SUBJECT, CalSubject); SetCtrlName(IDC_LOCATION, o->GetStr(FIELD_CAL_LOCATION)); SetCtrlName(IDC_DESCRIPTION, o->GetStr(FIELD_CAL_NOTES)); SetCtrlValue(IDC_ALL_DAY, AllDay = o->GetInt(FIELD_CAL_ALL_DAY)); if (d->Guests) { LJson j(o->GetStr(FIELD_ATTENDEE_JSON)); for (auto g: j.GetArray(NULL)) { LAutoPtr la(new ListAddr(App)); if (la) { la->sAddr = g.Get("email"); la->sName = g.Get("name"); d->Guests->Insert(la.Release()); } } d->Guests->ResizeColumnsToContent(); } if (d->Reminders) { d->Reminders->Empty(); LString Rem = o->GetStr(FIELD_CAL_REMINDERS); LString::Array a = Rem.SplitDelimit("\n"); for (unsigned i=0; iSetString(a[i])) d->Reminders->Insert(ri); else delete ri; } } d->Reminders->ResizeColumnsToContent(); } CalendarShowTimeAs Show = (CalendarShowTimeAs)o->GetInt(FIELD_CAL_SHOW_TIME_AS); switch (Show) { case CalFree: SetCtrlValue(IDC_AVAILABLE_TYPE, 0); break; case CalTentative: SetCtrlValue(IDC_AVAILABLE_TYPE, 1); break; default: case CalBusy: SetCtrlValue(IDC_AVAILABLE_TYPE, 2); break; } CalendarPrivacyType Priv = (CalendarPrivacyType)o->GetInt(FIELD_CAL_PRIVACY); switch (Priv) { default: SetCtrlValue(IDC_PRIVACY_TYPE, 0); break; case CalPublic: SetCtrlValue(IDC_PRIVACY_TYPE, 1); break; case CalPrivate: SetCtrlValue(IDC_PRIVACY_TYPE, 2); break; } int64 Col = o->GetInt(FIELD_COLOUR); if (Col >= 0) d->Colour->Value(Col); for (unsigned i=0; iGetName(); LString i_path; if (Item->GetFolder()) i_path = Item->GetFolder()->GetPath(); if (s_path && i_path && _stricmp(s_path, i_path) == 0) { SetCtrlValue(IDC_CALENDAR, i); break; } } char s[256] = ""; auto dt = o->GetDate(FIELD_CAL_START_UTC); if (dt) { #if DEBUG_DATES printf("Load.Start.UTC=%s\n", dt->Get().Get()); #endif LDateTime tmp = *dt; tmp.SetTimeZone(0, false); tmp.ToLocal(true); #if DEBUG_DATES printf("Load.Start.Local=%s\n", tmp.Get().Get()); #endif tmp.GetDate(s, sizeof(s)); SetCtrlName(IDC_START_DATE, s); tmp.GetTime(s, sizeof(s)); SetCtrlName(IDC_START_TIME, AllDay ? "" : s); SetCtrlEnabled(IDC_START_TIME, !AllDay); } dt = o->GetDate(FIELD_CAL_END_UTC); if (dt) { #if DEBUG_DATES printf("Load.End.UTC=%s\n", dt->Get().Get()); #endif LDateTime tmp = *dt; tmp.SetTimeZone(0, false); tmp.ToLocal(true); #if DEBUG_DATES printf("Load.End.Local=%s\n", tmp.Get().Get()); #endif tmp.GetDate(s, sizeof(s)); SetCtrlName(IDC_END_DATE, s); tmp.GetTime(s, sizeof(s)); SetCtrlName(IDC_END_TIME, AllDay ? "" : s); SetCtrlEnabled(IDC_END_TIME, !AllDay); } SetCtrlValue(IDC_REPEAT, o->GetInt(FIELD_CAL_RECUR)); LViewI *ctrl; if (GetViewById(IDC_SUBJECT, ctrl)) { LNotification note(LNotifyValueChanged); OnNotify(ctrl, note); } if (ValidStr(CalSubject)) { LString s; s.Printf("%s - %s", LLoadString(IDS_CAL_EVENT), CalSubject); Name(s); } UpdateRelative(); } void CalendarUi::OnSave() { // Sanity check if (!Item) { LAssert(!"No item."); return; } LDataI *o = Item->GetObject(); if (!o) { LAssert(!"No object."); return; } // Save UI values into object bool AllDay = false; o->SetStr(FIELD_CAL_SUBJECT, GetCtrlName(IDC_SUBJECT)); o->SetStr(FIELD_CAL_LOCATION, GetCtrlName(IDC_LOCATION)); o->SetStr(FIELD_CAL_NOTES, GetCtrlName(IDC_DESCRIPTION)); o->SetInt(FIELD_CAL_ALL_DAY, AllDay = (GetCtrlValue(IDC_ALL_DAY) != 0)); if (d->Guests) { List All; if (d->Guests->GetAll(All)) { LString::Array a; LString Sep(", "); for (auto la: All) { LString e; if (la->Serialize(e, true)) a.Add(e); } LString Guests = Sep.Join(a); o->SetStr(FIELD_TO, Guests); } } if (d->Reminders) { List All; if (d->Reminders->GetAll(All)) { LString::Array a; LString Sep("\n"); for (auto ri: All) { a.Add(ri->GetString()); } LString Reminders = Sep.Join(a); o->SetStr(FIELD_CAL_REMINDERS, Reminders); } else { o->SetStr(FIELD_CAL_REMINDERS, ""); } } int64 Show = GetCtrlValue(IDC_AVAILABLE_TYPE); switch (Show) { case 0: o->SetInt(FIELD_CAL_SHOW_TIME_AS, CalFree); break; case 1: o->SetInt(FIELD_CAL_SHOW_TIME_AS, CalTentative); break; default: case 2: o->SetInt(FIELD_CAL_SHOW_TIME_AS, CalBusy); break; } int64 Priv = GetCtrlValue(IDC_PRIVACY_TYPE); switch (Priv) { default: case 0: o->SetInt(FIELD_CAL_PRIVACY, CalDefaultPriv); break; case 1: o->SetInt(FIELD_CAL_PRIVACY, CalPublic); break; case 2: o->SetInt(FIELD_CAL_PRIVACY, CalPrivate); break; } auto Col = d->Colour->Value(); o->SetInt(FIELD_COLOUR, Col > 0 ? Col : -1); /* FIXME: change calendar item location if the user selects a different cal for (unsigned i=0; iSources.Length(); i++) { CalendarSource *src = d->Sources[i]; LAutoString s_path(src->GetPath()); LAutoString i_path; if (Item->GetFolder()) i_path = Item->GetFolder()->GetPath(); if (s_path && i_path && _stricmp(s_path, i_path) == 0) { SetCtrlValue(IDC_CALENDAR, i); break; } } */ LDateTime dt; dt.SetDate(GetCtrlName(IDC_START_DATE)); dt.SetTime(AllDay ? "0:0:0" : GetCtrlName(IDC_START_TIME)); #if DEBUG_DATES printf("Start.Local=%s\n", dt.Get().Get()); #endif dt.ToUtc(true); #if DEBUG_DATES printf("Start.UTC=%s\n", dt.Get().Get()); #endif o->SetDate(FIELD_CAL_START_UTC, &dt); dt.Empty(); dt.SetDate(GetCtrlName(IDC_END_DATE)); dt.SetTime(AllDay ? "11:59:59" : GetCtrlName(IDC_END_TIME)); #if DEBUG_DATES printf("End.Local=%s\n", dt.Get().Get()); #endif dt.ToUtc(true); #if DEBUG_DATES printf("End.UTC=%s\n", dt.Get().Get()); #endif o->SetDate(FIELD_CAL_END_UTC, &dt); o->SetInt(FIELD_CAL_RECUR, GetCtrlValue(IDC_REPEAT)); Item->Update(); Item->Save(); } ////////////////////////////////////////////////////////////// int LDateTimeViewBase = 1000; class LDateTimeView : public LLayout, public ResObject { LEdit *Edit; LDateDropDown *Date; LTimeDropDown *Time; public: LDateTimeView() : ResObject(Res_Custom) { AddView(Edit = new LEdit(10, 0, 0, 60, 20, NULL)); AddView(Date = new LDateDropDown()); AddView(Time = new LTimeDropDown()); Edit->SetId(LDateTimeViewBase++); Date->SetNotify(Edit); Date->SetId(LDateTimeViewBase++); Time->SetNotify(Edit); Time->SetId(LDateTimeViewBase++); } bool OnLayout(LViewLayoutInfo &Inf) { if (!Inf.Width.Max) { Inf.Width.Max = 220; Inf.Width.Min = 150; } else if (!Inf.Height.Max) { Inf.Height.Max = Inf.Height.Min = LSysFont->GetHeight() + 6; } else return false; return true; } void OnCreate() { AttachChildren(); } void OnPosChange() { LRect c = GetClient(); // int cy = c.Y(); // int py = Y(); LRect tc = c; tc.x1 = tc.x2 - 20; LRect dc = c; dc.x2 = tc.x1 - 1; dc.x1 = dc.x2 - 20; Date->SetPos(dc); Time->SetPos(tc); c.x2 = dc.x1 - 1; Edit->SetPos(c); } const char *Name() { return Edit->Name(); } bool Name(const char *s) { return Edit->Name(s); } }; class LDateTimeViewFactory : public LViewFactory { LView *NewView(const char *Class, LRect *Pos, const char *Text) { if (!_stricmp(Class, "LDateTimeView")) return new LDateTimeView; return NULL; } public: } DateTimeViewFactory; diff --git a/Code/CalendarView.cpp b/Code/CalendarView.cpp --- a/Code/CalendarView.cpp +++ b/Code/CalendarView.cpp @@ -1,3454 +1,3462 @@ #include "Scribe.h" #include "lgi/common/ListItemCheckBox.h" #include "lgi/common/ListItemRadioBtn.h" #include "lgi/common/ColourSelect.h" #include "lgi/common/DropFiles.h" #include "lgi/common/MonthView.h" #include "lgi/common/YearView.h" #include "lgi/common/Array.h" #include "lgi/common/ScrollBar.h" #include "lgi/common/MonthView.h" #include "lgi/common/SkinEngine.h" #include "lgi/common/Combo.h" #include "lgi/common/DisplayString.h" #include "lgi/common/Printer.h" #include "lgi/common/Notifications.h" #include "lgi/common/Box.h" #include "lgi/common/LgiRes.h" #include "CalendarView.h" #include "ScribePageSetup.h" #include "Store3Webdav/WebdavStore.h" #include "../Resources/resdefs.h" #include "resource.h" char ScribeCalendarObject[] = "com.memecode.Calendar"; #define BORDER_YEAR 28 #define THRESHOLD_EDGE 3 // Px #define WEEKEND_TINT_LEVEL 0.97f #define WEEKEND_TINT_COLOUR LColour(0, 0, 0xd0) #define TODAY_TINT_LEVEL 0.95f #define TODAY_TINT_COLOUR LColour(255, 0, 0) ///////////////////////////////////////////////////////////////////////////////////// #define IDM_CAL_DAY 100 #define IDM_CAL_WEEK 101 #define IDM_CAL_MONTH 102 #define IDM_CAL_YEAR 103 #define IDM_PREV 200 #define IDM_BACK 201 #define IDM_TODAY 202 #undef IDM_FORWARD #define IDM_FORWARD 203 #define IDM_NEXT 204 #define IDM_CONFIG 205 #define IDM_TODO 206 #define IDM_15MIN 207 #define IDM_30MIN 208 #define IDM_1HR 209 #define IDM_NEW_EVENT 210 #define IDC_TODO 300 ///////////////////////////////////////////////////////////////////////////////////// void LoadCalendarStringTable() { ShortDayNames[0] = LLoadString(IDS_CAL_SDAY_SUN); ShortDayNames[1] = LLoadString(IDS_CAL_SDAY_MON); ShortDayNames[2] = LLoadString(IDS_CAL_SDAY_TUE); ShortDayNames[3] = LLoadString(IDS_CAL_SDAY_WED); ShortDayNames[4] = LLoadString(IDS_CAL_SDAY_THU); ShortDayNames[5] = LLoadString(IDS_CAL_SDAY_FRI); ShortDayNames[6] = LLoadString(IDS_CAL_SDAY_SAT); FullDayNames[0] = LLoadString(IDS_CAL_LDAY_SUN); FullDayNames[1] = LLoadString(IDS_CAL_LDAY_MON); FullDayNames[2] = LLoadString(IDS_CAL_LDAY_TUE); FullDayNames[3] = LLoadString(IDS_CAL_LDAY_WED); FullDayNames[4] = LLoadString(IDS_CAL_LDAY_THU); FullDayNames[5] = LLoadString(IDS_CAL_LDAY_FRI); FullDayNames[6] = LLoadString(IDS_CAL_LDAY_SAT); ShortMonthNames[0] = LLoadString(IDS_CAL_SMONTH_JAN); ShortMonthNames[1] = LLoadString(IDS_CAL_SMONTH_FEB); ShortMonthNames[2] = LLoadString(IDS_CAL_SMONTH_MAR); ShortMonthNames[3] = LLoadString(IDS_CAL_SMONTH_APR); ShortMonthNames[4] = LLoadString(IDS_CAL_SMONTH_MAY); ShortMonthNames[5] = LLoadString(IDS_CAL_SMONTH_JUN); ShortMonthNames[6] = LLoadString(IDS_CAL_SMONTH_JUL); ShortMonthNames[7] = LLoadString(IDS_CAL_SMONTH_AUG); ShortMonthNames[8] = LLoadString(IDS_CAL_SMONTH_SEP); ShortMonthNames[9] = LLoadString(IDS_CAL_SMONTH_OCT); ShortMonthNames[10] = LLoadString(IDS_CAL_SMONTH_NOV); ShortMonthNames[11] = LLoadString(IDS_CAL_SMONTH_DEC); FullMonthNames[0] = LLoadString(IDS_CAL_LMONTH_JAN); FullMonthNames[1] = LLoadString(IDS_CAL_LMONTH_FEB); FullMonthNames[2] = LLoadString(IDS_CAL_LMONTH_MAR); FullMonthNames[3] = LLoadString(IDS_CAL_LMONTH_APR); FullMonthNames[4] = LLoadString(IDS_CAL_LMONTH_MAY); FullMonthNames[5] = LLoadString(IDS_CAL_LMONTH_JUN); FullMonthNames[6] = LLoadString(IDS_CAL_LMONTH_JUL); FullMonthNames[7] = LLoadString(IDS_CAL_LMONTH_AUG); FullMonthNames[8] = LLoadString(IDS_CAL_LMONTH_SEP); FullMonthNames[9] = LLoadString(IDS_CAL_LMONTH_OCT); FullMonthNames[10] = LLoadString(IDS_CAL_LMONTH_NOV); FullMonthNames[11] = LLoadString(IDS_CAL_LMONTH_DEC); } ///////////////////////////////////////////////////////////////////////////////////// class LColourItem : public LListItemColumn { LListItem *Item; LView *Colour; public: LColourItem(LListItem *i, int c, COLOUR col = -1) : LListItemColumn(i, c) { Item = i; Colour = LViewFactory::Create("LColourSelect"); LAssert(Colour != NULL); LArray colours; for (int n=0; nSetColourList(&colours); Colour->Value(col); } } ~LColourItem() { DeleteObj(Colour); } void OnPaintColumn(ItemPaintCtx &Ctx, int i, LItemColumn *c) { if (!Colour->IsAttached()) { Colour->Attach(Item->GetList()); } Colour->SetPos(Ctx); Colour->Visible(true); } void OnMouseClick(LMouse &m) { Colour->OnMouseClick(m); } int64 Value() { return Colour->Value(); } void Value(int64 i) { Colour->Value(i); } void Disconnect() { Colour->Detach(); } }; ///////////////////////////////////////////////////////////////////////////////////// CalendarTodoItem::CalendarTodoItem(ScribeWnd *app, Calendar *todo) { App = app; Todo = 0; Done = 0; SetTodo(todo); EditingLabel = false; } CalendarTodoItem::~CalendarTodoItem() { if (Todo) { LAssert(Todo->TodoView == this); Todo->TodoView = 0; } } int TodoCompare(LListItem *la, LListItem *lb, NativeInt d) { int Status = 0; CalendarTodoItem *a = dynamic_cast(la); CalendarTodoItem *b = dynamic_cast(lb); if (a && b) { int Col = abs((int)d) - 1; int Mul = d >= 0 ? 1 : -1; bool Atodo = a->Todo != 0; bool Btodo = b->Todo != 0; if (Atodo ^ Btodo) { Status = Btodo - Atodo; } else if (a->Todo && b->Todo) { switch (Col) { case 0: { // Completed int Acomplete = 0; int Bcomplete = 0; a->Todo->GetField(FIELD_CAL_COMPLETED, Acomplete); b->Todo->GetField(FIELD_CAL_COMPLETED, Bcomplete); if (Acomplete != Bcomplete) { Status = Mul * (Acomplete - Bcomplete); } else { goto ByDueDate; } break; } case 1: { // Subject BySubject: const char *Asub = 0; const char *Bsub = 0; a->Todo->GetField(FIELD_CAL_SUBJECT, Asub); b->Todo->GetField(FIELD_CAL_SUBJECT, Bsub); if (Asub && Bsub) { Status = Mul * _stricmp(Asub, Bsub); } break; } case 2: { // Due date ByDueDate: LDateTime Adate; LDateTime Bdate; a->Todo->GetField(FIELD_CAL_START_UTC, Adate); b->Todo->GetField(FIELD_CAL_START_UTC, Bdate); bool Ad = Adate.Year() != 0; bool Bd = Bdate.Year() != 0; if (Ad ^ Bd) { Status = Mul * (Bd - Ad); } else if (Ad && Bd) { Status = Mul * Adate.Compare(&Bdate); } else { goto BySubject; } break; } } } } return Status; } void CalendarTodoItem::Resort() { if (GetList()) { int Sort = 1; for (int i=0; iGetColumns(); i++) { LItemColumn *c = GetList()->ColumnAt(i); if (c) { if (c->Mark() == GLI_MARK_UP_ARROW) { Sort = -(i + 1); break; } else if (c->Mark() == GLI_MARK_DOWN_ARROW) { Sort = i + 1; break; } } } GetList()->Sort(TodoCompare, Sort); } } void CalendarTodoItem::SetTodo(Calendar *todo) { if (Todo) { Todo->TodoView = 0; } Todo = todo; if (Todo) { Todo->TodoView = this; int Completed = false; Todo->GetField(FIELD_CAL_COMPLETED, Completed); Done = new LListItemCheckBox(this, 0, Completed != 0); } else { DeleteObj(Done); } SetImage(Todo ? ICON_TODO : -1); } const char *CalendarTodoItem::GetText(int Col) { if (Todo) { switch (Col) { case 1: { // Name const char *s; if (Todo->GetField(FIELD_CAL_SUBJECT, s)) { return s; } break; } case 2: { // Due LDateTime d; if (Todo->GetField(FIELD_CAL_START_UTC, d)) { d.ToLocal(true); d.Get(DateCache, sizeof(DateCache)); return DateCache; } break; } } } else if (Col == 1) { if (EditingLabel) { EditingLabel = false; } else { return (char*)LLoadString(IDS_CLICK_TO_CREATE); } } return 0; } bool CalendarTodoItem::SetText(const char *s, int Col) { if (Col == 1) { if (Todo) { // Set the name... Todo->SetField(FIELD_CAL_SUBJECT, (char*)s); Resort(); } else { if (ValidStr(s)) { // Create new todo ScribeFolder *Cal = App->GetFolder(FOLDER_CALENDAR); if (Cal) { Thing *t = App->CreateItem(MAGIC_CALENDAR, Cal, false); if (t && t->IsCalendar()) { SetTodo(t->IsCalendar()); Todo->SetCalType(CalTodo); Todo->SetField(FIELD_CAL_SUBJECT, (char*)s); GetList()->Insert(new CalendarTodoItem(App)); Resort(); } } else { LgiMsg(GetList(), "No calendar folder.", AppName); } } else { return false; } } } return LListItem::SetText(s, Col); } void CalendarTodoItem::OnPaint(ItemPaintCtx &Ctx) { if (!Todo) { Ctx.Fore = LColour(L_LOW); Ctx.Back = LColour(L_WORKSPACE); } else if (Done && Done->Value()) { Ctx.Fore = LColour(L_LOW); } else { LDateTime d, Now; if (Todo->GetField(FIELD_CAL_START_UTC, d)) { d.ToLocal(true); Now.SetNow(); if (Now.Compare(&d) > 0) { Ctx.Fore.Set(255, 0, 0); } } } LListItem::OnPaint(Ctx); } void CalendarTodoItem::OnColumnNotify(int Col, int64 Data) { if (Col == 0) { Todo->SetField(FIELD_CAL_COMPLETED, Data ? 100 : 0); Resort(); } } void CalendarTodoItem::OnMouseClick(LMouse &m) { LListItem::OnMouseClick(m); if (m.Down()) { if (m.Left()) { int Col = Todo ? GetList()->ColumnAtX(m.x) : 1; switch (Col) { case 1: { if (!Todo) { EditingLabel = true; } EditLabel(1); break; } default: { if (Todo && m.Double()) { Todo->DoUI(); } break; } } } else if (m.Right()) { if (Todo) { Todo->DoContextMenu(m, GetList()); } } } } ///////////////////////////////////////////////////////////////////////////////////// class LMonthView : public LView, public MonthView { LRect rTitle; LRect rCells; LRect rLeft, rRight; int Cell; CalendarView *CalView; public: LMonthView(int Id, LDateTime *n, CalendarView *calView); void OnCellClick(int Cx, int Cy); void SeekMonth(int Dir); void OnMouseClick(LMouse &m); void OnPaint(LSurface *pDC); }; // This structure encodes the direction to move the cursor // on different key presses. The 'years' isn't implemented in // the OnKey handler. struct CalViewArrow { public: CalendarViewMode Mode; int Key; int Flags; int Hours; int Days; int Months; int Years; }; CalViewArrow Arrows[] = { // Flags, H D M Y // Week view movement {CAL_VIEW_WEEK, LK_LEFT, 0, 0, -1, 0, 0}, {CAL_VIEW_WEEK, LK_RIGHT, 0, 0, 1, 0, 0}, {CAL_VIEW_WEEK, LK_UP, 0, -1, 0, 0, 0}, {CAL_VIEW_WEEK, LK_DOWN, 0, 1, 0, 0, 0}, {CAL_VIEW_WEEK, LK_PAGEUP, 0, 0, -7, 0, 0}, {CAL_VIEW_WEEK, LK_PAGEDOWN,0, 0, 7, 0, 0}, {CAL_VIEW_WEEK, LK_PAGEUP, LGI_EF_CTRL, 0, 0, -1, 0}, {CAL_VIEW_WEEK, LK_PAGEDOWN,LGI_EF_CTRL, 0, 0, 1, 0}, // Month view movement {CAL_VIEW_MONTH, LK_LEFT, 0, 0, -1, 0, 0}, {CAL_VIEW_MONTH, LK_RIGHT, 0, 0, 1, 0, 0}, {CAL_VIEW_MONTH, LK_UP, 0, 0, -7, 0, 0}, {CAL_VIEW_MONTH, LK_DOWN, 0, 0, 7, 0, 0}, {CAL_VIEW_MONTH, LK_PAGEUP, 0, 0, 0, -1, 0}, {CAL_VIEW_MONTH, LK_PAGEDOWN,0, 0, 0, 1, 0}, {CAL_VIEW_MONTH, LK_PAGEUP, LGI_EF_CTRL, 0, 0, 0, -1}, {CAL_VIEW_MONTH, LK_PAGEDOWN,LGI_EF_CTRL, 0, 0, 0, 1}, // Year view movement {CAL_VIEW_YEAR, LK_LEFT, 0, 0, -1, 0, 0}, {CAL_VIEW_YEAR, LK_RIGHT, 0, 0, 1, 0, 0}, {CAL_VIEW_YEAR, LK_UP, 0, 0, 0, -1, 0}, {CAL_VIEW_YEAR, LK_DOWN, 0, 0, 0, 1, 0}, {CAL_VIEW_YEAR, LK_PAGEUP, 0, 0, 0, 0, -1}, {CAL_VIEW_YEAR, LK_PAGEDOWN,0, 0, 0, 0, 1} }; CalViewArrow *GetModeArrow(CalendarViewMode m, LKey &k) { for (int i=0; i CalendarView::CalendarViews; CalendarView::CalendarView(ScribeFolder *folder, int id, LRect *pos, const char *name) { CalendarViews.Add(this); if (Calendar::DayStart < 0) InitCalendarView(); DragMode = DragNone; DragEvent = NULL; LastTsOffset = 0; DayStart = Calendar::DayStart; DayEnd = Calendar::DayEnd; if (!App) App = folder->App; Mode = CAL_VIEW_MONTH; LVariant v; if (App->GetOptions()->GetValue(OPT_CalendarViewMode, v)) Mode = (CalendarViewMode) v.CastInt32(); if (id > 0) SetId(id); if (pos) SetPos(*pos); if (name) Name(name); SetPourLargest(true); Sunken(true); MonthX = 7; MonthY = 5; LFontType Type; Type.GetSystemFont("Small"); Font.Reset(Type.Create()); Cursor.SetNow(); LoadUsers(); OnOptionsChange(); } CalendarView::~CalendarView() { LVariant v; App->GetOptions()->SetValue(OPT_CalendarViewMode, v = (int)Mode); CalendarViews.Delete(this); } void CalendarView::OnOptionsChange() { LVariant v; if (App && App->GetOptions()->GetValue(OPT_CalendarFirstDayOfWeek, v)) FirstDayOfWeek = v.CastInt32(); for (auto c: CalendarViews) { if (c->IsAttached()) c->Invalidate(); } } CalendarView *Calendar::GetView() { if (CalendarView::CalendarViews.Length() == 0) return NULL; return CalendarView::CalendarViews[0]; } void CalendarView::LoadUsers() { LArray Sources; App->GetCalendarSources(Sources); LVariant v; if (App->GetOptions()->GetValue(OPT_CalendarFirstDayOfWeek, v)) FirstDayOfWeek = v.CastInt32(); LDateTime dt = Cursor; Cursor.Set("1/1/1900"); SetCursor(dt); // Do we really need this? // OnContentsChanged(); } void CalendarView::DeleteSource(CalendarSource *cs) { // Delete events out of 'Current' for (unsigned i=0; iGetSource() == cs) { Current.DeleteAt(i--); } } // Delete the reference in the options file... cs->Delete(); // Delete the C++ object and list ref... DeleteObj(cs); // Refresh the screen. Invalidate(); } bool CalendarView::GetEventsBetween(LArray &Events, LDateTime Start, LDateTime End) { bool Status = false; LDateTime EndMinute = End; EndMinute.AddMinutes(-1); for (unsigned i=0; i(GetWindow()); LArray all; if (w->CalLst->GetAll(all)) { for (auto a: all) a->OnPulse(); } } bool CalendarView::OnLayout(LViewLayoutInfo &Inf) { if (Inf.Width.Max == 0) { Inf.Width.Min = -1; Inf.Width.Max = -1; } else { Inf.Height.Min = -1; Inf.Height.Max = -1; } return true; } int CalendarView::OnNotify(LViewI *v, LNotification n) { switch (v->GetId()) { case IDC_VSCROLL: { if (n.Type == LNotifyScrollBarCreate) { SetupScroll(); } Invalidate(); break; } } return 0; } void CalendarView::SelectDropTarget(LDateTime *start, LDateTime *end) { /* if (start && DropStart) { if (*start == *DropStart) { // the same return; } } DeleteObj(DropStart); DeleteObj(DropEnd); if (start) { DropStart = new LDateTime; if (DropStart) { *DropStart = *start; } if (end) { DropEnd = new LDateTime; if (DropEnd) { *DropEnd = *end; } } } Invalidate(); */ } void CalendarView::OnSelect(Calendar *c, bool Ctrl, bool Shift) { if (!Ctrl) { Selection.Length(0); } else { Selection.Delete(c); } if (c) { Selection.Add(c); Invalidate(&c->ViewPos); } } CalendarViewMode CalendarView::GetViewMode() { return Mode; } void CalendarView::SetupScroll() { SetScrollBars(false, Mode == CAL_VIEW_WEEK); if (VScroll) { int Page = (int)(Calendar::DayEnd - Calendar::DayStart); VScroll->SetRange(24); VScroll->SetPage(Page); VScroll->Value(Calendar::DayStart); } } void CalendarView::SetViewMode(CalendarViewMode m) { Mode = m; SetCursor(Cursor); Invalidate(); SetupScroll(); OnCursorChange(); } LDateTime &CalendarView::GetCursor() { return Cursor; } void CalendarView::SetCursor(LDateTime &c) { LDateTime Old = Cursor; Cursor = c; SendNotify(LNotifyCursorChanged); switch (Mode) { default: LAssert(0); break; /* case CAL_VIEW_DAY: { break; } case CAL_VIEW_WEEKDAY: { break; } */ case CAL_VIEW_WEEK: { Start = Cursor; int DayOfWeek = Start.DayOfWeek(); int Diff = FirstDayOfWeek - DayOfWeek; if (Diff > 0) Diff -= 7; Start.AddDays(Diff); First = Start; break; } case CAL_VIEW_MONTH: { First = Cursor; First.Day(1); Start = First; int DayOfWeek = Start.DayOfWeek(); int Diff = FirstDayOfWeek - DayOfWeek; if (Diff > 0) Diff -= 7; Start.AddDays(Diff); break; } case CAL_VIEW_YEAR: { YearView v(&Cursor); v.SetCursor(0, 0); First = v.Get(); Start = Cursor; Start.Day(1); Start.Month(1); break; } } bool YearCh = Old.Year() != Cursor.Year(); bool MthCh = Old.Month() != Cursor.Month() || YearCh; bool DayCh = Old.Day() != Cursor.Day() || MthCh; OnCursorChange(DayCh, MthCh, YearCh); } void CalendarView::OnSourceDelete(CalendarSource *s) { for (size_t i=0; iGetEvents(Start, End, Current); } LHashTbl, bool> InCur; for (unsigned i=0; iSource = 0; } Current.Length(0); for (unsigned i=0; iGetEvents(s, e, Current); } LDateTime::GetDaylightSavingsInfo(Dst, s, &e); LWindow *Wnd = GetWindow(); if (Wnd) { char s[256]; sprintf_s(s, sizeof(s), "%s [%s %i]", LLoadString(IDS_CAL_VIEW), FullMonthNames[Cursor.Month()-1], Cursor.Year()); Wnd->Name(s); } } Invalidate(); } bool CalendarView::OnPrintPage(LPrintDC *pDC, int PageIndex) { LVariant Bx1, By1, Bx2, By2; LOptionsFile *Options = App->GetOptions(); LFontType FontType("Courier New", 8); FontType.GetSystemFont("small"); Bx1 = By1 = Bx2 = By2 = 1.0; // cm if (Options) { // read any options out.. #define GetMargin(opt, var) \ { LVariant v; if (Options->GetValue(opt, v)) var = v.CastDouble(); } GetMargin(OPT_MarginX1, Bx1); GetMargin(OPT_MarginY1, By1); GetMargin(OPT_MarginX2, Bx2); GetMargin(OPT_MarginY2, By2); } LAutoPtr ScreenFont = Font; if (pDC) { // setup device context double CmToInch = 0.393700787; auto ScreenDpi = LScreenDpi(); auto DcDpi = pDC->GetDpi(); double ScaleX = (double)ScreenDpi.x / DcDpi.x; double ScaleY = (double)ScreenDpi.y / DcDpi.y; // LRect c = GetClient(); PrintMargin.x1 = (int) (( (Bx1.CastDouble() * CmToInch) * DcDpi.x ) * ScaleX); PrintMargin.y1 = (int) (( (By1.CastDouble() * CmToInch) * DcDpi.y ) * ScaleY); PrintMargin.x2 = (int) (( pDC->X() - ((Bx2.CastDouble() * CmToInch) * DcDpi.x) ) * ScaleX); PrintMargin.y2 = (int) (( pDC->Y() - ((By2.CastDouble() * CmToInch) * DcDpi.y) ) * ScaleY); // setup font Font.Reset(FontType.Create(pDC)); if (Font) { Font->Colour(L_BLACK, L_WHITE); Font->Create(0, 0, pDC); LDisplayString ds(Font, " "); Font->TabSize(ds.X() * 8); OnPaint(pDC); } } Font = ScreenFont; return false; } int EventSorter(TimePeriod *a, TimePeriod *b) { return a->s.Compare(&b->s); } bool CalendarView::Overlap(LDateTime &Start, LDateTime &End, Calendar *a, Calendar *b) { if (a && b) { if (a->Overlap(b)) { LArray Ap, Bp; a->GetTimes(Start, End, Ap); b->GetTimes(Start, End, Bp); for (unsigned i=0; iFont->Colour(L_TEXT, L_MED); i->Font->Transparent(true); LDisplayString ds(i->Font, (char*)i->Txt); ds.Draw(pDC, i->x, i->y, &r); } void CalendarView::DrawSelectionBox(LSurface *pDC, LRect &r) { int Edge = 4; // int Width = 2; #ifdef WINDOWS int Op = pDC->Op(GDC_XOR); pDC->Colour(Rgba32(0xff, 0xff, 0xff, 0), 32); #else // Other platforms don't have a working XOR operator... so black is // a good default against their default White background. pDC->Colour(Rgb24(0, 0, 0), 24); #endif LRect p; // Top p.Set(r.x1, r.y1, r.x2, r.y1 + Edge - 1); PatternBox(pDC, p); // Bottom p.Set(r.x1, r.y2 - Edge + 1, r.x2, r.y2); PatternBox(pDC, p); // Left p.Set(r.x1, r.y1 + Edge, r.x1 + Edge - 1, r.y2 - Edge); PatternBox(pDC, p); // Right p.Set(r.x2 - Edge + 1, r.y1 + Edge, r.x2, r.y2 - Edge); PatternBox(pDC, p); // WriteDC("c:\\temp\\cal.bmp", pDC); #ifdef WINDOWS pDC->Op(Op); #endif } void CalendarView::OnPaint(LSurface *pDC) { #ifndef MAC // Mac is double buffered anyway LDoubleBuffer Buf(pDC); #endif LColour InMonth(L_WORKSPACE); LColour OutMonth = GdcMixColour(LColour(L_HIGH), LColour(L_WORKSPACE), 0.5); LColour CellEdge(0xc0, 0xc0, 0xc0); LSkinEngine *SkinEngine = LAppInst->SkinEngine; pDC->Colour(Rgb32(255, 255, 255), 32); pDC->Rectangle(); LRect c = GetClient(); c.Offset(-c.x1, -c.y1); float _Sx = 1.0; float _Sy = 1.0; if (pDC->IsPrint()) { c = PrintMargin; auto ScreenDpi = LScreenDpi(); auto DcDpi = pDC->GetDpi(); _Sx = (float)DcDpi.x / ScreenDpi.x; _Sy = (float)DcDpi.y / ScreenDpi.y; } float Scale = _Sx < _Sy ? _Sx : _Sy; SRect(c); Layout = c; if (Mode != CAL_VIEW_YEAR) { Title = c; Title.y2 = Title.y1 + Font->GetHeight() + (int)SY(6); Layout.y1 = Title.y2 + 1; } else { Title.ZOff(-1, -1); } for (auto &c: Current) { c.c->ViewPos.Empty(); } switch (Mode) { /* case CAL_VIEW_DAY: { break; } case CAL_VIEW_WEEKDAY: { break; } */ case CAL_VIEW_WEEK: { // Recalc day start/end int DayVisible = DayEnd - DayStart; DayStart = VScroll ? (int)VScroll->Value() : 6; DayEnd = DayStart + DayVisible; // Setup... LDateTime Dt = Start, Now; Dt.SetTime("0:0:0.0"); Now.SetNow(); LDisplayString ds(Font, "22:00p"); int TimeX = (int)((float)ds.X() + SX(10)); Layout.Set(TimeX, Title.y2 + 1, c.x2, c.y2); // d=0 is the hours column, d=1 is the first day (either sun or mon), etc... d=7 is last day for (int d=0; d<8; d++) { LDateTime Tomorrow = Dt; Tomorrow.AddDays(1); uint64 TodayTs, TomorrowTs; Dt.Get(TodayTs); Tomorrow.Get(TomorrowTs); // Heading int x1 = d ? TimeX + ((d-1) * (c.X()-TimeX) / 7) : 0; int x2 = d ? TimeX + ((d * (c.X()-TimeX) / 7) - 1) : TimeX - 1; LRect p( x1, Title.y1, x2, Title.y2); if (d) { int NameIdx = (FirstDayOfWeek+d-1) % 7; if (SkinEngine) { ColumnPaintInfo i = { Font, FullDayNames[NameIdx], (int)SX(2), (int)SY(3) }; LSkinState State; State.pScreen = pDC; State.Rect = p; State.View = this; SkinEngine->OnPaint_ListColumn(CalendarColumnPaint, &i, &State); } else { LWideBorder(pDC, p, DefaultRaisedEdge); Font->Colour(L_TEXT, L_MED); Font->Transparent(false); LDisplayString ds(Font, (char*)FullDayNames[NameIdx]); ds.Draw(pDC, p.x1 + (int)SX(2), p.y1 + (int)SY(2), &p); } } else { pDC->Colour(L_LOW); pDC->Rectangle(&p); } // Content area p.Set( x1, Title.y2 + 1, x2, c.y2); pDC->Colour(CellEdge); pDC->Line(p.x2, p.y1, p.x2, p.y2); int Divisions = DayEnd - DayStart; int DayOfWeek = Dt.DayOfWeek(); #define HourToY(hour) (p.y1 + (((hour)-(double)DayStart) * p.Y() / Divisions)) for (int h=DayStart; h<=DayEnd; h++) { LRect Hour( x1, (int)HourToY(h), x2, (int)HourToY(h+1)-1); LColour Back = DayOfWeek == 0 || DayOfWeek == 6 ? GdcMixColour(LColour(L_WORKSPACE), WEEKEND_TINT_COLOUR, WEEKEND_TINT_LEVEL) : OutMonth; if (DayOfWeek >= Calendar::WorkWeekStart && DayOfWeek <= Calendar::WorkWeekEnd && h >= Calendar::WorkDayStart && h < Calendar::WorkDayEnd) Back = InMonth; /* bool Select = Focus() && Cur->Day() == Dt.Day() && Cur->Hours() == h; if (Select) { Back = LC_FOCUS_SEL_BACK; } */ bool Today = Now.Day() == Dt.Day() && Now.Month() == Dt.Month() && Now.Year() == Dt.Year(); if (Today) { Back = GdcMixColour(Back, TODAY_TINT_COLOUR, TODAY_TINT_LEVEL); } if (d) { // Draw hourly blocks // Background pDC->Colour(Back); pDC->Rectangle(Hour.x1, Hour.y1, Hour.x2-1, Hour.y2-1); pDC->Colour(CellEdge); pDC->Line(Hour.x1, Hour.y2, Hour.x2, Hour.y2); // Date at the top.. if (h == DayStart) { char s[32]; Dt.GetDate(s, sizeof(s)); Font->Colour(/*Select ? LC_FOCUS_SEL_FORE :*/ L_LOW, L_MED); Font->Transparent(true); LDisplayString ds(Font, s); ds.Draw(pDC, Hour.x1 + 2, Hour.y1 + 2); } } else { // Draw times in the first column char s[32]; if (Now.GetFormat() & GDTF_24HOUR) sprintf_s(s, sizeof(s), "%i:00", h); else sprintf_s(s, sizeof(s), "%i:00%c", h == 0 ? 12 : h > 12 ? h - 12 : h, h >= 12 ? 'p' : 'a'); LRect Temp = Hour; LWideBorder(pDC, Temp, DefaultRaisedEdge); Font->Colour(L_TEXT, L_MED); Font->Transparent(false); LDisplayString ds(Font, s); ds.Draw(pDC, Temp.x1 + (int)SX(2), Temp.y1, &Temp); } } if (d) { // Draw events LArray All; for (uint32_t i=0; i EventArray; LArray Groups; for (uint32_t e=0; e 0) { // Check if it overlaps anything in the previous group EventArray *a = Groups[Groups.Length()-1]; for (uint32_t i=0; iLength(); i++) { TimePeriod *t = (*a)[i]; if (t->Overlap(*Ev)) { // It does... so add it. a->Add(Ev); Ev = 0; break; } } } if (Ev) { // Create new group EventArray *g = new EventArray; if (g) { g->Add(Ev); Groups.Add(g); } } } // Lay the groups of entries out and then paint them for (uint32_t g=0; gOnPaintView(pDC, Font, &Vp, &t); } } // Clean up the memory Groups.DeleteObjects(); for (unsigned i=0; i TomorrowTs) EndSec = (int)((TomorrowTs - TodayTs) / LDateTime::Second64Bit); else EndSec = (int)((rng.EndTs - TodayTs) / LDateTime::Second64Bit); int StartY = (int) HourToY((double)StartSec / LDateTime::HourLength); int EndY = (int) HourToY((double)EndSec / LDateTime::HourLength); /* printf("%f,%f - %i,%i - %i,%i\n", (double)StartSec / LDateTime::HourLength, (double)EndSec / LDateTime::HourLength, StartY, EndY, DayStart, Divisions); */ LRect r(p.x1 + 4, StartY, p.x2 - 4, EndY); DrawSelectionBox(pDC, r); } } // Increment date of day we're painting Dt = Tomorrow; } } break; } case CAL_VIEW_MONTH: { LDateTime *Cur = (DragStart.IsValid()) ? &DragStart : &Cursor; int ObjY = Font->GetHeight() + (int)SY(4); LDateTime i = Start, Now, Tomorrow; Now.SetNow(); i.SetTime("0:0:0.0"); Tomorrow = i; Tomorrow.AddDays(1); char Str[256]; for (int h=0; h<7; h++) { LRect p( Title.x1 + (h * Title.X() / MonthX), Title.y1, Title.x1 + (((h+1) * Title.X() / MonthX) - 1), Title.y2); int NameIdx = (h + FirstDayOfWeek) % 7; if (SkinEngine) { ColumnPaintInfo i = { Font, FullDayNames[NameIdx], (int)SX(2), (int)SY(3) }; LSkinState State; State.pScreen = pDC; State.Rect = p; State.View = this; SkinEngine->OnPaint_ListColumn(CalendarColumnPaint, &i, &State); } else { LWideBorder(pDC, p, DefaultRaisedEdge); Font->Colour(L_TEXT, L_MED); Font->Transparent(false); LDisplayString ds(Font, (char*)FullDayNames[NameIdx]); ds.Draw(pDC, p.x1 + (int)SX(2), p.y1 + (int)SY(2), &p); } } for (int y=0; yMonth(); bool Today = Now.Day() == i.Day() && Now.Month() == i.Month() && Now.Year() == i.Year(); if (i.Day() == Cur->Day() && i.Month() == Cur->Month() && i.Year() == Cur->Year()) { // Is cursor day Back = Focus() ? LColour(L_FOCUS_SEL_BACK) : GdcMixColour(LColour(L_FOCUS_SEL_BACK), LColour(L_WORKSPACE)); Font->Fore(L_FOCUS_SEL_FORE); } else { // normal day Back = (IsInMonth) ? InMonth : OutMonth; Font->Fore(L_TEXT); } if (DayOfWeek == 0 || DayOfWeek == 6) Back = GdcMixColour(Back, WEEKEND_TINT_COLOUR, WEEKEND_TINT_LEVEL); if (Today) Back = GdcMixColour(Back, TODAY_TINT_COLOUR, TODAY_TINT_LEVEL); int Edge = (int)SX(1); if (!pDC->IsPrint() || Back != LColour(L_WORKSPACE)) { pDC->Colour(Back); pDC->Rectangle(p.x1, p.y1, p.x2-Edge, p.y2-Edge); } pDC->Colour(CellEdge); pDC->Rectangle(p.x2-Edge+1, p.y1, p.x2, p.y2); pDC->Rectangle(p.x1, p.y2-Edge+1, p.x2-Edge, p.y2); if (pDC->IsPrint() && x == 0) { pDC->Rectangle(p.x1, p.y1, p.x1+Edge, p.y2-Edge); } Font->Transparent(true); Font->Back(Back); LDisplayString ds(Font, Str); ds.Draw(pDC, p.x1 + (int)SX(2), p.y1 + (int)SX(2)); LRect Clip = p; Clip.Inset(Edge, Edge); pDC->ClipRgn(&Clip); int CalY = ObjY + (int)SY(2); LArray All; uint32_t n; for (n=0; nGetTimes(i, Tomorrow, All); } All.Sort(EventSorter); for (n=0; nOnPaintView(pDC, Font, &Vp, &t); CalY += ObjY + (int)SY(2); } for (auto rng : Ranges) { if (rng.Overlap(i, Tomorrow)) { LRect Vp(p.x1 + (int)SX(3), p.y1 + CalY, p.x2 - (int)SX(4), p.y1 + CalY + ObjY); DrawSelectionBox(pDC, Vp); CalY += ObjY + (int)SY(2); break; } } pDC->ClipRgn(0); i = Tomorrow; Tomorrow.AddDays(1); } } break; } case CAL_VIEW_YEAR: { LDateTime *Cur = (DragStart.IsValid()) ? &DragStart : &Cursor; // int ObjY = Font->GetHeight() + (int)SY(2); LDateTime i = Start, Now, Tomorrow; YearView v(Cur); Now.SetNow(); i.Hours(0); i.Minutes(0); i.Seconds(0); i.Thousands(0); Tomorrow = i; Tomorrow.AddDays(1); Layout.x1 += (int)SX(BORDER_YEAR); int Fy = Font->GetHeight(); char Str[256]; for (int y=0; yTransparent(false); Font->Colour(L_BLACK, L_MED); LDisplayString ds(Font, (char*)ShortMonthNames[y]); ds.Draw(pDC, T.x1 + (int)SX(2), T.y1, &T); for (int x=0; xDay() && t.Month() == Cur->Month() && t.Year() == Cur->Year()) { // Is cursor day Back = Focus() ? LColour(L_FOCUS_SEL_BACK) : GdcMixColour(LColour(L_FOCUS_SEL_BACK), LColour(L_WORKSPACE)); Fore = LColour(L_FOCUS_SEL_FORE); } else { // Other day.. Fore = LColour(L_LOW); Back = v.IsMonth() ? InMonth : OutMonth; } // Weekend tint int Day = t.DayOfWeek(); if (Day == 0 || Day == 6) { Back = GdcMixColour(Back, WEEKEND_TINT_COLOUR, WEEKEND_TINT_LEVEL); } // Today tint.. if (v.IsMonth() && Now.Day() == t.Day() && Now.Month() == t.Month() && Now.Year() == t.Year()) { Back = GdcMixColour(Back, TODAY_TINT_COLOUR, TODAY_TINT_LEVEL); } // Fill cell background pDC->Colour(Back); pDC->Rectangle(p.x1, p.y1, p.x2-1, p.y2-1); pDC->Colour(CellEdge); pDC->Line(p.x2, p.y1, p.x2, p.y2); pDC->Line(p.x1, p.y2, p.x2, p.y2); // Draw day number if (v.IsMonth()) { Font->Transparent(true); Font->Colour(Fore, Back); sprintf_s(Str, sizeof(Str), "%i", t.Day()); LDisplayString ds(Font, Str); ds.Draw(pDC, p.x1+1, p.y1-1); } // Paint events LArray e; LDateTime Tomorrow(t); Tomorrow.AddDays(1); int Cy = p.y1 + Fy; if (v.IsMonth() && GetEventsBetween(e, t, Tomorrow)) { LRect Safe = p; Safe.y2--; for (uint32_t i=0; i Safe.y2) { c->ViewPos.ZOff(-1, -1); } else { LRect Vp(p.x1 + (int)SX(1), Cy, p.x2 - (int)SX(2), Cy + Fy); Vp.Bound(&Safe); c->OnPaintView(pDC, Font, &Vp, &e[i]); Cy += Fy + 1; } } } for (auto rng : Ranges) { if (rng.Overlap(t, Tomorrow)) { LRect Vp(p.x1 + (int)SX(1), Cy, p.x2 - (int)SX(2), Cy + Fy); DrawSelectionBox(pDC, Vp); Cy += Fy + 1; break; } } } } break; } default: { pDC->Colour(L_WHITE); pDC->Rectangle(); break; } } } bool CalendarView::OnKey(LKey &k) { switch (k.vkey) { case LK_ESCAPE: { if (IsCapturing() && k.Down()) { DragStart.Empty(); DragEnd.Empty(); Ranges.Length(0); DragMode = DragNone; Invalidate(); } return true; } default: { switch (k.c16) { case 'w': case 'W': { if (k.CtrlCmd() && k.Down() && GetWindow()) { GetWindow()->Quit(); return true; } break; } } } } /* if (k.c16 != 17 && k.Down()) { // Arrow behaviour CalViewArrow *Arrow = GetModeArrow(Mode, k); if (Arrow) { LDateTime c = Cursor; c.AddHours(Arrow->Hours); c.AddDays(Arrow->Days); c.AddMonths(Arrow->Months); c.AddMonths(Arrow->Years * 12); SetCursor(c); return true; } // Other commands switch (k.c16) { case LK_DELETE: { LVariant ConfirmDelete; App->GetOptions()->GetValue(OPT_ConfirmDelete, ConfirmDelete); if (!ConfirmDelete.CastInt32() || LgiMsg(this, LLoadString(IDS_DELETE_ASK), AppName, MB_YESNO) == IDYES) { OnDelete(); } return true; break; } } } */ return false; } void CalendarView::OnDelete() { Calendar *c; while ((c=Selection.First())) { c->OnDelete(); Selection.Delete(c); } } Calendar *CalendarView::CalendarAt(int x, int y) { switch (Mode) { default: LAssert(0); break; /* case CAL_VIEW_DAY: { break; } case CAL_VIEW_WEEKDAY: { break; } */ case CAL_VIEW_WEEK: case CAL_VIEW_MONTH: case CAL_VIEW_YEAR: { if (Layout.Overlap(x, y)) { for (uint32_t i=0; iViewPos.Overlap(x, y)) { return Current[i].c; } } } break; } } return 0; } LDateTime *CalendarView::TimeAt(int x, int y, int SnapMinutes, LPoint *Cell) { LPoint cell; if (!Cell) Cell = &cell; Cell->x = -1; Cell->y = -1; switch (Mode) { default: break; /* case CAL_VIEW_DAY: { break; } case CAL_VIEW_WEEKDAY: { break; } */ case CAL_VIEW_WEEK: { if (Layout.Overlap(x, y)) { LDateTime *Day = new LDateTime; if (Day) { int64 Page = (int)(Calendar::DayEnd - Calendar::DayStart); int FirstHr = 0; if (VScroll) { FirstHr = (int) VScroll->Value(); Page = VScroll->Page(); } Cell->x = ((x - Layout.x1) * 7) / Layout.X(); // day in week int MinuteOffset = ((y - Layout.y1) * (int)Page * 60) / Layout.Y(); Cell->y = ((y - Layout.y1) * (int)Page) / Layout.Y(); // hour of day LAssert(MinuteOffset / 60 == Cell->y); int Minutes = (FirstHr * 60) + MinuteOffset; if (SnapMinutes) { int Snap = Minutes % SnapMinutes; if (Snap) { Minutes -= Snap; } } *Day = Start; Day->AddDays(Cell->x); Day->Hours(Minutes / 60); Day->Minutes(Minutes % 60); Day->Seconds(0); Day->Thousands(0); return Day; } } break; } case CAL_VIEW_MONTH: { if (Layout.Overlap(x, y)) { LDateTime *Day = new LDateTime; if (Day) { Cell->x = ((x - Layout.x1) * MonthX) / Layout.X(); Cell->y = ((y - Layout.y1) * MonthY) / Layout.Y(); *Day = Start; Day->AddDays( (Cell->y * MonthX) + Cell->x ); Day->SetTime("0:0:0"); return Day; } } break; } case CAL_VIEW_YEAR: { if (Layout.Overlap(x, y)) { LDateTime *Day = new LDateTime; if (Day) { YearView v(&Cursor); Cell->x = (x - Layout.x1) * v.X() / Layout.X(); Cell->y = (y - Layout.y1) * v.Y() / Layout.Y(); v.SetCursor(Cell->x, Cell->y); *Day = v.Get(); Day->SetTime("0:0:0"); return Day; } } break; } } return 0; } LDateTime::GDstInfo *CalendarView::GetDstForDate(LDateTime t) { uint64 ts = t; for (uint32_t i=0; i= Prev && ts < Next) { return &Dst[i]; } } else if (ts > Dst[i].UtcTimeStamp) { return &Dst[i]; } } return 0; } bool CalendarView::HitTest(int x, int y, EventDragMode &mode, Calendar *&event) { if (!Layout.Overlap(x, y)) return false; for (uint32_t i=0; iViewPos.First(); r; r = c->ViewPos.Next()) { if (x >= r->x1 && x <= r->x2) { if (abs(y-r->y1) < THRESHOLD_EDGE) { event = c; mode = DragMoveStart; return true; } if (abs(y-r->y2) < THRESHOLD_EDGE) { event = c; mode = DragMoveEnd; return true; } } } } if (c->ViewPos.Overlap(x, y)) { event = c; mode = DragMoveSelection; return true; } } return false; } Calendar *CalendarView::NewEvent(LDateTime &dtStart, LDateTime &dtEnd) { CalendarSource *Src = FolderCalendarSource::GetCreateIn(); if (!Src) return NULL; Calendar *c = Src->NewEvent(); if (!c) return NULL; LDateTime Start, End; if (dtStart < dtEnd) { Start = dtStart; End = dtEnd; } else { Start = dtEnd; End = dtStart; } LDateTime n = Start; LDateTime::GDstInfo *CurDst = GetDstForDate(Start); if (CurDst) n.SetTimeZone(CurDst->Offset, false); char s[64]; sprintf_s(s, sizeof(s), "%+.1f", (double)n.GetTimeZone() / 60.0); c->SetField(FIELD_CAL_TIMEZONE, s); Start.ToUtc(true); c->SetField(FIELD_CAL_START_UTC, Start); End.ToUtc(true); c->SetField(FIELD_CAL_END_UTC, End); LgiTrace("Start=%s, End=%s\n", Start.Get().Get(), End.Get().Get()); c->OnCreate(); return c; } void CalendarView::OnMouseClick(LMouse &m) { EventDragMode HitMode = DragNone; Calendar *c = NULL; HitTest(m.x, m.y, HitMode, c); bool AlreadySelected = (c) ? Selection.HasItem(c) : false; LAutoPtr Hit(TimeAt(m.x, m.y, SnapMinutes)); /* if (Hit) printf("Hit=%s\n", Hit->Get().Get()); */ if (m.IsContextMenu()) { Focus(true); // Select the item if not already selected when asking for a context menu. if (c && !Selection.HasItem(c)) { Selection.Add(c); Invalidate(&c->ViewPos); } if (!c) { char sHit[64] = ""; if (Hit) Hit->GetTime(sHit, sizeof(sHit)); LString NewMsg; NewMsg.Printf("New event at %s", sHit); LSubMenu s; s.AppendItem(NewMsg, IDM_NEW_EVENT); LSubMenu *snap = s.AppendSub("Snap"); if (snap) { LMenuItem *it = snap->AppendItem("15 minutes", IDM_15MIN); if (it && SnapMinutes == 15) it->Checked(true); it = snap->AppendItem("30 minutes", IDM_30MIN); if (it && SnapMinutes == 30) it->Checked(true); it = snap->AppendItem("1 hour", IDM_1HR); if (it && SnapMinutes == 60) it->Checked(true); } m.ToScreen(); int Cmd = s.Float(this, m); switch (Cmd) { case IDM_NEW_EVENT: { LDateTime End = *Hit; End.AddHours(1); Calendar *ev = NewEvent(*Hit, End); if (ev) ev->DoUI(); break; } case IDM_15MIN: { SnapMinutes = 15; break; } case IDM_30MIN: { SnapMinutes = 30; break; } case IDM_1HR: { SnapMinutes = 60; break; } } } } else { Capture(m.Down()); if (m.Down()) { Focus(true); DragEvent = NULL; if (m.Left()) { Ranges.Length(0); if (!AlreadySelected) { OnSelect(c, m.Ctrl(), m.Shift()); } ClickPt.x = m.x; ClickPt.y = m.y; if (Hit) { DragStart = *Hit; if (HitMode == DragMoveStart) { DragMode = HitMode; DragStart = *c->GetObject()->GetDate(FIELD_CAL_END_UTC); DragEnd = *c->GetObject()->GetDate(FIELD_CAL_START_UTC); DragStart.ToLocal(true); DragEnd.ToLocal(true); DragEvent = c; TsRange &r = Ranges.New(); DragStart.Get(r.StartTs); DragEnd.Get(r.EndTs); } else if (HitMode == DragMoveEnd) { DragMode = HitMode; DragStart = *c->GetObject()->GetDate(FIELD_CAL_START_UTC); DragEnd = *c->GetObject()->GetDate(FIELD_CAL_END_UTC); DragStart.ToLocal(true); DragEnd.ToLocal(true); DragEvent = c; TsRange &r = Ranges.New(); DragStart.Get(r.StartTs); DragEnd.Get(r.EndTs); } else if (Selection.Length()) { DragMode = DragMoveSelection; DragEnd = *Hit; for (unsigned i=0; iGetObject()->GetDate(FIELD_CAL_START_UTC); dt.ToLocal(true); dt.Get(r.StartTs); dt = *s->GetObject()->GetDate(FIELD_CAL_END_UTC); if (dt.IsValid()) { dt.ToLocal(true); dt.Get(r.EndTs); } else { r.EndTs = r.StartTs + ((uint64)60 * 60 * LDateTime::Second64Bit); } // Does this new range overlap any existing range? for (unsigned n=0; n %s\n", DragStart.Get().Get(), DragEnd.Get().Get()); TsRange &r = Ranges.New(); DragStart.Get(r.StartTs); DragEnd.Get(r.EndTs); } } else { DragStart.Empty(); DragEnd.Empty(); } Invalidate(); } } else // up { if (DragStart.IsValid() && DragEnd.IsValid()) { switch (DragMode) { case DragNewEvent: { // New event... // printf("DragStart=%s, DragEnd=%s\n", DragStart.Get().Get(), DragEnd.Get().Get()); c = NewEvent(DragStart, DragEnd); if (c) c->DoUI(); OnContentsChanged(NULL); break; } case DragMoveSelection: { // Adjust the times of the selection by the offset. int64 TsOffset = DragEnd.Ts() - DragStart.Ts(); if (TsOffset != 0) { // TsOffset = 0; for (unsigned i=0; iGetObject(); LDateTime start_dt = *o->GetDate(FIELD_CAL_START_UTC); start_dt.Set(start_dt.Ts() + TsOffset); o->SetDate(FIELD_CAL_START_UTC, &start_dt); LDateTime end_dt = *o->GetDate(FIELD_CAL_END_UTC); end_dt.Set(end_dt.Ts() + TsOffset); o->SetDate(FIELD_CAL_END_UTC, &end_dt); o->SetInt(FIELD_STATUS, Store3Delayed); s->SetDirty(); } OnContentsChanged(NULL); } break; } case DragMoveStart: { if (DragEvent) { if (DragEnd > DragStart) { LDateTime dt = DragStart; dt.ToUtc(true); DragEvent->GetObject()->SetDate(FIELD_CAL_START_UTC, &dt); dt = DragEnd; dt.ToUtc(true); DragEvent->GetObject()->SetDate(FIELD_CAL_END_UTC, &dt); } else { LDateTime dt = DragEnd; dt.ToUtc(true); DragEvent->GetObject()->SetDate(FIELD_CAL_START_UTC, &dt); } DragEvent->SetDirty(); OnContentsChanged(NULL); } break; } case DragMoveEnd: { if (DragEvent) { if (DragEnd < DragStart) { LDateTime dt = DragEnd; dt.ToUtc(true); DragEvent->GetObject()->SetDate(FIELD_CAL_START_UTC, &dt); dt = DragStart; dt.ToUtc(true); DragEvent->GetObject()->SetDate(FIELD_CAL_END_UTC, &dt); } else { LDateTime dt = DragEnd; dt.ToUtc(true); DragEvent->GetObject()->SetDate(FIELD_CAL_END_UTC, &dt); } DragEvent->SetDirty(); OnContentsChanged(NULL); } break; } default: break; } } if (AlreadySelected) { OnSelect(c, m.Ctrl(), m.Shift()); } if (DragStart.IsValid() || Ranges.Length()) { Ranges.Length(0); DragStart.Empty(); DragEnd.Empty(); Invalidate(); } DragEvent = NULL; } } if (c) { c->OnMouseClick(m); } } void CalendarView::OnMouseMove(LMouse &m) { if (IsCapturing()) { LAutoPtr Hit(TimeAt(m.x, m.y, SnapMinutes)); if ( Hit && ( abs(m.x - ClickPt.x) > 4 || abs(m.y - ClickPt.y) > 4 ) ) { #if 1 switch (DragMode) { default: break; case DragMoveStart: case DragMoveEnd: case DragNewEvent: { LDateTime End = *Hit; if (End >= DragStart) End.AddMinutes(SnapMinutes); if (End != DragEnd) { DragEnd = End; if (DragStart < DragEnd) { DragStart.Get(Ranges[0].StartTs); DragEnd.Get(Ranges[0].EndTs); } else { DragStart.Get(Ranges[0].EndTs); DragEnd.Get(Ranges[0].StartTs); } Invalidate(); } break; } case DragMoveSelection: { DragEnd = *Hit; int64 TsOffset = DragEnd.Ts() - DragStart.Ts(); if (LastTsOffset != TsOffset) { LastTsOffset = TsOffset; Ranges.Length(0); for (unsigned i=0; iGetObject()->GetDate(FIELD_CAL_START_UTC); dt.ToLocal(true); dt.Get(r.StartTs); dt = *s->GetObject()->GetDate(FIELD_CAL_END_UTC); if (dt.IsValid()) { dt.ToLocal(true); dt.Get(r.EndTs); } else r.EndTs = r.StartTs + ((uint64)60 * 60 * LDateTime::Second64Bit); r.StartTs += TsOffset; r.EndTs += TsOffset; // Does this new range overlap any existing range? for (unsigned n=0; n &Data, LPoint Pt, int KeyState) { for (unsigned di=0; di in(new LFile); auto type = LGetFileMimeType(f); if (in->Open(f, O_READ)) { auto c = First->NewEvent(); if (c) c->Import(c->AutoCast(in), type); } } } else LgiTrace("%s:%i - Do data sources?\n", _FL); } else if (_stricmp(dd.Format, ScribeCalendarObject) == 0) { if (dd.Data.Length() == 0) continue; LVariant *v = &dd.Data[0]; if (v->Type == GV_BINARY && v->Value.Binary.Length >= sizeof(NativeInt)*2 && DragStart.IsValid()) { NativeInt *d = (NativeInt*) v->Value.Binary.Data; if (d[0] == MAGIC_CALENDAR && d[1] > 0) { char Str[32]; if (Mode == CAL_VIEW_WEEK || Mode == CAL_VIEW_DAY) { DragStart.Get(Str, sizeof(Str)); } else { DragStart.GetDate(Str, sizeof(Str)); } Calendar **c = (Calendar**) (d + 2); for (int i=0; iGetField(FIELD_CAL_START_UTC, s)) { bool HasEnd = c[i]->GetField(FIELD_CAL_END_UTC, e); LDateTime Diff; if (HasEnd) Diff = e - s; else Diff.Hours(1); s.Set(Str); c[i]->SetField(FIELD_CAL_START_UTC, s); if (HasEnd) { e = s + Diff; c[i]->SetField(FIELD_CAL_END_UTC, e); } c[i]->Save(); } } SetCursor(DragStart); } } } } SelectDropTarget(); return 0; } void CalendarView::OnDragInit(bool Success) { } char *CalendarView::TypeOf() { return 0; } bool CalendarView::GetData(LArray &Data) { if (Selection.Length() <= 0) return false; bool Status = false; for (unsigned di=0; di(s); if (t) { Status |= t->GetDropFiles(Files); } } if (Status) { LMouse m; GetMouse(m, true); Status |= CreateFileDrop(&dd, m, Files); } } else if (_stricmp(dd.Format, ScribeCalendarObject) == 0) { ssize_t Size = (2 + Selection.Length()) * sizeof(NativeInt); LArray d; if (d.Length(Size)) { d[0] = MAGIC_CALENDAR; d[1] = Selection.Length(); int n=0; Calendar **l = (Calendar**) (&d[2]); for (unsigned i=0; i 0; } class CalendarViewPrint : public LPrintEvents { CalendarView *cv; public: CalendarViewPrint(CalendarView *v) { cv = v; } bool OnPrintPage(LPrintDC *pDC, int PageIndex) { return cv->OnPrintPage(pDC, PageIndex); } }; ////////////////////////////////////////////////////////////////////////////// LMonthView::LMonthView(int Id, LDateTime *n, CalendarView *calView) : MonthView(n) { FirstDayOfWeek = CalendarView::FirstDayOfWeek; SetId(Id); rTitle.ZOff(-1, -1); rCells.ZOff(-1, -1); Cell = 1; CalView = calView; Set(n); } void LMonthView::OnCellClick(int Cx, int Cy) { SetCursor(Cx, Cy); Invalidate(); SendNotify(LNotifyCursorChanged); } void LMonthView::SeekMonth(int Dir) { LDateTime t = Cursor; t.AddMonths(Dir); Set(&t); Invalidate(); SendNotify(LNotifyCursorChanged); } void LMonthView::OnMouseClick(LMouse &m) { if (m.IsContextMenu()) { } else if (m.Left() && m.Down()) { if (rCells.Overlap(m.x, m.y)) { int x = (m.x - rCells.x1) / Cell; int y = (m.y - rCells.y1) / Cell; OnCellClick(x, y); } else if (rLeft.Overlap(m.x, m.y)) { SeekMonth(-1); } else if (rRight.Overlap(m.x, m.y)) { SeekMonth(1); } } } void LMonthView::OnPaint(LSurface *pDC) { pDC->Colour(L_WORKSPACE); pDC->Rectangle(); Cell = LView::X() / MonthView::X(); LSysFont->Colour(L_TEXT, L_WORKSPACE); LSysFont->Transparent(false); LRect Client = GetClient(); LDisplayString n(LSysBold, Title()); LSysBold->Colour(L_TEXT, L_WORKSPACE); LSysBold->Transparent(true); n.Draw(pDC, 6, 0); rTitle.ZOff(Client.X()-1, n.Y()*3/2-1); rRight = rTitle; rRight.x1 = rRight.x2 - LSysFont->GetHeight() + 1; rLeft = rRight; rLeft.Offset(-rLeft.Y(), 0); rCells.ZOff(Cell * MonthView::X(), Cell * MonthView::Y()); rCells.Offset(0, rTitle.Y()); LDisplayString DsLeft(LSysFont, "<"); DsLeft.Draw(pDC, rLeft.x1, rLeft.y1); LDisplayString DsRight(LSysFont, ">"); DsRight.Draw(pDC, rRight.x1, rRight.y1); LDateTime t = Start; auto Mode = CalView->GetViewMode(); auto CursorPos = MonthView::GetCursor(); for (int y=0; yColour(L_FOCUS_SEL_FORE, L_FOCUS_SEL_BACK); else { LColour Fore, Back; if (InMonth) { Fore = LColour(L_TEXT); Back.Rgb(0xf7, 0xf7, 0xf7); } else { Fore.Rgb(192, 192, 192); Back = LColour(L_WORKSPACE); } if (Mode == CAL_VIEW_WEEK && y == CursorPos.y) { Back = Back.Mix(LColour(L_FOCUS_SEL_BACK), 0.1f); } LSysFont->Colour(Fore, Back); } LRect r; r.ZOff(Cell-2, Cell-2); r.Offset(x*Cell, y*Cell+rCells.y1); int Cx = Cell - Ds.X(); int Cy = Cell - Ds.Y(); Ds.Draw(pDC, r.x1+(Cx>>1), r.y1+(Cy>>1), &r); if (!t.AddDays(1)) { LAssert(!"Add days failed."); break; } } } } ////////////////////////////////////////////////////////////////////////////// LArray CalendarViewWindows; CalendarViewWnd::CalendarViewWnd(ScribeFolder *folder) { CalendarViewWindows.Add(this); App = folder ? folder->App : 0; Cv = 0; Split = 0; Todo = 0; HorBox = NULL; VerBox = NULL; CalLst = NULL; MonthV = NULL; Name("Calendar View"); if (!SerializeState(App->GetOptions(), OPT_CalendarViewPos, true)) { LRect p(0, 0, 600, 500); SetPos(p); MoveToCenter(); } LDateTime Now; Now.SetNow(); OnOptionsChange(); #if !defined(WINDOWS) SetIcon("_cal.png"); #endif auto ToolBar = folder->App->LoadToolbar(this, folder->App->GetResourceFile(ResToolbarFile), folder->App->GetToolbarImgList()); if (ToolBar) { AddView(ToolBar); ToolBar->AppendButton(RemoveAmp(LLoadString(IDS_WEEK)), IDM_CAL_WEEK, TBT_RADIO, true, IMG_CAL_WEEK); ToolBar->AppendButton(RemoveAmp(LLoadString(IDS_MONTH)), IDM_CAL_MONTH, TBT_RADIO, true, IMG_CAL_MONTH); ToolBar->AppendButton(RemoveAmp(LLoadString(IDS_YEAR)), IDM_CAL_YEAR, TBT_RADIO, true, IMG_CAL_YEAR); ToolBar->AppendSeparator(); ToolBar->AppendButton(0, IDM_PREV, TBT_PUSH, true, IMG_CAL_PREV); ToolBar->AppendButton(0, IDM_BACK, TBT_PUSH, true, IMG_CAL_BACK); ToolBar->AppendButton(LLoadString(IDS_TODAY), IDM_TODAY, TBT_PUSH, true, IMG_CAL_TODAY); ToolBar->AppendButton(0, IDM_FORWARD, TBT_PUSH, true, IMG_CAL_FORWARD); ToolBar->AppendButton(0, IDM_NEXT, TBT_PUSH, true, IMG_CAL_NEXT); ToolBar->AppendSeparator(); ToolBar->AppendButton(RemoveAmp(LLoadString(IDS_TODO)), IDM_TODO, TBT_TOGGLE, true, IMG_CAL_TODO); // ToolBar->AppendButton(RemoveAmp(LLoadString(IDS_CONFIGURE)), IDM_CONFIG, TBT_PUSH, true, IMG_CAL_CONFIG); ToolBar->AppendButton(RemoveAmp(LLoadString(IDS_PRINT)), IDM_PRINT, TBT_PUSH, true, IMG_PRINT); ToolBar->AppendButton(RemoveAmp(LLoadString(IDS_HELP)), IDM_HELP, TBT_PUSH, true, IMG_HELP); } // Month control #if defined(WINDOWS) int ColPixels = 160; #else int ColPixels = 180; #endif LCss::Len ColPx(LCss::LenPx, (float)ColPixels); LCss::Len Auto("auto"); LCss::Len Pad("10px"); Cv = new CalendarView(folder, IDC_CALENDAR, NULL, "Calendar View"); AddView(HorBox = new LBox); HorBox->AddView(VerBox = new LBox); HorBox->GetCss(true)->BackgroundColor(LColour(L_WORKSPACE)); VerBox->SetVertical(true); VerBox->GetCss(true)->Width(ColPx); VerBox->GetCss(true)->BackgroundColor(LColour(L_WORKSPACE)); VerBox->AddView(MonthV = new LMonthView(IDC_MONTH_VIEW, &Now, Cv)); MonthV->GetCss(true)->Height(ColPx); LRect r(0, 0, ColPixels-1, ColPixels-1); MonthV->SetPos(r); // c->Padding(Pad); // List of calendar sources... VerBox->AddView(CalLst = new LList(IDC_LIST, 0, 0, 100, 100, "Calendar Sources")); CalLst->AddColumn("x", 20); CalLst->AddColumn("Calendar", 150); // Main layout view HorBox->AddView(Cv); if (Cv) { Cv->OnCursorChange(false, true, false); Cv->Visible(true); switch (Cv->GetViewMode()) { default: break; case CAL_VIEW_WEEK: SetCtrlValue(IDM_CAL_WEEK, 1); break; case CAL_VIEW_MONTH: SetCtrlValue(IDM_CAL_MONTH, 1); break; case CAL_VIEW_YEAR: SetCtrlValue(IDM_CAL_YEAR, 1); break; } } if (Cv && CalLst) { for (unsigned i=0; i(s); CalLst->Insert(Li); bool IsCreateIn = FolderCalendarSource::GetCreateIn() == s; Li->Select(IsCreateIn); } } #if WINNATIVE CreateClassW32("Calendar", LoadIcon(LProcessInst(), MAKEINTRESOURCE(IDI_CALENDER))); #endif if (Attach(0)) { AttachChildren(); Visible(true); LVariant ViewTodo; App->GetOptions()->GetValue(OPT_CalendarViewTodo, ViewTodo); SetCtrlValue(IDM_TODO, ViewTodo.CastInt32()); if (ViewTodo.CastInt32()) { // Layout(); } } } CalendarViewWnd::~CalendarViewWnd() { if (CalLst) CalLst->RemoveAll(); // The CalendarView owns the list items. SerializeState(App->GetOptions(), OPT_CalendarViewPos, false); LVariant s; App->GetOptions()->SetValue(OPT_CalendarViewTodo, s = (int)GetCtrlValue(IDM_TODO)); CalendarViewWindows.Delete(this); } void CalendarViewWnd::OptionsChange() { LVariant v; int FirstDayOfWeek = 0; if (App->GetOptions()->GetValue(OPT_CalendarFirstDayOfWeek, v)) FirstDayOfWeek = v.CastInt32(); if (MonthV) { MonthV->FirstDayOfWeek = CalendarView::FirstDayOfWeek; MonthV->Invalidate(); } } void CalendarViewWnd::OnOptionsChange() { if (!CalendarView::App && CalendarViewWindows.Length() > 0) { CalendarView::App = CalendarViewWindows[0]->App; } CalendarView::OnOptionsChange(); for (auto w: CalendarViewWindows) w->OptionsChange(); } bool CalendarViewWnd::OnKey(LKey &k) { switch (k.vkey) { case 'w': case 'W': { if (k.CtrlCmd() && k.Down()) { Quit(); return true; } break; } } return LWindow::OnKey(k); } int CalendarViewWnd::OnCommand(int Cmd, int Event, OsView WndHandle) { CalViewArrow *Va = 0; switch (Cmd) { case IDM_CAL_DAY: { Cv->SetViewMode(CAL_VIEW_DAY); break; } case IDM_CAL_WEEK: { Cv->SetViewMode(CAL_VIEW_WEEK); break; } case IDM_CAL_MONTH: { Cv->SetViewMode(CAL_VIEW_MONTH); break; } case IDM_CAL_YEAR: { Cv->SetViewMode(CAL_VIEW_YEAR); break; } case IDM_PREV: { LKey k; k.c16 = LK_PAGEUP; k.Flags = LGI_EF_CTRL; Va = GetModeArrow(Cv->GetViewMode(), k); break; } case IDM_BACK: { LKey k; k.c16 = LK_PAGEUP; Va = GetModeArrow(Cv->GetViewMode(), k); break; } case IDM_TODAY: { LDateTime Dt; Dt.SetNow(); Dt.Minutes(0); Dt.Seconds(0); Dt.Thousands(0); Cv->SetCursor(Dt); break; } case IDM_FORWARD: { LKey k; k.c16 = LK_PAGEDOWN; Va = GetModeArrow(Cv->GetViewMode(), k); break; } case IDM_NEXT: { LKey k; k.c16 = LK_PAGEDOWN; k.Flags = LGI_EF_CTRL; Va = GetModeArrow(Cv->GetViewMode(), k); break; } case IDM_PRINT: { auto *Printer = Cv && App ? App->GetPrinter() : NULL; if (Printer) { CalendarViewPrint Cvp(Cv); - Printer->Print(&Cvp, "Scribe Calendar", -1, this); + Printer->Print(&Cvp, NULL, "Scribe Calendar", -1, this); } break; } case IDM_HELP: { App->LaunchHelp("calendar.html"); break; } } if (Va) { LDateTime Cursor = Cv->GetCursor(); Cursor.AddHours(Va->Hours); Cursor.AddDays(Va->Days); Cursor.AddMonths(Va->Months); Cursor.AddMonths(Va->Years * 12); Cv->SetCursor(Cursor); } return 0; } LMessage::Result CalendarViewWnd::OnEvent(LMessage *m) { switch (m->Msg()) { case M_CHANGE: { if (m->A() == IDC_LIST) { // One of the calendar source's has changed... // Check it's still in our list and 'valid' CalendarSource *s = (CalendarSource*)m->B(); LArray All; CalLst->GetAll(All); if (All.IndexOf(s) >= 0) Cv->OnContentsChanged(s); } break; } #ifdef WIN32 case WM_CLOSE: { Quit(); return 0; } #endif } return LWindow::OnEvent(m); } LString CalendarViewWnd::LoadString(int id) { return LString(LLoadString(id)).Replace("&",""); } LString CalendarViewWnd::UnusedKey() { LString Key; // Find an unused index for the new source... while (true) { Key.Printf("%s.Source-%i", OPT_CalendarSources, LRand(1000)); if (App->GetOptions()->LockTag(Key, _FL)) App->GetOptions()->Unlock(); else break; } return Key; } int CalendarViewWnd::OnNotify(LViewI *c, LNotification n) { static bool Processing = false; if (Processing) return 0; Processing = true; switch (c->GetId()) { case IDC_CALENDAR: { if (Cv && MonthV && n.Type == LNotifyCursorChanged) { LDateTime t = Cv->GetCursor(); MonthV->Set(&t); MonthV->Invalidate(); } break; } case IDC_MONTH_VIEW: { if (Cv && MonthV && n.Type == LNotifyCursorChanged) { LDateTime t; t = MonthV->Get(); Cv->SetCursor(t); } break; } case IDC_LIST: { if (!CalLst) break; switch (n.Type) { default: break; case LNotifyValueChanged: { if (Cv) { FolderCalendarSource *Src = dynamic_cast(CalLst->GetSelected()); if (Src) Cv->OnContentsChanged(Src); } break; } case LNotifyItemContextMenu: { LMouse m; GetMouse(m); m.ToScreen(); LListItem *Sel = CalLst->GetSelected(); LSubMenu s; if (Sel) { LSubMenu *ColMenu = s.AppendSub("Colour"); if (ColMenu) { BuildMarkMenu( ColMenu, MS_None, 0); } } s.AppendItem(LLoadString(IDS_ADD_LOCAL_CAL_FOLDER), IDM_ADD_LOCAL_CAL); s.AppendItem(LLoadString(IDS_ADD_CAL_URL), IDM_ADD_CAL_URL); s.AppendSeparator(); s.AppendItem(LoadString(IDS_EDIT), IDM_EDIT, Sel != NULL); s.AppendItem(LoadString(IDS_DELETE), IDM_DELETE, Sel != NULL); int Id = s.Float(this, m); switch (Id) { case IDM_ADD_LOCAL_CAL: { - FolderDlg Dlg(this, App, MAGIC_CALENDAR); - if (Dlg.DoModal()) + auto Dlg = new FolderDlg(this, App, MAGIC_CALENDAR); + Dlg->DoModal([this, Dlg](auto dlg, auto ctrlId) { - auto Key = UnusedKey(); - auto Parts = Key.SplitDelimit("."); - - // Create the source... - FolderCalendarSource *cs = new FolderCalendarSource(App, Parts.Last()); - if (cs) + if (ctrlId) { - cs->SetPath(Dlg.Get()); - cs->SetColour(CalendarSource::FindUnusedColour()); - CalLst->Insert(cs); // CalLst doesn't own the ptr - cs->Write(); + auto Key = UnusedKey(); + auto Parts = Key.SplitDelimit("."); + + // Create the source... + FolderCalendarSource *cs = new FolderCalendarSource(App, Parts.Last()); + if (cs) + { + cs->SetPath(Dlg->Get()); + cs->SetColour(CalendarSource::FindUnusedColour()); + CalLst->Insert(cs); // CalLst doesn't own the ptr + cs->Write(); + } + + App->SaveOptions(); } - - App->SaveOptions(); - } + delete dlg; + }); break; } case IDM_ADD_CAL_URL: { - LInput dlg(this); - if (dlg.DoModal()) + auto dlg = new LInput(this); + dlg->DoModal([this, dlg](auto dialog, auto ctrlId) { - auto Key = UnusedKey(); - auto Parts = Key.SplitDelimit("."); - auto Url = dlg.GetStr(); + if (ctrlId) + { + auto Key = UnusedKey(); + auto Parts = Key.SplitDelimit("."); + auto Url = dlg->GetStr(); - // Create the source... - RemoteCalendarSource *cs = new RemoteCalendarSource(App, Parts.Last()); - if (cs) - { - cs->SetColour(CalendarSource::FindUnusedColour()); - cs->SetUri(Url); - CalLst->Insert(cs); // CalLst doesn't own the ptr - cs->Write(); + // Create the source... + RemoteCalendarSource *cs = new RemoteCalendarSource(App, Parts.Last()); + if (cs) + { + cs->SetColour(CalendarSource::FindUnusedColour()); + cs->SetUri(Url); + CalLst->Insert(cs); // CalLst doesn't own the ptr + cs->Write(); + } + + App->SaveOptions(); } - - App->SaveOptions(); - } + delete dialog; + }); break; } case IDM_EDIT: { CalendarSource *Src = dynamic_cast(Sel); if (!Src) break; Src->EditPath(this, Cv); break; } case IDM_DELETE: { CalendarSource *Src = dynamic_cast(Sel); if (Cv && Src) { auto *Lst = Src->LListItem::GetList(); if (Lst) Lst->Remove(Src); Cv->DeleteSource(Src); } break; } default: { int Idx = Id - IDM_MARK_BASE; if (Idx >= 0 && Idx < CountOf(MarkColours32)) { LColour Mc(MarkColours32[Idx], 32); CalendarSource *Src = dynamic_cast(Sel); if (Src) { Src->SetColour(Mc); Src->Write(); } } break; } } break; } } break; } case IDC_TODO: { if (Todo && n.Type == LNotifyItemColumnClicked) { int Col = 0; LMouse m; if (Todo->GetColumnClickInfo(Col, m)) { int Sort = 0; for (int i=0; iGetColumns(); i++) { LItemColumn *c = Todo->ColumnAt(i); if (c) { if (i == Col) { if (c->Mark() == GLI_MARK_DOWN_ARROW) { c->Mark(GLI_MARK_UP_ARROW); Sort = -(i + 1); } else { c->Mark(GLI_MARK_DOWN_ARROW); Sort = i + 1; } } else { c->Mark(GLI_MARK_NONE); } } } Todo->Sort(TodoCompare, Sort); } } break; } } Processing = false; return 0; } ////////////////////////////////////////////////////////////////////////////// void OpenCalender(ScribeFolder *folder) { if (!CalendarView::CalendarViews.Length()) { new CalendarViewWnd(folder); } } void CalendarSource::FolderDelete(ScribeFolder *f) { for (auto s: AllSources) s->OnFolderDelete(f); } LColour CalendarSource::FindUnusedColour() { // MarkColours32 LArray Used; Used.Length(IDM_MARK_MAX); for (auto &s: AllSources) { auto c = s->GetColour(); uint32_t c32 = c.c32(); for (int i=0; i KeyArr; typedef LAutoPtr KeyArrAuto; struct GpgJob { enum JobType { JobGetKeys, JobCheckSig, JobDecrypt, } Type; LViewI *Owner; GpgJob(JobType t) { Type = t; Owner = NULL; } // Get keys: LAutoPtr Emails; // Check sig: LAutoStreamI Msg; LMessage::Param UserValue; // Decrypt LString Password; }; struct LTempFile : public LFile { LString Path; LTempFile(const char *path) { if (Open(path, O_READ)) { Path = path; } } ~LTempFile() { if (Path) { Close(); FileDev->Delete(Path, false); } } }; struct GpgConnectorPriv : public LThread, public LMutex { LThreadEvent Event; private: bool Loop; KeyArr Keys; LArray Work; uint64 KeysTs; public: GpgConnectorPriv() : LThread("GpgConnectorPrivThread"), LMutex("GpgConnectorPrivMutex") { Loop = true; KeysTs = 0; Run(); } ~GpgConnectorPriv() { Loop = false; Event.Signal(); while (!IsExited()) LSleep(1); } void AddWork(GpgJob *j) { if (Lock(_FL)) { Work.Add(j); Unlock(); } } private: LString RunGpg(const char *Args) { LString Output; LSubProcess p(GpgBinPath, Args); if (p.Start(true, false)) { char Buf[256]; while (true) { ssize_t r = p.Read(&Buf, sizeof(Buf)-1); if (r > 0) { Buf[r] = 0; Output += Buf; } else break; } } return Output; } void ParseKeys(KeyArr &k, LString &str) { bool GotDash = false; LString::Array a = str.Split(EOL_SEQUENCE); LString KeyId; for (unsigned i=0; i 0) { if (b[0].Equals("sec") || b[0].Equals("pub")) { if (b.Length() == 4) KeyId = b[2]; } else if (b[0].Equals("uid")) { LString Nm = Ln(21, -1).Strip(); LAutoString Name, Addr; DecodeAddrName(Nm, Name, Addr, NULL); if (ValidStr(Name) && ValidStr(Addr)) { GpgConnector::KeyInfo &Cur = k.New(); Cur.KeyId = KeyId; Cur.Name = Name; Cur.Email = Addr; } else LgiTrace("%s:%i - Error parsing '%s'\n", _FL, Nm.Get()); } } } else if (stristr(Ln, "--------")) { GotDash = true; } } } bool GetKeys() { uint64 Now = LCurrentTime(); if (Now - KeysTs > GPG_KEY_STALE_TIMEOUT) { KeysTs = Now; LString s = RunGpg("-k"); if (s) ParseKeys(Keys, s); s = RunGpg("-K"); if (s) { KeyArr Priv; ParseKeys(Priv, s); LHashTbl, GpgConnector::KeyInfo*> Hash; for (auto &k: Keys) { if (k.Email) Hash.Add(k.Email, &k); else LgiTrace("%s:%i - No email for key?\n", _FL); } for (unsigned i=0; iFlags |= GPG_HAS_PRIV_KEY; } } } return Keys.Length() > 0; } bool Save(const char *Path, LString &s) { LFile f; if (!f.Open(Path, O_WRITE)) return false; f.SetSize(0); return f.Write(s, s.Length()) == s.Length(); } bool Save(const char *Path, LStreamI *s) { LFile f; if (!f.Open(Path, O_WRITE)) return false; f.SetSize(0); LCopyStreamer Cp; int64 SrcSz = s->GetSize(); int64 Copied = Cp.Copy(s, &f); return SrcSz == Copied; } void CheckSignature(LViewI *Owner, LAutoStreamI UserMsg, LMessage::Param UserVal) { LString Msg; int64 Sz = 0; int64 Rd = 0; ptrdiff_t HdrSize = 0; LString Start; LArray Segs; LMime Tmp; LString Hdrs; char *Boundary = NULL; LAutoPtr Resp(new GpgSigCheckResponse); if (!Resp) return; // Not much else we can do. Resp->UserValue = UserVal; if (!UserMsg) { Resp->Error.Printf(LLoadString(IDS_GNUPG_ERR_NOMSG)); goto OnSigCheckError; } // We have to parse out just the part of the email that is signed. And we // can't use the LMime class because that would decode it too much and lose // the exact formatting. Read the whole thing into memory and find the MIME // boundary. Sz = UserMsg->GetSize(); UserMsg->SetPos(0); if (!Msg.Length((int)Sz)) { Resp->Error.Printf("Can't size string to " LPrintfInt64, Sz); goto OnSigCheckError; } for (int64 i=0; iRead(Msg.Get() + i, Msg.Length() - i); if (Rd <= 0) { Resp->Error.Printf("Read error: i=%i, Rd=%i, Sz=" LPrintfInt64, i, Rd, Sz); goto OnSigCheckError; } i += Rd; } HdrSize = Msg.Find("\r\n\r\n"); Hdrs = Msg(0, HdrSize); Tmp.SetHeaders(Hdrs); Boundary = Tmp.GetBoundary(); if (!Boundary) { Resp->Error = LLoadString(IDS_GNUPG_ERR_NO_BOUNDARY); goto OnSigCheckError; } // Now look through the message and find all the segments... Start.Printf("\r\n--%s", Boundary); for (ptrdiff_t i = 0; i < (ptrdiff_t)Msg.Length(); ) { ptrdiff_t Next = Msg.Find(Start, i); if (Next > 0) { // Is it an end or starting boundary? ptrdiff_t k = Next + Start.Length(); LString Chars = Msg(k, k + 2); bool IsEnd = Chars == "--"; if (IsEnd) { if (Segs.Length() == 0) { Resp->Error = LLoadString(IDS_GNUPG_ERR_NO_MIME); goto OnSigCheckError; } Segs.Last().Len = Next - Segs.Last().Start; i = k + 2; } else { if (Segs.Length() > 0) { // Finish last segment Segs.Last().Len = Next - Segs.Last().Start; } char *m = Msg; while (IsWhiteSpace(m[k])) k++; LRange &r = Segs.New(); r.Start = k; r.Len = 0; i = k; } } else break; } if (Segs.Length() == 2) { // char *m = Msg; LRange &Body = Segs[0]; LRange &Sig = Segs[1]; // Get the 2 parts from the whole message.. LString SignedText = Msg(Body.Start, Body.Start + Body.Len); LString Signature = Msg(Sig.Start, Sig.Start + Sig.Len); ptrdiff_t Break = Signature.Find("\r\n\r\n"); if (Break > 0) Signature = Signature(Break + 4, -1); // Save them to files: LFile::Path TextPath = ScribeTempPath(), SigPath = ScribeTempPath(); TextPath += "signed.txt"; SigPath += "signature.txt"; if (!Save(TextPath, SignedText) || !Save(SigPath, Signature)) { Resp->Error = LLoadString(IDS_GNUPG_ERR_SAVE_FAILED); goto OnSigCheckError; } // Now call GnuPG to verify the signature... LString Args; Args.Printf("--verify \"%s\" \"%s\"", SigPath.GetFull().Get(), TextPath.GetFull().Get()); LString Output = RunGpg(Args); if (Output) { LString::Array a = Output.SplitDelimit("\n"); for (unsigned i=0; i= 0) { LString::Array w = Line.SplitDelimit(" "); // Check if the signature is good... if (w.Length() > 2) Resp->SignatureMatch = w[1].Lower() == "good"; // Get identity... w = Line.SplitDelimit("\""); if (w.Length() >= 2 && w[1].Find("@") >= 0) { Resp->Identity = w[1]; } } else if (Line.Lower().Find("signature made") >= 0) { // Get date... LString::Array w = Line.SplitDelimit(" "); int MadeIdx = -1; LString::Array DateParts; for (unsigned i=0; i 0) Resp->TimeStamp.SetDate(w[i]); else if (w[i].Find(":") > 0) { Resp->TimeStamp.SetTime(w[i]); MadeIdx = -1; } else if (w[i].Lower() == "made") MadeIdx = i; else if (MadeIdx >= 0 && (int)i > MadeIdx) DateParts.New() = w[i]; } if (Resp->TimeStamp.Year() == 0 && DateParts.Length() > 0) { LString s = LString(" ").Join(DateParts); Resp->TimeStamp.SetDate(s); } } } } else { Resp->Error = LLoadString(IDS_GNUPG_ERR_NO_OUTPUT); } #ifndef _DEBUG // Clean up temporary files... FileDev->Delete(TextPath, false); FileDev->Delete(SigPath, false); #endif printf("Txt=%s\nSig=%s\n", (const char*)TextPath, (const char*)SigPath); } else { Resp->Error.Printf(LLoadString(IDS_GNUPG_ERR_WRONG_SEGS), Segs.Length()); } OnSigCheckError: Owner->PostEvent(M_GNUPG_SIG_CHECK, (LMessage::Param) Resp.Release()); } void Decrypt(GpgJob *j) { LAutoPtr Resp(new GpgDecryptResponse); if (!Resp || !j) return; Resp->UserValue = j->UserValue; LFile::Path InPath = ScribeTempPath(); InPath += "encrypted.txt"; LFile::Path OutPath = ScribeTempPath(); OutPath += "decrypted.txt"; if (!Save(InPath, j->Msg)) { Resp->Error = "Failed to save file for decrypting."; } else { LString Args; Args.Printf("--batch --passphrase-fd 0 --output \"%s\" --decrypt \"%s\"", OutPath.GetFull().Get(), InPath.GetFull().Get()); LSubProcess Proc(GpgBinPath, Args); if (!Proc.Start(true, true)) { Resp->Error = "Can't start the GnuPG sub-process."; } else { char Buf[256]; LVariant v; if (Proc.GetValue(LDomPropToString(StreamReadable), v) && v.CastInt32() != 0) { ssize_t r = Proc.Read(Buf, sizeof(Buf)-1); Buf[MAX(r, 0)] = 0; } LString PswStr; PswStr.Printf("%s\n", j->Password.Get()); ssize_t w = Proc.Write(PswStr.Get(), PswStr.Length()); if (w < 0) { ssize_t r = Proc.Read(Buf, sizeof(Buf)-1); Buf[MAX(r, 0)] = 0; Resp->Error = Buf[0] ? Buf : "Can't write to the GnuPG sub-process."; } else { ssize_t r = Proc.Read(Buf, sizeof(Buf)-1); Buf[MAX(r, 0)] = 0; int Result = Proc.Wait(); LFile *f; if (Result) { if (Buf[0]) Resp->Error = Buf[0]; else Resp->Error.Printf("GnuPG encryption process failed with code: %i", Result); } else if ( !Resp->Data.Reset(f = new LTempFile(OutPath)) || !f->IsOpen()) { Resp->Data.Reset(); Resp->Error.Printf("Decrypt failed: Can't open '%s' for reading.", OutPath.GetFull().Get()); } else { // Success? } } } } // Clean up input file (encrypted) // The output file will be deleted by the M_GNUPG_DECRYPT handler. if (LFileExists(InPath)) { FileDev->Delete(InPath, false); } j->Owner->PostEvent(M_GNUPG_DECRYPT, (LMessage::Param) Resp.Release()); } int Main() { LThreadEvent::WaitStatus s; while ((s = Event.Wait()) == LThreadEvent::WaitSignaled) { if (!Loop) break; LAutoPtr j; if (Lock(_FL)) { if (Work.Length()) { j.Reset(Work[0]); Work.DeleteAt(0, true); } Unlock(); } if (j) { switch (j->Type) { case GpgJob::JobGetKeys: { if (Keys.Length() == 0) GetKeys(); LHashTbl,bool> Map; for (unsigned i=0; iEmails->Length(); i++) { Map.Add((*j->Emails)[i], true); } KeyArrAuto Inf(new KeyArr); for (unsigned i=0; iNew(); out.Email = in.Email.Get(); out.Name = in.Name.Get(); out.KeyId = in.KeyId.Get(); out.Flags = in.Flags; } j->Owner->PostEvent(M_GNUPG_KEY_INFO, 0, (LMessage::Param)Inf.Release()); break; } case GpgJob::JobCheckSig: { CheckSignature(j->Owner, j->Msg, j->UserValue); break; } case GpgJob::JobDecrypt: { Decrypt(j); break; } default: { LAssert(!"Invalid type."); break; } } } } return 0; } }; bool GpgConnector::IsInstalled() { #ifdef WINNATIVE char *Str = NULL; errno_t Err = _dupenv_s(&Str, NULL, "PATH"); if (Err) { LgiTrace("%s:%i - _dupenv_s failed with %i\n", _FL, Err); return false; } LString Path = Str; free(Str); #else LString Path = getenv("PATH"); #ifdef MAC Path += LGI_PATH_SEPARATOR"/opt/local/bin"; #endif #endif LString::Array Parts = Path.Split(LGI_PATH_SEPARATOR); for (unsigned i = 0; i < Parts.Length(); i++) { LFile::Path p(Parts[i]); p += GpgBin; if (p.IsFile()) { GpgBinPath = p; return true; } } return false; } GpgConnector::GpgConnector() { d = new GpgConnectorPriv; } GpgConnector::~GpgConnector() { delete d; } bool GpgConnector::GetKeyInfo(LViewI *Target, LString::Array &Emails) { if (!Target #if LGI_VIEW_HANDLE || !Target->Handle() #endif ) { LAssert(!"Invalid target."); return false; } if (!d->Lock(_FL)) return false; GpgJob *j = new GpgJob(GpgJob::JobGetKeys); if (j) { j->Emails.Reset(new LString::Array(Emails)); j->Owner = Target; d->AddWork(j); } d->Unlock(); return d->Event.Signal(); } bool GpgConnector::CheckSignature(LViewI *Target, LAutoStreamI Rfc822Msg, LMessage::Param UserVal) { if (!Target #if LGI_VIEW_HANDLE || !Target->Handle() #endif ) { LAssert(!"Invalid target."); return false; } if (!d->Lock(_FL)) return false; GpgJob *j = new GpgJob(GpgJob::JobCheckSig); if (j) { j->Owner = Target; j->Msg = Rfc822Msg; j->UserValue = UserVal; d->AddWork(j); } d->Unlock(); return d->Event.Signal(); } bool GpgConnector::Decrypt(LViewI *Target, LAutoStreamI Data, LString Password, LMessage::Param UserVal) { if (!Target #if LGI_VIEW_HANDLE || !Target->Handle() #endif ) { LAssert(!"Invalid target."); return false; } if (!d->Lock(_FL)) return false; GpgJob *j = new GpgJob(GpgJob::JobDecrypt); if (j) { j->Owner = Target; j->Msg = Data; j->Password = Password; j->UserValue = UserVal; d->AddWork(j); } d->Unlock(); return d->Event.Signal(); } //////////////////////////////////////////////////////////////////////////////////////////////// struct MailUiGpgPriv { struct UserPassword { uint64 Ts; LString Email; LString Password; }; // Objs ScribeWnd *App; MailUi *Ui; // UI LCheckBox *Enc; LCheckBox *Sign; LCheckBox *Attach; LTextLabel *Msg; KeyArrAuto Inf; LTableLayout *Table; LButton *Decrypt, *Install; // Options bool WritingEmail; bool Encrypted; bool Signed; // Passwords LArray Psw; MailUiGpgPriv(ScribeWnd *app, MailUi *ui, bool writingEmail) { App = app; Ui = ui; Enc = NULL; Sign = NULL; Attach = NULL; Msg = NULL; Table = NULL; Decrypt = NULL; Install = NULL; WritingEmail = writingEmail; Encrypted = false; Signed = false; } AddressList *GetAddrLst() { AddressList *AddrLst = NULL; Ui->GetViewById(IDC_TO, AddrLst); return AddrLst; } void NotInstalled() { SetError(LLoadString(IDS_GNUPG_ERR_NOT_INSTALLED)); if (Install) Install->Visible(true); if (Enc) Enc->Enabled(false); if (Sign) Sign->Enabled(false); if (Attach) Attach->Enabled(false); } void Set(const char *Str, LColour &Col) { if (!Msg || !Table) { LgiTrace("%s:%i - Can't set message.\n", _FL); return; } Msg->GetCss(true)->Color(Col); Msg->Name(Str); LNotification note(LNotifyTableLayoutRefresh); Table->OnNotify(Msg, note); } void SetError(const char *Str) { Set(Str, cError); } void SetWarning(const char *Str) { Set(Str, cWarn); } void SetStatus(const char *Str) { Set(Str, cTxt); } void SetSuccess(const char *Str) { Set(Str, cGood); } - LString GetPassword(LViewI *Parent, const char *Addr) + void GetPassword(LViewI *Parent, LString Addr, std::function Callback) { for (unsigned i=0; iDoModal([this, Dlg, Addr, Callback](auto dlg, auto ctrlId) { - UserPassword &p = Psw.New(); - p.Email = Addr; - p.Password = Dlg.GetStr(); - p.Ts = LCurrentTime(); + if (ctrlId) + { + UserPassword &p = Psw.New(); + p.Email = Addr; + p.Password = Dlg->GetStr(); + p.Ts = LCurrentTime(); - return p.Password; - } - - return LString(); + if (Callback) + Callback(p.Password); + } + delete dlg; + }); } }; LCss::Len Px(int px) { return LCss::Len(LCss::LenPx, (float)px); } MailUiGpg::MailUiGpg(ScribeWnd *App, MailUi *Ui, int ColX1, int ColX2, bool WritingEmail) { d = new MailUiGpgPriv(App, Ui, WritingEmail); int TextY = 6; #ifdef MAC int ChkY = 1; #else int ChkY = 6; #endif if (AddView(d->Table = new LTableLayout(50))) { Mail *m = Ui->GetItem(); LDataI *obj = m ? m->GetObject() : NULL; int x = 0; LTextLabel *Txt; auto *c = d->Table->GetCell(x++, 0); c->Position(LCss::PosAbsolute); c->Left(Px(ColX1 - PANEL_BORDER_PX)); c->Top(Px(TextY - PANEL_BORDER_PX)); c->Add(Txt = new LTextLabel(IDC_STATIC, ColX1, TextY, -1, -1, "GnuPG:")); if (WritingEmail) { c = d->Table->GetCell(x++, 0); c->Position(LCss::PosAbsolute); c->Left(Px(ColX2 - PANEL_BORDER_PX + 1)); c->Top(Px(ChkY - PANEL_BORDER_PX)); c->Add(d->Enc = new LCheckBox(IDC_ENCRYPT, 0, 0, -1, -1, LLoadString(IDS_GNUPG_ENCRYPT))); c = d->Table->GetCell(x++, 0); c->PaddingTop(Px(ChkY - PANEL_BORDER_PX)); c->Add(d->Sign = new LCheckBox(IDC_SIGN, 0, 0, -1, -1, LLoadString(IDS_GNUPG_SIGN))); c = d->Table->GetCell(x++, 0); c->PaddingTop(Px(ChkY - PANEL_BORDER_PX)); c->Add(d->Attach = new LCheckBox(IDC_ATTACH_PUB_KEY, 0, 0, -1, -1, LLoadString(IDS_GNUPG_ATTACH_PUB_KEY))); } else { c = d->Table->GetCell(x++, 0); c->Position(LCss::PosAbsolute); c->Left(Px(ColX2 - PANEL_BORDER_PX + 1)); if (c->Add(d->Decrypt = new LButton(IDC_DECRYPT, 0, 0, -1, -1, LLoadString(IDS_GNUPG_DECRYPT)))) { LDataPropI *seg = obj ? obj->GetObj(FIELD_MIME_SEG) : NULL; auto mimeType = seg ? seg->GetStr(FIELD_MIME_TYPE) : NULL; if (mimeType) { d->Signed = !_stricmp(mimeType, sMultipartSigned); d->Encrypted = !_stricmp(mimeType, sMultipartEncrypted); } d->Decrypt->Enabled(d->Encrypted); } } c = d->Table->GetCell(x++, 0); c->PaddingTop(Px(TextY - PANEL_BORDER_PX)); c->PaddingLeft(LCss::Len(LCss::LenEm, 1.0f)); c->Add(d->Msg = new LTextLabel(IDC_MSG, 0, 0, 300, -1, "...")); c = d->Table->GetCell(x++, 0); c->TextAlign(LCss::AlignRight); if ((d->Install = new LButton(IDC_INSTALL, 0, 0, -1, -1, LLoadString(IDS_GNUPG_GET_GPG)))) { d->Install->Visible(false); c->Add(d->Install); } if (d->Encrypted) { d->SetWarning("Message is encrypted."); } } } MailUiGpg::~MailUiGpg() { delete d; if (GetParent()) { LNotification note(LNotifyItemDelete); GetParent()->OnNotify(this, note); } } void MailUiGpg::OnRecipientChange() { if (!d->WritingEmail || !d->Enc || !d->Sign) return; // Don't care... AddressList *AddrLst = d->GetAddrLst(); if (!AddrLst) { d->SetError("No AddressList."); return; } GpgConnector *Gpg = d->App->GetGpgConnector(); if (!Gpg) { d->NotInstalled(); return; } List a; if (!AddrLst->GetAll(a)) { d->SetStatus(LLoadString(IDS_GNUPG_ERR_NO_RECIP)); return; } if (d->Enc->Value() || d->Sign->Value()) { // Do we have public keys for each of the recipients? d->SetWarning(LLoadString(IDS_GNUPG_CHECKING)); LString::Array Emails; for (auto i: a) { if (i->sAddr) Emails.New() = i->sAddr; } LCombo *cbo; if (d->Ui->GetViewById(IDC_FROM, cbo)) { const char *Frm = cbo->Name(); LAutoString Name, Email; DecodeAddrName(Frm, Name, Email, NULL); if (Email) Emails.New() = Email; } Gpg->GetKeyInfo(this, Emails); } else { d->SetStatus(LLoadString(IDS_GNUPG_ERR_NO_SIGN_ENC)); // Revert list items to no colour... for (auto i: a) { i->SetInt(FIELD_COLOUR, cDefaultListItemColour); i->Update(); } } } // This gets all the events from MailUi::OnNotify as well as any events // from MailUiGpg's child controls. // \returns non-zero if further processing in MailUi::OnNotify should be blocked. int MailUiGpg::OnNotify(LViewI *Ctrl, LNotification n) { switch (Ctrl->GetId()) { case IDC_TO: { switch (n.Type) { case LNotifyItemInsert: case LNotifyItemDelete: // Recipients changed... check for keys... OnRecipientChange(); break; default: break; } break; } case IDC_ENCRYPT: case IDC_SIGN: { OnRecipientChange(); break; } case IDC_DECRYPT: { - Decrypt(); + Decrypt(NULL); break; } case IDC_INSTALL: { LExecute(GpgInstall); break; } } return 0; } void DeleteChildSegments(LDataPropI *d) { GDataIt It = d->GetList(FIELD_MIME_SEG); for (unsigned i=0; iLength(); ) { LDataPropI *c = (*It)[i]; DeleteChildSegments(c); LDataI *cdi = dynamic_cast(c); if (cdi) { // This deletes the on disk representation. cdi->Delete(); // This removes the object from the segment tree and // frees the memory delete cdi; } else { LgiTrace("%s:%i - DeleteChildSegments: Wrong object type.\n", _FL); LAssert(0); i++; // Skip this? Maybe we should break? } } } LString MailUiGpg::GetPublicKey(const char *Email) { LString s; if (Email) { LString Args; Args.Printf("-a --export %s", Email); LSubProcess Proc(GpgBinPath, Args); if (!Proc.Start(true, false)) { d->SetError(LLoadString(IDS_GNUPG_ERR_CANT_START)); return 1; } ssize_t r; char Buf[256] = ""; while ((r = Proc.Read(Buf, sizeof(Buf)-1)) > 0) { Buf[r] = 0; s += Buf; } int Result = Proc.Wait(); if (Result) { LString Msg; Msg.Printf(LLoadString(IDS_GNUPG_ERR_CODE), Result); d->SetError(Buf[0] ? Buf : Msg); s.Empty(); } } return s; } bool MailUiGpg::ReadFile(LArray &Data, const char *Path) { LFile f; if (!f.Open(Path, O_READ)) { LString s; s.Printf(LLoadString(IDS_ERROR_CANT_READ), Path); d->SetError(s); return false; } if (f.GetSize() < 0) { LString s; s.Printf(LLoadString(IDS_GNUPG_ERR_NO_CONTENT), Path); d->SetError(s); return false; } if (!Data.Length((uint32_t)f.GetSize())) { d->SetError("Memory alloc failed."); return 1; } if (f.Read(&Data[0], Data.Length()) != Data.Length()) { LString s; s.Printf(LLoadString(IDS_GNUPG_ERR_READ_FAIL), Path); d->SetError(s); return false; } return true; } -int MailUiGpg::Decrypt() +void MailUiGpg::Decrypt(std::function callback) { // Get the connector... GpgConnector *Conn = d->App->GetGpgConnector(); if (!Conn) { d->NotInstalled(); - return 1; + DecryptStatus(1); } // Find the right attachment... Mail *m = d->Ui->GetItem(); if (!m) { d->SetError(LLoadString(IDS_GNUPG_ERR_NO_MAIL)); - return 1; + DecryptStatus(1); } LDataPropI *Root = m->GetObject()->GetObj(FIELD_MIME_SEG); if (!Root) { d->SetError(LLoadString(IDS_GNUPG_ERR_NO_ROOT)); - return 1; + DecryptStatus(1); } auto Mt = Root->GetStr(FIELD_MIME_TYPE); if (!Mt || _stricmp(Mt, sMultipartEncrypted)) { d->SetError(LLoadString(IDS_GNUPG_ERR_WRONG_MIME)); - return 1; + DecryptStatus(1); } LDataI *EncryptedObj = NULL; LArray Objs; if (!m->GetAttachmentObjs(Objs)) - return 1; + DecryptStatus(1); for (unsigned i=0; iGetStr(FIELD_MIME_TYPE); if (Mt && !_stricmp(Mt, sAppOctetStream)) { EncryptedObj = Objs[i]; break; } } if (!EncryptedObj) { d->SetError(LLoadString(IDS_GNUPG_ERR_NO_ENC_MIME)); - return 1; + DecryptStatus(1); } // Get the password for decryption... LHashTbl,ScribeAccount*> Map; for (auto a : *m->App->GetAccounts()) { LVariant v = a->Identity.Email(); if (v.Str()) { char *Email = v.Str(); if (Email) Map.Add(Email, a); } } GDataIt ToLst = m->GetTo(); LString::Array ToEmail; if (ToLst) { for (LDataPropI *t = ToLst->First(); t; t = ToLst->Next()) { auto Email = t->GetStr(FIELD_EMAIL); if (Email) { if (Map.Find(Email)) ToEmail.New() = Email; } } } if (ToEmail.Length() == 0) { d->SetError(LLoadString(IDS_GNUPG_ERR_NO_ID)); - return 1; - } - - LString Pass = d->GetPassword(d->Ui, ToEmail[0]); - if (!Pass) - { - d->SetWarning(LLoadString(IDS_GNUPG_DECRYPT_CANCEL)); - return 1; + DecryptStatus(1); } - // Send the file to by decrypted... - LAutoStreamI Data = EncryptedObj->GetStream(_FL); - if (!Data) + d->GetPassword(d->Ui, ToEmail[0], [&](auto Pass) { - d->SetError(LLoadString(IDS_GNUPG_ERR_NO_DATA)); - return 1; - } + if (!Pass) + { + d->SetWarning(LLoadString(IDS_GNUPG_DECRYPT_CANCEL)); + DecryptStatus(1); + } + + // Send the file to by decrypted... + LAutoStreamI Data = EncryptedObj->GetStream(_FL); + if (!Data) + { + d->SetError(LLoadString(IDS_GNUPG_ERR_NO_DATA)); + DecryptStatus(1); + } - if (!Conn->Decrypt(this, Data, Pass, (LMessage::Param) m)) - { - d->SetError(LLoadString(IDS_GNUPG_ERR_DECRYPT_FAIL)); - return 1; - } - - return 0; + if (!Conn->Decrypt(this, Data, Pass, (LMessage::Param) m)) + { + d->SetError(LLoadString(IDS_GNUPG_ERR_DECRYPT_FAIL)); + DecryptStatus(1); + } + + DecryptStatus(0); + }); } -int MailUiGpg::SignEncrypt(bool uSign, bool uEncrypt, bool uAttachPublicKey) +void MailUiGpg::SignEncrypt(bool uSign, bool uEncrypt, bool uAttachPublicKey, std::function callback) { // Save the message normally Mail *m = d->Ui->GetItem(); bool IsInPublicFolder = m->GetFolder() && m->GetFolder()->IsPublicFolders(); if (IsInPublicFolder) { LDateTime n; n.SetNow(); m->SetDateSent(&n); m->Update(); } d->Ui->OnDataEntered(); d->Ui->OnSave(); LDataI *Root = dynamic_cast(m->GetObject()->GetObj(FIELD_MIME_SEG)); if (!Root) { d->SetError(LLoadString(IDS_GNUPG_ERR_NO_ROOT)); - return 1; + DecryptStatus(1); } // Check that the send has a private key setup... LString FromEmail = m->GetFromStr(FIELD_EMAIL); if (!FromEmail) { d->SetError(LLoadString(IDS_GNUPG_ERR_NO_SENDER_EMAIL)); - return 1; + DecryptStatus(1); } LString PrivKeyId; if (d->Inf) { for (unsigned i=0; iInf->Length(); i++) { GpgConnector::KeyInfo &ki = (*d->Inf)[i]; if (!_stricmp(ki.Email, FromEmail)) { PrivKeyId = ki.KeyId; break; } } } if (!PrivKeyId) { LString s; s.Printf(LLoadString(IDS_GNUPG_ERR_NO_PRIV_KEY), FromEmail.Get()); d->SetError(s); - return 1; + DecryptStatus(1); } if (uAttachPublicKey) { LString PubKey = GetPublicKey(FromEmail); if (!PubKey) { LString s; s.Printf(LLoadString(IDS_GNUPG_ERR_NO_PUB_KEY), FromEmail.Get()); d->SetError(s); - return 1; + DecryptStatus(1); } Attachment *a = new Attachment(m->App); if (a) { LAutoStreamI Data(new LMemStream(PubKey, PubKey.Length())); if (Data) { if (!a->ImportStream("public-key.asc", "application/pgp-keys", Data)) { d->SetError(LLoadString(IDS_GNUPG_ERR_IMPORT_FAIL)); - return 1; + DecryptStatus(1); } else { m->AttachFile(a); } } else { d->SetError("Allocation failed."); - return 1; + DecryptStatus(1); } } } // Re-write the MIME hierarchy to have the message and attachments encrypted // 1) Get the password - LString Psw = d->GetPassword(d->Ui, FromEmail.Get()); - if (!Psw) - { - d->SetStatus(LLoadString(IDS_GNUPG_ERR_SIGN_ENC_CANCEL)); - return 1; - } - - // 1) Export the message to a file: - const char *BaseName = "encrypted.asc"; - LFile::Path p = ScribeTempPath(); - p += BaseName; - LFile f; - if (!f.Open(p, O_READWRITE)) + d->GetPassword(d->Ui, FromEmail.Get(), [&](auto Psw) { - d->SetError(LLoadString(IDS_GNUPG_ERR_TEMP_WRITE)); - return 1; - } - f.SetSize(0); - f.SetPos(0); - LMime Mime(ScribeTempPath()); - Store3ToGMime(&Mime, Root); + if (!Psw) + { + d->SetStatus(LLoadString(IDS_GNUPG_ERR_SIGN_ENC_CANCEL)); + DecryptStatus(1); + } - if (!Mime.GetBoundary()) - { - // No boundary... so set it and propagate the change back - char b[64]; - CreateMimeBoundary(b, sizeof(b)); - Mime.SetBoundary(b); - Root->SetStr(FIELD_INTERNET_HEADER, Mime.GetHeaders()); + // 1) Export the message to a file: + const char *BaseName = "encrypted.asc"; + LFile::Path p = ScribeTempPath(); + p += BaseName; + LFile f; + if (!f.Open(p, O_READWRITE)) + { + d->SetError(LLoadString(IDS_GNUPG_ERR_TEMP_WRITE)); + DecryptStatus(1); + } + f.SetSize(0); + f.SetPos(0); + LMime Mime(ScribeTempPath()); + Store3ToGMime(&Mime, Root); + + if (!Mime.GetBoundary()) + { + // No boundary... so set it and propagate the change back + char b[64]; + CreateMimeBoundary(b, sizeof(b)); + Mime.SetBoundary(b); + Root->SetStr(FIELD_INTERNET_HEADER, Mime.GetHeaders()); - // If we don't do this then LMime will create it again later - // when we actually go to send the message, but it will be - // different then and the signing will fail. - } + // If we don't do this then LMime will create it again later + // when we actually go to send the message, but it will be + // different then and the signing will fail. + } - if (!Mime.Text.Encode.Push(&f)) - { - d->SetError(LLoadString(IDS_GNUPG_ERR_EXPORT_TEMP)); - return 1; - } + if (!Mime.Text.Encode.Push(&f)) + { + d->SetError(LLoadString(IDS_GNUPG_ERR_EXPORT_TEMP)); + DecryptStatus(1); + } - #if 1 - if (uSign) - { - // Remove any white space from the end of the file... this is - // to make sure the signing process is standardized. See - // https://www.ietf.org/rfc/rfc3156.txt - // Part 5: OpenPGP signed data - // This is probably not very efficient but it's usually only the - // 2 bytes: "\r\n" - int64 Size, Pos; - while ( (Size = f.GetSize()) > 0) + #if 1 + if (uSign) { - Pos = f.SetPos(Size-1); - if (Pos != Size - 1) - break; - - char c; - ssize_t Rd = f.Read(&c, 1); - if (Rd == 1 && - strchr(WhiteSpace, c)) + // Remove any white space from the end of the file... this is + // to make sure the signing process is standardized. See + // https://www.ietf.org/rfc/rfc3156.txt + // Part 5: OpenPGP signed data + // This is probably not very efficient but it's usually only the + // 2 bytes: "\r\n" + int64 Size, Pos; + while ( (Size = f.GetSize()) > 0) { - f.SetSize(Size - 1); - } - else break; - } - } - #endif + Pos = f.SetPos(Size-1); + if (Pos != Size - 1) + break; + + char c; + ssize_t Rd = f.Read(&c, 1); + if (Rd == 1 && + strchr(WhiteSpace, c)) + { + f.SetSize(Size - 1); + } + else break; + } + } + #endif - f.Close(); + f.Close(); - if (!uEncrypt) - { - // Just signing... move MIME tree into child node - LDataI *NewRoot = Root->GetStore()->Create(MAGIC_ATTACHMENT); - if (!NewRoot) + if (!uEncrypt) { - d->SetError(LLoadString(IDS_GNUPG_ERR_NEW_ATTACH_FAIL)); - return 1; - } - - // Copy over the root node headers - NewRoot->SetStr(FIELD_INTERNET_HEADER, Root->GetStr(FIELD_INTERNET_HEADER)); + // Just signing... move MIME tree into child node + LDataI *NewRoot = Root->GetStore()->Create(MAGIC_ATTACHMENT); + if (!NewRoot) + { + d->SetError(LLoadString(IDS_GNUPG_ERR_NEW_ATTACH_FAIL)); + DecryptStatus(1); + } - // Reparent the old root to the new root, and then attach that to the message... - if (!Root->Save(NewRoot) || - !m->GetObject()->SetObj(FIELD_MIME_SEG, NewRoot)) - { - d->SetError(LLoadString(IDS_GNUPG_ERR_REPARENT)); - return 1; - } + // Copy over the root node headers + NewRoot->SetStr(FIELD_INTERNET_HEADER, Root->GetStr(FIELD_INTERNET_HEADER)); - Root = NewRoot; - } + // Reparent the old root to the new root, and then attach that to the message... + if (!Root->Save(NewRoot) || + !m->GetObject()->SetObj(FIELD_MIME_SEG, NewRoot)) + { + d->SetError(LLoadString(IDS_GNUPG_ERR_REPARENT)); + DecryptStatus(1); + } + + Root = NewRoot; + } - // 2) Encrypt/sign the file: - LString InFile(p); - p--; - p += "encrypted.gpg"; - LString OutFile(p); - if (LFileExists(OutFile)) - { - FileDev->Delete(OutFile, false); - } - - LString Args, s; - Args.Printf("--batch --passphrase-fd 0 -u 0x%s", - PrivKeyId.Get()); + // 2) Encrypt/sign the file: + LString InFile(p); + p--; + p += "encrypted.gpg"; + LString OutFile(p); + if (LFileExists(OutFile)) + { + FileDev->Delete(OutFile, false); + } - if (uEncrypt) - { - GDataIt To = m->GetTo(); - for (LDataPropI *Recip = To->First(); Recip; Recip = To->Next()) + LString Args, s; + Args.Printf("--batch --passphrase-fd 0 -u 0x%s", + PrivKeyId.Get()); + + if (uEncrypt) { - auto Email = Recip->GetStr(FIELD_EMAIL); - LAssert(Email != NULL); - if (Email) + GDataIt To = m->GetTo(); + for (LDataPropI *Recip = To->First(); Recip; Recip = To->Next()) { - s.Printf(" --recipient %s", Email); - Args += s; - } - else - { - d->SetError(LLoadString(IDS_GNUPG_ERR_RECIP_NO_EMAIL)); - return 1; + auto Email = Recip->GetStr(FIELD_EMAIL); + LAssert(Email != NULL); + if (Email) + { + s.Printf(" --recipient %s", Email); + Args += s; + } + else + { + d->SetError(LLoadString(IDS_GNUPG_ERR_RECIP_NO_EMAIL)); + DecryptStatus(1); + } } } - } - s.Printf(" --armor -o \"%s\" %s \"%s\"", - OutFile.Get(), - uSign && uEncrypt ? "-se" : (uSign ? "--detach-sign" : "-e"), - InFile.Get()); - Args += s; + s.Printf(" --armor -o \"%s\" %s \"%s\"", + OutFile.Get(), + uSign && uEncrypt ? "-se" : (uSign ? "--detach-sign" : "-e"), + InFile.Get()); + Args += s; - LSubProcess Proc(GpgBinPath, Args); - char Buf[256]; - if (!Proc.Start(true, true)) - { - d->SetError(LLoadString(IDS_GNUPG_ERR_CANT_START)); - return 1; - } - - LString PswStr; - PswStr.Printf("%s\n", Psw.Get()); - ssize_t w = Proc.Write(PswStr.Get(), PswStr.Length()); - if (w < 0) - { - d->SetError(LLoadString(IDS_GNUPG_ERR_WRITE)); - return 1; - } - - ssize_t r = Proc.Read(Buf, sizeof(Buf)-1); - Buf[MAX(r, 0)] = 0; - int Result = Proc.Wait(); - if (Result) - { - d->SetError(Buf[0] ? Buf : LLoadString(IDS_GNUPG_ERR_ENCRYPT_FAIL)); - return 1; - } - - // 3) Import the encrypted message and replace contents of MIME tree. - LArray InData, OutData; - if (!ReadFile(OutData, OutFile)) - return 1; - - #ifndef _DEBUG - // Clean up temporary files... - FileDev->Delete(InFile, false); - FileDev->Delete(OutFile, false); - #endif + LSubProcess Proc(GpgBinPath, Args); + char Buf[256]; + if (!Proc.Start(true, true)) + { + d->SetError(LLoadString(IDS_GNUPG_ERR_CANT_START)); + DecryptStatus(1); + } - if (uEncrypt) - { - // Clear out all existing attachments... - DeleteChildSegments(Root); - } - - // Setup the root MIME node to have the right type and fields... - LAutoStreamI Data; + LString PswStr; + PswStr.Printf("%s\n", Psw.Get()); + ssize_t w = Proc.Write(PswStr.Get(), PswStr.Length()); + if (w < 0) + { + d->SetError(LLoadString(IDS_GNUPG_ERR_WRITE)); + DecryptStatus(1); + } - { - // By using a LMime object we preserve the existing headers in the MIME - // segment while still being able to change the MIME type and charset. - LMime Tmp; - Tmp.SetHeaders(Root->GetStr(FIELD_INTERNET_HEADER)); - Tmp.SetMimeType(uSign ? sMultipartSigned : sMultipartEncrypted); - Tmp.SetCharset("utf-8"); - Tmp.SetSub( "Content-Type", - "protocol", - uEncrypt ? "application/pgp-encrypted" : "application/pgp-signature"); - Root->SetStr(FIELD_INTERNET_HEADER, Tmp.GetHeaders()); - - // Set a body message - const char *BodyMsg = "This is an OpenPGP/MIME encrypted message (RFC 4880 and 3156)\n"; - Data.Reset(new LMemStream(BodyMsg, strlen(BodyMsg))); - Root->SetStream(Data); - - Root->Save(); - } + ssize_t r = Proc.Read(Buf, sizeof(Buf)-1); + Buf[MAX(r, 0)] = 0; + int Result = Proc.Wait(); + if (Result) + { + d->SetError(Buf[0] ? Buf : LLoadString(IDS_GNUPG_ERR_ENCRYPT_FAIL)); + DecryptStatus(1); + } + + // 3) Import the encrypted message and replace contents of MIME tree. + LArray InData, OutData; + if (!ReadFile(OutData, OutFile)) + DecryptStatus(1); - if (uEncrypt) - { - // Attach some app info... - LDataI *AppInfo = Root->GetStore()->Create(MAGIC_ATTACHMENT); - if (!AppInfo) - { - d->SetError(LLoadString(IDS_GNUPG_ERR_NEW_ATTACH_FAIL)); - return 1; - } - AppInfo->SetStr(FIELD_MIME_TYPE, "application/pgp-encrypted"); - const char *AppInfoMsg = "Version: 1\n"; - Data.Reset(new LMemStream(AppInfoMsg, strlen(AppInfoMsg))); - AppInfo->SetStream(Data); - AppInfo->Save(Root); - } - - { - // Attach the new data to the email... - LDataI *File = Root->GetStore()->Create(MAGIC_ATTACHMENT); - if (!File) - { - d->SetError(LLoadString(IDS_GNUPG_ERR_NEW_ATTACH_FAIL)); - return 1; - } - + #ifndef _DEBUG + // Clean up temporary files... + FileDev->Delete(InFile, false); + FileDev->Delete(OutFile, false); + #endif + if (uEncrypt) { - // Attach the encrypted data here... - LMime Tmp; - Tmp.SetMimeType(sAppOctetStream); - Tmp.SetFileName(BaseName); - Tmp.Set("Content-Disposition", "inline"); - Tmp.SetSub("Content-Disposition", "filename", BaseName); - File->SetStr(FIELD_INTERNET_HEADER, Tmp.GetHeaders()); - + // Clear out all existing attachments... + DeleteChildSegments(Root); } - else // uSign + + // Setup the root MIME node to have the right type and fields... + LAutoStreamI Data; + { - // Set up the signature attachment + // By using a LMime object we preserve the existing headers in the MIME + // segment while still being able to change the MIME type and charset. LMime Tmp; - Tmp.SetMimeType("application/pgp-signature"); - Tmp.SetFileName(BaseName); - Tmp.Set("Content-Description", "OpenPGP digital signature"); - Tmp.Set("Content-Disposition", "attachment"); - Tmp.SetSub("Content-Disposition", "filename", BaseName); - File->SetStr(FIELD_INTERNET_HEADER, Tmp.GetHeaders()); + Tmp.SetHeaders(Root->GetStr(FIELD_INTERNET_HEADER)); + Tmp.SetMimeType(uSign ? sMultipartSigned : sMultipartEncrypted); + Tmp.SetCharset("utf-8"); + Tmp.SetSub( "Content-Type", + "protocol", + uEncrypt ? "application/pgp-encrypted" : "application/pgp-signature"); + Root->SetStr(FIELD_INTERNET_HEADER, Tmp.GetHeaders()); + + // Set a body message + const char *BodyMsg = "This is an OpenPGP/MIME encrypted message (RFC 4880 and 3156)\n"; + Data.Reset(new LMemStream(BodyMsg, strlen(BodyMsg))); + Root->SetStream(Data); + + Root->Save(); + } + + if (uEncrypt) + { + // Attach some app info... + LDataI *AppInfo = Root->GetStore()->Create(MAGIC_ATTACHMENT); + if (!AppInfo) + { + d->SetError(LLoadString(IDS_GNUPG_ERR_NEW_ATTACH_FAIL)); + DecryptStatus(1); + } + AppInfo->SetStr(FIELD_MIME_TYPE, "application/pgp-encrypted"); + const char *AppInfoMsg = "Version: 1\n"; + Data.Reset(new LMemStream(AppInfoMsg, strlen(AppInfoMsg))); + AppInfo->SetStream(Data); + AppInfo->Save(Root); } - Data.Reset(new LMemStream(&OutData[0], OutData.Length())); - File->SetStream(Data); + { + // Attach the new data to the email... + LDataI *File = Root->GetStore()->Create(MAGIC_ATTACHMENT); + if (!File) + { + d->SetError(LLoadString(IDS_GNUPG_ERR_NEW_ATTACH_FAIL)); + DecryptStatus(1); + } - File->Save(Root); - } + if (uEncrypt) + { + // Attach the encrypted data here... + LMime Tmp; + Tmp.SetMimeType(sAppOctetStream); + Tmp.SetFileName(BaseName); + Tmp.Set("Content-Disposition", "inline"); + Tmp.SetSub("Content-Disposition", "filename", BaseName); + File->SetStr(FIELD_INTERNET_HEADER, Tmp.GetHeaders()); + + } + else // uSign + { + // Set up the signature attachment + LMime Tmp; + Tmp.SetMimeType("application/pgp-signature"); + Tmp.SetFileName(BaseName); + Tmp.Set("Content-Description", "OpenPGP digital signature"); + Tmp.Set("Content-Disposition", "attachment"); + Tmp.SetSub("Content-Disposition", "filename", BaseName); + File->SetStr(FIELD_INTERNET_HEADER, Tmp.GetHeaders()); + } + + Data.Reset(new LMemStream(&OutData[0], OutData.Length())); + File->SetStream(Data); + + File->Save(Root); + } - // Tell the UI that the object has changed... - LArray ChangeArr; - ChangeArr.Add(m->GetObject()); - d->App->SetContext(_FL); - d->App->OnChange(ChangeArr, 0); + // Tell the UI that the object has changed... + LArray ChangeArr; + ChangeArr.Add(m->GetObject()); + d->App->SetContext(_FL); + d->App->OnChange(ChangeArr, 0); - return 0; + DecryptStatus(0); + }); } -int MailUiGpg::OnCommand(int Cmd, int Event, OsView From) +void MailUiGpg::DoCommand(int Cmd, std::function callback) { switch (Cmd) { case IDM_SEND_MSG: { if (!d || !d->Enc || !d->Sign || !d->Attach || !d->Ui) { // Resending an old mail. // There is no Enc/Sign UI. - return 0; + DecryptStatus(0); } if (!d->Enc->Value() && !d->Sign->Value()) { - return 0; // No need to sign &| encrypt + DecryptStatus(0); // No need to sign &| encrypt } Mail *m = d->Ui->GetItem(); if (!m) { d->SetError(LLoadString(IDS_GNUPG_ERR_NOMSG)); - return 1; + DecryptStatus(1); } - if (SignEncrypt(d->Sign->Value() != 0, - d->Enc->Value() != 0, - d->Attach->Value() != 0)) - return 1; + SignEncrypt(d->Sign->Value() != 0, + d->Enc->Value() != 0, + d->Attach->Value() != 0, + [&](auto status) + { + if (!status) + { + // Send the email.. + m->Send(true); - // Send the email.. - m->Send(true); - - // Close the window... - d->Ui->Quit(); - - // Bypass the normal code - return 1; + // Close the window... + d->Ui->Quit(); + } + + // Bypass the normal code + DecryptStatus(1); + }); } } - return 0; + DecryptStatus(0); } LMessage::Result MailUiGpg::OnEvent(LMessage *Msg) { switch (Msg->Msg()) { case M_GNUPG_KEY_INFO: { d->Inf.Reset( (KeyArr*) Msg->B() ); AddressList *AddrLst = d->GetAddrLst(); if (d->Inf && AddrLst) { int NoKey = 0; List a; if (AddrLst->GetAll(a)) { LHashTbl, GpgConnector::KeyInfo*> Map; for (unsigned i=0; iInf->Length(); i++) { GpgConnector::KeyInfo *ki = &(*d->Inf)[i]; Map.Add(ki->Email, ki); } for (auto i: a) { // Check if this recipient has a key... GpgConnector::KeyInfo *ki = (i->sAddr) ? Map.Find(i->sAddr) : NULL; i->SetInt(FIELD_COLOUR, ki ? cDefaultListItemColour : cError.c32()); i->Update(); if (!ki) NoKey++; } } if (NoKey > 0) d->SetError(LLoadString(IDS_GNUPG_ERR_ONE_OR_MORE)); else if (d->Enc && d->Sign) { if (d->Enc->Value() && d->Sign->Value()) d->SetSuccess(LLoadString(IDS_GNUPG_ENCRYPTED_AND_SIGNED)); else if (d->Enc->Value()) d->SetSuccess(LLoadString(IDS_GNUPG_ENCRYPTED)); else if (d->Sign->Value()) d->SetSuccess(LLoadString(IDS_GNUPG_SIGNED)); else LAssert(0); } else LAssert(0); } else d->SetError("Parameter error."); break; } case M_GNUPG_SIG_CHECK: { Mail *m = d->Ui ? d->Ui->GetItem() : NULL; LAutoPtr Resp((GpgSigCheckResponse*)Msg->A()); if (!Resp || m != (Mail*)Resp->UserValue) { d->SetError("M_GNUPG_SIG_CHECK: bad response"); break; } if (Resp->Error) { d->SetError(Resp->Error); break; } LString Dt = Resp->TimeStamp.Get(); const char *Type = Resp->SignatureMatch ? LLoadString(IDS_GNUPG_GOOD_SIG) : LLoadString(IDS_GNUPG_BAD_SIG); LString Msg; if (Resp->Identity) Msg.Printf(LLoadString(IDS_GNUPG_SIG_MSG), Type, Resp->Identity.Get(), Dt.Get()); else Msg.Printf(LLoadString(IDS_GNUPG_SIG_NO_ID), Type, Dt.Get()); if (Resp->SignatureMatch) d->SetSuccess(Msg); else d->SetError(Msg); break; } case M_GNUPG_DECRYPT: { LAutoPtr Resp((GpgDecryptResponse*)Msg->A()); if (!Resp) { d->SetError(LLoadString(IDS_GNUPG_ERR_INVALID_DECRYPTION)); break; } if (!Resp->Data) { d->SetError(LLoadString(IDS_GNUPG_ERR_NO_OUTPUT)); break; } if (Resp->Error) { d->SetError(Resp->Error); break; } Mail *m = d->Ui->GetItem(); if (!m || m != (Mail*)Resp->UserValue) { d->SetError("Incorrect mail object after decryption."); break; } d->SetSuccess(LLoadString(IDS_GNUPG_DECRYPT_OK)); LDataI *Obj = m->GetObject(); if (!Obj) { d->SetError(LLoadString(IDS_GNUPG_ERR_NOMSG)); break; } // This turns of notification processing, the message is changing // but we don't want it to get dirty. d->Ui->_Running = false; // Unload the attachments, because they will point to stale back-end // objects, that "SetStream" will delete. m->UnloadAttachments(); // Set the stream of the object (which will MIME parse the data, // replacing the MIME Seg tree) Obj->SetStream(Resp->Data); // Re-enable the notifcation processing. d->Ui->_Running = true; // Get the UI to reload and display the object... LArray Items; Items.Add(Obj); m->App->SetContext(_FL); m->App->OnChange(Items, 0); // Change the button to disabled... no need for it anymore. SetCtrlEnabled(IDC_DECRYPT, false); break; } } return LView::OnEvent(Msg); } bool MailUiGpg::Pour(LRegion &r) { LRect lrg = FindLargest(r); if (!lrg.Valid()) return false; int y = LSysFont->GetHeight() + 12; lrg.y2 = lrg.y1 + MIN(y, lrg.Y()) - 1; SetPos(lrg); if (d->Table) { LRect c = GetClient(); c.Inset(PANEL_BORDER_PX, PANEL_BORDER_PX); d->Table->SetPos(c); } return true; } void MailUiGpg::OnCreate() { AttachChildren(); LResources::StyleElement(this); // This is in OnCreate because the CheckSignature needs a valid // view handle to post the message back to us. if (d->Signed) { GpgConnector *Conn = d->App->GetGpgConnector(); if (Conn) { Mail *m = d->Ui->GetItem(); LDataI *obj = m ? m->GetObject() : NULL; LAutoStreamI Msg; if (obj) Msg = obj->GetStream(_FL); if (Msg) { Conn->CheckSignature(this, Msg, (LMessage::Param)m); d->SetWarning(LLoadString(IDS_GNUPG_CHECK_MSG)); } else { d->SetError(LLoadString(IDS_GNUPG_ERR_NOMSG)); } } else { d->NotInstalled(); } } } void MailUiGpg::OnPaint(LSurface *pDC) { LCssTools Tools(this); LRect c = GetClient(); Tools.PaintBorder(pDC, c); Tools.PaintPadding(pDC, c); Tools.PaintContent(pDC, c); } diff --git a/Code/Encryption/GnuPG.h b/Code/Encryption/GnuPG.h --- a/Code/Encryption/GnuPG.h +++ b/Code/Encryption/GnuPG.h @@ -1,94 +1,96 @@ #ifndef _SCRIBE_GNUPH_H_ #define _SCRIBE_GNUPH_H_ enum GpgFlags { GPG_HAS_PRIV_KEY = 0x1, }; struct GpgSigCheckResponse { bool SignatureMatch; LString Error; LString Identity; LDateTime TimeStamp; LMessage::Param UserValue; GpgSigCheckResponse() { SignatureMatch = false; UserValue = 0; } }; struct GpgDecryptResponse { LString Error; LAutoStreamI Data; LMessage::Param UserValue; GpgDecryptResponse() { UserValue = 0; } }; class GpgConnector { struct GpgConnectorPriv *d; public: static bool IsInstalled(); GpgConnector(); ~GpgConnector(); struct KeyInfo { // One or more of GpgFlags int Flags; LString KeyId; LString Name; LString Email; }; /// This will respond by sending the target a M_GNUPG_KEY_INFO message bool GetKeyInfo(LViewI *Target, LString::Array &Emails); /// This will respond by sending the target a M_GNUPG_SIG_CHECK message bool CheckSignature(LViewI *Target, LAutoStreamI Rfc822Msg, LMessage::Param UserVal); /// This will respond by sending the target a M_GNUPG_DECRYPT message bool Decrypt(LViewI *Target, LAutoStreamI Data, LString Password, LMessage::Param UserVal); }; class MailUiGpg : public LView { struct MailUiGpgPriv *d; bool ReadFile(LArray &Data, const char *Path); public: MailUiGpg(ScribeWnd *App, MailUi *Ui, int ColX1, int ColX2, bool WritingEmail); ~MailUiGpg(); const char *GetClass() { return "MailUiGpg"; } // Actions - int SignEncrypt(bool uSign, bool uEncrypt, bool uAttachPublicKey); - int Decrypt(); + void SignEncrypt(bool uSign, bool uEncrypt, bool uAttachPublicKey, std::function callback); + void Decrypt(std::function callback); LString GetPublicKey(const char *Email); // Mail Events void OnRecipientChange(); // View Events bool Pour(LRegion &r); void OnPaint(LSurface *pDC); void OnCreate(); int OnNotify(LViewI *Ctrl, LNotification n); - int OnCommand(int Cmd, int Event, OsView From); + void DoCommand(int Cmd, std::function callback); LMessage::Result OnEvent(LMessage *Msg); + + int OnCommand(int Cmd, int Event, OsView Wnd) { LAssert(!"Call DoCommand..."); return 0; } }; #endif \ No newline at end of file diff --git a/Code/Exp_Scribe.cpp b/Code/Exp_Scribe.cpp --- a/Code/Exp_Scribe.cpp +++ b/Code/Exp_Scribe.cpp @@ -1,715 +1,714 @@ #include "Scribe.h" #include "lgi/common/List.h" #include "ScribeFolderSelect.h" #include "lgi/common/ProgressDlg.h" #include "../Resources/resdefs.h" #include "lgi/common/Store3.h" #include "lgi/common/LgiRes.h" #include "lgi/common/FileSelect.h" /* #define OPT_ScribeExpSrcPaths "ExpSrcPaths" //(char*) #define OPT_ScribeExpDstPath "ExpDstPath" //(char*) #define OPT_ScribeExpFolders "ExpFlds" //(char*) #define OPT_ScribeExpAll "ExpAll" //(bool) #define OPT_ScribeExpExclude "ExpExc" //(bool) */ class ScribeExport : public LDialog, public LDataEventsI { ScribeWnd *App; LDataStoreI *Folders; ScribeFolder *Mailbox; LList *Lst; ScribeFolder *Spam; ScribeFolder *Trash; void OnNew(LDataFolderI *parent, LArray &new_items, int pos, bool is_new) { } bool OnDelete(LDataFolderI *parent, LArray &items) { return true; } bool OnMove(LDataFolderI *new_parent, LDataFolderI *old_parent, LArray &items) { return true; } bool OnChange(LArray &items, int FieldHint) { return true; } public: bool AllFolders; bool ExceptTrashSpam; char *DestPath; LArray SrcPaths; int MailCreated; int MailSkipped; int MailErrors; int ContactCreated; int ContactSkipped; int ContactErrors; ScribeExport(ScribeWnd *app) { Folders = 0; Lst = 0; Mailbox = 0; DestPath = 0; MailCreated = 0; MailSkipped = 0; MailErrors = 0; ContactCreated = 0; ContactSkipped = 0; ContactErrors = 0; Spam = 0; Trash = 0; SetParent(App = app); if (LoadFromResource(IDD_SCRIBE_EXPORT)) { MoveToCenter(); EnableCtrls(false); GetViewById(IDC_SRC_FOLDERS, Lst); LVariant s; if (Lst && App->GetOptions()->GetValue(OPT_ScribeExpSrcPaths, s) && s.Str()) { LToken t(s.Str(), ":"); for (unsigned i=0; Lst && iSetText(t[i]); Lst->Insert(n); } } Lst->ResizeColumnsToContent(); } if (App->GetOptions()->GetValue(OPT_ScribeExpDstPath, s) && ValidStr(s.Str())) { SetCtrlName(IDC_FOLDER, s.Str()); } else { SetCtrlName(IDC_FOLDER, "/"); } LVariant n; if (App->GetOptions()->GetValue(OPT_ScribeExpAll, n)) { SetCtrlValue(IDC_ALL, n.CastInt32()); } if (App->GetOptions()->GetValue(OPT_ScribeExpExclude, n)) { SetCtrlValue(IDC_NO_SPAM_TRASH, n.CastInt32()); } else { SetCtrlValue(IDC_NO_SPAM_TRASH, true); } if (App->GetOptions()->GetValue(OPT_ScribeExpFolders, s) && s.Str()) { SetCtrlName(IDC_DEST, s.Str()); OnSelectFolders(); } OnAll(); } } ~ScribeExport() { DeleteObj(Folders); DeleteArray(DestPath); SrcPaths.DeleteArrays(); } void OnAll() { SetCtrlEnabled(IDC_SRC_FOLDERS, !GetCtrlValue(IDC_ALL)); SetCtrlEnabled(IDC_ADD_SRC_FOLDER, !GetCtrlValue(IDC_ALL)); SetCtrlEnabled(IDC_DEL_SRC_FOLDER, !GetCtrlValue(IDC_ALL)); } void OnSelectFolders() { const char *s = GetCtrlName(IDC_DEST); if (LFileExists(s)) { for (unsigned i=0; iGetStorageFolders().Length(); i++) { /* FIXME char *f = App->GetStorageFolders()->GetFileName(); if (f) { if (_stricmp(f, s) == 0) { LgiMsg(this, LLoadString(IDS_ERROR_CANT_OPEN_FOLDERS), AppName, MB_OK, s); EnableCtrls(false); return; } } */ } DeleteObj(Folders); Folders = OpenMail3(s, this, false); if (Folders && Folders->GetInt(FIELD_STATUS) == Store3Success) { EnableCtrls(true); } else { LgiMsg(this, LLoadString(IDS_ERROR_CANT_OPEN_FOLDERS), AppName, MB_OK, s); EnableCtrls(false); } } } void EnableCtrls(bool e) { SetCtrlEnabled(IDC_DEST, !e); SetCtrlEnabled(IDC_ALL, e); SetCtrlEnabled(IDC_NO_SPAM_TRASH, e); SetCtrlEnabled(IDC_SRC_FOLDERS, e); SetCtrlEnabled(IDC_ADD_SRC_FOLDER, e); SetCtrlEnabled(IDC_DEL_SRC_FOLDER, e); SetCtrlEnabled(IDC_FOLDER, e); SetCtrlEnabled(IDC_SET_FOLDER, e); SetCtrlEnabled(IDOK, e); } void LoadFolders() { if (!Mailbox) { // StorageItem *Root = Folders->GetRoot(); LDataFolderI *Root = Folders->GetRoot(); if (Root) { /* Root->SetObject(Mailbox = new ScribeFolder("MailBox", MAGIC_NONE)); if (Mailbox) { Mailbox->Store = Root; Mailbox->LoadFolders(); } */ } } } ScribeFolder *GetFolder(char *Path, ScribeFolder *CreateAs = 0) { ScribeFolder *f = 0; if (Path && Mailbox) { LToken t(Path, "/"); f = Mailbox; for (unsigned i=0; iGetChildFolder(); Child; Child = Child->GetNextFolder()) { auto n = Child->GetName(true); if (n.Equals(t[i])) { f = Child; Found = true; break; } } if (!Found) { if (CreateAs) { f = f->CreateSubDirectory(t[i], CreateAs->GetItemType()); if (!f) break; } else { f = 0; break; } } } } return f; } LString ContactKey(Contact *c) { LString p; const char *f = 0, *l = 0; auto e = c->GetAddrAt(0); c->Get(OPT_First, f); c->Get(OPT_Last, l); p.Printf("%s,%s,%s", e.Get(), f, l); return p; } bool ExportFolder(char *ToPath, const char *FromPath, bool Children, LProgressDlg *Prog) { bool Status = false; if (ToPath && FromPath) { ScribeFolder *From = App->GetFolder(FromPath); if (From) { if ((!Spam || From != Spam) && (!Trash || From != Trash)) { ScribeFolder *To = GetFolder(ToPath, From); if (To) { bool FromLoaded = From->IsLoaded(); bool ToLoaded = From->IsLoaded(); - switch ((uint32_t)To->GetItemType()) + auto ProcessItem = [&]() { - case MAGIC_MAIL: + switch ((uint32_t)To->GetItemType()) { - if (Prog) - Prog->SetDescription(FromPath); - - To->LoadThings(); - From->LoadThings(); + case MAGIC_MAIL: + { + if (Prog) + Prog->SetDescription(FromPath); - LHashTbl,Mail*> ToMsgs; - for (auto t: To->Items) - { - Mail *m = t->IsMail(); - if (m) + LHashTbl,Mail*> ToMsgs; + for (auto t: To->Items) { - auto Id = m->GetMessageId(true); - if (Id) + Mail *m = t->IsMail(); + if (m) { - ToMsgs.Add(Id, m); + auto Id = m->GetMessageId(true); + if (Id) + { + ToMsgs.Add(Id, m); + } } } - } - int InitMailErrors = MailErrors; - uint64 Last = LCurrentTime(); + int InitMailErrors = MailErrors; - for (auto t: From->Items) - { - if (Prog && Prog->IsCancelled()) - break; + for (auto t: From->Items) + { + if (Prog && Prog->IsCancelled()) + break; - Mail *m = t->IsMail(); - if (m) - { - auto Id = m->GetMessageId(true); - if (Id) + Mail *m = t->IsMail(); + if (m) { - if (!ToMsgs.Find(Id)) + auto Id = m->GetMessageId(true); + if (Id) { - // Create new mail... - Mail *n = new Mail(App); - if (n) + if (!ToMsgs.Find(Id)) { - *n = (Thing&)*m; - n->SetParentFolder(To); - n->SetObject(To->GetObject()->GetStore()->Create(MAGIC_MAIL), false, _FL); - if (n->GetObject()) + // Create new mail... + Mail *n = new Mail(App); + if (n) { - MailCreated++; + *n = (Thing&)*m; + n->SetParentFolder(To); + n->SetObject(To->GetObject()->GetStore()->Create(MAGIC_MAIL), false, _FL); + if (n->GetObject()) + { + MailCreated++; - // Now create all the attachments - List Att; - if (m->GetAttachments(&Att)) - { - for (auto OldAttachment: Att) + // Now create all the attachments + List Att; + if (m->GetAttachments(&Att)) { - Attachment *NewAttachment = new Attachment(m->App, OldAttachment); - if (NewAttachment) + for (auto OldAttachment: Att) { - n->AttachFile(NewAttachment); - NewAttachment->SetObject(n->GetObject()->GetStore()->Create(MAGIC_ATTACHMENT), false, _FL); + Attachment *NewAttachment = new Attachment(m->App, OldAttachment); + if (NewAttachment) + { + n->AttachFile(NewAttachment); + NewAttachment->SetObject(n->GetObject()->GetStore()->Create(MAGIC_ATTACHMENT), false, _FL); + } } } } + else MailErrors++; + } else MailErrors++; - } - else MailErrors++; + else MailSkipped++; } - else MailSkipped++; + else MailErrors++; } - else MailErrors++; + + if (Prog) + Prog->Value(Prog->Value() + 1); + } + + Status |= MailErrors == InitMailErrors; + break; + } + case MAGIC_CONTACT: + { + LHashTbl,Contact*> ToContacts; + for (auto t: To->Items) + { + Contact *c = t->IsContact(); + if (c) + { + auto k = ContactKey(c); + if (k) + ToContacts.Add(k, c); + } } - if (Prog) + int InitContactErrors = ContactErrors; + uint64 Last = LCurrentTime(); + for (auto t: From->Items) { - Prog->Value(Prog->Value() + 1); - uint64 Now = LCurrentTime(); - if (Now > Last + 300) + if (Prog && Prog->IsCancelled()) + break; + + Contact *c = t->IsContact(); + if (c) { - LYield(); - Last = Now; + auto k = ContactKey(c); + if (k) + { + if (!ToContacts.Find(k)) + { + Contact *n = new Contact(App); + if (n) + { + *n = (Thing&)*c; + n->SetParentFolder(To); + } + else ContactErrors++; + } + else ContactSkipped++; + } + else ContactErrors++; + } + + if (Prog) + Prog->Value(Prog->Value() + 1); + } + + Status |= ContactErrors == InitContactErrors; + break; + } + + if (!FromLoaded) + { + From->UnloadThings(); + } + if (!ToLoaded) + { + To->UnloadThings(); + } + + if (Children) + { + char t[256]; + char f[256]; + LString n; + + for (ScribeFolder *c = From->GetChildFolder(); c && (!Prog || !Prog->IsCancelled()); c = c->GetNextFolder()) + { + n = c->GetName(true); + if (n) + { + strcpy_s(t, sizeof(t), ToPath); + char *e = t + strlen(t) - 1; + if (*e++ != '/') *e++ = '/'; + strcpy_s(e, sizeof(t)-(e-t), n); + + strcpy_s(f, sizeof(f), FromPath); + e = f + strlen(f) - 1; + if (*e++ != '/') *e++ = '/'; + strcpy_s(e, sizeof(f)-(e-f), n); + + ExportFolder(t, f, true, Prog); } } } - - Status |= MailErrors == InitMailErrors; - break; } - case MAGIC_CONTACT: - { - // bool FromLoaded = From->IsLoaded(); - To->LoadThings(); - From->LoadThings(); - - LHashTbl,Contact*> ToContacts; - for (auto t: To->Items) - { - Contact *c = t->IsContact(); - if (c) - { - auto k = ContactKey(c); - if (k) - ToContacts.Add(k, c); - } - } - - int InitContactErrors = ContactErrors; - uint64 Last = LCurrentTime(); - for (auto t: From->Items) - { - if (Prog && Prog->IsCancelled()) - break; - - Contact *c = t->IsContact(); - if (c) - { - auto k = ContactKey(c); - if (k) - { - if (!ToContacts.Find(k)) - { - Contact *n = new Contact(App); - if (n) - { - *n = (Thing&)*c; - n->SetParentFolder(To); - } - else ContactErrors++; - } - else ContactSkipped++; - } - else ContactErrors++; - } + }; - if (Prog) - { - Prog->Value(Prog->Value() + 1); - uint64 Now = LCurrentTime(); - if (Now > Last + 300) - { - LYield(); - Last = Now; - } - } - } - - Status |= ContactErrors == InitContactErrors; - break; - } - } - - if (!FromLoaded) - { - From->UnloadThings(); - } - if (!ToLoaded) + To->LoadThings(NULL, [&](auto status) { - To->UnloadThings(); - } - - if (Children) - { - char t[256]; - char f[256]; - LString n; - - for (ScribeFolder *c = From->GetChildFolder(); c && (!Prog || !Prog->IsCancelled()); c = c->GetNextFolder()) + From->LoadThings(NULL, [&](auto status) { - n = c->GetName(true); - if (n) - { - strcpy_s(t, sizeof(t), ToPath); - char *e = t + strlen(t) - 1; - if (*e++ != '/') *e++ = '/'; - strcpy_s(e, sizeof(t)-(e-t), n); - - strcpy_s(f, sizeof(f), FromPath); - e = f + strlen(f) - 1; - if (*e++ != '/') *e++ = '/'; - strcpy_s(e, sizeof(f)-(e-f), n); - - ExportFolder(t, f, true, Prog); - } - } - } + ProcessItem(); + }); + }); } } } } return Status; } int OnNotify(LViewI *c, LNotification n) { switch (c->GetId()) { case IDC_ALL: { OnAll(); break; } case IDC_SET_DEST: { - LFileSelect s; - s.Parent(this); - s.Type("Scribe Folders", "*.mail3"); - s.Type("All Files", LGI_ALL_FILES); - if (s.Open()) + auto s = new LFileSelect(this); + s->Type("Scribe Folders", "*.mail3"); + s->Type("All Files", LGI_ALL_FILES); + s->Open([this](auto dlg, auto status) { - DeleteObj(Folders); - EnableCtrls(false); - SetCtrlName(IDC_DEST, s.Name()); - OnSelectFolders(); - } + if (status) + { + DeleteObj(Folders); + EnableCtrls(false); + SetCtrlName(IDC_DEST, dlg->Name()); + OnSelectFolders(); + } + delete dlg; + }); break; } case IDC_ADD_SRC_FOLDER: { - if (Lst) + if (!Lst) + break; + + auto s = new FolderDlg(this, App); + s->DoModal([this, s](auto dlg, auto status) { - FolderDlg s(this, App); - if (s.DoModal() && ValidStr(s.Get())) + if (status && ValidStr(s->Get())) { bool Has = false; for (auto n : *Lst) { const char *p = n->GetText(0); - if (p && _stricmp(p, s.Get()) == 0) + if (p && _stricmp(p, s->Get()) == 0) { Has = true; break; } } if (!Has) { LListItem *i = new LListItem; if (i) { - i->SetText(s.Get()); + i->SetText(s->Get()); Lst->Insert(i); Lst->ResizeColumnsToContent(); } } } - } + + delete dlg; + }); break; } case IDC_DEL_SRC_FOLDER: { if (Lst) { List i; if (Lst->GetSelection(i)) { i.DeleteObjects(); } } break; } case IDC_SET_FOLDER: { LoadFolders(); - if (Mailbox) + if (!Mailbox) + break; + + auto s = new FolderDlg(this, App, MAGIC_NONE, Mailbox); + s->DoModal([this, s](auto dlg, auto ctrlId) { - FolderDlg s(this, App, MAGIC_NONE, Mailbox); - if (s.DoModal()) - { - SetCtrlName(IDC_FOLDER, s.Get()); - } - } + if (ctrlId) + SetCtrlName(IDC_FOLDER, s->Get()); + delete dlg; + }); break; } case IDOK: { LoadFolders(); AllFolders = GetCtrlValue(IDC_ALL) != 0; if ((ExceptTrashSpam = GetCtrlValue(IDC_NO_SPAM_TRASH) != 0)) { Spam = App->GetFolder("/Spam"); Trash = App->GetFolder(FOLDER_TRASH); } DestPath = NewStr(GetCtrlName(IDC_FOLDER)); if (Lst) { for (auto i : *Lst) { SrcPaths.Add(NewStr(i->GetText(0))); } } // fall through } case IDCANCEL: { LVariant v; if (Lst) { LStringPipe p; int n=0; for (auto i : *Lst) { p.Print("%s%s", n ? (char*)":" : (char*) "", i->GetText(0)); } char *s = p.NewStr(); if (s) { App->GetOptions()->SetValue(OPT_ScribeExpSrcPaths, v = s); DeleteArray(s); } } App->GetOptions()->SetValue(OPT_ScribeExpDstPath, v = GetCtrlName(IDC_FOLDER)); App->GetOptions()->SetValue(OPT_ScribeExpAll, v = (int)GetCtrlValue(IDC_ALL)); App->GetOptions()->SetValue(OPT_ScribeExpExclude, v = (int)GetCtrlValue(IDC_NO_SPAM_TRASH)); App->GetOptions()->SetValue(OPT_ScribeExpFolders, v = GetCtrlName(IDC_DEST)); EndModal(c->GetId() == IDOK); } } return 0; } int CountItems(ScribeFolder *f, bool Children) { int Status = 0; /* if (f && f->Store) { for (StorageItem *i = f->Store->GetChild(); i; i = i->GetNext()) { if (i->GetType() == MAGIC_MAIL || i->GetType() == MAGIC_CONTACT) { Status++; } } if (Children) { for (ScribeFolder *c = f->GetChildFolder(); c; c = c->GetNextFolder()) { if ((!Spam || c != Spam) && (!Trash || c != Trash)) { Status += CountItems(c, Children); } } } } */ return Status; } }; void ExportScribe(ScribeWnd *App) { - ScribeExport Dlg(App); - if (Dlg.DoModal()) + auto Dlg = new ScribeExport(App); + Dlg->DoModal([Dlg, App](auto dlg, auto ctrlId) { + if (ctrlId) { - LProgressDlg Prog(App); - Prog.SetDescription("Initializing..."); - Prog.SetType("items"); + { + LProgressDlg Prog(App); + Prog.SetDescription("Initializing..."); + Prog.SetType("items"); - LMailStore *Ms = App->GetDefaultMailStore(); - if (!Ms) - return; + auto Ms = App->GetDefaultMailStore(); + if (!Ms) + return; - int Items = 0; - if (Dlg.AllFolders) - { - Items += Dlg.CountItems(Ms->Root, true); - } - else - { - for (unsigned i=0; iAllFolders) + { + Items += Dlg->CountItems(Ms->Root, true); + } + else { - Items += Dlg.CountItems(App->GetFolder(Dlg.SrcPaths[i]), false); + for (auto path: Dlg->SrcPaths) + Items += Dlg->CountItems(App->GetFolder(path), false); } - } - Prog.SetRange(Items); + Prog.SetRange(Items); - if (Dlg.AllFolders) - { - Dlg.ExportFolder(Dlg.DestPath, "/", true, &Prog); - } - else - { - for (unsigned i=0; iAllFolders) + { + Dlg->ExportFolder(Dlg->DestPath, "/", true, &Prog); + } + else { - char Dest[256]; - strcpy_s(Dest, sizeof(Dest), Dlg.DestPath); - char *e = Dest + strlen(Dest) - 1; - if (*e == '/') *e = 0; - strcat(Dest, Dlg.SrcPaths[i]); + for (unsigned i=0; iSrcPaths.Length() && !Prog.IsCancelled(); i++) + { + char Dest[256]; + strcpy_s(Dest, sizeof(Dest), Dlg->DestPath); + char *e = Dest + strlen(Dest) - 1; + if (*e == '/') *e = 0; + strcat(Dest, Dlg->SrcPaths[i]); - bool s = Dlg.ExportFolder(Dest, Dlg.SrcPaths[i], false, &Prog); - if (!s) - { - break; + bool s = Dlg->ExportFolder(Dest, Dlg->SrcPaths[i], false, &Prog); + if (!s) + { + break; + } } } } + + LgiMsg( App, + "Mail export complete.\n" + "\n" + " Email: %i created, %i already exist, %i errors\n" + " Contacts: %i created, %i already exist, %i errors", + "Export", + MB_OK, + Dlg->MailCreated, + Dlg->MailSkipped, + Dlg->MailErrors, + Dlg->ContactCreated, + Dlg->ContactSkipped, + Dlg->ContactErrors + ); } - - LgiMsg( App, - "Mail export complete.\n" - "\n" - " Email: %i created, %i already exist, %i errors\n" - " Contacts: %i created, %i already exist, %i errors", - "Export", - MB_OK, - Dlg.MailCreated, - Dlg.MailSkipped, - Dlg.MailErrors, - Dlg.ContactCreated, - Dlg.ContactSkipped, - Dlg.ContactErrors - ); - } + delete dlg; + }); } diff --git a/Code/FolderCalendarSource.cpp b/Code/FolderCalendarSource.cpp --- a/Code/FolderCalendarSource.cpp +++ b/Code/FolderCalendarSource.cpp @@ -1,423 +1,427 @@ #include "Scribe.h" #include "CalendarView.h" #include "resdefs.h" ///////////////////////////////////////////////////////////////////////////////////// LArray CalendarSource::AllSources; LString CalendarSource::GetKey() { LString k; if (Id) k.Printf("%s.%s", OPT_CalendarSources, Id.Get()); return k; } LColour CalendarSource::GetColour() { return Colour; } ///////////////////////////////////////////////////////////////////////////////////// FolderCalendarSource::FolderCalendarSource(ScribeWnd *a, const char *id) { Id = id; App = a; Folder = NULL; } FolderCalendarSource::~FolderCalendarSource() { } void FolderCalendarSource::OnPulse() { if (!Folder) { Folder = App->GetFolder(Path); if (Folder) OnChange(false); } } void FolderCalendarSource::OnFolderDelete(ScribeFolder *f) { if (Folder == f) { Folder = NULL; OnChange(true); } } void FolderCalendarSource::SetColour(LColour c) { Colour = c; OnChange(false); } bool FolderCalendarSource::Delete() { LString k = GetKey(); bool r = App->GetOptions()->DeleteTag(k); if (r) { if (Folder) App->RemoveThingSrc(Folder); App->SaveOptions(); } else LAssert(!"Delete failed."); return r; } void FolderCalendarSource::SetPath(const char *p) { Path = p; Folder = App->GetFolder(Path); if (Path) { Write(); OnChange(false); } } void FolderCalendarSource::OnChange(bool IsDelete) { Update(); if (!GetList()) return; auto w = GetList()->GetWindow(); if (!w) return; CalendarView *cv = NULL; if (!w->GetViewById(IDC_CALENDAR, cv)) return; if (IsDelete) cv->OnSourceDelete(this); else cv->OnContentsChanged(this); } bool FolderCalendarSource::Read() { if (!Folder) { if (Id) { LString k = GetKey(); LXmlTag *t = App->GetOptions()->LockTag(k, _FL); if (t) { char *Col = t->GetAttr("Colour"); if (Col) Colour.Set((uint32_t)atoi64(Col), 32); else Colour.Empty(); Path = t->GetAttr("Path"); Display = t->GetAsInt("Display"); Folder = App->GetFolder(Path); App->GetOptions()->Unlock(); OnChange(false); return true; } } else LAssert(0); } return Folder != NULL; } bool FolderCalendarSource::Write() { LVariant v; if (!Id) { LXmlTag *t = App->GetOptions()->LockTag(OPT_CalendarSources, _FL); if (t) { LString Key; for (int i=0; i<100; i++) { Key.Printf("Source-%i", LRand(10000)); if (!t->GetChildTag(Key)) { Id = Key; break; } } App->GetOptions()->Unlock(); } } if (Id) { LString Key = GetKey(); LXmlTag *t = App->GetOptions()->LockTag(Key, _FL); if (!t) { App->GetOptions()->CreateTag(Key); t = App->GetOptions()->LockTag(Key, _FL); } if (t) { SaveAttr(t, CalendarSource::OptPath, Path); t->SetAttr(CalendarSource::OptColour, (int64) Colour.c32()); t->SetAttr(CalendarSource::OptDisplay, Display); t->SetAttr(CalendarSource::OptObject, GetClass()); App->GetOptions()->Unlock(); } else return false; } return true; } Calendar *FolderCalendarSource::NewEvent() { Calendar *c = new Calendar(App); if (!c) { return NULL; } c->App = App; if (!Folder) { Folder = App->GetFolder(Path); } if (!Folder) { LAssert(!"No folder?"); DeleteObj(c); return NULL; } LDataStoreI *Ms = Folder->GetObject()->GetStore(); if (!Ms) { LAssert(!"No mail store?"); DeleteObj(c); return NULL; } c->SetObject(Ms->Create(c->Type()), false, _FL); SetParentFolder(c, Folder); return c; } bool FolderCalendarSource::Match(char *Email) { bool Status = false; return Status; } void FolderCalendarSource::EditPath(LView *parent, CalendarView *cv) { if (!GetPath()) return; - FolderDlg Dlg(parent, App, MAGIC_CALENDAR); - if (!Dlg.DoModal()) - return; - - SetPath(Dlg.Get()); - if (cv) - cv->OnContentsChanged(this); + auto Dlg = new FolderDlg(parent, App, MAGIC_CALENDAR); + Dlg->DoModal([this, Dlg, cv](auto dlg, auto ctrlId) + { + if (ctrlId) + { + SetPath(Dlg->Get()); + if (cv) + cv->OnContentsChanged(this); + } + delete dlg; + }); } bool FolderCalendarSource::GetEvents(LDateTime &StartTs, LDateTime &EndTs, LArray &Events) { Read(); if (!Display) return false; LDateTime Start = StartTs; Start.ToUtc(); LDateTime End = EndTs; End.ToUtc(); LArray Search; - if (Folder) + if (!Folder) + return false; + + Folder->LoadThings(NULL, [&](auto Status) { - Folder->LoadThings(); - - for (auto t : Folder->Items) + for (auto t : Folder->Items) { Calendar *c = t->IsCalendar(); if (c) Search.Add(c); } - } - else return false; - for (auto c: Search) - { - LDateTime s, e; - if (c->GetCalType() == CalEvent && - c->GetField(FIELD_CAL_START_UTC, s)) + for (auto c: Search) { - int Recur = 0; - c->GetField(FIELD_CAL_RECUR, Recur); + LDateTime s, e; + if (c->GetCalType() == CalEvent && + c->GetField(FIELD_CAL_START_UTC, s)) + { + int Recur = 0; + c->GetField(FIELD_CAL_RECUR, Recur); + + const char *Sub = NULL; + c->GetField(FIELD_CAL_SUBJECT, Sub); - const char *Sub = NULL; - c->GetField(FIELD_CAL_SUBJECT, Sub); + if (Recur) + { + LArray Times; + if (c->GetTimes(Start, End, Times)) + { + SetCalendarsSource(c); + for (auto &t: Times) + { + t.src = this; + Events.Add(t); + } + } + } + else + { + if (!c->GetField(FIELD_CAL_END_UTC, e)) + { + e = s; + e.AddHours(1); + } - if (Recur) - { - LArray Times; - if (c->GetTimes(Start, End, Times)) - { - SetCalendarsSource(c); - for (auto &t: Times) + #if 0 + printf("%s: %s > %s, %s < %s\n", + Sub, + s.Get().Get(), + End.Get().Get(), + e.Get().Get(), + Start.Get().Get()); + #endif + if (s > End || e < Start) { - t.src = this; - Events.Add(t); + // Is before/after the range + } + else + { + TimePeriod &tp = Events.New(); + tp.src = this; + tp.c = c; + tp.s = s; + tp.e = e; + tp.ToLocal(); + SetCalendarsSource(c); } } } - else - { - if (!c->GetField(FIELD_CAL_END_UTC, e)) - { - e = s; - e.AddHours(1); - } - - #if 0 - printf("%s: %s > %s, %s < %s\n", - Sub, - s.Get().Get(), - End.Get().Get(), - e.Get().Get(), - Start.Get().Get()); - #endif - if (s > End || e < Start) - { - // Is before/after the range - } - else - { - TimePeriod &tp = Events.New(); - tp.src = this; - tp.c = c; - tp.s = s; - tp.e = e; - tp.ToLocal(); - SetCalendarsSource(c); - } - } } - } + }); return true; } void FolderCalendarSource::OnMouseClick(LMouse &m) { if (m.IsContextMenu()) { } else if (m.Down() && m.Left() && Parent) { int Col = Parent->ColumnAtX(m.x); if (Col == 0) { Display = !Display; Update(); Parent->SendNotify(LNotifyValueChanged); } else if (Col > 0) { SetCreateIn(this); } } } void FolderCalendarSource::OnPaintColumn(LItem::ItemPaintCtx &Ctx, int i, LItemColumn *c) { if (i == 0) { LRect r = Ctx; Ctx.pDC->Colour(Ctx.Back); for (int i=0; i<4; i++) { Ctx.pDC->Box(&r); r.Inset(1, 1); } Ctx.pDC->Colour(Colour); if (Display) Ctx.pDC->Rectangle(&r); else { Ctx.pDC->Box(&r); r.Inset(1, 1); Ctx.pDC->Colour(Ctx.Back); Ctx.pDC->Rectangle(&r); } } else { bool PathErr = (i == 1 && Path && !Folder); if (PathErr) Ctx.Fore = LColour::Red; LListItem::OnPaintColumn(Ctx, i, c); if (PathErr) { Ctx.pDC->Colour(Ctx.Fore); int Cy = Ctx.y1 + (Ctx.Y() >> 1) + 1; Ctx.pDC->Line(Ctx.x1, Cy, Ctx.x2, Cy); } } } const char *FolderCalendarSource::GetText(int i) { if (i == 1) { if (Folder && !Path) Path = Folder->GetPath(); return Path; } return NULL; } CalendarSource *CalendarSource::CreateIn = 0; void CalendarSource::SetCreateIn(CalendarSource *New) { if (CreateIn != New) { CreateIn = New; if (CreateIn) { if (CreateIn->Id) { LVariant v; v = CreateIn->Id.Get(); CreateIn->App->GetOptions()->SetValue(OPT_CalendarCreateIn, v); } else if (CreateIn->App) { CreateIn->App->GetOptions()->DeleteValue(OPT_CalendarCreateIn); } } } } diff --git a/Code/ImpExp_Mbox.cpp b/Code/ImpExp_Mbox.cpp --- a/Code/ImpExp_Mbox.cpp +++ b/Code/ImpExp_Mbox.cpp @@ -1,254 +1,254 @@ #include #include #include #include #include "Scribe.h" #include "lgi/common/NetTools.h" #include "lgi/common/Edit.h" #include "lgi/common/TextLabel.h" #include "lgi/common/TextFile.h" #include "resdefs.h" #include "lgi/common/LgiRes.h" #include "lgi/common/FileSelect.h" /////////////////////////////////////////////////////////////////////////// ChooseFolderDlg::ChooseFolderDlg ( ScribeWnd *parent, bool IsExport, const char *Title, const char *Msg, char *DefFolder, int FolderType, - LArray *Files + LString::Array *Files ) { Type = FolderType; SetParent(App = parent); DestFolder = 0; Export = IsExport; Lst = 0; if (LoadFromResource(Export ? IDD_FILES_EXPORT : IDD_FILES_IMPORT)) { Name(Title); if (GetViewById(IDC_FOLDER, Folder)) { Folder->Name(DefFolder ? DefFolder : (char*)"/"); Folder->Enabled(false); } SetCtrlName(IDC_MSG, Msg); if (GetViewById(IDC_FILES, Lst)) { if (Files) { for (unsigned i=0; iLength(); i++) - { InsertFile((*Files)[i]); - } } } } MoveToCenter(); } -ChooseFolderDlg::~ChooseFolderDlg() -{ - DeleteArray(DestFolder); - SrcFiles.DeleteArrays(); -} - void ChooseFolderDlg::InsertFile(const char *f) { - if (Lst) - { - bool Has = false; - for (auto n : *Lst) - { - char Path[MAX_PATH_LEN]; - LMakePath(Path, sizeof(Path), n->GetText(0), n->GetText(1)); - if (_stricmp(Path, f) == 0) - { - Has = true; - break; - } - } + if (!Lst) + return; - if (!Has) + bool Has = false; + for (auto n : *Lst) + { + char Path[MAX_PATH_LEN]; + LMakePath(Path, sizeof(Path), n->GetText(0), n->GetText(1)); + if (_stricmp(Path, f) == 0) { - LListItem *n = new LListItem; - if (n) - { - LString file = f; - auto parts = file.RSplit(DIR_STR, 1); - if (parts.Length() == 2) - { - n->SetText(parts[0], 0); - n->SetText(parts[1], 1); - Lst->Insert(n); - } - else LAssert(0); - } + Has = true; + break; } } + + if (Has) + return; + + LListItem *n = new LListItem; + if (!n) + return; + + auto parts = LString(f).RSplit(DIR_STR, 1); + if (parts.Length() == 2) + { + n->SetText(parts[0], 0); + n->SetText(parts[1], 1); + Lst->Insert(n); + } + else delete n; } int ChooseFolderDlg::OnNotify(LViewI *Ctrl, LNotification n) { switch (Ctrl->GetId()) { case IDC_FILES: { if (Lst && n.Type == LNotifyDeleteKey) { List Sel; if (Lst->GetSelection(Sel)) { Sel.DeleteObjects(); } } break; } case IDC_REMOVE_FILES: { if (Lst) { List Sel; if (Lst->GetSelection(Sel)) { Sel.DeleteObjects(); } } break; } case IDC_PICK_FILES: { - if (Lst) - { - LFileSelect s; + if (!Lst) + break; + + auto s = new LFileSelect(this); - s.Parent(this); - s.MultiSelect(!Export); - s.Type("All Files", LGI_ALL_FILES); - s.Type("MBOX Files", "*.mbx;*.mbox"); - s.Type("Outlook Express Folders", "*.mbx;*.dbx"); - s.Type("Mozilla Address Book", "*.mab"); - s.Type("Eudora Address Book", "NNdbase.txt"); - - if (s.Open()) + s->MultiSelect(!Export); + s->Type("All Files", LGI_ALL_FILES); + s->Type("MBOX Files", "*.mbx;*.mbox"); + s->Type("Outlook Express Folders", "*.mbx;*.dbx"); + s->Type("Mozilla Address Book", "*.mab"); + s->Type("Eudora Address Book", "NNdbase.txt"); + s->Open([this](auto dlg, auto status) + { + if (status) { if (Export) - { Lst->Empty(); - } - for (int i=0; iLength(); i++) + InsertFile((*dlg)[i]); } - } + delete dlg; + }); break; } case IDC_SET_FOLDER: { - FolderDlg Dlg(this, App, Type); - if (Folder) + if (!Folder) + break; + + auto Dlg = new FolderDlg(this, App, Type); + Dlg->DoModal([this, Dlg](auto dlg, auto ctrlId) { - if (Dlg.DoModal()) + if (ctrlId) { - char *f = Dlg.Get(); + auto f = Dlg->Get(); if (f) - { - Folder->Name(f); - } + this->Folder->Name(f); } - } + delete dlg; + }); break; } case IDOK: { if (Lst) { for (auto n : *Lst) { char Path[MAX_PATH_LEN]; LMakePath(Path, sizeof(Path), n->GetText(0), n->GetText(1)); - SrcFiles.Insert(NewStr(Path)); + SrcFiles.Add(Path); } } if (Folder) { DestFolder = NewStr(Folder->Name()); } EndModal(1); break; } case IDCANCEL: { EndModal(0); break; } } return 0; } /////////////////////////////////////////////////////////////////////////// void Import_UnixMBox(ScribeWnd *Parent) { ScribeFolder *Cur = Parent->GetCurrentFolder(); LString Path; if (Cur) Path = Cur->GetPath(); - ChooseFolderDlg Dlg(Parent, false, LLoadString(IDS_MBOX_IMPORT), LLoadString(IDS_MBOX_SELECT_FOLDER), Path); - if (Dlg.DoModal() && Dlg.DestFolder) + + auto Dlg = new ChooseFolderDlg(Parent, false, LLoadString(IDS_MBOX_IMPORT), LLoadString(IDS_MBOX_SELECT_FOLDER), Path); + Dlg->DoModal([Dlg, Parent](auto dlg, auto ctrlId) { - ScribeFolder *Folder = Parent->GetFolder(Dlg.DestFolder); - if (Folder) + if (ctrlId && Dlg->DestFolder) { - for (auto File: Dlg.SrcFiles) + ScribeFolder *Folder = Parent->GetFolder(Dlg->DestFolder); + if (Folder) { - LAutoPtr F(new LTextFile); - if (F->Open(File, O_READ)) - Folder->Import(Folder->AutoCast(F), sMimeMbox); + for (auto File: Dlg->SrcFiles) + { + LAutoPtr F(new LTextFile); + if (F->Open(File, O_READ)) + Folder->Import(Folder->AutoCast(F), sMimeMbox); + } } } - } + delete dlg; + }); } void Export_UnixMBox(ScribeWnd *Parent) { ScribeFolder *Cur = Parent->GetCurrentFolder(); LString Path; if (Cur) Path = Cur->GetPath(); - ChooseFolderDlg Dlg(Parent, + auto Dlg = new ChooseFolderDlg(Parent, true, LLoadString(IDS_MBOX_EXPORT), LLoadString(IDS_MBOX_EXPORT_FOLDER), Path); - if (Dlg.DoModal() && Dlg.DestFolder) + Dlg->DoModal([Dlg, Parent](auto dlg, auto ctrlId) { - ScribeFolder *Folder = Parent->GetFolder(Dlg.DestFolder); - if (Folder) + if (ctrlId && Dlg->DestFolder) { - for (auto File: Dlg.SrcFiles) + ScribeFolder *Folder = Parent->GetFolder(Dlg->DestFolder); + if (Folder) { - if (!LFileExists(File) || - LgiMsg(Parent, LLoadString(IDS_ERROR_FILE_EXISTS), AppName, MB_YESNO, File) == IDYES) + for (auto File: Dlg->SrcFiles) { - LAutoPtr F(new LFile); - if (F->Open(File, O_WRITE)) - Folder->Export(Folder->AutoCast(F), sMimeMbox); + if (!LFileExists(File) || + LgiMsg(Parent, LLoadString(IDS_ERROR_FILE_EXISTS), AppName, MB_YESNO, File) == IDYES) + { + LAutoPtr F(new LFile); + if (F->Open(File, O_WRITE)) + Folder->Export(Folder->AutoCast(F), sMimeMbox); + } } } } - } + delete dlg; + }); } diff --git a/Code/ImpExp_Outlook.cpp b/Code/ImpExp_Outlook.cpp --- a/Code/ImpExp_Outlook.cpp +++ b/Code/ImpExp_Outlook.cpp @@ -1,3916 +1,3939 @@ #undef UNICODE #include "Scribe.h" #include "lgi/common/Com.h" #include "mapix.h" #include "mapiutil.h" #include "lgi/common/Button.h" #include "lgi/common/Combo.h" #include "lgi/common/Edit.h" #include "lgi/common/ProgressDlg.h" #include "lgi/common/RtfHtml.h" #include "Calendar.h" #include "resdefs.h" #include "lgi/common/LgiRes.h" #if _MSC_VER >= 1400 typedef ULONG_PTR UI_TYPE; #else typedef ULONG UI_TYPE; #endif typedef HRESULT (STDAPICALLTYPE *pWrapCompressedRTFStream)(LPSTREAM lpCompressedRTFStream, ULONG ulFlags, LPSTREAM FAR * lpUncompressedRTFStream); #define PR_SMTP_ADDRESS (PROP_TAG(PT_STRING8, 0x39fe)) #define PR_SENDER_SMTP_ADDRESS (PROP_TAG(PT_STRING8, 0x0065)) #define PR_SENDER_SMTP_ADDRESS2 (PROP_TAG(PT_STRING8, 0x0c1f)) #ifndef PR_INTERNET_MESSAGE_ID #define PR_INTERNET_MESSAGE_ID 0x1035001E #endif #ifndef PR_BODY_HTML #define PR_BODY_HTML 0x1013001E #endif #ifndef PR_ATTACH_CONTENT_ID #define PR_ATTACH_CONTENT_ID 0x3712001E #endif char *RClientsEmail = "HKEY_LOCAL_MACHINE\\SOFTWARE\\Clients\\Mail"; // Helper classes/functions void RemoveChars(char *Str, int Start, int Len) { memmove(Str, Str + Len, strlen(Str + Len) + 1); } void InsertChars(char *Ins, char *Str, int At) { auto Len = strlen(Ins); memmove(Str + Len, Str, strlen(Str) + 1); memcpy(Str, Ins, Len); } class MapiEntry { public: uchar *Bin; int Len; MapiEntry(SPropValue *v) { Len = 0; Bin = 0; if (v) { Len = v->Value.bin.cb; Bin = new uchar[Len]; if (Bin) { memcpy(Bin, v->Value.bin.lpb, Len); } } } ~MapiEntry() { DeleteArray(Bin); } }; SPropValue *MapiGetField(SRow *Row, int Field) { SPropValue *v = 0; if (Row) { for (unsigned i=0; icValues; i++) { if (PROP_ID(Row->lpProps[i].ulPropTag) == PROP_ID(Field)) { return Row->lpProps + i; } } } return v; } SPropValue *MapiGetProp(IMAPIProp *Props, int Field) { SPropTagArray InTag; InTag.cValues = 1; InTag.aulPropTag[0] = Field; SPropValue *OutTag = 0; ULONG Tags = 0; if (Props) { if (Props->GetProps(&InTag, 0, &Tags, &OutTag) == S_OK) { if (Tags == 1) { return OutTag; } } } return 0; } int MapiCastInt(SPropValue *Val) { if (Val && PROP_TYPE(Val->ulPropTag) == PT_LONG) { return Val->Value.l; } if (Val && PROP_TYPE(Val->ulPropTag) == PT_SHORT) { return Val->Value.i; } return 0; } char *MapiCastString(SPropValue *Val) { if (Val && PROP_TYPE(Val->ulPropTag) == PT_STRING8) { return Val->Value.lpszA; } return 0; } bool MapiCastBinary(SPropValue *Val, void *&Ptr, int &Size) { if (Val && PROP_TYPE(Val->ulPropTag) == PT_BINARY) { Ptr = Val->Value.bin.lpb; Size = Val->Value.bin.cb; return true; } return false; } char *MapiGetPropStr(IMAPIProp *Props, int Field) { SPropValue *Val = MapiGetProp(Props, Field); if (Val) { return MapiCastString(Val); } return 0; } int MapiGetPropInt(IMAPIProp *Props, int Field) { SPropValue *Val = MapiGetProp(Props, Field); if (Val) { return MapiCastInt(Val); } return 0; } bool MapiSetPropStr(IMAPIProp *Props, int Field, const char *Str, bool Unicode = false) { if (Props && Str) { LAssert(PROP_TYPE(Field) == PT_STRING8); SPropValue p; LString n; p.ulPropTag = Field; if (Unicode) { p.Value.lpszA = n = LToNativeCp(Str); } else { p.Value.lpszA = (LPSTR)Str; } bool Status = Props->SetProps(1, &p, 0) == S_OK; return Status; } return false; } bool MapiSetPropLong(IMAPIProp *Props, int Field, ULONG lng) { if (Props) { SPropValue p; p.ulPropTag = Field; p.Value.l = lng; return Props->SetProps(1, &p, 0) == S_OK; } return false; } bool MapiSetPropBool(IMAPIProp *Props, int Field, bool b) { if (Props) { SPropValue p; p.ulPropTag = Field; p.Value.b = b; return Props->SetProps(1, &p, 0) == S_OK; } return false; } bool MapiSetPropDate(IMAPIProp *Props, int Field, const LDateTime &d, bool AdjustTz = true) { if (d.Year()) { // Set sent date SPropValue Prop; Prop.ulPropTag = Field; LDateTime a = d; if (AdjustTz) a.ToUtc(); SYSTEMTIME st; st.wDay = a.Day(); st.wMonth = a.Month(); st.wYear = a.Year(); st.wMinute = a.Minutes(); st.wHour = a.Hours(); st.wSecond = a.Seconds(); st.wMilliseconds = 0; if (SystemTimeToFileTime(&st, &Prop.Value.ft)) { return Props->SetProps(1, &Prop, 0) == S_OK; } } return false; } class LMapiList { LPMAPITABLE List; ULONG Rows; SRowSet *BaseRow; uint32_t i, StartIndex; bool ReleaseList; public: LMapiList(LPMAPITABLE &list, bool release = true) { List = list; list = NULL; Rows = 0; BaseRow = 0; i = 0; StartIndex = 0; ReleaseList = release; if (List) { HRESULT r = List->GetRowCount(0, &Rows); if (Rows && List->SeekRow(BOOKMARK_BEGINNING, 0, NULL) == S_OK) { if (List->QueryRows(Rows, 0, &BaseRow) == S_OK) { // Ok } } } } ~LMapiList() { if (List && ReleaseList) { List->Release(); } } int Index() { return i; } int Length() { return Rows; } bool More() { return (BaseRow && i >= StartIndex && i < BaseRow->cRows + StartIndex); } void Next() { i++; if (BaseRow->cRows < Rows && i-StartIndex >= BaseRow->cRows) { // run into the end of the list section int s = StartIndex + BaseRow->cRows; if (List->QueryRows(Rows-StartIndex, 0, &BaseRow) == S_OK) { StartIndex = s; } } } SRowSet *Current() { if (More()) // in range { return BaseRow + i - StartIndex; } return 0; } SPropValue *GetField(int Field) { if (More()) // in range { return MapiGetField(BaseRow->aRow + i - StartIndex, Field); } return 0; } }; // DEBUG STUFF #ifdef _DEBUG class LRow : public LListItem { SRow *Row; char **Data; int Cols; public: LRow(SRow *row) { Cols = 0; Row = row; if (Row) { Cols = Row->cValues; Data = new char*[Cols]; if (Data) { memset(Data, 0, sizeof(*Data)*Cols); } } } const char *GetText(int i) { SPropValue *Value = (Row)?Row->lpProps+i:0; if (Value) { char Str[256] = ""; DeleteArray(Data[i]); switch (PROP_TYPE(Value->ulPropTag)) { case PT_I2: { sprintf_s(Str, sizeof(Str), "%i", Value->Value.i); break; } case PT_I4: { sprintf_s(Str, sizeof(Str), "%i", Value->Value.l); break; } case PT_R8: { sprintf_s(Str, sizeof(Str), "%f", Value->Value.dbl); break; } case PT_BOOLEAN: { sprintf_s(Str, sizeof(Str), "%s", (Value->Value.b)?"true":"false"); break; } case PT_STRING8: { sprintf_s(Str, sizeof(Str), "%s", Value->Value.lpszA); break; } case PT_ERROR: { sprintf_s(Str, sizeof(Str), "e(%08.8X)", Value->Value.err); break; } case PT_BINARY: { sprintf_s(Str, sizeof(Str), "Bin(%i)", Value->Value.bin.cb); break; } default: { sprintf_s(Str, sizeof(Str), "#TYPE(%04.4X)", PROP_TYPE(Value->ulPropTag)); break; } } if (strlen(Str)>0) { Data[i] = NewStr(Str); } return (Data[i])?Data[i]:""; } return ""; } }; class LShowTable : public LDialog { SRowSet *Table; LList *List; public: LShowTable(LView *parent, SRowSet *table) { SetParent(parent); SetPos(LRect(0, 0, 660, 490)); MoveToCenter(); Name("Show Table"); Table = table; Children.Insert(new LButton(101, 550, 420, 60, 20, "Close")); Children.Insert(List = new LList(100, 10, 10, 600, 400)); if (List && Table) { if (Table->cRows > 0) { SRow *First = Table->aRow; for (unsigned i=0; icValues; i++) { char Str[256]; sprintf_s(Str, sizeof(Str), "%08.8X", First->lpProps[i].ulPropTag); List->AddColumn(Str, 80); } for (unsigned i=0; icRows; i++) { List->Insert(new LRow(Table->aRow+i)); } } - - DoModal(); } } int OnNotify(LViewI *Ctrl, LNotification n) { if (Ctrl->GetId() == 101) { EndModal(0); } return 0; } }; void ViewTable(LPMAPITABLE Table, LView *Wnd) { LMapiList Lst(Table); if (Lst.Current()) { - LShowTable Tbl(Wnd, Lst.Current()); + auto Tbl = new LShowTable(Wnd, Lst.Current()); + Tbl->DoModal(NULL); } } #endif // _DEBUG class EntryRef { public: char *DisplayName; int Size; LPENTRYID Entry; EntryRef() { DisplayName = 0; Size = 0; Entry = 0; } bool OpenRoot(LComPtr &Session, UI_TYPE UiHnd, LComPtr &MsgStore, LComPtr &RootFolder) { if (!Session) { LgiTrace("%s:%i - No Session.\n", _FL); return false; } if (Session->OpenMsgStore( UiHnd, Size, // entry bytes Entry, // ptr to entry NULL, // default interface: IMsgStore MAPI_BEST_ACCESS, MsgStore.Set()) != S_OK || !MsgStore) { LgiTrace("%s:%i - OpenMsgStore failed.\n", _FL); return false; } SPropValue *SubTree = MapiGetProp(MsgStore, PR_IPM_SUBTREE_ENTRYID); if (!SubTree) { LgiTrace("%s:%i - Failed to get PR_IPM_SUBTREE_ENTRYID.\n", _FL); return false; } ULONG ObjType; if (MsgStore->OpenEntry(SubTree->Value.bin.cb, (LPENTRYID)SubTree->Value.bin.lpb, NULL, MAPI_MODIFY, &ObjType, (IUnknown**) RootFolder.Set()) != S_OK || !RootFolder) { LgiTrace("%s:%i - OpenEntry failed.\n", _FL); MsgStore.Release(); return false; } return true; } }; class MessageStores : public List { LPMAPITABLE MsgStores; public: bool Status; MessageStores(LPMAPISESSION Session) { Status = false; MsgStores = 0; if (Session->GetMsgStoresTable(0, &MsgStores) == S_OK && MsgStores) { for (LMapiList Lst(MsgStores, false); Lst.More(); Lst.Next()) { SPropValue *DisplayName = Lst.GetField(PR_DISPLAY_NAME); SPropValue *Entry = Lst.GetField(PR_ENTRYID); if (DisplayName && Entry) { LAutoPtr Ref(new EntryRef); if (Ref) { Ref->DisplayName = MapiCastString(DisplayName); void *p = 0; int s = 0; if (Ref->DisplayName && MapiCastBinary(Entry, p, s)) { Ref->Size = s; Ref->Entry = (ENTRYID*) p; Insert(Ref.Release()); } } } } Status = true; } } ~MessageStores() { if (MsgStores) MsgStores->Release(); DeleteObjects(); } }; class ChooseMessageStoreDlg : public LDialog { ScribeWnd *App; LCombo *CStore; LEdit *Folder; bool InitOk; bool Directory; MessageStores *Stores; public: int Size; LPENTRYID Entry; EntryRef *Ref; LString FolderName; ChooseMessageStoreDlg(ScribeWnd *Wnd, LPMAPISESSION Session, bool Dir = true) { App = Wnd; Size = 0; Entry = 0; InitOk = false; Directory = Dir; Ref = 0; Stores = 0; CStore = 0; Folder = 0; if (LoadFromResource(IDD_OUTLOOK_IMPORT)) { Stores = new MessageStores(Session); if (Stores && Stores->Status) { if (GetViewById(IDC_MSG_STORE, CStore)) { InitOk = true; for (auto e: *Stores) CStore->Insert(e->DisplayName); } } if (GetViewById(IDC_FOLDER, Folder)) { Folder->Name("/"); Folder->Enabled(false); } } MoveToCenter(); } ~ChooseMessageStoreDlg() { DeleteObj(Stores); } bool InitCheck() { return InitOk; } void OnCreate() { LViewI *v = FindControl(IDC_TABLE); if (v) { LRect c = GetClient(); c.Inset(10, 10); v->SetPos(c); } } int OnNotify(LViewI *Ctrl, LNotification n) { switch (Ctrl->GetId()) { case IDC_SET_FOLDER: { - FolderDlg Dlg(this, App); - if (Folder) + if (!Folder) + break; + + auto Dlg = new FolderDlg(this, App); + Dlg->DoModal([this, Dlg](auto dlg, auto ctrlId) { - if (Dlg.DoModal()) - { - Folder->LView::Name(Dlg.Get()); - } - } + if (ctrlId) + this->Folder->LView::Name(Dlg->Get()); + delete dlg; + }); break; } case IDOK: { if (CStore) { Ref = Stores->ItemAt((int)CStore->Value()); if (Ref) { Size = Ref->Size; Entry = Ref->Entry; } } if (Directory && Folder) FolderName = Folder->Name(); EndModal(1); break; } case IDCANCEL: { EndModal(0); break; } } return 0; } }; // Import / Export parameters class ImportParams { public: ScribeWnd *App; Progress *Prog; Progress *ItemProg; bool AllFolders; LString::Array Import; ImportParams() { AllFolders = false; App = NULL; Prog = NULL; ItemProg = NULL; } ~ImportParams() { } }; class ExportParams { public: ScribeWnd *App; Progress *Prog; Progress *ItemProg; bool AllFolders; List Include; List Export; List Exclude; ExportParams() { AllFolders = false; App = 0; Prog = 0; ItemProg = 0; } }; int StrCmp(char *a, char *b, NativeInt d) { return _stricmp(a, b); } // Importer class class OutlookIO { friend class MailMapiSource; friend class ImportDlg; friend class ExportDlg; ScribeWnd *App; ScribeAccount *Account; LAutoString DefClient; HINSTANCE hMapi; LComPtr Session; LComPtr MsgStore; UI_TYPE Ui; LComPtr AddrBook; MAPIINITIALIZE *MAPIInitialize; MAPILOGONEX *MAPILogonEx; MAPIUNINITIALIZE *MAPIUninitialize; MAPIALLOCATEBUFFER *MAPIAllocateBuffer; MAPIFREEBUFFER *MAPIFreeBuffer; pWrapCompressedRTFStream WrapCompressedRTFStream; bool ImportEmail(ScribeFolder *Folder, Mail *&To, IMessage *From, bool Post); public: OutlookIO(ScribeWnd *Wnd, int Flags, ScribeAccount *Account = 0); ~OutlookIO(); bool LoadSession(LViewI *Parent, char *ResetClient); LComPtr &GetSession() { return Session; } UI_TYPE GetUiHnd() { return Ui; } // Import functions bool Import( ImportParams *P, IMAPIFolder *In, ScribeFolder *Out, LString::Array &Path); - bool ImportPersonalAddressBook(); + void ImportPersonalAddressBook(std::function callback); // Export functions bool Export( ExportParams *P, ScribeFolder *In, IMAPIFolder *Out, int Depth = 0); bool ExportCalendar(Calendar *c, IMessage *m); int CountFolders(ScribeFolder *f); // Workers bool ImportItem(ScribeFolder *Out, IMAPIFolder *In, SPropValue *EntryId); int FolderType(IMAPIFolder *In); }; // UI classes class AddFolderDlg : public LDialog { OutlookIO *Io; LTree *Tree; void Load(IMAPIFolder *f, LTreeItem *i) { if (f && i) { LPMAPITABLE Folders = 0; if (f->GetHierarchyTable(0, &Folders) == S_OK) { for (LMapiList Lst(Folders); Lst.More(); Lst.Next()) { SPropValue *v = Lst.GetField(PR_ENTRYID); LString Name = LFromNativeCp(MapiCastString(Lst.GetField(PR_DISPLAY_NAME))); if (v && Name) { LTreeItem *ChildItem = new LTreeItem; if (ChildItem) { ChildItem->SetText(Name); i->Insert(ChildItem); i->Expanded(true); IMAPIFolder *ChildFolder = 0; ULONG Type; if (f->OpenEntry(v->Value.bin.cb, (LPENTRYID)v->Value.bin.lpb, NULL, MAPI_BEST_ACCESS, &Type, (IUnknown**)&ChildFolder) == S_OK && ChildFolder) { Load(ChildFolder, ChildItem); ChildFolder->Release(); } } } } } } } public: LAutoString Path; AddFolderDlg(LView *parent, OutlookIO *io, EntryRef *Store) { Io = io; SetParent(parent); if (LoadFromResource(IDD_OUTLOOK_ADD_FLD)) { MoveToCenter(); GetViewById(IDC_FOLDERS, Tree); auto Session = Io->GetSession(); if (Tree && Session && Store) { LTreeItem *RootItem = new LTreeItem; if (RootItem) { RootItem->SetText(Store->DisplayName); Tree->Insert(RootItem); RootItem->Expanded(true); LComPtr MsgStore; LComPtr RootFolder; if (Store->OpenRoot(Session, Io->GetUiHnd(), MsgStore, RootFolder)) Load(RootFolder, RootItem); } } } } int OnNotify(LViewI *c, LNotification n) { switch (c->GetId()) { case IDOK: { LArray p; for (LTreeItem *s = Tree ? Tree->Selection() : 0; s; s = s->GetParent()) { p.AddAt(0, s); } LStringPipe n; for (unsigned i=1; iGetText()); } Path.Reset(n.NewStr()); LAssert(Path != NULL); // Fall thru } case IDCANCEL: { EndModal(c->GetId() == IDOK); break; } } return 0; } }; class IoDlg : public LDialog { protected: ScribeWnd *App; List Clients; OutlookIO *Io; char *DefClient; LAutoPtr Stores; LList *SrcFolders; LComPtr *MapiFolder; ScribeFolder **OurFolder; public: IoDlg( ScribeWnd *app, OutlookIO *io, LComPtr *mapifolder, ScribeFolder *&scribefolder) { App = app; Io = io; DefClient = 0; SrcFolders = 0; MapiFolder = mapifolder; OurFolder = &scribefolder; } ~IoDlg() { Clients.DeleteArrays(); DeleteArray(DefClient); } virtual void AddFolder() {} virtual void OnLogin(bool e) {} void InitSrcClients() { LRegKey Email(false, RClientsEmail); LCombo *SrcClient; if (Email.GetKeyNames(Clients) && GetViewById(IDC_CLIENT, SrcClient)) { Clients.Sort(StrCmp); DefClient = NewStr(Email.GetStr()); int i = 0; for (auto c: Clients) { SrcClient->Insert(c); if (DefClient && _stricmp(DefClient, c) == 0) { SrcClient->Value(i); } i++; } } } void AddPath(char *p) { if (SrcFolders && p) { bool Has = false; for (auto i : *SrcFolders) { const char *s = i->GetText(0); if (s && _stricmp(s, p) == 0) { Has = true; break; } } if (!Has) { LListItem *i = new LListItem; if (i) { i->SetText(p); SrcFolders->Insert(i); SrcFolders->ResizeColumnsToContent(); } } } } int OnNotify(LViewI *v, LNotification n) { bool Ok = false; switch (v->GetId()) { case IDC_LOGIN: { const char *NewClient = GetCtrlName(IDC_CLIENT); if (!NewClient) { LgiMsg(this, "Unknown Client Name.", AppName); break; } if (!Io->LoadSession(this, DefClient)) { LgiMsg(this, "Outlook LoadSession failed.\n", AppName); break; } OnLogin(true); Stores.Reset(new MessageStores(Io->GetSession())); LCombo *StoreCbo; if (!Stores || !GetViewById(IDC_MSG_STORE, StoreCbo)) { LgiMsg(this, "No store combo?\n", AppName); break; } for (auto e: *Stores) StoreCbo->Insert(e->DisplayName); StoreCbo->Value(0); break; } case IDC_ADD_SRC_FOLDER: { AddFolder(); break; } case IDC_DEL_SRC_FOLDER: { if (!SrcFolders) { LgiTrace("%s:%i - SrcFolders is NULL.\n", _FL); break; } List s; if (!SrcFolders->GetSelection(s)) { LgiTrace("%s:%i - GetSelection failed.\n", _FL); break; } s.DeleteObjects(); break; } } return 0; } }; class ImportDlg : public IoDlg { ImportParams *Params; public: ImportDlg( ScribeWnd *app, ImportParams *params, OutlookIO *io, LComPtr *mapifolder, ScribeFolder *&scribefolder) : IoDlg(app, io, mapifolder, scribefolder) { SetParent(App = app); Params = params; if (LoadFromResource(IDD_OUTLOOK_IMPORT)) { MoveToCenter(); OnLogin(false); GetViewById(IDC_SRC_FOLDERS, SrcFolders); LVariant s; if (App->GetOptions()->GetValue(OPT_OutlookImportSrc, s) && s.Str()) { LToken t(s.Str(), ","); for (unsigned i=0; iGetOptions()->GetValue(OPT_OutlookImportDst, s) && s.Str()) SetCtrlName(IDC_FOLDER, s.Str()); else { auto Cur = App->GetCurrentFolder(); if (Cur) { auto Path = Cur->GetPath(); if (Path) SetCtrlName(IDC_FOLDER, Path); } } if (App->GetOptions()->GetValue(OPT_OutlookImportAll, s)) { SetCtrlValue(IDC_ALL, s.CastInt32()); } InitSrcClients(); } } void OnLogin(bool e) { SetCtrlEnabled(IDC_CLIENT, !e); SetCtrlEnabled(IDC_LOGIN, !e); SetCtrlEnabled(IDC_MSG_STORE, e); SetCtrlEnabled(IDC_ALL, e); SetCtrlEnabled(IDC_SRC_FOLDERS, e); SetCtrlEnabled(IDC_ADD_SRC_FOLDER, e); SetCtrlEnabled(IDC_DEL_SRC_FOLDER, e); SetCtrlEnabled(IDC_SET_FOLDER, e); SetCtrlEnabled(IDOK, e); SetCtrlEnabled(IDC_FOLDER, false); } void AddFolder() { if (Stores) { EntryRef *e = (*Stores)[(int)GetCtrlValue(IDC_MSG_STORE)]; - if (e) + if (!e) { - AddFolderDlg Dlg(this, Io, e); - if (Dlg.DoModal()) + auto Dlg = new AddFolderDlg(this, Io, e); + Dlg->DoModal([this, Dlg](auto dlg, auto ctrlId) { - AddPath(Dlg.Path); - } + if (ctrlId) + AddPath(Dlg->Path); + delete dlg; + }); } } } int OnNotify(LViewI *v, LNotification n) { bool Ok = false; switch (v->GetId()) { case IDC_SET_FOLDER: { - FolderDlg Dlg(this, App); - if (Dlg.DoModal()) + auto Dlg = new FolderDlg(this, App); + Dlg->DoModal([this, Dlg](auto dlg, auto ctrlId) { - SetCtrlName(IDC_FOLDER, Dlg.Get()); - } + if (ctrlId) + SetCtrlName(IDC_FOLDER, Dlg->Get()); + delete dlg; + }); break; } case IDOK: { Ok = true; // fall thru } case IDCANCEL: { LVariant k; const char *s; if (SrcFolders) { Params->Import.Empty(); Params->Import.SetFixedLength(false); for (auto i : *SrcFolders) Params->Import.New() = i->GetText(0); Params->Import.SetFixedLength(true); auto s = LString(",").Join(Params->Import); if (s) App->GetOptions()->SetValue(OPT_OutlookImportSrc, k = s); else App->GetOptions()->DeleteValue(OPT_OutlookImportSrc); } s = GetCtrlName(IDC_FOLDER); if (s) { App->GetOptions()->SetValue(OPT_OutlookImportDst, k = s); if (Ok) *OurFolder = App->GetFolder(s); } else { App->GetOptions()->DeleteValue(OPT_OutlookImportDst); } Params->AllFolders = GetCtrlValue(IDC_ALL) != 0; App->GetOptions()->SetValue(OPT_OutlookImportAll, k = (int)Params->AllFolders); if (Ok && Io->GetSession() && Stores) { EntryRef *e = (*Stores)[(int)GetCtrlValue(IDC_MSG_STORE)]; if (e) { e->OpenRoot(Io->GetSession(), Io->GetUiHnd(), Io->MsgStore, *MapiFolder); } } EndModal(v->GetId()); break; } } return IoDlg::OnNotify(v, n); } }; class ExportDlg : public IoDlg { ExportParams *Params; public: ExportDlg( ScribeWnd *app, ExportParams *params, OutlookIO *io, LComPtr *MapiFolder, ScribeFolder *&ScribeFolder) : IoDlg(app, io, MapiFolder, ScribeFolder) { SetParent(app); Params = params; if (LoadFromResource(IDD_OUTLOOK_EXPORT)) { MoveToCenter(); OnLogin(false); GetViewById(IDC_SRC_FOLDERS, SrcFolders); SetCtrlValue(IDC_NO_SPAM_TRASH, true); LVariant v; if (App->GetOptions()->GetValue(OPT_OutlookExportSrc, v) && v.Str()) { LToken t(v.Str(), ","); for (unsigned i=0; iGetOptions()->GetValue(OPT_OutlookExportDst, v)) { SetCtrlName(IDC_FOLDER, v.Str()); } if (App->GetOptions()->GetValue(OPT_OutlookExportAll, v)) { SetCtrlValue(IDC_ALL, v.CastInt32()); } if (App->GetOptions()->GetValue(OPT_OutlookExportExclude, v)) { SetCtrlValue(IDC_NO_SPAM_TRASH, v.CastInt32()); } InitSrcClients(); } } void OnLogin(bool e) { SetCtrlEnabled(IDC_CLIENT, !e); SetCtrlEnabled(IDC_LOGIN, !e); SetCtrlEnabled(IDC_MSG_STORE, e); SetCtrlEnabled(IDC_ALL, e); SetCtrlEnabled(IDC_NO_SPAM_TRASH, e); SetCtrlEnabled(IDC_SRC_FOLDERS, e); SetCtrlEnabled(IDC_ADD_SRC_FOLDER, e); SetCtrlEnabled(IDC_DEL_SRC_FOLDER, e); SetCtrlEnabled(IDC_SET_FOLDER, e); SetCtrlEnabled(IDOK, e); SetCtrlEnabled(IDC_FOLDER, false); } void AddFolder() { - FolderDlg Fs(this, App); - if (Fs.DoModal() && Fs.Get()) + auto Fs = new FolderDlg(this, App); + Fs->DoModal([this, Fs](auto dlg, auto ctrlId) { - AddPath(Fs.Get()); - } + if (ctrlId && Fs->Get()) + AddPath(Fs->Get()); + delete dlg; + }); } LComPtr GetSubFolderByName(IMAPIFolder *f, char *name) { LComPtr ChildFolder; if (f && name) { LPMAPITABLE Folders = 0; if (f->GetHierarchyTable(0, &Folders) == S_OK) { for (LMapiList Lst(Folders); Lst.More(); Lst.Next()) { SPropValue *v = Lst.GetField(PR_ENTRYID); char *Name = MapiCastString(Lst.GetField(PR_DISPLAY_NAME)); if (v && Name) { if (_stricmp(Name, name) == 0) { ULONG Type; f->OpenEntry(v->Value.bin.cb, (LPENTRYID)v->Value.bin.lpb, NULL, MAPI_BEST_ACCESS, &Type, (IUnknown**)ChildFolder.Set()); } } } } } return ChildFolder; } int OnNotify(LViewI *c, LNotification n) { switch (c->GetId()) { case IDC_SET_FOLDER: { if (Stores) { EntryRef *e = (*Stores)[(int)GetCtrlValue(IDC_MSG_STORE)]; if (e) { - AddFolderDlg Dlg(this, Io, e); - if (Dlg.DoModal() == IDOK) + auto Dlg = new AddFolderDlg(this, Io, e); + Dlg->DoModal([this, Dlg](auto dlg, auto id) { - SetCtrlName(IDC_FOLDER, Dlg.Path); - } + if (id) + SetCtrlName(IDC_FOLDER, Dlg->Path); + delete dlg; + }); } } break; } case IDOK: { Params->Include.Empty(); Params->AllFolders = GetCtrlValue(IDC_ALL) != 0; for (auto i : *SrcFolders) { ScribeFolder *f = App->GetFolder(i->GetText(0)); if (f) { if (!Params->Export.HasItem(f)) { Params->Export.Insert(f); } if (!Params->Include.HasItem(f)) { Params->Include.Insert(f); ScribeFolder *p = f; while (p = p->GetFolder()) { if (!Params->Include.HasItem(p)) { Params->Include.Insert(p); } } } } } if (GetCtrlValue(IDC_NO_SPAM_TRASH)) { Params->Exclude.Insert(App->GetFolder("/Spam")); Params->Exclude.Insert(App->GetFolder(FOLDER_TRASH)); } // Get the scribe folder to start at... *OurFolder = App->GetFolder("/"); // Get the mapi folder to start at.. if (Io->GetSession() && Stores) { EntryRef *e = (*Stores)[(int)GetCtrlValue(IDC_MSG_STORE)]; if (e) { // Open the store... e->OpenRoot(Io->GetSession(), Io->GetUiHnd(), Io->MsgStore, *MapiFolder); // Drill down to the select sub-folder... const char *DstFolder = GetCtrlName(IDC_FOLDER); if (DstFolder) { LToken t(DstFolder, "/"); if (t.Length() > 0) { LComPtr f = *MapiFolder; for (unsigned i=0; iGetText(0)); } char *Fld = p.NewStr(); if (Fld) { App->GetOptions()->SetValue(OPT_OutlookExportSrc, v = Fld); DeleteArray(Fld); } else { App->GetOptions()->DeleteValue(OPT_OutlookExportSrc); } App->GetOptions()->SetValue(OPT_OutlookExportDst, v = GetCtrlName(IDC_FOLDER)); App->GetOptions()->SetValue(OPT_OutlookExportAll, v = GetCtrlValue(IDC_ALL)); App->GetOptions()->SetValue(OPT_OutlookExportExclude, v = GetCtrlValue(IDC_NO_SPAM_TRASH)); EndModal(c->GetId() == IDOK); break; } } return IoDlg::OnNotify(c, n); } }; void Import_Outlook(ScribeWnd *Wnd, int Flags) { OutlookIO Imp(Wnd, Flags); } void Export_Outlook(ScribeWnd *Wnd) { OutlookIO Exp(Wnd, EXP_OUTLOOK_EMAIL); } OutlookIO::OutlookIO(ScribeWnd *Wnd, int Flags, ScribeAccount *account) { App = Wnd; hMapi = NULL; Ui = (UI_TYPE) ((Wnd) ? Wnd->Handle() : 0); MAPIInitialize = NULL; MAPILogonEx = NULL; MAPIUninitialize = NULL; MAPIAllocateBuffer = NULL; MAPIFreeBuffer = NULL; WrapCompressedRTFStream = NULL; Account = account; ImportParams IParams; IParams.App = App; ExportParams EParams; EParams.App = App; LComPtr MapiFolder; ScribeFolder *OurFolder = NULL; + #if 0 // FIXME if (Flags == IMP_OUTLOOK) { ImportDlg Dlg(Wnd, &IParams, this, &MapiFolder, OurFolder); if (Dlg.DoModal() == IDOK) { } else return; } else if (Flags == EXP_OUTLOOK_EMAIL) { ExportDlg Dlg(Wnd, &EParams, this, &MapiFolder, OurFolder); if (Dlg.DoModal() == IDOK) { } else return; } if (Session && MapiFolder && OurFolder) { if (Flags == EXP_OUTLOOK_EMAIL) { LProgressDlg *Prog = new LProgressDlg(Wnd); if (Prog) { Prog->SetDescription("Initializing..."); if (EParams.Export.Length()) { Prog->SetRange(EParams.Export.Length()); } else { LMailStore *Ms = App->GetDefaultMailStore(); Prog->SetRange(Ms ? CountFolders(Ms->Root) : 0); } EParams.Prog = Prog->ItemAt(0); EParams.ItemProg = Prog->Push(); if (EParams.ItemProg) { EParams.ItemProg->SetDescription("Items..."); } } for (auto *f = OurFolder->GetChildFolder(); f; f = f->GetNextFolder()) { Export(&EParams, f, MapiFolder); } DeleteObj(Prog); } else { if (Flags == IMP_OUTLOOK_PAB) { ImportPersonalAddressBook(); } else { LProgressDlg *Prog = new LProgressDlg(Wnd); if (Prog) { Prog->SetDescription("Initializing..."); if (IParams.Import.Length()) { Prog->SetRange(IParams.Import.Length()); } else { LMailStore *Ms = App->GetDefaultMailStore(); Prog->SetRange(Ms ? CountFolders(Ms->Root) : 0); } IParams.Prog = Prog->ItemAt(0); IParams.ItemProg = Prog->Push(); if (IParams.ItemProg) { IParams.ItemProg->SetDescription("Items..."); } } LString::Array Path; Import(&IParams, MapiFolder, OurFolder, Path); DeleteObj(Prog); } } } + #endif if (MsgStore) MsgStore.Release(); } OutlookIO::~OutlookIO() { if (AddrBook) AddrBook.Release(); if (Session) { Session->Logoff(0, 0, 0); Session.Release(); } if (MsgStore) MsgStore.Release(); if (MAPIUninitialize) { MAPIUninitialize(); } if (hMapi) { FreeLibrary(hMapi); hMapi = NULL; } } bool OutlookIO::LoadSession(LViewI *Parent, char *ResetClient) { bool Status = false; char Cur[MAX_PATH_LEN]; GetCurrentDirectory(sizeof(Cur), Cur); DefClient.Reset(NewStr(ResetClient)); HRESULT Error = S_OK; hMapi = LoadLibraryA("mapi32.dll"); if (hMapi) { MAPIInitialize = (MAPIINITIALIZE*) GetProcAddress(hMapi, "MAPIInitialize"); MAPILogonEx = (MAPILOGONEX*) GetProcAddress(hMapi, "MAPILogonEx"); MAPIAllocateBuffer = (MAPIALLOCATEBUFFER*) GetProcAddress(hMapi, "MAPIAllocateBuffer"); MAPIFreeBuffer = (MAPIFREEBUFFER*) GetProcAddress(hMapi, "MAPIFreeBuffer"); WrapCompressedRTFStream = (pWrapCompressedRTFStream) GetProcAddress(hMapi, "WrapCompressedRTFStream"); if (MAPIInitialize && MAPILogonEx && MAPIAllocateBuffer && MAPIFreeBuffer && MAPIInitialize(NULL) == S_OK) { MAPIUninitialize = (MAPIUNINITIALIZE*) GetProcAddress(hMapi, "MAPIUninitialize"); if (!Account) { if (MAPILogonEx(Parent ? (UI_TYPE)Parent->Handle() : Ui, NULL, NULL, MAPI_LOGON_UI | MAPI_EXTENDED, Session.Set()) == S_OK && Session) { SetCurrentDirectory(Cur); Status = true; } else { LgiMsg(App, LLoadString(IDS_MAPI_LOGIN_FAILED), AppName); } } } else { LgiMsg(App, LLoadString(IDS_ERROR_MAPI_INIT_FAILED), AppName); } } else { LgiMsg(App, LLoadString(IDS_ERROR_MAPI_NOT_INSTALLED), AppName); } if (!Status && hMapi) { MAPIUninitialize(); MAPIUninitialize = NULL; MAPIInitialize = NULL; MAPILogonEx = NULL; MAPIAllocateBuffer = NULL; MAPIFreeBuffer = NULL; WrapCompressedRTFStream = NULL; FreeLibrary(hMapi); hMapi = NULL; } return Status; } Mail *MatchEmail(ScribeFolder *f, Mail *m1) { f->LoadThings(); for (auto t: f->Items) { Mail *m2 = t->IsMail(); if (m2) { const char *id1, *id2; if ((id1 = m1->GetMessageId()) && (id2 = m2->GetMessageId()) && strcmp(id1, id2) == 0) { return m2; } } } for (auto t: f->Items) { Mail *m2 = t->IsMail(); if (m2) { if (m1->GetSubject() && m2->GetSubject() && strcmp(m1->GetSubject(), m2->GetSubject()) == 0 && *m1->GetDateSent() == *m2->GetDateSent()) { return m2; } } } return 0; } int MapNetCpToWin(int Cp) { switch (Cp) { case 708: return 1256; case 720: return 1256; case 28596: return 1256; case 10004: return 1256; case 1256: return 1256; case 775: return 1257; case 28594: return 1257; case 1257: return 1257; case 852: return 1250; case 28592: return 1250; case 10029: return 1250; case 1250: return 1250; case 51936: return 936; case 936: return 936; case 52936: return 936; case 10008: return 936; case 950: return 950; case 20000: return 950; case 20002: return 950; case 10002: return 950; case 866: return 1251; case 28595: return 1251; case 20866: return 1251; case 21866: return 1251; case 10007: return 1251; case 1251: return 1251; case 29001: return 1252; case 20106: return 1252; case 737: return 1253; case 28597: return 1253; case 10006: return 1253; case 1253: return 1253; case 869: return 1253; case 862: return 1255; case 38598: return 1255; case 28598: return 1255; case 10005: return 1255; case 1255: return 1255; case 20420: return 1256; case 20880: return 1251; case 21025: return 1251; case 20277: return 1252; case 1142: return 1252; case 20278: return 1252; case 1143: return 1252; case 1147: return 1252; case 20273: return 1252; case 1141: return 1252; case 875: return 1253; case 20423: return 1253; case 20424: return 1255; case 20871: return 1252; case 1149: return 1252; case 1148: return 1252; case 20280: return 1252; case 1144: return 1252; case 50930: return 932; case 50939: return 932; case 50931: return 932; case 20290: return 932; case 50933: return 949; case 20833: return 949; case 870: return 1250; case 50935: return 936; case 20284: return 1252; case 1145: return 1252; case 20838: return 874; case 50937: return 950; case 1026: return 1254; case 20905: return 1254; case 20285: return 1252; case 1146: return 1252; case 37: return 1252; case 1140: return 1252; case 861: return 1252; case 10079: return 1252; case 57006: return 57006; case 57003: return 57003; case 57002: return 57002; case 57010: return 57010; case 57008: return 57008; case 57009: return 57009; case 57007: return 57007; case 57011: return 57011; case 57004: return 57004; case 57005: return 57005; case 51932: return 932; case 50220: return 932; case 50222: return 932; case 50221: return 932; case 10001: return 932; case 932: return 932; case 949: return 949; case 51949: return 949; case 50225: return 949; case 1361: return 1361; case 10003: return 949; case 28593: return 1254; case 28605: return 1252; case 20108: return 1252; case 437: return 1252; case 20107: return 1252; case 874: return 874; case 857: return 1254; case 28599: return 1254; case 10081: return 1254; case 1254: return 1254; case 1200: return 1200; case 1201: return 1200; case 65000: return 1200; case 65001: return 1200; case 20127: return 1252; case 1258: return 1258; case 850: return 1252; case 20105: return 1252; case 28591: return 1252; case 10000: return 1252; case 1252: return 1252; } return 0; } LAutoString MapiToUtf(const char *Charset, const char *Text) { LAutoString s; char16 *w = (char16*) LNewConvertCp(LGI_WideCharset, Text, Charset ? Charset : LAnsiToLgiCp()); if (w) { s.Reset(WideToUtf8(w)); DeleteArray(w); } return s; } bool OutlookIO::ImportEmail(ScribeFolder *Folder, Mail *&To, IMessage *From, bool Post) { if (!To) return false; if (!From) { To->DecRef(); return false; } char *MsgId = MapiGetPropStr(From, PR_INTERNET_MESSAGE_ID); if (MsgId) { To->SetMessageId(MsgId); } // Get the codepage... int CodePage = MapNetCpToWin(MapiGetPropInt(From, 0x3FDE0003)); const char *Charset = LAnsiToLgiCp(CodePage); // Get Internet Header fairly early in the peice to allow us to // extract the codepage for the body of the message. char *Header = MapiGetPropStr(From, PR_TRANSPORT_MESSAGE_HEADERS); if (Header) { To->SetInternetHeader(Header); } // Get subject char *Subject = MapiGetPropStr(From, PR_SUBJECT); if (Subject) { LAutoString a = MapiToUtf(Charset, Subject); To->SetSubject(a); } // Get sent date SPropValue *Sent = MapiGetProp(From, PR_CLIENT_SUBMIT_TIME); if (Sent) { SYSTEMTIME st; FileTimeToSystemTime(&Sent->Value.ft, &st); LDateTime dt; dt.Day(st.wDay); dt.Month(st.wMonth); dt.Year(st.wYear); dt.Minutes(st.wMinute); dt.Hours(st.wHour); dt.Seconds(st.wSecond); To->SetDateSent(&dt); } // Do replication side of things... Mail *Match = MatchEmail(Folder, To); if (Match) { To->DecRef(); return true; } // Get from char *SenderEmail = MapiGetPropStr(From, PR_SENDER_EMAIL_ADDRESS); char *SenderName = MapiGetPropStr(From, PR_SENT_REPRESENTING_NAME); if (!SenderName) SenderName = MapiGetPropStr(From, PR_SENDER_NAME); To->GetFrom()->SetStr(FIELD_NAME, SenderName); if (SenderEmail && strchr(SenderEmail, '@')) { To->GetFrom()->SetStr(FIELD_EMAIL, SenderEmail); } else { // Ok we have to look up their email address ourselves. SPropValue *EmailEntryId = MapiGetProp(From, PR_SENDER_ENTRYID); if (EmailEntryId) { if (!AddrBook) Session->OpenAddressBook(Ui, NULL, 0, AddrBook.Set()); if (AddrBook) { ULONG Type = 0; IMailUser *User = 0; if (AddrBook->OpenEntry(EmailEntryId->Value.bin.cb, (LPENTRYID) EmailEntryId->Value.bin.lpb, 0, MAPI_BEST_ACCESS, &Type, (IUnknown**)&User) == S_OK && User) { char *E1 = MapiGetPropStr(User, PR_EMAIL_ADDRESS); char *E2 = MapiGetPropStr(User, PR_SMTP_ADDRESS); if (E1 && strchr(E1, '@')) { To->GetFrom()->SetStr(FIELD_EMAIL, E1); } else if (E2 && strchr(E2, '@')) { To->GetFrom()->SetStr(FIELD_EMAIL, E2); } } } } } // for all recipients LPMAPITABLE Recipients = 0; if (From->GetRecipientTable(0, &Recipients) == S_OK && Recipients) { for (LMapiList Lst(Recipients); Lst.More(); Lst.Next()) { SPropValue *Name = Lst.GetField(PR_DISPLAY_NAME); SPropValue *Type = Lst.GetField(PR_RECIPIENT_TYPE); SPropValue *Email1 = Lst.GetField(PR_EMAIL_ADDRESS); SPropValue *Email2 = Lst.GetField(PR_SMTP_ADDRESS); if (Name || Email1 || Email2) { LDataPropI *r = To->GetTo()->Create(To->GetObject()->GetStore()); if (r) { if (Name) { r->SetStr(FIELD_NAME, MapiCastString(Name)); } char *E1 = (Email1) ? MapiCastString(Email1) : 0; char *E2 = (Email2) ? MapiCastString(Email2) : 0; if (E1 && strchr(E1, '@')) { r->SetStr(FIELD_EMAIL, E1); } else if (E2 && strchr(E2, '@')) { r->SetStr(FIELD_EMAIL, E2); } if (Type) { LONG Flags = Type->Value.ul; if (Flags & MAPI_TO) { r->SetInt(FIELD_CC, 0); } else if (Flags & MAPI_CC) { r->SetInt(FIELD_CC, 1); } else if (Flags & MAPI_BCC) { r->SetInt(FIELD_CC, 2); } } To->GetTo()->Insert(r); } } } } // Get Body/Text char *Body = MapiGetPropStr(From, PR_BODY); if (Body) { LAutoString a = MapiToUtf(Charset, Body); To->SetBody(a); To->SetBodyCharset("utf-8"); } char *Html = MapiGetPropStr(From, PR_BODY_HTML); if (Html) { To->SetHtml(Html); } else { // Rtf/HTML IStream *Comp = 0; if (WrapCompressedRTFStream && From->OpenProperty( PR_RTF_COMPRESSED, &IID_IStream, 0, 0, (IUnknown**) &Comp) == S_OK) { IStream *Uncomp = 0; if (WrapCompressedRTFStream(Comp, 0, &Uncomp) == S_OK) { LStringPipe p; char Buf[1024]; ULONG r; while (Uncomp->Read(Buf, sizeof(Buf), &r) == S_OK) { if (r) { p.Push(Buf, r); } else break; } char *Rtf = p.NewStr(); if (Rtf) { { int i=0; char p[256]; for (; true; i++) { #ifdef WINDOWS sprintf_s(p, sizeof(p), "c:\\temp\\rtf-%i.txt", i); #else sprintf_s(p, sizeof(p), "/tmp/rtf-%i.txt", i); #endif if (!LFileExists(p)) break; } LFile f; if (f.Open(p, O_WRITE)) { f.Write(Rtf, strlen(Rtf)); } } LAutoString HtmlStr(MsRtfToHtml(Rtf)); To->SetHtml(HtmlStr); DeleteArray(Rtf); } Uncomp->Release(); } Comp->Release(); } } // Get Attachments LPMAPITABLE Attachments = 0; if (From->GetAttachmentTable(0, &Attachments) == S_OK && Attachments) { for (LMapiList Lst(Attachments); Lst.More(); Lst.Next()) { SPropValue *Num = Lst.GetField(PR_ATTACH_NUM); if (!Num) continue; IAttach *Attach = 0; if (From->OpenAttach(Num->Value.ul, NULL, MAPI_BEST_ACCESS, &Attach) != S_OK || !Attach) continue; SPropValue *Method = MapiGetProp(Attach, PR_ATTACH_METHOD); if (Method) { char *AttachName = MapiGetPropStr(Attach, PR_ATTACH_LONG_FILENAME); char *ContentId = MapiGetPropStr(Attach, PR_ATTACH_CONTENT_ID); switch (Method->Value.ul) { case ATTACH_BY_VALUE: { Attachment *File = 0; IStream *Stream = 0; if (Attach->OpenProperty(PR_ATTACH_DATA_BIN, &IID_IStream, 0, 0, (IUnknown**)&Stream) == S_OK && Stream) { STATSTG Stat; if (Stream->Stat(&Stat, STATFLAG_DEFAULT) == S_OK) { int Size = Stat.cbSize.LowPart; char *Data = new char[Size]; if (Data) { int Block = 128 << 10; char *p = Data; for (int i=0; iRead(p, (ULONG)Len, NULL) != S_OK) { break; } p += Len; } if (p == Data + Size) { File = new Attachment(Folder->App); if (File) { File->Set(Data, Size); } } DeleteArray(Data); } } Stream->Release(); } if (File) { char *MimeType = MapiGetPropStr(Attach, PR_ATTACH_MIME_TAG); File->SetMimeType(MimeType ? MimeType : "application/octet-stream"); if (AttachName) { File->SetName(AttachName); } else { char Str[256]; sprintf_s(Str, sizeof(Str), "file%i", Num->Value.ul); File->SetName(Str); } if (ContentId) { File->SetContentId(ContentId); } To->AttachFile(File); // remove attachment from memory if (File->GetObject()) { /* FIXME File->Store->Object = 0; File->Store = 0; DeleteObj(File); */ } } break; } case ATTACH_BY_REFERENCE: case ATTACH_BY_REF_RESOLVE: case ATTACH_BY_REF_ONLY: { char *FileName = MapiGetPropStr(Attach, PR_ATTACH_LONG_PATHNAME); if (FileName) { To->AttachFile(App, FileName); } break; } case ATTACH_EMBEDDED_MSG: { break; } } } Attach->Release(); } } // Set Sent/Received/Read/Unread Flags SPropValue *Flags = MapiGetProp(From, PR_MESSAGE_FLAGS); if (Flags) { ulong f = 0; if (Flags->Value.ul & (MSGFLAG_SUBMIT | MSGFLAG_UNSENT) && !Post) { f |= MAIL_CREATED; } else { f |= MAIL_RECEIVED; } if (Flags->Value.ul & MSGFLAG_HASATTACH) { f |= MAIL_ATTACHMENTS; } if (Flags->Value.ul & MSGFLAG_READ) { f |= MAIL_READ; } To->SetFlags(f); } To->Update(); To->Save(Folder); To = NULL; return true; } bool PropToDate(SPropValue *p, LDateTime &dt) { if (p && PROP_TYPE(p->ulPropTag) == PT_SYSTIME) { uint64 i = (((uint64)p->Value.ft.dwHighDateTime) << 32) | p->Value.ft.dwLowDateTime; return dt.Set(i); } return false; } Contact *MatchContact(ScribeFolder *f, Contact *c1) { f->LoadThings(); for (auto t: f->Items) { Contact *c2 = t->IsContact(); if (c2) { #define CmpOpt(name) \ { \ const char *s1, *s2; \ if (!c1->Get(name, s1) || \ !c2->Get(name, s2) || \ _stricmp(s1, s2) != 0) \ { \ continue; \ } \ } CmpOpt(OPT_First); CmpOpt(OPT_Last); CmpOpt(OPT_Email); return c2; } } return 0; } Calendar *MatchCalendar(ScribeFolder *f, Calendar *c1) { f->LoadThings(); for (auto t: f->Items) { Calendar *c2 = t->IsCalendar(); if (c2) { LDateTime d1, d2; if (c1->GetField(FIELD_CAL_START_UTC, d1) && c2->GetField(FIELD_CAL_START_UTC, d2) && d1 != d2) { continue; } if (c1->GetField(FIELD_CAL_END_UTC, d1) && c2->GetField(FIELD_CAL_END_UTC, d2) && d1 != d2) { continue; } const char *s1, *s2; if (c1->GetField(FIELD_CAL_SUBJECT, s1) && c2->GetField(FIELD_CAL_SUBJECT, s2) && _stricmp(s1, s2) != 0) { continue; } int t1, t2; if (c1->GetField(FIELD_CAL_TYPE, t1) && c2->GetField(FIELD_CAL_TYPE, t2) && t1 != t2) { continue; } return c2; } } return 0; } bool OutlookIO::ImportItem(ScribeFolder *Out, IMAPIFolder *In, SPropValue *EntryId) { - LYield(); - bool Status = false; if (Out && In && EntryId) { LDataStoreI::StoreTrans StoreTransaction = Out->GetObject()->GetStore()->StartTransaction(); ULONG Type = 0; IUnknown *Item = 0; HRESULT e; if ((e = In->OpenEntry( EntryId->Value.bin.cb, (LPENTRYID)EntryId->Value.bin.lpb, NULL, MAPI_BEST_ACCESS, &Type, &Item)) == S_OK && Item) { switch (Type) { case MAPI_MESSAGE: { IMessage *Msg = 0; if (Item->QueryInterface(IID_IMessage, (void**)&Msg) == S_OK && Msg) { SPropValue *Class = MapiGetProp(Msg, PR_ORIG_MESSAGE_CLASS); if (!Class) { Class = MapiGetProp(Msg, PR_MESSAGE_CLASS); } if (Class) { char *sClass = MapiCastString(Class); bool Note = _strnicmp(sClass, "IPM.Note", 8) == 0; bool Post = _strnicmp(sClass, "IPM.Post", 8) == 0; bool Document = _strnicmp(sClass, "IPM.Document", 12) == 0; if (Note || Document || Post) { // Email Mail *Email = new Mail(Out->App); if (Email) { Email->App = App; Status = ImportEmail(Out, Email, Msg, Post); } LAssert(Email == NULL); } else if (_stricmp(sClass, "IPM.Contact") == 0) { // Contact Contact *c = new Contact(Out->App); if (c) { c->App = App; #define ConvertStr(opt, tag) \ { auto s = LFromNativeCp(MapiGetPropStr(Msg, tag)); \ c->Set(opt, s); } ConvertStr(OPT_First, PR_GIVEN_NAME); ConvertStr(OPT_Last, PR_SURNAME); char *Email = 0; #define CheckTagForEmail(tag) \ if (!Email) { char *e = MapiGetPropStr(Msg, tag); \ if (e && strchr(e, '@')) Email = e; } CheckTagForEmail(0x81b0001e); CheckTagForEmail(0x805d001e); CheckTagForEmail(0x8060001e); CheckTagForEmail(0x81ae001e); c->Set(OPT_Email, Email); Contact *Match = MatchContact(Out, c); if (Match) { DeleteObj(c); c = Match; } ConvertStr(OPT_HomeStreet, PR_STREET_ADDRESS); ConvertStr(OPT_HomeSuburb, PR_LOCALITY); // city ConvertStr(OPT_HomePostcode, PR_POSTAL_CODE); ConvertStr(OPT_HomeState, PR_STATE_OR_PROVINCE); ConvertStr(OPT_HomeCountry, PR_COUNTRY); ConvertStr(OPT_WorkPhone, PR_BUSINESS_TELEPHONE_NUMBER); ConvertStr(OPT_HomePhone, PR_HOME_TELEPHONE_NUMBER); ConvertStr(OPT_HomeMobile, PR_CELLULAR_TELEPHONE_NUMBER); ConvertStr(OPT_HomeFax, PR_PRIMARY_FAX_NUMBER); char *WebPage = MapiGetPropStr(Msg, PR_PERSONAL_HOME_PAGE); if (!WebPage) WebPage = MapiGetPropStr(Msg, PR_BUSINESS_HOME_PAGE); c->Set(OPT_HomeWebPage, WebPage); ConvertStr(OPT_Nick, PR_NICKNAME); ConvertStr(OPT_Spouse, PR_SPOUSE_NAME); ConvertStr(OPT_Note, PR_BODY); c->Save(Out); Status = true; } } else if (_stricmp(sClass, "IPM.Appointment") == 0) { Calendar *c = new Calendar(Out->App); if (c) { c->App = App; bool Historical = false; LDateTime dt, Now; Now.SetNow(); SPropValue *p = MapiGetProp(Msg, PR_START_DATE); if (PropToDate(p, dt)) { Historical = dt < Now; c->SetField(FIELD_CAL_START_UTC, dt); } p = MapiGetProp(Msg, PR_END_DATE); if (PropToDate(p, dt)) { c->SetField(FIELD_CAL_END_UTC, dt); } char *s = MapiGetPropStr(Msg, PR_SUBJECT); if (s) c->SetField(FIELD_CAL_SUBJECT, s); Calendar *Match = MatchCalendar(Out, c); if (Match) { c->DecRef(); c = Match; } s = MapiGetPropStr(Msg, 0x810C001E); if (s) c->SetField(FIELD_CAL_LOCATION, s); s = MapiGetPropStr(Msg, PR_BODY); if (s) c->SetField(FIELD_CAL_NOTES, s); // Only import alarms for future objects, otherwise a replicate from // Outlook -> Scribe sets off all the old alarms. Which is annoying. if (!Historical) { p = MapiGetProp(Msg, 0x81EE000B); if (p && p->Value.b) { /* FIXME c->SetField(FIELD_CAL_REMINDER_ACTION, true); int i = MapiCastInt(MapiGetProp(Msg, 0x81E90003)); c->SetField(FIELD_CAL_REMINDER_TIME, -i); */ } } // Erm, you'd usually have to map the names here, but I used the Outlook list // to write my calendar, so it doesn't need converting... ;) int ShowAs = MapiCastInt(MapiGetProp(Msg, 0x81930003)); c->SetField(FIELD_CAL_SHOW_TIME_AS, ShowAs); c->Save(Out); Status = true; } } } Msg->Release(); } else { LgiTrace("%s:%i - QueryInterface for IMessage failed.\n", __FILE__, __LINE__); } break; } } Item->Release(); } else { LgiTrace("%s:%i - OpenEntry failed.\n", __FILE__, __LINE__); } } return Status; } int OutlookIO::FolderType(IMAPIFolder *In) { int Status = MAGIC_MAIL; LPMAPITABLE Items = 0; if (In && In->GetContentsTable(0, &Items) == S_OK) { // Loop through all the items bool Done = false; for (LMapiList Lst(Items); Lst.More() && !Done; Lst.Next()) { SPropValue *EntryId = Lst.GetField(PR_ENTRYID); if (EntryId) { ULONG Type = 0; IUnknown *Item = 0; if (In->OpenEntry( EntryId->Value.bin.cb, (LPENTRYID)EntryId->Value.bin.lpb, NULL, MAPI_BEST_ACCESS, &Type, &Item) == S_OK && Item) { IMessage *Msg = 0; if (Item->QueryInterface(IID_IMessage, (void**)&Msg) == S_OK && Msg) { char *Vs; SPropValue *v = MapiGetProp(Msg, PR_MESSAGE_CLASS); if (v && (Vs = MapiCastString(v))) { if (_stricmp(Vs, "IPM.Note") == 0) { Status = MAGIC_MAIL; Done = true; } else if (_stricmp(Vs, "IPM.Contact") == 0) { Status = MAGIC_CONTACT; Done = true; } else if (_stricmp(Vs, "IPM.Appointment") == 0) { Status = MAGIC_CALENDAR; Done = true; } } Msg->Release(); } } } } } return Status; } LString ConvertToString(LString::Array &Path) { LString d("/"); LString ret = d + d.Join(Path); return ret; } bool OutlookIO::Import( ImportParams *P, IMAPIFolder *In, ScribeFolder *Out, LString::Array &Path) { bool Status = false; if (App && Out && In) { const char *FolderName = Out->GetText(0); if (FolderName && P->Prog) { char FolderStr[256]; sprintf_s(FolderStr, sizeof(FolderStr), "Folder: '%s'", FolderName); P->Prog->SetDescription(FolderStr); - LYield(); } bool ImportThisFolder = P->AllFolders; if (!ImportThisFolder) { auto CurPath = ConvertToString(Path); for (auto s: P->Import) { if (_stricmp(CurPath, s) == 0) { ImportThisFolder = true; break; } } } if (ImportThisFolder) { // look through all the items for mail and contacts LPMAPITABLE Items = 0; if (In->GetContentsTable(0, &Items) == S_OK) { // ViewTable(Items, Wnd); Status = true; LMapiList Lst(Items); if (Lst.Length() > 0) { int GoodConvert = 0; if (P->ItemProg) { P->ItemProg->SetDescription("Processing items..."); P->ItemProg->SetRange(Lst.Length()); } // Loop through all the items for (; Lst.More() && !P->ItemProg->IsCancelled(); Lst.Next()) { SPropValue *v = Lst.GetField(PR_ENTRYID); if (v) { if (ImportItem(Out, In, v)) { GoodConvert++; } else { #ifdef _DEBUG // try it again ImportItem(Out, In, v); #endif } } if (P->ItemProg) { P->ItemProg->Value(Lst.Index()); } } if (P->ItemProg) { P->ItemProg->SetDescription(""); P->ItemProg->Value(0); P->ItemProg->SetRange(0); } } } } // look through all the children folders for more stuff to import LPMAPITABLE Folders = 0; if (In->GetHierarchyTable(0, &Folders) == S_OK) { // Loop through all the folders for (LMapiList Lst(Folders); Lst.More() && !P->Prog->IsCancelled(); Lst.Next()) { SPropValue *v = Lst.GetField(PR_ENTRYID); SPropValue *Name = Lst.GetField(PR_DISPLAY_NAME); if (v && Name) { IMAPIFolder *ChildIn; ULONG Type; if (In->OpenEntry(v->Value.bin.cb, (LPENTRYID)v->Value.bin.lpb, NULL, MAPI_BEST_ACCESS, &Type, (IUnknown**)&ChildIn) == S_OK && ChildIn) { // Check that this folder exists auto OutlookName = LFromNativeCp(MapiCastString(Name)); char Str[256]; auto OutPath = Out->GetPath(); strcpy_s(Str, sizeof(Str), OutPath); if (Str[strlen(Str)-1] != '/') strcat(Str, "/"); strcat(Str, OutlookName); ScribeFolder *ChildOut = App->GetFolder(Str); if (!ChildOut) { ChildOut = Out->CreateSubDirectory(OutlookName, FolderType(ChildIn)); } if (ChildOut) { Path.New() = MapiCastString(Name); Status &= Import(P, ChildIn, ChildOut, Path); ChildOut->Save(); Path.PopLast(); } ChildIn->Release(); } } } } } return Status; } -bool OutlookIO::ImportPersonalAddressBook() +void OutlookIO::ImportPersonalAddressBook(std::function callback) { - bool Status = false;; - IAddrBook *AddrBook = 0; - HRESULT Error = S_OK; - ScribeFolder *DestFolder = 0; - - FolderDlg Dlg(App, App, MAGIC_CONTACT); - if (Dlg.DoModal()) + auto Dlg = new FolderDlg(App, App, MAGIC_CONTACT); + Dlg->DoModal([this, Dlg, callback](auto dlg, auto ctrlId) { - DestFolder = App->GetFolder(Dlg.Get()); - } - - if (DestFolder && - (Error = Session->OpenAddressBook(Ui, NULL, 0, &AddrBook)) == S_OK && - AddrBook) - { - ULONG EntryIDSize = 0; - ENTRYID *PersonalAddrBook = 0; - if (AddrBook->GetPAB(&EntryIDSize, &PersonalAddrBook) == S_OK && - PersonalAddrBook) + if (!ctrlId) + { + delete dlg; + if (callback) + callback(false); + return; + } + + bool Status = false; + HRESULT Error = S_OK; + IAddrBook *AddrBook = 0; + auto DestFolder = App->GetFolder(Dlg->Get()); + if (DestFolder && + (Error = Session->OpenAddressBook(Ui, NULL, 0, &AddrBook)) == S_OK && + AddrBook) { - ULONG Type = 0; - IDistList *DistList = 0; - if (AddrBook->OpenEntry(EntryIDSize, - PersonalAddrBook, - NULL, - 0, - &Type, - (IUnknown**)&DistList) == S_OK && - DistList) + ULONG EntryIDSize = 0; + ENTRYID *PersonalAddrBook = 0; + if (AddrBook->GetPAB(&EntryIDSize, &PersonalAddrBook) == S_OK && + PersonalAddrBook) { - int NewContacts = 0; - LPMAPITABLE DistContents = 0; - if (DistList->GetContentsTable(0, &DistContents) == S_OK && - DistContents) + ULONG Type = 0; + IDistList *DistList = 0; + if (AddrBook->OpenEntry(EntryIDSize, + PersonalAddrBook, + NULL, + 0, + &Type, + (IUnknown**)&DistList) == S_OK && + DistList) { - for (LMapiList Lst(DistContents); Lst.More(); Lst.Next()) + int NewContacts = 0; + LPMAPITABLE DistContents = 0; + if (DistList->GetContentsTable(0, &DistContents) == S_OK && + DistContents) { - SPropValue *v = Lst.GetField(PR_ENTRYID); - if (v) + for (LMapiList Lst(DistContents); Lst.More(); Lst.Next()) { - ULONG type = 0; - IMailUser *User = 0; - if (DistList->OpenEntry( v->Value.bin.cb, - (LPENTRYID) v->Value.bin.lpb, - 0, - 0, - &type, - (IUnknown**)&User) == S_OK && - User) + SPropValue *v = Lst.GetField(PR_ENTRYID); + if (v) { - SPropValue *Array = 0; - ULONG Values = 0; - - if (User->GetProps( NULL, // Props - 0, - &Values, - &Array) == S_OK && - Array) + ULONG type = 0; + IMailUser *User = 0; + if (DistList->OpenEntry( v->Value.bin.cb, + (LPENTRYID) v->Value.bin.lpb, + 0, + 0, + &type, + (IUnknown**)&User) == S_OK && + User) { - Contact *Person = (Contact*)App->CreateItem(MAGIC_CONTACT, DestFolder, false); - if (Person) + SPropValue *Array = 0; + ULONG Values = 0; + + if (User->GetProps( NULL, // Props + 0, + &Values, + &Array) == S_OK && + Array) { - for (unsigned n=0; nCreateItem(MAGIC_CONTACT, DestFolder, false); + if (Person) { - switch (Array[n].ulPropTag) + for (unsigned n=0; nSet(OPT_First, Name); - Person->Set(OPT_Last, Space+1); - - const char *Last = 0; - Person->Get(OPT_Last, Last); - int n=0; + int Spaces = 0; + for (int k=0; Name.Get()[k]; k++) + { + if (Name.Get()[k] == ' ') Spaces++; + } + + if (Spaces == 1) + { + char *Space = strchr(Name, ' '); + *Space = 0; + Person->Set(OPT_First, Name); + Person->Set(OPT_Last, Space+1); + + const char *Last = 0; + Person->Get(OPT_Last, Last); + int n=0; + } + else + { + Person->Set(OPT_First, Name); + } } - else - { - Person->Set(OPT_First, Name); - } + break; } - break; - } - case PR_EMAIL_ADDRESS: - case PR_SMTP_ADDRESS: - { - char *Addr = Array[n].Value.lpszA; - if (strchr(Addr, '@')) - { - Person->Set(OPT_Email, Array[n].Value.lpszA); - } - break; - } - default: - { - if (PROP_ID(Array[n].ulPropTag) >= 0x8000 && - PROP_ID(Array[n].ulPropTag) <= 0x8100 && - PROP_TYPE(Array[n].ulPropTag) == PT_STRING8) + case PR_EMAIL_ADDRESS: + case PR_SMTP_ADDRESS: { char *Addr = Array[n].Value.lpszA; if (strchr(Addr, '@')) { Person->Set(OPT_Email, Array[n].Value.lpszA); } + break; } - break; + default: + { + if (PROP_ID(Array[n].ulPropTag) >= 0x8000 && + PROP_ID(Array[n].ulPropTag) <= 0x8100 && + PROP_TYPE(Array[n].ulPropTag) == PT_STRING8) + { + char *Addr = Array[n].Value.lpszA; + if (strchr(Addr, '@')) + { + Person->Set(OPT_Email, Array[n].Value.lpszA); + } + } + break; + } } } + + Person->Save(); + NewContacts++; } - - Person->Save(); - NewContacts++; } } } } } + + char Msg[256]; + sprintf_s(Msg, sizeof(Msg), "%i contacts imported from Outlook.", NewContacts); + LgiMsg(App, Msg, AppName, MB_OK); + + Status = true; } - - char Msg[256]; - sprintf_s(Msg, sizeof(Msg), "%i contacts imported from Outlook.", NewContacts); - LgiMsg(App, Msg, AppName, MB_OK); - - Status = true; + else + { + LgiMsg(App, "Couldn't open the personal address book.", "Error", MB_OK); + } } else { - LgiMsg(App, "Couldn't open the personal address book.", "Error", MB_OK); + LgiMsg(App, "No personal address book.", "Error", MB_OK); } } else { - LgiMsg(App, "No personal address book.", "Error", MB_OK); + LgiMsg(App, "Couldn't open the address book.", "Error", MB_OK); } - } - else - { - LgiMsg(App, "Couldn't open the address book.", "Error", MB_OK); - } - - return Status; + + delete dlg; + if (callback) + callback(Status); + }); } void Import_OutlookContacts(ScribeWnd *Parent) { #ifdef WIN32 Import_Outlook(Parent, IMP_OUTLOOK_PAB); #else ScribeFolder *Contacts = Parent->GetFolder(FOLDER_CONTACTS); LFileSelect Select; Select.Parent(Parent); Select.Type("Outlook Contacts", "*.csv"); if (Contacts && Select.Open()) { ImpRecordSet Rs; if (ReadCsv(Select.Name(), Rs) > 0) { for (ImpRecord *r = Rs.First(); r; r = Rs.Next()) { Contact *c = new Contact; if (c) { CopyField(c, OPT_First, r, "First Name"); CopyField(c, OPT_Last, r, "Last Name"); CopyField(c, OPT_Email, r, "E-mail Address"); CopyField(c, OPT_Street, r, "Home Street"); CopyField(c, OPT_Suburb, r, "Home City"); CopyField(c, OPT_State, r, "Home State"); CopyField(c, OPT_Postcode, r, "Home Postal Code"); CopyField(c, OPT_Country, r, "Home Country"); CopyField(c, OPT_Work, r, "Business Phone"); CopyField(c, OPT_Home, r, "Home Phone"); CopyField(c, OPT_Mobile, r, "Mobile Phone"); CopyField(c, OPT_Fax, r, "Business Fax"); CopyField(c, OPT_WebPage, r, "Web Page"); c->Save(Contacts); } } } } #endif } //////////////////////////////////////////////////////////////////////////// bool SetPropToEntryId(SPropValue &Prop, int Id, char *Email, char *Name) { /* struct SenderEntry { char Flags[4]; // 0 char Muid[16]; // MAPI_ONE_OFF_UID uint16 wVersion; // 0 uint16 wFlags; // MAPI_ONE_OFF_NO_RICH_INFO (1) char *DisplayName; char *AddrType; char *EmailAddr; }; */ uchar MapiOneOffUid[] = MAPI_ONE_OFF_UID; char AddrType[] = "SMTP"; if (Email && Name) { LMemQueue p; int32 i32 = 0; int16 i16 = 0; p.Write(&i32, sizeof(i32)); p.Write(MapiOneOffUid, sizeof(MapiOneOffUid)); p.Write(&i16, sizeof(i16)); i16 = MAPI_ONE_OFF_NO_RICH_INFO; p.Write(&i16, sizeof(i16)); #define WriteStr(s) \ p.Write(s ? s : "", (s ? strlen(s) : 0) + 1); if (Name) { WriteStr(Name); } else if (Email) { WriteStr(Email); } WriteStr(AddrType); WriteStr(Email); Prop.ulPropTag = Id; Prop.Value.bin.cb = (ULONG) p.GetSize(); Prop.Value.bin.lpb = (uchar*)p.New(); return Prop.Value.bin.lpb != 0; } return false; }; int OutlookIO::CountFolders(ScribeFolder *f) { int Count = 1; for (ScribeFolder *c = f->GetChildFolder(); c; c = c->GetNextFolder()) { Count += CountFolders(c); } return Count; } bool OutlookIO::ExportCalendar(Calendar *c, IMessage *m) { bool Status = false; if (c && m) { LDateTime dt; const char *s; int i; if (c->GetCalType() == CalEvent) { MapiSetPropStr(m, PR_MESSAGE_CLASS, "IPM.Appointment", false); } else if (c->GetCalType() == CalTodo) { MapiSetPropStr(m, PR_MESSAGE_CLASS, "IPM.Task", false); } char Tz[64]; int TzOff = dt.SystemTimeZone(); sprintf_s(Tz, sizeof(Tz), "(+%.2f)", (double)TzOff / 60); MapiSetPropStr(m, 0x8091001E, Tz); if (c->GetField(FIELD_CAL_START_UTC, dt)) { MapiSetPropDate(m, PR_START_DATE, dt); MapiSetPropDate(m, 0x80800040, dt); } if (c->GetField(FIELD_CAL_END_UTC, dt)) { MapiSetPropDate(m, PR_END_DATE, dt); MapiSetPropDate(m, 0x80810040, dt); } if (c->GetField(FIELD_CAL_SUBJECT, s)) MapiSetPropStr(m, PR_SUBJECT, s); if (c->GetField(FIELD_CAL_LOCATION, s)) MapiSetPropStr(m, 0x810C001E, s); if (c->GetField(FIELD_CAL_NOTES, s)) MapiSetPropStr(m, PR_BODY, s); /* FIXME if (c->GetField(FIELD_CAL_REMINDER_ACTION, i) && i) { MapiSetPropBool(m, 0x81EE000B, true); if (c->GetField(FIELD_CAL_REMINDER_TIME, i)) MapiSetPropLong(m, 0x81E90003, -i); } */ if (c->GetField(FIELD_CAL_SHOW_TIME_AS, i)) MapiSetPropLong(m, 0x81930003, i); MapiSetPropLong(m, PR_MESSAGE_FLAGS, 1); /* MapiSetPropLong(m, 0x80030003, 369); MapiSetPropLong(m, 0x807D0003, 2); MapiSetPropBool(m, 0x8001000B, true); MapiSetPropBool(m, 0x8002000B, false); */ } return Status; } bool OutlookIO::Export( ExportParams *P, ScribeFolder *In, IMAPIFolder *Out, int Depth) { bool Status = false; HRESULT Err; // 'In' is created/found as a sub-folder of 'Out' if (App && Out && In && !P->Exclude.HasItem(In) && (P->AllFolders || P->Include.HasItem(In)) && (!P->Prog || !P->Prog->IsCancelled())) { // Create/find the output folder LPMAPITABLE Folders = 0; if ((Err = Out->GetHierarchyTable(0, &Folders)) == S_OK) { // ViewTable(Folders, App); IMAPIFolder *Match = 0; auto InName = In->GetName(true); // Loop through all the folders { for (LMapiList Lst(Folders); Lst.More() && !P->Prog->IsCancelled(); Lst.Next()) { SPropValue *Name = Lst.GetField(PR_DISPLAY_NAME); auto n = LFromNativeCp(MapiCastString(Name)); if (n && InName && _stricmp(n, InName) == 0) { SPropValue *v = Lst.GetField(PR_ENTRYID); if (v) { ULONG Type; if (Out->OpenEntry( v->Value.bin.cb, (LPENTRYID)v->Value.bin.lpb, NULL, MAPI_BEST_ACCESS, &Type, (IUnknown**)&Match) == S_OK) { break; } } } } } if (!Match) { Out->CreateFolder(FOLDER_GENERIC, InName, 0, 0, OPEN_IF_EXISTS, &Match); } if (Match) { auto FolderPath = In->GetPath(); if (P->AllFolders || P->Export.HasItem(In)) { if (P->Prog) { char s[256]; sprintf_s(s, sizeof(s), "Loading %s...", FolderPath ? FolderPath.Get() : InName.Get()); P->Prog->SetDescription(s); - LYield(); } // Export all the contained items, by first scanning existing entries so // that we can skip existing copies of the items in Scribe LPMAPITABLE Items = 0; LHashTbl,bool> MsgIds; if ((Err = Match->GetContentsTable(0, &Items)) == S_OK) { for (LMapiList Lst(Items); Lst.More(); Lst.Next()) { SPropValue *v = Lst.GetField(PR_ENTRYID); if (v) { ULONG Type = 0; IUnknown *Item = 0; if ((Err = Match->OpenEntry(v->Value.bin.cb, (LPENTRYID)v->Value.bin.lpb, NULL, MAPI_BEST_ACCESS, &Type, &Item)) == S_OK && Item) { if (Type == MAPI_MESSAGE) { IMessage *Msg = 0; if (Item->QueryInterface(IID_IMessage, (void**)&Msg) == S_OK && Msg) { char *MsgId = MapiGetPropStr(Msg, PR_INTERNET_MESSAGE_ID); if (MsgId) { MsgIds.Add(MsgId, true); } Msg->Release(); } } Item->Release(); } else { LgiTrace("%s:%i - Match->OpenEntry failed, Err=%x\n", __FILE__, __LINE__, Err); } } } } // Copy items from Scribe to the MAPI store bool WasLoaded = In->IsLoaded(); In->LoadThings(); if (P->Prog) { char s[256]; sprintf_s(s, sizeof(s), "Exporting %s...", FolderPath ? FolderPath.Get() : InName.Get()); P->Prog->SetDescription(s); - LYield(); } if (P->ItemProg) { P->ItemProg->Value(0); P->ItemProg->SetRange(In->Items.Length()); P->ItemProg->SetType("email"); P->ItemProg->Cancel(false); - LYield(); } for (auto t: In->Items) { if (P->ItemProg && P->ItemProg->IsCancelled()) break; Mail *m = t->IsMail(); if (m) { char MsgId[256]; sprintf_s(MsgId, sizeof(MsgId), "<%s>", m->GetMessageId(true)); if (!MsgIds.Find(MsgId)) { IMessage *Msg; if ((Err = Match->CreateMessage(0, 0, &Msg)) == S_OK) { SPropValue v; Msg->SetProps(1, &v, 0); MapiSetPropLong(Msg, PR_MESSAGE_FLAGS, m->GetFlags() & MAIL_READ ? MSGFLAG_READ : 0); MapiSetPropLong(Msg, PR_OBJECT_TYPE, MAPI_MESSAGE); MapiSetPropStr(Msg, PR_MESSAGE_CLASS, "IPM.Note", false); MapiSetPropStr(Msg, PR_INTERNET_MESSAGE_ID, MsgId, false); if (m->GetSubject()) { MapiSetPropStr(Msg, PR_SUBJECT, m->GetSubject(), true); } // Write the sender... if (m->GetFrom()) { SPropValue Prop; if (SetPropToEntryId(Prop, PR_SENDER_ENTRYID, (char*)m->GetFromStr(FIELD_EMAIL), (char*)m->GetFromStr(FIELD_NAME))) { Msg->SetProps(1, &Prop, 0); DeleteArray(Prop.Value.bin.lpb); } if (m->GetFromStr(FIELD_EMAIL)) { MapiSetPropStr(Msg, PR_SENDER_ADDRTYPE, "SMTP", false); MapiSetPropStr(Msg, PR_SENDER_EMAIL_ADDRESS, m->GetFromStr(FIELD_EMAIL), false); MapiSetPropStr(Msg, PR_SENT_REPRESENTING_ADDRTYPE, "SMTP", false); MapiSetPropStr(Msg, PR_SENT_REPRESENTING_EMAIL_ADDRESS, m->GetFromStr(FIELD_EMAIL), false); } auto Name = m->GetFromStr(FIELD_EMAIL) ? m->GetFromStr(FIELD_EMAIL) : m->GetFromStr(FIELD_EMAIL); if (Name) { MapiSetPropStr(Msg, PR_SENDER_NAME, Name, true); MapiSetPropStr(Msg, PR_SENT_REPRESENTING_NAME, Name, true); } } // Write out the recipients table if (m->GetTo()->Length()) { ADRLIST *Adr = 0; if (MAPIAllocateBuffer(CbNewADRLIST((ULONG)m->GetTo()->Length()), (void**) &Adr) == S_OK) { Adr->cEntries = (ULONG)m->GetTo()->Length(); int n = 0; for (LDataPropI *a = m->GetTo()->First(); a; a = m->GetTo()->Next(), n++) { Adr->aEntries[n].cValues = 3 + (a->GetStr(FIELD_EMAIL) ? 1 : 0) + (a->GetStr(FIELD_NAME) ? 1 : 0); Adr->aEntries[n].ulReserved1 = 0; if (MAPIAllocateBuffer( sizeof(SPropValue) * Adr->aEntries[n].cValues, (void**) &Adr->aEntries[n].rgPropVals) == S_OK) { SPropValue *p = Adr->aEntries[n].rgPropVals; int i = 0; if (SetPropToEntryId(p[0], PR_ENTRYID, (char*)a->GetStr(FIELD_EMAIL), (char*)a->GetStr(FIELD_NAME))) { i++; } else { Adr->aEntries[n].cValues--; } switch (a->GetInt(FIELD_CC)) { default: case MAIL_ADDR_TO: p[i].Value.l = MAPI_TO; break; case MAIL_ADDR_CC: p[i].Value.l = MAPI_CC; break; case MAIL_ADDR_BCC: p[i].Value.l = MAPI_BCC; break; } p[i].ulPropTag = PR_RECIPIENT_TYPE; p[i++].dwAlignPad = 0; p[i].ulPropTag = PR_ADDRTYPE; p[i].dwAlignPad = 0; p[i++].Value.lpszA = "SMTP"; p[i].ulPropTag = PR_DISPLAY_NAME; p[i].Value.lpszA = (char*) (a->GetStr(FIELD_NAME) ? a->GetStr(FIELD_NAME) : a->GetStr(FIELD_EMAIL)); p[i++].dwAlignPad = 0; if (a->GetStr(FIELD_EMAIL)) { p[i].ulPropTag = PR_EMAIL_ADDRESS; p[i].Value.lpszA = (char*) a->GetStr(FIELD_EMAIL); p[i++].dwAlignPad = 0; } } } HRESULT e; if ((e = Msg->ModifyRecipients(MODRECIP_ADD, Adr)) != S_OK) { // _asm int 3 } for (unsigned i=0; icEntries; i++) { MAPIFreeBuffer(Adr->aEntries[i].rgPropVals); } MAPIFreeBuffer(Adr); } } if (m->GetBody()) { LAutoString cs = m->GetCharSet(); if (cs) { char *u8 = (char*) LNewConvertCp("utf-8", m->GetBody(), cs); MapiSetPropStr(Msg, PR_BODY, u8, true); DeleteArray(u8); } else { MapiSetPropStr(Msg, PR_BODY, m->GetBody(), false); } } if (m->GetHtml()) { MapiSetPropStr(Msg, PR_BODY_HTML, m->GetHtml(), true); } if (m->GetInternetHeader()) { MapiSetPropStr(Msg, PR_TRANSPORT_MESSAGE_HEADERS, m->GetInternetHeader(), false); } MapiSetPropDate(Msg, PR_CLIENT_SUBMIT_TIME, *m->GetDateSent()); MapiSetPropDate(Msg, PR_MESSAGE_DELIVERY_TIME, *m->GetDateReceived()); // Write out any attachments List Att; if (m->GetAttachments(&Att)) { for (auto a: Att) { char *Ptr; ssize_t Size; if (a->Get(&Ptr, &Size)) { ULONG AttachmentNum = 0; LPATTACH Attach = 0; if ((Err = Msg->CreateAttach(0, 0, &AttachmentNum, &Attach)) == S_OK) { MapiSetPropLong(Attach, PR_ATTACH_METHOD, ATTACH_BY_VALUE); if (a->GetName()) { MapiSetPropStr(Attach, PR_ATTACH_LONG_FILENAME, a->GetName(), false); auto d = a->GetName(); const char *Dir = 0; while (*d) { if (*d == '/' || *d == '\\') { Dir = d; } d++; } if (Dir) { Dir++; } else { Dir = a->GetName(); } MapiSetPropStr(Attach, PR_DISPLAY_NAME, Dir, false); } if (a->GetMimeType()) { MapiSetPropStr(Attach, PR_ATTACH_MIME_TAG, a->GetMimeType(), false); } if (a->GetContentId()) { MapiSetPropStr(Attach, PR_ATTACH_CONTENT_ID, a->GetContentId(), false); } SPropValue Bin; Bin.dwAlignPad = 0; Bin.ulPropTag = PR_ATTACH_DATA_BIN; Bin.Value.bin.cb = (ULONG) Size; Bin.Value.bin.lpb = (uchar*)Ptr; Attach->SetProps(1, &Bin, 0); Attach->SaveChanges(0); Attach->Release(); } else { LgiTrace("%s:%i - Msg->CreateAttach failed, Err=%x\n", __FILE__, __LINE__, Err); } } } } Msg->SaveChanges(0); Msg->Release(); } else { LgiTrace("%s:%i - Match->CreateMessage failed, Err=%x\n", __FILE__, __LINE__, Err); } } } Calendar *Cal = t->IsCalendar(); if (Cal) { if (Cal->GetCalType() == CalEvent) { IMessage *Msg; if ((Err = Match->CreateMessage(0, 0, &Msg)) == S_OK) { ExportCalendar(Cal, Msg); Msg->SaveChanges(0); Msg->Release(); } } else { // Find todo folder... } } if (P->ItemProg) - { P->ItemProg->Value(P->ItemProg->Value()+1); - } - LYield(); } if (P->Prog) - { P->Prog->Value(P->Prog->Value()+1); - LYield(); - } if (!WasLoaded) { In->UnloadThings(); } } // Export child folders... for (ScribeFolder *c = In->GetChildFolder(); c; c = c->GetNextFolder()) { Export(P, c, Match, Depth + 1); } Match->Release(); } } else { LgiTrace("%s:%i - Out->GetHierarchyTable failed, Err=%x\n", _FL, Err); } } return Status; } ////////////////////////////////////////////////////////////////////////////// class MailMapiSource : public MailSource { protected: OutlookIO *Mapi; ScribeWnd *Parent; ScribeAccount *Account; UI_TYPE Ui; IMsgStore *MsgStore; IMAPIFolder *pFolder; List Entries; bool ListEntries(); + bool OnMsgStore(HRESULT Result); public: MailMapiSource(ScribeWnd *parent, ScribeAccount *account); ~MailMapiSource(); // Connection bool Open(LSocketI *S, const char *RemoteHost, int Port, const char *User, const char *Password, LDom *SettingStore, int Flags = 0); bool Close(); // Commands available while connected ssize_t GetMessages() override; bool Receive(LArray &Trans, MailCallbacks *Callbacks); bool Delete(int Message); int Sizeof(int Message); bool GetUid(int Message, char *Id, int IdLen); bool GetUidList(LString::Array &Id); LString GetHeaders(int Message); }; MailMapiSource::MailMapiSource(ScribeWnd *parent, ScribeAccount *account) { MsgStore = 0; pFolder = 0; Parent = parent; Account = account; Ui = (UI_TYPE) ((Parent) ? Parent->Handle() : 0); Mapi = new OutlookIO(Parent, 0, Account); } MailMapiSource::~MailMapiSource() { if (pFolder) { pFolder->Release(); } if (MsgStore) { MsgStore->Release(); } DeleteObj(Mapi); } +bool MailMapiSource::OnMsgStore(HRESULT Result) +{ + if (FAILED(Result)) + return false; + + if (!MsgStore) + return false; + + SPropValue *SubTree = MapiGetProp(MsgStore, PR_IPM_SUBTREE_ENTRYID); + if (!SubTree) + return false; + + ULONG ObjType; + IMAPIFolder *Root = 0; + if (MsgStore->OpenEntry(SubTree->Value.bin.cb, (LPENTRYID)SubTree->Value.bin.lpb, NULL, MAPI_BEST_ACCESS, &ObjType, (IUnknown**) &Root) != S_OK || + !Root) + return false; + + LPMAPITABLE Folders = 0; + if (Root->GetHierarchyTable(0, &Folders) != S_OK) + return false; + + // Loop through all the folders + bool Status = false; + for (LMapiList Lst(Folders); Lst.More(); Lst.Next()) + { + SPropValue *v = Lst.GetField(PR_ENTRYID); + SPropValue *Name = Lst.GetField(PR_DISPLAY_NAME); + if (v && Name) + { + char *FolderName = MapiCastString(Name); + if (FolderName && + _stricmp(FolderName, "Inbox") == 0) + { + ULONG Type; + Status |= Root->OpenEntry(v->Value.bin.cb, + (LPENTRYID)v->Value.bin.lpb, + NULL, + MAPI_BEST_ACCESS, + &Type, + (IUnknown**)&pFolder) == S_OK && + pFolder; + } + } + } + + return Status; +} + bool MailMapiSource::Open(LSocketI *S, const char *RemoteHost, int Port, const char *User, const char *Password, LDom *SettingStore, int Flags) { bool Status = true; if (Account && Mapi) { LVariant Server = Account->Receive.Server(); if (Server.Str()) { char PassStr[128] = ""; GPassword p; Account->Receive.GetPassword(&p); p.Get(PassStr); Mapi->MAPILogonEx( Ui, Server.Str(), ValidStr(PassStr) ? PassStr : 0, MAPI_EXTENDED, Mapi->Session.Set()); } if (!Status) { Mapi->MAPILogonEx( Ui, NULL, NULL, MAPI_LOGON_UI | MAPI_EXTENDED, Mapi->Session.Set()); if (Account && Mapi->Session) { // when we're called with a blank account we should be nice and // record what the user entered for their profile LPMAPITABLE Tbl = 0; if (Mapi->Session->GetStatusTable(0, &Tbl) == SUCCESS_SUCCESS) { // ViewTable(Tbl, Window); for (LMapiList Lst(Tbl); Lst.More(); Lst.Next()) { SPropValue *Type = Lst.GetField(PR_RESOURCE_TYPE); SPropValue *Name = Lst.GetField(PR_DISPLAY_NAME); if (MapiCastInt(Type) == MAPI_SUBSYSTEM) { // this should be the profile name Account->Receive.Server(MapiCastString(Name)); break; } } } } } if (Mapi->Session) { - HRESULT Error = S_OK; - LVariant UserName = Account->Receive.UserName(); if (!ValidStr(UserName.Str())) { - ChooseMessageStoreDlg Dlg(Parent, Mapi->Session, false); - if (Dlg.InitCheck() && - Dlg.DoModal() == IDOK && - Dlg.Ref) + auto Dlg = new ChooseMessageStoreDlg(Parent, Mapi->Session, false); + if (!Dlg->InitCheck()) + { + delete Dlg; + } + else { - Account->Receive.UserName(Dlg.Ref->DisplayName); - - Error = Mapi->Session->OpenMsgStore(Ui, - Dlg.Ref->Size, // entry bytes - Dlg.Ref->Entry, // ptr to entry - NULL, // default interface: IMsgStore - MAPI_BEST_ACCESS, - &MsgStore); + Dlg->DoModal([this, Dlg](auto dlg, auto id) + { + if (id && Dlg->Ref) + { + Account->Receive.UserName(Dlg->Ref->DisplayName); + + auto Error = Mapi->Session->OpenMsgStore(Ui, + Dlg->Ref->Size, // entry bytes + Dlg->Ref->Entry, // ptr to entry + NULL, // default interface: IMsgStore + MAPI_BEST_ACCESS, + &MsgStore); + OnMsgStore(Error); + } + delete dlg; + }); } } else { MessageStores Entries(Mapi->Session); if (Entries.Status) { for (auto e: Entries) { if (e->DisplayName && _stricmp(e->DisplayName, UserName.Str()) == 0) { - Error = Mapi->Session->OpenMsgStore(Ui, + auto Error = Mapi->Session->OpenMsgStore(Ui, e->Size, // entry bytes e->Entry, // ptr to entry NULL, // default interface: IMsgStore MAPI_BEST_ACCESS, &MsgStore); - break; + if (OnMsgStore(Error)) + break; } } } } - - if (MsgStore) - { - SPropValue *SubTree = MapiGetProp(MsgStore, PR_IPM_SUBTREE_ENTRYID); - if (SubTree) - { - ULONG ObjType; - IMAPIFolder *Root = 0; - if (MsgStore->OpenEntry(SubTree->Value.bin.cb, (LPENTRYID)SubTree->Value.bin.lpb, NULL, MAPI_BEST_ACCESS, &ObjType, (IUnknown**) &Root) == S_OK && - Root) - { - LPMAPITABLE Folders = 0; - if (Root->GetHierarchyTable(0, &Folders) == S_OK) - { - // Loop through all the folders - for (LMapiList Lst(Folders); Lst.More(); Lst.Next()) - { - SPropValue *v = Lst.GetField(PR_ENTRYID); - SPropValue *Name = Lst.GetField(PR_DISPLAY_NAME); - if (v && Name) - { - char *FolderName = MapiCastString(Name); - if (FolderName && - _stricmp(FolderName, "Inbox") == 0) - { - ULONG Type; - Status = Root->OpenEntry(v->Value.bin.cb, - (LPENTRYID)v->Value.bin.lpb, - NULL, - MAPI_BEST_ACCESS, - &Type, - (IUnknown**)&pFolder) == S_OK && - pFolder; - } - } - } - } - - // Root->Release(); - } - } - } } } return Status; } bool MailMapiSource::Close() { if (Mapi && Mapi->Session) { return true; } return false; } bool MailMapiSource::ListEntries() { if (pFolder && Entries.Length() < 1) { LPMAPITABLE Items = 0; if (pFolder->GetContentsTable(0, &Items) == S_OK) { for (LMapiList Lst(Items); Lst.More(); Lst.Next()) { SPropValue *v = Lst.GetField(PR_ENTRYID); if (v) { Entries.Insert(new MapiEntry(v)); } } } } return Entries.Length() > 0; } ssize_t MailMapiSource::GetMessages() { ssize_t Status = 0; if (ListEntries()) { Status = Entries.Length(); } return Status; } bool MailMapiSource::Receive(LArray &Trans, MailCallbacks *Callbacks) { bool Status = false; for (unsigned n=0; nIndex); if (e && Trans[n]->Stream) { ULONG Type = 0; IUnknown *Item = 0; if (pFolder->OpenEntry( e->Len, (LPENTRYID)e->Bin, NULL, MAPI_BEST_ACCESS, &Type, &Item) == S_OK && Item) { switch (Type) { case MAPI_MESSAGE: { IMessage *IMsg = 0; if (Item->QueryInterface(IID_IMessage, (void**)&IMsg) == S_OK) { SPropValue *Class = MapiGetProp(IMsg, PR_ORIG_MESSAGE_CLASS); if (!Class) { Class = MapiGetProp(IMsg, PR_MESSAGE_CLASS); } if (Class) { char *sClass = MapiCastString(Class); bool Note = _stricmp(sClass, "IPM.Note") == 0; bool Post = _stricmp(sClass, "IPM.Post") == 0; bool Document = _strnicmp(sClass, "IPM.Document", 12) == 0; if (Note || Post || Document) { // FIXME // Status |= Trans[n]->Status = Mapi->ImportEmail(dynamic_cast(Msg), IMsg, Post); } } IMsg->Release(); } break; } } Item->Release(); } } } return Status; } bool MailMapiSource::Delete(int Message) { return false; } int MailMapiSource::Sizeof(int Message) { return 0; } bool MailMapiSource::GetUid(int Message, char *Id, int IdLen) { return false; } bool MailMapiSource::GetUidList(LString::Array &Id) { return false; } LString MailMapiSource::GetHeaders(int Message) { return NULL; } MailSource *NewOutlookMailSource(ScribeWnd *Parent, ScribeAccount *Account) { return new MailMapiSource(Parent, Account); } diff --git a/Code/Imp_CsvContacts.cpp b/Code/Imp_CsvContacts.cpp --- a/Code/Imp_CsvContacts.cpp +++ b/Code/Imp_CsvContacts.cpp @@ -1,569 +1,581 @@ #include "Scribe.h" #include "resdefs.h" #include "lgi/common/Db.h" #include "lgi/common/XmlTree.h" #include "lgi/common/LgiRes.h" #include "lgi/common/FileSelect.h" ////////////////////////////////////////////////////////////////////////// class LFieldMap : public LListItem { LDbField &From; ItemFieldDef *To; LString Txt; public: LFieldMap(LDbField &from) : From(from) { To = 0; #ifdef WIN32 Txt = LFromNativeCp(From.Name()); #else Txt = From.Name(); #endif for (ItemFieldDef *f = ContactFieldDefs; f->Option; f++) { if (_stricmp(From.Name(), f->DisplayText) == 0) { To = f; break; } } } int FieldId() { return To ? To->FieldId : -1; } void FieldId(int i) { To = GetFieldDefById(i); Update(); } const char *GetText(int c) { switch (c) { case 0: { return Txt; } case 1: { if (To) { const char *n = LLoadString(To->FieldId); return n ? n : To->DisplayText; } break; } } return 0; } void OnMouseClick(LMouse &m) { auto RClick = new LSubMenu; if (RClick) { int n=0; for (ItemFieldDef *f = ContactFieldDefs; f->Option; f++) { const char *Name = LLoadString(f->FieldId); RClick->AppendItem(Name ? Name : f->DisplayText, 1000+n++, true); } if (Parent->GetMouse(m, true)) { int i = RClick->Float(Parent, m.x, m.y, m.Right()); if (i>=1000) { To = ContactFieldDefs + i - 1000; Update(); } } DeleteObj(RClick); } } void Convert(Contact *c) { if (From && To) { LVariant v; if (From.Get(v)) { char *s = v.Str(); if (s) { if (To->FieldId != FIELD_NOTE) { char *Out = s; for (char *In=s; *In; In++) { if (*In != '\r' && *In != '\n') { *Out++ = *In; } } *Out++ = 0; } c->Set(To->Option, s); } } } } }; class LImpCsv : public LDialog { - ScribeWnd *App; - LList *Map; + ScribeWnd *App = NULL; + LList *Map = NULL; public: + LAutoPtr Database; List Mapping; - ScribeFolder *Folder; - bool Merge; + ScribeFolder *Folder = NULL; + bool Merge = false; - LImpCsv(ScribeWnd *app, LDbRecordset *Rs) + LImpCsv(ScribeWnd *app, LDb *db) { - Folder = 0; - Map = 0; - Merge = false; + Database.Reset(db); SetParent(App = app); if (LoadFromResource(IDD_CSV_IMPORT)) { MoveToCenter(); - if (GetViewById(IDC_MAPPING, Map)) - { Map->DrawGridLines(true); - for (int i=0; iFields(); i++) - { - LDbField &Fld = (*Rs)[i]; - LFieldMap *m = new LFieldMap(Fld); - if (m) - { - Mapping.Insert(m); - Map->Insert(m); - } - } - } } Folder = App->GetCurrentFolder(); if (Folder && Folder->GetItemType() != MAGIC_CONTACT) - { Folder = App->GetFolder(FOLDER_CONTACTS); - } if (Folder) SetCtrlName(IDC_FOLDER, Folder->GetPath()); } + void SetRecords(LDbRecordset *Rs) + { + if (!Rs) + return; + for (int i=0; iFields(); i++) + { + LDbField &Fld = (*Rs)[i]; + auto m = new LFieldMap(Fld); + if (m) + { + Mapping.Insert(m); + Map->Insert(m); + } + } + } + void SaveMapping(const char *File) { LFile f; if (Map && f.Open(File, O_WRITE)) { f.SetSize(0); LXmlTree Xml; LXmlTag Root; Root.SetTag("field-map"); List All; Map->GetAll(All); for (auto i: All) { LXmlTag *c; Root.InsertTag(c = new LXmlTag); if (c) { c->SetTag("mapping"); c->SetAttr("from", i->GetText(0)); if (i->FieldId() >= 0) { char s[32]; sprintf_s(s, sizeof(s), "%i", i->FieldId()); c->SetAttr("to", s); } } } Xml.Write(&Root, &f); } else { LgiMsg(this, "Couldn't open '%s'\n", AppName, MB_OK, File); } } void LoadMapping(const char *File) { LFile f; if (Map && f.Open(File, O_READ)) { LXmlTree Xml; LXmlTag Root; Root.SetTag("field-map"); if (Xml.Read(&Root, &f, 0)) { List All; Map->GetAll(All); for (auto i: All) { for (auto t: Root.Children) { char *From1 = t->GetAttr("from"); const char *From2 = i->GetText(0); if (From1 && From2 && _stricmp(From1, From2) == 0) { char *Id; if ((Id = t->GetAttr("to"))) { i->FieldId(atoi(Id)); } break; } } } } } else { LgiMsg(this, "Couldn't open '%s'\n", AppName, MB_OK, File); } } int OnNotify(LViewI *c, LNotification n) { switch (c->GetId()) { case IDC_SAVE: { - LFileSelect s; - s.Parent(this); - s.Type("XML", "*.xml"); - s.Type("All Files", LGI_ALL_FILES); - if (s.Save()) + auto s = new LFileSelect(this); + s->Type("XML", "*.xml"); + s->Type("All Files", LGI_ALL_FILES); + s->Save([&](auto dlg, auto id) { - SaveMapping(s.Name()); - } + if (id) + SaveMapping(s->Name()); + delete dlg; + }); break; } case IDC_LOAD: { - LFileSelect s; - s.Parent(this); - s.Type("XML", "*.xml"); - s.Type("All Files", LGI_ALL_FILES); - if (s.Open()) + auto s = new LFileSelect(this); + s->Type("XML", "*.xml"); + s->Type("All Files", LGI_ALL_FILES); + s->Open([this](auto dlg, auto id) { - LoadMapping(s.Name()); - } + if (id) + LoadMapping(dlg->Name()); + delete dlg; + }); break; } case IDC_BROWSE_FOLDER: { - FolderDlg d(this, App, MAGIC_CONTACT); - if (d.DoModal()) + auto Dlg = new FolderDlg(this, App, MAGIC_CONTACT); + Dlg->DoModal([this, Dlg](auto dlg, auto id) { - char *NewPath = d.Get(); - if (NewPath) + if (id) { - Folder = App->GetFolder(NewPath); - if (Folder) - SetCtrlName(IDC_FOLDER, Folder->GetPath()); + auto NewPath = Dlg->Get(); + if (NewPath) + { + this->Folder = App->GetFolder(NewPath); + if (this->Folder) + SetCtrlName(IDC_FOLDER, this->Folder->GetPath()); + } } - } + delete dlg; + }); break; } case IDOK: { Merge = GetCtrlValue(IDC_MODE) == 0; } case IDCANCEL: { EndModal(c->GetId() == IDOK); break; } } return 0; } }; bool ImgCsvMatch(const char *a, const char *b) { if (!a && !b) { return true; } if (a && b) { return _stricmp(a, b) == 0; } return false; } void ImportCsv(ScribeWnd *App) { - LFileSelect s; - s.Parent(App); - s.Type("Comma Separated Text", "*.csv;*.txt"); - s.Type("All Files", LGI_ALL_FILES); - if (s.Open() && LFileExists(s.Name())) + auto s = new LFileSelect(App); + s->Type("Comma Separated Text", "*.csv;*.txt"); + s->Type("All Files", LGI_ALL_FILES); + s->Open([App](auto s, auto status) { - LDb *Db = OpenCsvDatabase(s.Name()); - if (Db) + LAutoPtr mem(s); + if (!status || !LFileExists(s->Name())) + return; + + auto Db = OpenCsvDatabase(s->Name()); + auto *Dlg = new LImpCsv(App, Db); + if (!Dlg) + return; + + Dlg->SetRecords(Dlg->Database->TableAt(0)); + Dlg->DoModal([Dlg, App](auto dlg, auto id) { - LDbRecordset *Rs = Db->TableAt(0); - if (Rs) + LAutoPtr mem(dlg); + if (!id) + return; + + if (!Dlg->Folder) + return; + + LDbRecordset *Rs = Dlg->Database->TableAt(0); + for (bool b=Rs->MoveFirst(); b; b=Rs->MoveNext()) { - LImpCsv Dlg(App, Rs); - if (Dlg.DoModal()) + Contact *c = new Contact(App); + if (c) { - if (Dlg.Folder) + c->App = App; + + for (auto fm: Dlg->Mapping) + fm->Convert(c); + + if (Dlg->Merge) { - for (bool b=Rs->MoveFirst(); b; b=Rs->MoveNext()) - { - Contact *c = new Contact(App); - if (c) - { - c->App = App; - - for (auto fm: Dlg.Mapping) - { - fm->Convert(c); - } - - if (Dlg.Merge) - { - Contact *m = 0; - const char *CFirst = 0, *CLast = 0; - c->Get(OPT_First, CFirst); - c->Get(OPT_Last, CLast); + Contact *m = 0; + const char *CFirst = 0, *CLast = 0; + c->Get(OPT_First, CFirst); + c->Get(OPT_Last, CLast); - for (auto t: Dlg.Folder->Items) - { - Contact *i = t->IsContact(); - if (i) - { - const char *IFirst = 0, *ILast = 0; - i->Get(OPT_First, IFirst); - i->Get(OPT_Last, ILast); + for (auto t: Dlg->Folder->Items) + { + Contact *i = t->IsContact(); + if (i) + { + const char *IFirst = 0, *ILast = 0; + i->Get(OPT_First, IFirst); + i->Get(OPT_Last, ILast); - if (ImgCsvMatch(IFirst, CFirst) && - ImgCsvMatch(ILast, CLast)) - { - m = i; - break; - } - } - } - - if (m) - { - // Convert across fields. - for (ItemFieldDef *Def = ContactFieldDefs; Def->FieldId; Def++) - { - const char *s; - if (c->Get(Def->DisplayText, s)) - { - #ifdef WIN32 - auto t = LFromNativeCp(s); - if (t) - m->Set(Def->Option, t); - #else - m->Set(Def->Option, s); - #endif - } - } - - m->Save(); - c->DecRef(); - c = NULL; - m->Update(); - } - else - { - // No match, new entry - c->Save(Dlg.Folder); - } - } - else + if (ImgCsvMatch(IFirst, CFirst) && + ImgCsvMatch(ILast, CLast)) { - c->Save(Dlg.Folder); + m = i; + break; } } } + + if (m) + { + // Convert across fields. + for (ItemFieldDef *Def = ContactFieldDefs; Def->FieldId; Def++) + { + const char *s; + if (c->Get(Def->DisplayText, s)) + { + #ifdef WIN32 + auto t = LFromNativeCp(s); + if (t) + m->Set(Def->Option, t); + #else + m->Set(Def->Option, s); + #endif + } + } + + m->Save(); + c->DecRef(); + c = NULL; + m->Update(); + } + else + { + // No match, new entry + c->Save(Dlg->Folder); + } + } + else + { + c->Save(Dlg->Folder); } } } - - DeleteObj(Db); - } - } + }); + }); } class LExportCsv : public LDialog { ScribeWnd *App; public: char *Folder; bool SubFolders; LExportCsv(ScribeWnd *app) { Folder = 0; SubFolders = false; SetParent(App = app); if (LoadFromResource(IDD_CSV_EXPORT)) { MoveToCenter(); SetCtrlEnabled(IDC_FOLDERS, false); LString p; ScribeFolder *f = App->GetCurrentFolder(); if (f && f->GetItemType() == MAGIC_CONTACT && (p = f->GetPath()) != 0) { SetCtrlName(IDC_FOLDERS, p); } else if ((f = App->GetFolder(FOLDER_CONTACTS)) != 0 && (p = f->GetPath()) != 0) { SetCtrlName(IDC_FOLDERS, p); } } } ~LExportCsv() { DeleteArray(Folder); } int OnNotify(LViewI *v, LNotification n) { switch (v->GetId()) { case IDC_SET_FOLDER: { - FolderDlg d(this, App, MAGIC_CONTACT); - if (d.DoModal()) + auto Dlg = new FolderDlg(this, App, MAGIC_CONTACT); + Dlg->DoModal([this, Dlg](auto dlg, auto id) { - char *NewPath = d.Get(); - if (NewPath) - { - SetCtrlName(IDC_FOLDERS, NewPath); - } - } + if (id) + SetCtrlName(IDC_FOLDERS, Dlg->Get()); + delete dlg; + }); break; } case IDOK: { Folder = NewStr(GetCtrlName(IDC_FOLDERS)); SubFolders = GetCtrlValue(IDC_SUB_FOLDERS) != 0; } case IDCANCEL: { EndModal(v->GetId() == IDOK); break; } } return 0; } }; void ExportCsv(ScribeWnd *App) { - LExportCsv Dlg(App); - if (Dlg.DoModal()) + auto Dlg = new LExportCsv(App); + Dlg->DoModal([Dlg, App](auto dlg, auto id) { - ScribeFolder *Folder = App->GetFolder(Dlg.Folder); - if (Folder) + if (id) { - LFileSelect s; - s.Parent(App); - s.Type("Comma Separated Text", "*.csv"); - s.Type("All Files", LGI_ALL_FILES); - if (s.Save()) + ScribeFolder *Folder = App->GetFolder(Dlg->Folder); + if (Folder) { - LString MsgStr = AskOverwriteMsg(s.Name()); - bool Exists = LFileExists(s.Name()); - if (!Exists || LgiMsg(App, MsgStr, AppName, MB_YESNO) == IDYES) + auto s = new LFileSelect(App); + s->Type("Comma Separated Text", "*.csv"); + s->Type("All Files", LGI_ALL_FILES); + s->Save([App, SubFolders = Dlg->SubFolders, Folder](auto s, auto status) { - int Exported = 0; - const char *Error = 0; - - if (Exists) - FileDev->Delete(s.Name()); - - LDb *Db = OpenCsvDatabase(s.Name()); - LAssert(Db != NULL); - if (Db) + if (status) { - LDbRecordset *Rs = Db->TableAt(0); - LAssert(Rs != NULL); - if (Rs) + LString MsgStr = AskOverwriteMsg(s->Name()); + bool Exists = LFileExists(s->Name()); + if (!Exists || LgiMsg(App, MsgStr, AppName, MB_YESNO) == IDYES) { - for (ItemFieldDef *f=ContactFieldDefs; f->Option && f->FieldId; f++) + int Exported = 0; + const char *Error = 0; + + if (Exists) + FileDev->Delete(s->Name()); + + auto Db = OpenCsvDatabase(s->Name()); + LAssert(Db != NULL); + if (Db) { - Rs->InsertField(f->DisplayText, GV_STRING); - } - Rs->InsertField("AltEmail", GV_STRING); + LDbRecordset *Rs = Db->TableAt(0); + LAssert(Rs != NULL); + if (Rs) + { + for (ItemFieldDef *f=ContactFieldDefs; f->Option && f->FieldId; f++) + { + Rs->InsertField(f->DisplayText, GV_STRING); + } + Rs->InsertField("AltEmail", GV_STRING); - List Contacts; - App->GetContacts(Contacts, Folder, Dlg.SubFolders); - if (Contacts[0]) - { - for (auto c: Contacts) - { - if (Rs->AddNew()) + List Contacts; + App->GetContacts(Contacts, Folder, SubFolders); + if (Contacts[0]) { - int Index = 0; - for (ItemFieldDef *f=ContactFieldDefs; f->Option && f->FieldId; f++, Index++) + for (auto c: Contacts) { - const char *n; - if (c->Get(f->Option, n)) + if (Rs->AddNew()) { - LVariant v(n); - (*Rs)[Index].Set(v); + int Index = 0; + for (ItemFieldDef *f=ContactFieldDefs; f->Option && f->FieldId; f++, Index++) + { + const char *n; + if (c->Get(f->Option, n)) + { + LVariant v(n); + (*Rs)[Index].Set(v); + } + } + + auto Emails = LString(",").Join(c->GetEmails().Slice(1)); + if (Emails) + (*Rs)[Index] = Emails; + + if (Rs->Update()) + Exported++; } } - - auto Emails = LString(",").Join(c->GetEmails().Slice(1)); - if (Emails) - (*Rs)[Index] = Emails; + } + else Error = "No Contacts."; + } + else Error = "Couldn't open record set."; - if (Rs->Update()) - Exported++; - } - } + DeleteObj(Db); } - else Error = "No Contacts."; - } - else Error = "Couldn't open record set."; + else Error = "Couldn't open database."; - DeleteObj(Db); + LgiMsg(App, LLoadString(IDS_EXPORT_MSG), AppName, MB_OK, Exported, Error?Error:(char*)""); + } } - else Error = "Couldn't open database."; - - LgiMsg(App, LLoadString(IDS_EXPORT_MSG), AppName, MB_OK, Exported, Error?Error:(char*)""); - } + delete s; + }); } } - } + delete dlg; + }); } diff --git a/Code/Imp_Eml.cpp b/Code/Imp_Eml.cpp --- a/Code/Imp_Eml.cpp +++ b/Code/Imp_Eml.cpp @@ -1,209 +1,213 @@ #include "Scribe.h" #include "lgi/common/Db.h" #include "lgi/common/XmlTree.h" #include "lgi/common/FileSelect.h" #include "resdefs.h" ////////////////////////////////////////////////////////////////////////// class LImportEml : public LDialog { ScribeWnd *App; public: - LAutoString In; + LString In; ScribeFolder *Out; int TotalEmail; LImportEml(ScribeWnd *app) { Out = 0; SetParent(App = app); if (LoadFromResource(IDD_EML_IMPORT)) { MoveToCenter(); } if ((Out = App->GetCurrentFolder())) SetCtrlName(IDC_OUT_FOLDER, Out->GetPath()); } int Scan(LTreeNode *Parent, char *Folder) { LDirectory d; int Email = 0; int ChildEmail = 0; LTreeItem *i = new LTreeItem; for (int b=d.First(Folder); b; b=d.Next()) { if (d.IsDir()) { char c[MAX_PATH_LEN]; d.Path(c, sizeof(c)); ChildEmail += Scan(i, c); } else { char *ext = strrchr(d.GetName(), '.'); if (ext && !_stricmp(ext, ".eml")) { Email++; } } } if (Email || ChildEmail) { char *Leaf = strrchr(Folder, DIR_CHAR); char Msg[MAX_PATH_LEN]; sprintf_s(Msg, sizeof(Msg), "%s (%i)", Leaf + 1, Email); i->SetText(Msg); Parent->Insert(i); Parent->Expanded(true); } else { DeleteObj(i); } return Email + ChildEmail; } int OnNotify(LViewI *c, LNotification n) { switch (c->GetId()) { case IDC_SET_IN: { - LFileSelect s; - s.Parent(this); - s.Type("Email Files", "*.eml"); - s.Type("All Files", LGI_ALL_FILES); - if (s.Open()) + auto s = new LFileSelect(this); + s->Type("Email Files", "*.eml"); + s->Type("All Files", LGI_ALL_FILES); + s->Open([this](auto dlg, auto status) { - char p[MAX_PATH_LEN]; - strcpy_s(p, sizeof(p), s.Name()); - LTrimDir(p); - SetCtrlName(IDC_IN_FOLDER, p); + if (status) + { + char p[MAX_PATH_LEN]; + strcpy_s(p, sizeof(p), dlg->Name()); + LTrimDir(p); + SetCtrlName(IDC_IN_FOLDER, p); - if (LDirExists(p)) - { - LTree *t; - if (GetViewById(IDC_TREE, t)) + if (LDirExists(p)) { - TotalEmail = Scan(t, p); + LTree *t; + if (GetViewById(IDC_TREE, t)) + { + TotalEmail = Scan(t, p); + } } } - } + delete dlg; + }); break; } case IDC_SET_OUT: { - FolderDlg d(this, App, MAGIC_MAIL); - if (d.DoModal()) + auto Dlg = new FolderDlg(this, App, MAGIC_MAIL); + Dlg->DoModal([this, Dlg](auto dlg, auto id) { - char *NewPath = d.Get(); - if (NewPath) + if (id) { - Out = App->GetFolder(NewPath); + Out = App->GetFolder(Dlg->Get()); if (Out) SetCtrlName(IDC_OUT_FOLDER, Out->GetPath()); } - } + delete dlg; + }); break; } case IDOK: { - In.Reset(NewStr(GetCtrlName(IDC_IN_FOLDER))); + In = GetCtrlName(IDC_IN_FOLDER); + // fall through } case IDCANCEL: { EndModal(c->GetId() == IDOK); break; } } return 0; } }; void ImportEmlFolders(ScribeWnd *App, LProgressPane *Prog, ScribeFolder *Out, char *In, int *Errors) { LDirectory d; { - DoEvery Timer(300); - LDataStoreI::StoreTrans Trans = Out->GetObject()->GetStore()->StartTransaction(); for (int b=d.First(In); b && !Prog->IsCancelled(); b=d.Next()) { if (!d.IsDir()) { char *ext = strrchr(d.GetName(), '.'); if (ext && !_stricmp(ext, ".eml")) { Thing *t = App->CreateItem(MAGIC_MAIL, NULL, false); if (t) { char c[MAX_PATH_LEN]; d.Path(c, sizeof(c)); LAutoPtr Eml(new LFile); if (Eml->Open(c, O_READ)) { if (t->Import(t->AutoCast(Eml), sMimeMessage)) { t->Save(Out); } else (*Errors)++; } else (*Errors)++; } else (*Errors)++; Prog->Value(Prog->Value() + 1); - if (Timer.DoNow()) - LYield(); } } } } for (int b=d.First(In); b && !Prog->IsCancelled(); b=d.Next()) { if (d.IsDir()) { char c[MAX_PATH_LEN]; d.Path(c, sizeof(c)); ScribeFolder *Child = Out->GetSubFolder(d.GetName()); if (!Child) { Child = Out->CreateSubDirectory(d.GetName(), MAGIC_MAIL); } if (Child) { ImportEmlFolders(App, Prog, Child, c, Errors); } } } } void ImportEml(ScribeWnd *App) { - LImportEml Dlg(App); - if (Dlg.DoModal()) + auto Dlg = new LImportEml(App); + Dlg->DoModal([Dlg, App](auto dlg, auto id) { - int Errors = 0; - LProgressDlg Prog(App); - Prog.SetRange(Dlg.TotalEmail); - Prog.SetDescription("Email"); - ImportEmlFolders(App, Prog.ItemAt(0), Dlg.Out, Dlg.In, &Errors); - if (Errors) + if (id) { - Prog.Visible(false); - LgiMsg(App, "%i email failed to import.", AppName, MB_OK, Errors); + int Errors = 0; + LProgressDlg Prog(App); + Prog.SetRange(Dlg->TotalEmail); + Prog.SetDescription("Email"); + ImportEmlFolders(App, Prog.ItemAt(0), Dlg->Out, Dlg->In, &Errors); + if (Errors) + { + Prog.Visible(false); + LgiMsg(App, "%i email failed to import.", AppName, MB_OK, Errors); + } } - } + delete dlg; + }); } diff --git a/Code/Imp_Eudora.cpp b/Code/Imp_Eudora.cpp --- a/Code/Imp_Eudora.cpp +++ b/Code/Imp_Eudora.cpp @@ -1,181 +1,177 @@ #include "Scribe.h" #include "resdefs.h" #include "lgi/common/LgiRes.h" char EudoraPathKey[] = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\Eudora.exe"; bool ImportEudoraAddresss(ScribeWnd *App, ScribeFolder *Folder, char *File) { bool Status = false; if (App && Folder && File) { char *Text = LReadTextFile(File); if (Text) { char *Alias = 0; char *Address = 0; LToken L(Text, "\r\n"); for (unsigned i=0; i 2) { if (_stricmp(S[0], "alias") == 0) { DeleteArray(Alias); Alias = NewStr(S[1]); DeleteArray(Address); Address = NewStr(S[2]); } else if (_stricmp(S[0], "note") == 0) { if (Alias && Address) { Contact *c = new Contact(App); if (c) { c->App = App; // set basic information c->Set(OPT_Nick, Alias); c->Set(OPT_Email, Address); // parse through all the tags.. char *n = 0; for (char *s = strchr(L[i], '<'); s && *s; s = n) { char *Var = ++s; n = strchr(Var, '>'); if (n) { *n++ = 0; char *Val = strchr(Var, ':'); if (Val) { *Val++ = 0; #define Map(From, To) else if (_stricmp(Var, From) == 0) c->Set(To, Val) if (_stricmp(Var, "first") == 0) { c->Set(OPT_First, Val); } Map("last", OPT_Last); Map("address", OPT_HomeStreet); Map("city", OPT_HomeSuburb); Map("state", OPT_HomeState); Map("country", OPT_HomeCountry); Map("zip", OPT_HomePostcode); Map("phone", OPT_HomePhone); Map("fax", OPT_HomeFax); Map("mobile", OPT_HomeMobile); Map("web", OPT_HomeWebPage); // Map("title", ); Map("company", OPT_Company); Map("address2", OPT_WorkStreet); Map("city2", OPT_WorkSuburb); Map("country2", OPT_WorkCountry); Map("zip2", OPT_WorkPostcode); Map("phone2", OPT_WorkPhone); Map("fax2", OPT_WorkFax); Map("web2", OPT_WorkWebPage); else if (_stricmp(Var, "name") == 0) { char *Sp = strchr(Val, ' '); if (Sp) { *Sp++ = 0; c->Set(OPT_First, Val); c->Set(OPT_Last, Sp); } else { c->Set(OPT_First, Val); } } else if (_stricmp(Var, "otheremail") == 0) { } } } char *Next = strchr(n, '<'); if (!Next && strlen(n) > 0) { c->Set(OPT_Note, n); n = 0; } else n = Next; } - Status |= (Folder->WriteThing(c) != Store3Error); + Status |= (Folder->WriteThing(c, NULL) != Store3Error); } } } } } } } return Status; } -bool Import_EudoraAddressBook(ScribeWnd *App) +void Import_EudoraAddressBook(ScribeWnd *App) { - bool Status = false; - char Str[256] = "/"; #ifdef WIN32 - HKEY hKey; - if (RegOpenKeyA(HKEY_LOCAL_MACHINE, EudoraPathKey, &hKey) == ERROR_SUCCESS) - { - DWORD Type = 0; - DWORD Size = sizeof(Str); - RegQueryValueExA(hKey, "Path", 0, &Type, (uchar*)Str, &Size); - RegCloseKey(hKey); - } + HKEY hKey; + if (RegOpenKeyA(HKEY_LOCAL_MACHINE, EudoraPathKey, &hKey) == ERROR_SUCCESS) + { + DWORD Type = 0; + DWORD Size = sizeof(Str); + RegQueryValueExA(hKey, "Path", 0, &Type, (uchar*)Str, &Size); + RegCloseKey(hKey); + } #endif - LArray Files; + LString::Array Files; LMakePath(Str, sizeof(Str), Str, "NNdbase.txt"); if (LFileExists(Str)) { - Files.Add(NewStr(Str)); + Files.Add(Str); } // Get default path.. char DefaultFolder[256] = "/Contacts"; ScribeFolder *f = App->GetCurrentFolder(); if (!f || f->GetItemType() != MAGIC_CONTACT) { f = App->GetFolder(FOLDER_CONTACTS); } if (f && f->GetItemType() == MAGIC_CONTACT) { auto p = f->GetPath(); if (p) { strcpy_s(DefaultFolder, sizeof(DefaultFolder), p); } } // Ask user... - ChooseFolderDlg Dlg(App, + auto Dlg = new ChooseFolderDlg(App, false, "Eudora", LLoadString(IDS_SELECT_IO), DefaultFolder, MAGIC_CONTACT, &Files); - if (Dlg.DoModal() && Dlg.SrcFiles[0]) + Dlg->DoModal([App, Dlg](auto dlg, auto id) { - Status = ImportEudoraAddresss(App, App->GetFolder(Dlg.DestFolder), Dlg.SrcFiles[0]); - } - - Files.DeleteArrays(); - - return Status; + if (id && Dlg->SrcFiles[0]) + ImportEudoraAddresss(App, App->GetFolder(Dlg->DestFolder), Dlg->SrcFiles[0]); + delete dlg; + }); } diff --git a/Code/Imp_Mozilla.cpp b/Code/Imp_Mozilla.cpp --- a/Code/Imp_Mozilla.cpp +++ b/Code/Imp_Mozilla.cpp @@ -1,599 +1,604 @@ // #include #include "Scribe.h" #include "lgi/common/Map.h" #include "resdefs.h" #include "lgi/common/TextFile.h" #include "lgi/common/LgiRes.h" #include "lgi/common/FileSelect.h" #include "v3.6.14/sqlite3.h" void ToRecord(LMap &r, char *Str) { char *n; for (char *s=strchr(Str, '('); s && *s; s=n) { n = strchr(s, ')'); if (n) { s++; *n++ = 0; if (*s++ == '^') { int a = htoi(s); s = strchr(s, '^'); if (s) { int b = htoi(++s); r[a] = b; } } n = strchr(n, '('); } } } void ToMap(LMap &m, char *Str) { char *n; for (char *s=strchr(Str, '('); s && *s; s=n) { n = strchr(s, ')'); if (n) { s++; *n++ = 0; char *e = strchr(s, '='); if (e) { *e++ = 0; int Var = htoi(s); if (Var > 0) { m[Var] = e; } } n = strchr(n, '('); } } } char *Decode(char *s) { // static const char *Cp = "iso-8859-1"; if (s && strchr(s, '$')) { char Hex[3] = {0, 0, 0}; LStringPipe p(4 << 10); char *b = s; while (*s) { if (*s == '$') { if (b < s) { p.Push(b, (int) (s - b)); } s++; if (s[0] && s[1]) { Hex[0] = *s++; Hex[1] = *s++; char c = htoi(Hex); p.Push(&c, 1); /* if (*s == '$') { s++; if (s[0] && s[1]) { Hex[0] = *s++; Hex[1] = *s++; char16 c = c1 | (htoi(Hex) << 8); char *Utf8 = WideToUtf8(&c, sizeof(c)); if (Utf8) { p.Push(Utf8); DeleteArray(Utf8); } } } */ b = s; } else break; } else s++; } if (b < s) { p.Push(b, (int) (s - b)); } return p.NewStr(); } return NewStr(s); } struct Sqlite { bool Open; sqlite3 *Db; Sqlite(const char *File) { Open = Check(sqlite3_open(File, &Db)); } ~Sqlite() { if (Open) sqlite3_close(Db); } bool Check(int Code, const char *Sql = NULL) { if (Code == SQLITE_OK || Code == SQLITE_DONE) return true; const char *Err = sqlite3_errmsg(Db); LgiTrace("%s:%i - Sqlite error %i: %s\n%s", _FL, Code, Err, Sql?Sql:(char*)"", Sql?"\n":""); LAssert(!"Db Error"); return false; } }; bool ImportMozillaAddresss(ScribeWnd *App, ScribeFolder *Folder, char *File) { if (!App || !Folder || !File) { LgiTrace("%s:%i - Param error.\n", _FL); return false; } auto Ext = LGetExtension(File); if (!Ext) { LgiTrace("%s:%i - No extension '%s'.\n", _FL, File); return false; } bool Status = false; if (!stricmp(Ext, "mab")) { char *Text = LReadTextFile(File); if (Text) { int Angle = 0; int Square = 0; List Blocks; List Records; char *StartAngle = 0; char *StartSquare = 0; // Parse MAB... for (char *s=Text; s && *s; s++) { if (*s == '<') { if (_strnicmp(s+1, "!--", 3) == 0) { char *e = strstr(s, "-->"); if (e) s = e + 3; else break; } else { Angle++; if (Angle == 1) StartAngle = s + 1; } } else if (*s == '>') { if (Angle > 0) { if (StartAngle && Angle == 1) Blocks.Insert(NewStr(StartAngle, s - StartAngle)); Angle--; } } else if (Angle == 0) { if (*s == '[') { if (++Square == 1) StartSquare = s + 1; } else if (*s == ']') { if (StartSquare && Square == 1) Records.Insert(NewStr(StartSquare, s - StartSquare)); Square--; } } } // Parse blocks.. bool Fields = true; LMap Field; LMap Data; for (auto b: Blocks) { if (Fields) { ToMap(Field, b); Fields = false; } else { ToMap(Data, b); } } for (auto r: Records) { LMap Record; ToRecord(Record, r); int Flds = 0; Contact *c = new Contact(App); if (c) { c->App = App; #define MapField(From, To) \ { \ char *d = Data[Record[Field.Reverse((char*)From)]]; \ if (d) \ { \ char *s = Decode(d); \ if (s) \ { \ c->Set(To, s); \ Flds++; \ DeleteArray(s); \ } \ } \ } MapField("FirstName", OPT_First); MapField("LastName", OPT_Last); MapField("NickName", OPT_Nick); MapField("PrimaryEmail", OPT_Email); MapField("WorkPhone", OPT_WorkPhone); MapField("HomePhone", OPT_HomePhone); MapField("FaxNumber", OPT_HomeFax); MapField("CellularNumber", OPT_HomeMobile); MapField("HomeAddress", OPT_HomeStreet); MapField("HomeAddress2", OPT_HomeSuburb); MapField("HomeState", OPT_HomeState); MapField("HomeZipCode", OPT_HomePostcode); MapField("HomeCountry", OPT_HomeCountry); MapField("WorkAddress", OPT_WorkStreet); MapField("WorkAddress2", OPT_WorkSuburb); MapField("WorkState", OPT_WorkState); MapField("WorkZipCode", OPT_WorkPostcode); MapField("WorkCountry", OPT_WorkCountry); MapField("Company", OPT_Company); MapField("WebPage1", OPT_WorkWebPage); MapField("WebPage2", OPT_HomeWebPage); MapField("CustomFields", OPT_CustomFields); MapField("Notes", OPT_Note); // HomeCity // WorkCity // JobTitle // Department // BirthYear // BirthMonth // BirthDay // LastModifiedDate // RecordKey // AddrCharSet // LastRecordKey // ListName // ListNickName // ListDescription // ListTotalAddresses // LowercaseListName // SecondEmail // PreferMailFormat // PagerNumber #undef MapField if (Flds > 0) { - Folder->WriteThing(c); + Folder->WriteThing(c, NULL); Status = true; } else { DeleteObj(c); } } } } else { LgiMsg( App, "Couldn't read from '%s'\n" "Is Mozilla still open?", AppName, MB_OK, File); } } else if (!stricmp(Ext, "sqlite")) { Sqlite s(File); if (!s.Open) { LgiTrace("%s:%i - Failed to open '%s'\n", _FL, File); return false; } LString Sql = "select * from properties"; sqlite3_stmt *Stmt = NULL; if (!s.Check(sqlite3_prepare_v2(s.Db, Sql, -1, &Stmt, 0), Sql)) return false; LHashTbl, Contact*> Map; int r; while ((r = sqlite3_step(Stmt)) == SQLITE_ROW) { const char *card = (const char *)sqlite3_column_text(Stmt, 0); auto name = sqlite3_column_text(Stmt, 1); auto value = sqlite3_column_text(Stmt, 2); Contact *c = Map.Find(card); if (!c) { if ((c = new Contact(App))) Map.Add(card, c); } if (!c || !name || !value) continue; printf("%s, %s, %s\n", card, name, value); #define MapField(src, dst) \ if (!stricmp((const char*)name, src)) c->Set(dst, (char*)value) MapField("FirstName", OPT_First); MapField("LastName", OPT_Last); MapField("NickName", OPT_Nick); MapField("PrimaryEmail", OPT_Email); MapField("WorkPhone", OPT_WorkPhone); MapField("HomePhone", OPT_HomePhone); MapField("FaxNumber", OPT_HomeFax); MapField("CellularNumber", OPT_HomeMobile); MapField("HomeAddress", OPT_HomeStreet); MapField("HomeAddress2", OPT_HomeSuburb); MapField("HomeState", OPT_HomeState); MapField("HomeZipCode", OPT_HomePostcode); MapField("HomeCountry", OPT_HomeCountry); MapField("WorkAddress", OPT_WorkStreet); MapField("WorkAddress2", OPT_WorkSuburb); MapField("WorkState", OPT_WorkState); MapField("WorkZipCode", OPT_WorkPostcode); MapField("WorkCountry", OPT_WorkCountry); MapField("Company", OPT_Company); MapField("WebPage1", OPT_WorkWebPage); MapField("WebPage2", OPT_HomeWebPage); MapField("CustomFields", OPT_CustomFields); MapField("Notes", OPT_Note); #undef MapField } s.Check(sqlite3_finalize(Stmt), 0); Status = Map.Length() > 0; for (auto p: Map) - Folder->WriteThing(p.value); + Folder->WriteThing(p.value, NULL); } else { LgiTrace("%s:%i - Unsupported address book format '%s'\n", _FL, File); } return Status; } -bool Import_MozillaAddressBook(ScribeWnd *App) +void Import_MozillaAddressBook(ScribeWnd *App) { - bool Status = false; LFileSelect Select; - LArray Files; + LArray FindFiles; LArray Ext; LFile::Path Str( #ifdef LINUX LSP_HOME #else LSP_USER_APP_DATA #endif ); #ifdef LINUX Str += ".thunderbird"; #endif Ext.Add("abook.mab"); Ext.Add("abook.sqlite"); LgiTrace("Searching '%s' for thunderbird address book files.\n", Str.GetFull().Get()); - LRecursiveFileSearch(Str, &Ext, &Files); + LRecursiveFileSearch(Str, &Ext, &FindFiles); // Get default path.. char DefaultFolder[256] = "/Contacts"; ScribeFolder *f = App->GetCurrentFolder(); if (!f || f->GetItemType() != MAGIC_CONTACT) { f = App->GetFolder(FOLDER_CONTACTS); } if (f && f->GetItemType() == MAGIC_CONTACT) { auto p = f->GetPath(); if (p) strcpy_s(DefaultFolder, sizeof(DefaultFolder), p); } + LString::Array Files; + for (auto f: FindFiles) + Files.Add(f); + FindFiles.DeleteArrays(); + // Ask user... - ChooseFolderDlg Dlg(App, + auto Dlg = new ChooseFolderDlg(App, false, AppName, "Select input files and destination directory", DefaultFolder, MAGIC_CONTACT, &Files); - if (Dlg.DoModal() && Dlg.SrcFiles[0]) + Dlg->DoModal([App, Dlg](auto dlg, auto id) { - Status = ImportMozillaAddresss(App, App->GetFolder(Dlg.DestFolder), Dlg.SrcFiles[0]); - } - - Files.DeleteArrays(); - - return Status; + if (id && Dlg->SrcFiles[0]) + ImportMozillaAddresss(App, App->GetFolder(Dlg->DestFolder), Dlg->SrcFiles[0]); + delete dlg; + }); } #ifndef WIN32 int GetPrivateProfileStringA(const char *lpAppName, const char *lpKeyName, const char *lpDefault, char *lpReturnedString, int nSize, const char *lpFileName) { // FIXME LAssert(0); return 0; } #endif -bool Import_MozillaMail(ScribeWnd *App) +void Import_MozillaMail(ScribeWnd *App) { char Path[MAX_PATH_LEN]; - if (LGetSystemPath(LSP_USER_APP_DATA, Path, sizeof(Path))) + if (!LGetSystemPath(LSP_USER_APP_DATA, Path, sizeof(Path))) + return; + + LMakePath(Path, sizeof(Path), Path, "Thunderbird\\profiles.ini"); + if (!LFileExists(Path)) + { + LgiMsg(App, LLoadString(IDS_ERROR_FILE_DOESNT_EXIST), AppName, MB_OK, Path); + return; + } + + char s[128]; + if (GetPrivateProfileStringA("Profile0", "Path", "", s, sizeof(s), Path) <= 0) + return; + + if (LIsRelativePath(s)) + { + LTrimDir(Path); + LMakePath(Path, sizeof(Path), Path, s); + } + else + { + strcpy_s(Path, sizeof(Path), s); + } + LMakePath(Path, sizeof(Path), Path, "Mail"); + if (!LDirExists(Path)) { - LMakePath(Path, sizeof(Path), Path, "Thunderbird\\profiles.ini"); - if (LFileExists(Path)) + LgiMsg(App, LLoadString(IDS_ERROR_FOLDER_DOESNT_EXIST), AppName, MB_OK, Path); + return; + } + + LArray FindFiles; + if (!LRecursiveFileSearch(Path, 0, &FindFiles)) + return; + + // Clear out index files... + for (unsigned i=0; i 0) + FindFiles.DeleteAt(i); + DeleteArray(f); + i--; + } + } + + LString::Array Files; + for (auto f: FindFiles) + Files.New() = f; + FindFiles.DeleteArrays(); + + // Do UI + ScribeFolder *Cur = App->GetCurrentFolder(); + LString CurPath; + if (Cur) + CurPath = Cur->GetPath(); + auto Dlg = new ChooseFolderDlg(App, + false, + "Mozilla/Thunderbird", + LLoadString(IDS_IMPORT), + CurPath, + MAGIC_MAIL, + &Files); + Dlg->DoModal([App, Dlg](auto dlg, auto id) + { + if (id && Dlg->DestFolder) + { + ScribeFolder *Dest = App->GetFolder(Dlg->DestFolder); + if (Dest) { - if (LIsRelativePath(s)) + for (auto Src: Dlg->SrcFiles) { - LTrimDir(Path); - LMakePath(Path, sizeof(Path), Path, s); - } - else - { - strcpy_s(Path, sizeof(Path), s); - } - LMakePath(Path, sizeof(Path), Path, "Mail"); - if (LDirExists(Path)) - { - LArray Files; - if (LRecursiveFileSearch(Path, 0, &Files)) + char *Name = strrchr(Src, DIR_CHAR); + if (!Name) Name = Src; + else Name++; + + ScribeFolder *Child = Dest->CreateSubDirectory(Name, MAGIC_MAIL); + if (Child) { - // Clear out index files... - for (unsigned i=0; i f(new LTextFile); + if (f->Open(Src, O_READ)) { - char *f = Files[i]; - int64 Size = LFileSize(f); - char *Ext = strrchr(f, '.'); - if - ( - Size == 0 - || - ( - Ext - && - ( - _stricmp(Ext, ".msf") == 0 - || - _stricmp(Ext, ".dat") == 0 - ) - ) - ) - { - Files.DeleteAt(i); - DeleteArray(f); - i--; - } - } - - // Do UI - ScribeFolder *Cur = App->GetCurrentFolder(); - LString CurPath; - if (Cur) - CurPath = Cur->GetPath(); - ChooseFolderDlg Dlg(App, - false, - "Mozilla/Thunderbird", - LLoadString(IDS_IMPORT), - CurPath, - MAGIC_MAIL, - &Files); - if (Dlg.DoModal() && Dlg.DestFolder) - { - ScribeFolder *Dest = App->GetFolder(Dlg.DestFolder); - if (Dest) - { - for (auto Src: Dlg.SrcFiles) - { - char *Name = strrchr(Src, DIR_CHAR); - if (!Name) Name = Src; - else Name++; - - ScribeFolder *Child = Dest->CreateSubDirectory(Name, MAGIC_MAIL); - if (Child) - { - LAutoPtr f(new LTextFile); - if (f->Open(Src, O_READ)) - { - Child->Import(Child->AutoCast(f), sMimeMbox); - } - } - } - - Dest->Expanded(true); - } - else - { - LgiMsg(App, LLoadString(IDS_ERROR_FOLDER_DOESNT_EXIST), AppName, MB_OK, Dlg.DestFolder); - } + Child->Import(Child->AutoCast(f), sMimeMbox); } } } - else - { - LgiMsg(App, LLoadString(IDS_ERROR_FOLDER_DOESNT_EXIST), AppName, MB_OK, Path); - } + + Dest->Expanded(true); } + else LgiMsg(App, LLoadString(IDS_ERROR_FOLDER_DOESNT_EXIST), AppName, MB_OK, Dlg->DestFolder); } - else - { - LgiMsg(App, LLoadString(IDS_ERROR_FILE_DOESNT_EXIST), AppName, MB_OK, Path); - } - } - return false; + delete dlg; + }); } diff --git a/Code/Imp_NetscapeContacts.cpp b/Code/Imp_NetscapeContacts.cpp --- a/Code/Imp_NetscapeContacts.cpp +++ b/Code/Imp_NetscapeContacts.cpp @@ -1,180 +1,186 @@ #include "Scribe.h" #include "lgi/common/FileSelect.h" typedef LXmlTag ImpRecord; class ImpRecordSet : public List { public: List Fields; ImpRecordSet() {} ~ImpRecordSet() { for (auto c: Fields) { DeleteArray(c); } for (auto r: *this) { DeleteObj(r); } } }; int ReadCsv(const char *Name, ImpRecordSet &Rs, bool HasHeadings = true) { int Status = 0; LFile f; if (f.Open(Name, O_READ)) { char Buf[1024]; bool Done = false; if (HasHeadings) { // read field headings while (!Done) { char *c = Buf; while ( !f.Eof() && f.Read(c, 1) == 1 && !strchr(",\r\n", *c)) { c++; } Done = strchr("\r\n", *c) != 0; if (Done) f >> *c; *c++ = 0; Rs.Fields.Insert(TrimStr(Buf, "\"' \t\r\n")); } } // read records while (!f.Eof()) { ImpRecord *r = new ImpRecord; if (r) { Done = false; auto It = Rs.Fields.begin(); char *Field = *It; while (!Done) { char *c = Buf; for ( ; f.Read(c, 1) == 1 && !f.Eof() && !strchr(",\r\n", *c); c++) { } Done = strchr("\r\n", *c) != 0; if (Done) f >> *c; *c++ = 0; char *Str = TrimStr(Buf, "\"'"); if (Str && Field) { r->SetAttr(Field, Str); } DeleteArray(Str); Field = *(++It); } Rs.Insert(r); Status++; } } } return Status; } static void CopyField(Contact *d, const char *Dest, LXmlTag *s, const char *Src) { if (d && Dest && s && Src) { char *Str; if ((Str = s->GetAttr(Src))) d->Set(Dest, Str); } } void Import_NetscapeContacts(ScribeWnd *Parent) { - if (Parent) - { - LFileSelect Select; - - Select.Parent(Parent); - Select.Type("Netscape Contacts", "*.csv"); + if (!Parent) + return; - if (Select.Open()) + auto Select = new LFileSelect(Parent); + Select->Type("Netscape Contacts", "*.csv"); + Select->Open([Parent](auto dlg, auto status) + { + if (status) { - FolderDlg Dlg(Parent, Parent, MAGIC_CONTACT); - if (Dlg.DoModal()) + LString Name = dlg->Name(); + auto Dlg = new FolderDlg(Parent, Parent, MAGIC_CONTACT); + Dlg->DoModal([Parent, Dlg, Name](auto dlg, auto id) { - ScribeFolder *Contacts = Parent->GetFolder(Dlg.Get()); - if (Contacts) + if (id) { - ImpRecordSet Rs; + ScribeFolder *Contacts = Parent->GetFolder(Dlg->Get()); + if (Contacts) + { + ImpRecordSet Rs; - // Pre populated the field names - Rs.Fields.Insert(NewStr("DisplayName")); - Rs.Fields.Insert(NewStr("Surname")); - Rs.Fields.Insert(NewStr("First")); - Rs.Fields.Insert(NewStr("Notes")); - Rs.Fields.Insert(NewStr("City")); - Rs.Fields.Insert(NewStr("State")); - Rs.Fields.Insert(NewStr("Email")); - Rs.Fields.Insert(NewStr("Title")); - Rs.Fields.Insert(NewStr("Unknown")); - Rs.Fields.Insert(NewStr("Address")); - Rs.Fields.Insert(NewStr("PostCode")); - Rs.Fields.Insert(NewStr("Country")); - Rs.Fields.Insert(NewStr("PhoneWork")); - Rs.Fields.Insert(NewStr("Fax")); - Rs.Fields.Insert(NewStr("PhoneHome")); - Rs.Fields.Insert(NewStr("Organization")); - Rs.Fields.Insert(NewStr("Nick")); - Rs.Fields.Insert(NewStr("Mobile")); - Rs.Fields.Insert(NewStr("Pager")); - Rs.Fields.Insert(NewStr("Unknown2")); + // Pre populated the field names + Rs.Fields.Insert(NewStr("DisplayName")); + Rs.Fields.Insert(NewStr("Surname")); + Rs.Fields.Insert(NewStr("First")); + Rs.Fields.Insert(NewStr("Notes")); + Rs.Fields.Insert(NewStr("City")); + Rs.Fields.Insert(NewStr("State")); + Rs.Fields.Insert(NewStr("Email")); + Rs.Fields.Insert(NewStr("Title")); + Rs.Fields.Insert(NewStr("Unknown")); + Rs.Fields.Insert(NewStr("Address")); + Rs.Fields.Insert(NewStr("PostCode")); + Rs.Fields.Insert(NewStr("Country")); + Rs.Fields.Insert(NewStr("PhoneWork")); + Rs.Fields.Insert(NewStr("Fax")); + Rs.Fields.Insert(NewStr("PhoneHome")); + Rs.Fields.Insert(NewStr("Organization")); + Rs.Fields.Insert(NewStr("Nick")); + Rs.Fields.Insert(NewStr("Mobile")); + Rs.Fields.Insert(NewStr("Pager")); + Rs.Fields.Insert(NewStr("Unknown2")); - // read file - if (ReadCsv(Select.Name(), Rs, false) > 0) - { - for (auto r: Rs) + // read file + if (ReadCsv(Name, Rs, false) > 0) { - Contact *c = new Contact(Parent); - if (c) + for (auto r: Rs) { - c->App = Parent; + Contact *c = new Contact(Parent); + if (c) + { + c->App = Parent; - CopyField(c, OPT_First, r, "First"); - CopyField(c, OPT_Last, r, "Surname"); - CopyField(c, OPT_Email, r, "Email"); - CopyField(c, OPT_HomeStreet, r, "Address"); - CopyField(c, OPT_HomeSuburb, r, "City"); - CopyField(c, OPT_HomeState, r, "State"); - CopyField(c, OPT_HomePostcode, r, "PostCode"); - CopyField(c, OPT_HomeCountry, r, "Country"); - CopyField(c, OPT_WorkPhone, r, "PhoneWork"); - CopyField(c, OPT_HomePhone, r, "PhoneHome"); - CopyField(c, OPT_HomeFax, r, "Fax"); - CopyField(c, OPT_HomeMobile, r, "Mobile"); - // CopyField(c, OPT_WebPage, r, "Web Page"); - CopyField(c, OPT_Note, r, "Notes"); - CopyField(c, OPT_Nick, r, "Nick"); + CopyField(c, OPT_First, r, "First"); + CopyField(c, OPT_Last, r, "Surname"); + CopyField(c, OPT_Email, r, "Email"); + CopyField(c, OPT_HomeStreet, r, "Address"); + CopyField(c, OPT_HomeSuburb, r, "City"); + CopyField(c, OPT_HomeState, r, "State"); + CopyField(c, OPT_HomePostcode, r, "PostCode"); + CopyField(c, OPT_HomeCountry, r, "Country"); + CopyField(c, OPT_WorkPhone, r, "PhoneWork"); + CopyField(c, OPT_HomePhone, r, "PhoneHome"); + CopyField(c, OPT_HomeFax, r, "Fax"); + CopyField(c, OPT_HomeMobile, r, "Mobile"); + // CopyField(c, OPT_WebPage, r, "Web Page"); + CopyField(c, OPT_Note, r, "Notes"); + CopyField(c, OPT_Nick, r, "Nick"); - c->Save(Contacts); + c->Save(Contacts); + } } } } } - } + delete dlg; + }); } - } + delete dlg; + }); } diff --git a/Code/Imp_OutlookExpress.cpp b/Code/Imp_OutlookExpress.cpp --- a/Code/Imp_OutlookExpress.cpp +++ b/Code/Imp_OutlookExpress.cpp @@ -1,682 +1,719 @@ /* ** FILE: Imp_OutlookExpress.cpp ** AUTHOR: Matthew Allen ** DATE: 4/2/2000 ** DESCRIPTION: Scribe importer ** ** Copyright (C) 2000, Matthew Allen ** fret@memecode.com */ #include "Scribe.h" #include "resdefs.h" #include "lgi/common/ProgressDlg.h" #include "lgi/common/LgiRes.h" #include "lgi/common/FileSelect.h" bool ImportMBX(ScribeWnd *Parent, ScribeFolder *ParentFolder, char *FileName) { LFile F; if (FileName && ParentFolder && Parent && F.Open(FileName, O_READ)) { char Magic[4] = {'J', 'M', 'F', '6'}; char Buf[4]; if (F.Read(Buf, 4) == 4 && memcmp(Magic, Buf, 4) == 0) { int32 Msgs; F.Seek(4, SEEK_CUR); F >> Msgs; // Create folder for these messages char n[256]; strcpy_s(n, sizeof(n), FileName); char *Ls = 0; // last slash for (Ls = n+strlen(n)-1; Ls > n; Ls--) { if (*Ls == DIR_CHAR) { Ls++; break; } } char *Dot = strchr(Ls, '.'); if (Dot) { *Dot = 0; } ScribeFolder *Folder = ParentFolder->CreateSubDirectory(Ls, MAGIC_MAIL); if (Folder) { // Skip unknown feilds F.Seek(4 + 4 + 1 + 63, SEEK_CUR); // Read the messages for (int i=0; i> MsgId; int MsgNum = 0; F >> MsgNum; int MsgTotalSize = 0; F >> MsgTotalSize; int MsgTextSize; F >> MsgTextSize; // Create item Mail *m = dynamic_cast(Parent->CreateItem(MAGIC_MAIL, Folder, false)); if (m) { LMemStream Text(&F, 0, MsgTextSize); // Decode email into fields m->OnAfterReceive(&Text); m->SetFlags(MAIL_RECEIVED | MAIL_READ | ((m->HasAttachments()) ? MAIL_ATTACHMENTS : 0) ); } // Seek to the next message F.Seek(MsgStartPos+MsgTotalSize, SEEK_SET); } } else { return false; } return true; } } return false; } #ifdef WIN32 #pragma pack(push, 1) #endif class DbxMessagePtr { public: uint MessagePos; uint TablePos; char Reserved[4]; }; // #define DbxTableSize 0x27c class DbxTable { public: // Header /* uint FilePos; char Reserved1[13]; uint Msgs; char Reserved2[3]; */ uint FilePos; char Reserved1[4]; uint ListPtr; uint NextPtr; char Reserved2[8]; }; class DbxMsgTable { public: uint FilePos; uint MessageLength; char Reserved[17]; uint FirstBlockPos; }; class DbxMsgBlock { public: /* uint FilePos; uint Flags; uint BlockSize; uint NextBlockPos; */ uint FilePos; uint Increase; uint Include; uint Next; uint UseNet; }; #ifdef WIN32 #pragma pack(pop) #endif class ImportDBX { private: ScribeWnd *Parent = NULL; ScribeFolder *ParentFolder = NULL; LFile F; uint64 FileSize = 0; List Used; class MsgInfo { public: int Pos; bool IsNews; MsgInfo(int p, bool n) { Pos = p; IsNews = n; } }; List MsgList; bool IsUsed(ssize_t i) { for (auto n: Used) { if (*n == i) return true; } return false; } void SetUsed(ssize_t i) { if (!IsUsed(i)) { Used.Insert(new ssize_t(i)); } } bool ReadMessage(ssize_t Pos, bool IsNews, ScribeFolder *Folder) { bool Status = false; if (!IsUsed(Pos) && F.Seek(Pos, SEEK_SET)) { LTempStream Text(ScribeTempPath()); SetUsed(Pos); while (Pos > 0) { DbxMsgBlock Msg; ZeroObj(Msg); F.Read(&Msg, sizeof(Msg)-4); if (Pos != Msg.FilePos) { break; } Pos += sizeof(Msg)-4; // int Next = Pos + Msg.Next; auto End = Pos + Msg.Include; char Buf[1024]; while (Pos < End) { ssize_t R = F.Read(Buf, MIN(End-Pos, sizeof(Buf))); Text.Write(Buf, R); Pos += R; } F.Seek(Msg.Next, SEEK_SET); Pos = Msg.Next; } Mail *m = new Mail(Parent); if (m) { m->App = Parent; m->OnAfterReceive(&Text); m->SetFlags(MAIL_RECEIVED | MAIL_READ | // ((MsgTable.Reserved[3] == 3) ? MAIL_READ : 0) | (m->GetAttachments(0) ? MAIL_ATTACHMENTS : 0)); m->SetDirty(); m->Save(Folder); Status = true; } } return Status; } bool ReadMsgHeader(int Pos) { bool Status = false; if (!IsUsed(Pos) && F.Seek(Pos, SEEK_SET)) { SetUsed(Pos); DbxMsgBlock Msg; F.Read(&Msg, sizeof(Msg)); if (Msg.FilePos == Pos) { Pos += sizeof(Msg); int32 Self, MsgPos = 0, NewsPost = false, Count = 0; do { F.Read(&Self, sizeof(Self)); if ((Self & 0xff) == 0x84) { if (!MsgPos) { MsgPos = Self >> 8; } } if ((Self & 0xff) == 0x83) { NewsPost = true; } Count++; } while ((Self & 0x7f) > 0); if (MsgPos) { MsgList.Insert(new MsgInfo(MsgPos, NewsPost != 0)); Status |= true; } else { F.Read(&Self, sizeof(Self)); F.Read(&MsgPos, sizeof(MsgPos)); MsgList.Insert(new MsgInfo(MsgPos, NewsPost != 0)); Status |= true; } } } return Status; } bool ReadTable(ssize_t Pos) { bool Status = false; if (Pos > 0 && (uint64)Pos < FileSize && !IsUsed(Pos)) { DbxTable Tbl; F.Seek(Pos, SEEK_SET); SetUsed(Pos); F.Read(&Tbl, sizeof(Tbl)); if (Tbl.FilePos == Pos) { Pos += sizeof(Tbl); Status |= ReadTable(Tbl.NextPtr); Status |= ReadTable(Tbl.ListPtr); F.Seek(Pos, SEEK_SET); while (true) { bool Actioned = false; DbxMessagePtr Node; F.Seek(Pos, SEEK_SET); Pos += F.Read(&Node, sizeof(Node)); if (Node.MessagePos > 0 && Node.MessagePos < FileSize && Node.MessagePos != Node.TablePos) { Actioned |= ReadMsgHeader(Node.MessagePos); } if (Node.TablePos > 0 && Node.TablePos < FileSize && Node.TablePos != Node.MessagePos) { Actioned |= ReadTable(Node.TablePos); } if (Actioned) { Status = true; } else break; } } } return Status; } void Clean() { Used.DeleteObjects(); MsgList.DeleteObjects(); } public: ImportDBX(ScribeWnd *parent) { Parent = parent; ParentFolder = 0; } ~ImportDBX() { Clean(); } bool Import(ScribeFolder *parentFolder, char *FileName) { Clean(); ParentFolder = parentFolder; if (sizeof(DbxTable) != 24) { // packing is screwed LgiMsg(Parent, "Compiled object 'DbxTable' is the wrong size", AppName, MB_OK); return false; } LProgressDlg Dlg(Parent); Dlg.SetDescription("Reading tables..."); Dlg.SetRange(1); LProgressPane *Import = Dlg.Push(); if (Import) - { Import->SetDescription("Importing messages..."); - LYield(); - } if (Parent && ParentFolder && FileName && F.Open(FileName, O_READ)) { FileSize = F.GetSize(); // Create folder for these messages char n[256]; strcpy_s(n, sizeof(n), FileName); char *Ls = strrchr(n, DIR_CHAR); // last slash if (Ls) { Ls++; char *Dot = strchr(Ls, '.'); if (Dot) { *Dot = 0; } // Make sure the path is unique, no overwriting previous folders LStringPipe NewPath(256); auto ParentPath = ParentFolder->GetPath(); NewPath.Print("/%s/%s", ParentPath.Get(), Ls); auto BaseNewPath = NewPath.NewGStr(); LString NewPathStr = BaseNewPath.Get(); while (Parent->GetFolder(NewPathStr)) { char *Num = NewPathStr.Get() + BaseNewPath.Length() - 1; for (; Num>NewPathStr && IsDigit(*Num); Num--) ; if (!IsDigit(*Num)) Num++; int i = atoi(Num); NewPath.Print("%s%i", BaseNewPath.Get(), i+1); NewPathStr = NewPath.NewGStr(); } // Create output folder ScribeFolder *Folder = ParentFolder->CreateSubDirectory(Ls, MAGIC_MAIL); if (Folder) { // Read header F.Seek(0x30, SEEK_SET); uint TableLoc; F >> TableLoc; if (!TableLoc) TableLoc = 0x1e254; ReadTable(TableLoc); // Import msg list Dlg.Value(1); if (Import) { Import->SetRange(MsgList.Length()); Import->SetType("Mail"); } for (auto i: MsgList) { ReadMessage(i->Pos, i->IsNews, Folder); if (Import) - { Import->Value(Import->Value()+1); - LYield(); - } } /* List Msgs; while (Tbl) { int Pos = F.GetPosition(); F.Read(Tbl, DbxTableSize); if (Tbl->FilePos == Pos) { for (int i=0; iMsgs; i++) { Msgs.Insert(new int(Tbl->Msg[i].MessagePos)); } } if (Tbl->Msgs < 76 || Tbl->FilePos != Pos) { DeleteArray(Tbl); } } */ // Read in messages /* for (int *MsgPos = Msgs.First(); MsgPos; MsgPos = Msgs.Next()) { bool Read = true; DbxMsgTable MsgTable; F.Seek(*MsgPos, SEEK_SET); F.Read(&MsgTable, sizeof(MsgTable)); if (MsgTable.FilePos == *MsgPos) { // Read in blocks GBytePipe Msg; DbxMsgBlock MsgBlock; MsgBlock.NextBlockPos = MsgTable.FirstBlockPos; while (MsgBlock.NextBlockPos) { int SeekTo = MsgBlock.NextBlockPos & 0xFFFFFF; F.Seek(SeekTo, SEEK_SET); F.Read(&MsgBlock, sizeof(MsgBlock)); if (MsgBlock.FilePos == SeekTo) { uchar *Data = new uchar[MsgBlock.BlockSize]; if (Data) { F.Read(Data, MsgBlock.BlockSize); Msg.Push(Data, MsgBlock.BlockSize); DeleteArray(Data); } } } // Msg contains email... int Size = Msg.Sizeof(); if (Size > 0) { Mail *m = dynamic_cast(Parent->CreateItem(MAGIC_MAIL, Folder, false)); if (m) { m->Text = new char[Size+1]; if (m->Text) { // Read data into item Msg.Pop((uchar*) m->Text, Size); m->Text[Size] = 0; // Decode email into fields m->OnAfterReceive(); m->SetFlags(MAIL_RECEIVED | ((MsgTable.Reserved[3] == 3) ? MAIL_READ : 0) | ((m->Store->GetChild() != 0) ? MAIL_ATTACHMENTS : 0)); m->StoreDirty = true; } } } } } */ return true; } } } return false; } }; - -void Import_OutlookExpress(ScribeWnd *Parent, bool v5) +struct ImportOe : public LProgressDlg { - // Get the base directory - char Dir[256] = ""; - if (!LGetSystemPath(LSP_LOCAL_APP_DATA, Dir, sizeof(Dir))) - { - // Just in case - LGetSystemPath(LSP_OS, Dir, sizeof(Dir)); - } - - // Search for the folder files -DoFileSearch: + ScribeWnd *App; + bool v5; + int Imported = 0, TotalFiles = 0; + LString Dir; LArray Files; LArray Ext; - if (v5) - Ext.Add("*.dbx"); - else - Ext.Add("*.mbx"); + LString::Array FileArr; + ScribeFolder *Folder = NULL; - LRecursiveFileSearch(Dir, &Ext, &Files); - - if (Files.Length() == 0) + ImportOe(ScribeWnd *app, bool ver5) : + App(app), + v5(ver5), + LProgressDlg(app) { - if (LgiMsg( Parent, - "%i outlook express data files found in:\n" - "%s\n" - "Do you want to select a different directory to search?", - AppName, - MB_YESNO, - Files.Length(), - Dir) == IDYES) + // Get the base directory + auto Dir = LGetSystemPath(LSP_LOCAL_APP_DATA); + if (!Dir) + // Just in case + Dir = LGetSystemPath(LSP_OS); + + // Search for the folder files + Ext.Add(v5 ? "*.dbx" : "*.mbx"); + + DoFileSearch(); + if (Files.Length() == 0) { - LFileSelect Select; - Select.Parent(Parent); - if (Select.OpenFolder()) + if (LgiMsg( App, + "%i outlook express data files found in:\n" + "%s\n" + "Do you want to select a different directory to search?", + AppName, + MB_YESNO, + (int)Files.Length(), + Dir.Get()) == IDYES) { - strcpy_s(Dir, sizeof(Dir), Select.Name()); - goto DoFileSearch; + auto Select = new LFileSelect(App); + Select->OpenFolder([&](auto dlg, auto status) + { + if (status) + { + strcpy_s(Dir, sizeof(Dir), Select->Name()); + DoFileSearch(); + ProcessFiles(); + } + delete dlg; + }); } } + else ProcessFiles(); } - if (Files.Length() > 0) + void DoFileSearch() { + LRecursiveFileSearch(Dir, &Ext, &Files); + } + + void ProcessFiles() + { + if (Files.Length() == 0) + return; + // Strip files for (unsigned i=0; iGetCurrentFolder(); + auto Current = App->GetCurrentFolder(); LString CurrentPath; if (Current) - CurrentPath = Current->GetPath(); - ChooseFolderDlg Dlg(Parent, - false, - AppName, - LLoadString(IDS_OE_IMPORT), - CurrentPath, - MAGIC_MAIL, - &Files); - if (Dlg.DoModal()) + CurrentPath = Current->GetPath(); + + auto Dlg = new ChooseFolderDlg( App, + false, + AppName, + LLoadString(IDS_OE_IMPORT), + CurrentPath, + MAGIC_MAIL, + &FileArr); + Dlg->DoModal([this, Dlg](auto dlg, auto id) { - ScribeFolder *Folder = Parent->GetFolder(Dlg.DestFolder); - if (Folder) + LAutoPtr mem(dlg); + + if (!id) + return; + + ScribeFolder *Folder = App->GetFolder(Dlg->DestFolder); + if (!Folder) { - LProgressDlg PrgDlg(Parent); - PrgDlg.SetDescription("Importing folders..."); - PrgDlg.SetRange(Files.Length()); - LYield(); + LgiMsg(App, "Error locating that folder.", AppName, MB_OK); + return; + } + + SetDescription("Importing folders..."); + SetRange(FileArr.Length()); - int Imported = 0; - int i=1; - for (auto FileName: Dlg.SrcFiles) - { - if (v5) - { - ImportDBX Filter(Parent); - if (Filter.Import(Folder, FileName)) - { - Imported++; - } - } - else - { - if (ImportMBX(Parent, Folder, FileName)) - { - Imported++; - } - } + FileArr = Dlg->SrcFiles; + TotalFiles = (int)FileArr.Length(); + + SetPulse(100); // Start processing... each timeout allows the message loop to run a bit + }); + }; - PrgDlg.Value(i); - LYield(); - i++; - } + void OnFinished() + { + LgiMsg( App, + "%i of %i %s files imported successfully.", + AppName, + MB_OK, + Imported, + TotalFiles, + (v5) ? (char*)"DBX" : (char*)"MBX"); + + delete this; + } - PrgDlg.Visible(false); - char *FileType = (v5) ? (char*)"DBX" : (char*)"MBX"; - LgiMsg(Parent, "%i of %i %s files imported successfully.", AppName, MB_OK, Imported, Files.Length(), FileType); + void OnPulse() + { + if (FileArr.Length()) + { + auto FileName = FileArr.Last(); + FileArr.PopLast(); + + if (v5) + { + ImportDBX Filter(App); + if (Filter.Import(Folder, FileName)) + Imported++; } else { - LgiMsg(Parent, "Error locating that folder.", AppName, MB_OK); + if (ImportMBX(App, Folder, FileName)) + Imported++; } + + (*this)++; // inc progress bar... } + else OnFinished(); } +}; + +void Import_OutlookExpress(ScribeWnd *Parent, bool v5) +{ + new ImportOe(Parent, v5); } diff --git a/Code/ManageMailStores.cpp b/Code/ManageMailStores.cpp --- a/Code/ManageMailStores.cpp +++ b/Code/ManageMailStores.cpp @@ -1,573 +1,593 @@ #include "lgi/common/Lgi.h" #include "Scribe.h" #include "Store3Mail3/Mail3.h" #include "ManageMailStores.h" #include "lgi/common/List.h" #include "lgi/common/ListItemCheckBox.h" #include "lgi/common/Edit.h" #include "lgi/common/LgiRes.h" #include "lgi/common/FileSelect.h" #include "resdefs.h" class FmtDlg : public LDialog { public: int Ver; FmtDlg(LViewI *p, int ver) { Ver = -1; SetParent(p); if (LoadFromResource(IDD_FOLDER_FORMAT)) { MoveToCenter(); SetCtrlValue(IDC_FORMAT, ver); } } int OnNotify(LViewI *Ctrl, LNotification n) { switch (Ctrl->GetId()) { case IDOK: { Ver = (int)GetCtrlValue(IDC_FORMAT); // Fall through } case IDCANCEL: { EndModal(Ctrl->GetId() == IDOK); break; } } return 0; } }; //////////////////////////////////////////////////////////////////////////////////////// class SubFolderDlg : public LDialog, public LXmlTreeUi { ScribeWnd *App; void FolderSelector(int OutputCtrl, int Limit) { - FolderDlg Dlg(this, App, Limit); - if (Dlg.DoModal()) + auto Dlg = new FolderDlg(this, App, Limit); + Dlg->DoModal([this, Dlg, OutputCtrl](auto dlg, auto id) { - LEdit *e; - if (GetViewById(OutputCtrl, e)) + if (id) { - e->Name(Dlg.Get()); + LEdit *e; + if (GetViewById(OutputCtrl, e)) + e->Name(Dlg->Get()); } - } + delete dlg; + }); } public: SubFolderDlg(LView *parent, ScribeWnd *app) { App = app; SetParent(parent); if (App->GetOptions() && LoadFromResource(IDD_SUB_FOLDERS)) { Map(OPT_Inbox, IDC_INBOX, GV_STRING); Map(OPT_Outbox, IDC_OUTBOX, GV_STRING); Map(OPT_Sent, IDC_SENT, GV_STRING); Map(OPT_Trash, IDC_TRASH, GV_STRING); Map(OPT_Contacts, IDC_CONTACT_FLD, GV_STRING); Map(OPT_Templates, IDC_TEMPLATES, GV_STRING); Map(OPT_Calendar, IDC_CALENDER, GV_STRING); Map(OPT_Filters, IDC_FILTERS_FLD, GV_STRING); Map(OPT_Groups, IDC_GROUPS_FLD, GV_STRING); Map(OPT_SpamFolder, IDC_SPAM_FLD, GV_STRING); Map(OPT_HasTemplates, IDC_HAS_TEMPLATES, GV_BOOL); Map(OPT_HasGroups, IDC_HAS_GROUPS, GV_BOOL); Map(OPT_HasCalendar, IDC_HAS_CAL_EVENTS, GV_BOOL); Map(OPT_HasFilters, IDC_HAS_FILTERS, GV_BOOL); Map(OPT_HasSpam, IDC_HAS_SPAM, GV_BOOL); Convert(App->GetOptions(), this, true); MoveToCenter(); - DoModal(); } } int OnNotify(LViewI *Ctrl, LNotification n) { switch (Ctrl->GetId()) { case IDOK: Convert(App->GetOptions(), this, false); EndModal(1); break; case IDCANCEL: EndModal(0); break; case IDC_SET_INBOX: FolderSelector(IDC_INBOX, MAGIC_MAIL); break; case IDC_SET_OUTBOX: FolderSelector(IDC_OUTBOX, MAGIC_MAIL); break; case IDC_SET_SENT: FolderSelector(IDC_SENT, MAGIC_MAIL); break; case IDC_SET_TRASH: FolderSelector(IDC_TRASH, MAGIC_ANY); break; case IDC_SET_CONTACTS: FolderSelector(IDC_CONTACT_FLD, MAGIC_CONTACT); break; case IDC_SET_TEMPLATES: FolderSelector(IDC_TEMPLATES, MAGIC_MAIL); break; case IDC_SET_CALENDER: FolderSelector(IDC_CALENDER, MAGIC_CALENDAR); break; case IDC_SET_FILTERS: FolderSelector(IDC_FILTERS_FLD, MAGIC_FILTER); break; case IDC_SET_GROUPS: FolderSelector(IDC_GROUPS_FLD, MAGIC_GROUP); break; case IDC_SET_SPAM: FolderSelector(IDC_SPAM_FLD, MAGIC_MAIL); break; } return 0; } }; //////////////////////////////////////////////////////////////////////////////////////// LVariant FolderFullPath(const char *Path) { char f[MAX_PATH_LEN]; if (LIsRelativePath(Path)) { LMakePath(f, sizeof(f), LGetExePath(), Path); return f; } return Path; } int GetFolderVersion(const char *f) { LVariant Path = FolderFullPath(f); if (LDirExists(Path.Str())) { char p[MAX_PATH_LEN]; LMakePath(p, sizeof(p), Path.Str(), MAIL3_DB_FILE); if (LFileExists(p)) { return 3; } } return 0; } -bool EditWebdav(LViewI *parent, LXmlTag *t) +void EditWebdav(LViewI *parent, LXmlTag *t, std::function callback) { - LDialog dlg; + auto dlg = new LDialog(parent); - dlg.SetParent(parent); - dlg.LoadFromResource(IDD_WEBDAV_PROPS); - dlg.MoveSameScreen(parent); + dlg->LoadFromResource(IDD_WEBDAV_PROPS); + dlg->MoveSameScreen(parent); - dlg.SetCtrlName(IDC_DESC, t->GetAttr(OPT_MailStoreName)); - dlg.SetCtrlName(IDC_CONTACTS_URL, t->GetAttr(OPT_MailStoreContactUrl)); - dlg.SetCtrlName(IDC_CALENDAR_URL, t->GetAttr(OPT_MailStoreCalendarUrl)); - dlg.SetCtrlName(IDC_USERNAME, t->GetAttr(OPT_MailStoreUserName)); - dlg.SetCtrlName(IDC_PASSWORD, t->GetAttr(OPT_MailStorePassword)); + dlg->SetCtrlName(IDC_DESC, t->GetAttr(OPT_MailStoreName)); + dlg->SetCtrlName(IDC_CONTACTS_URL, t->GetAttr(OPT_MailStoreContactUrl)); + dlg->SetCtrlName(IDC_CALENDAR_URL, t->GetAttr(OPT_MailStoreCalendarUrl)); + dlg->SetCtrlName(IDC_USERNAME, t->GetAttr(OPT_MailStoreUserName)); + dlg->SetCtrlName(IDC_PASSWORD, t->GetAttr(OPT_MailStorePassword)); - int res = dlg.DoModal(); - if (res != IDOK) - return false; - - t->SetAttr(OPT_MailStoreName, dlg.GetCtrlName(IDC_DESC)); - t->SetAttr(OPT_MailStoreContactUrl, dlg.GetCtrlName(IDC_CONTACTS_URL)); - t->SetAttr(OPT_MailStoreCalendarUrl, dlg.GetCtrlName(IDC_CALENDAR_URL)); - t->SetAttr(OPT_MailStoreUserName, dlg.GetCtrlName(IDC_USERNAME)); - t->SetAttr(OPT_MailStorePassword, dlg.GetCtrlName(IDC_PASSWORD)); - return true; + dlg->DoModal([t, callback](auto dlg, auto res) + { + if (res == IDOK) + { + t->SetAttr(OPT_MailStoreName, dlg->GetCtrlName(IDC_DESC)); + t->SetAttr(OPT_MailStoreContactUrl, dlg->GetCtrlName(IDC_CONTACTS_URL)); + t->SetAttr(OPT_MailStoreCalendarUrl, dlg->GetCtrlName(IDC_CALENDAR_URL)); + t->SetAttr(OPT_MailStoreUserName, dlg->GetCtrlName(IDC_USERNAME)); + t->SetAttr(OPT_MailStorePassword, dlg->GetCtrlName(IDC_PASSWORD)); + callback(true); + } + else callback(false); + delete dlg; + }); } class StoreItem : public LListItem { ScribeWnd *App; LListItemCheckBox *Disable; public: LXmlTag Tag; StoreItem(ScribeWnd *app) : Tag(OPT_MailStore) { App = app; Disable = new LListItemCheckBox(this, 2, false); } const char *GetText(int Col) { switch (Col) { case 0: return Tag.GetAttr(OPT_MailStoreName); case 1: return Tag.GetAttr(OPT_MailStoreLocation); } return 0; } bool SetText(const char *s, int Col) { switch (Col) { case 0: Tag.SetAttr(OPT_MailStoreName, s); break; case 1: Tag.SetAttr(OPT_MailStoreLocation, s); break; } Update(); GetList()->ResizeColumnsToContent(); return true; } bool XmlIo(LXmlTag *t, bool Write) { if (Write) { Tag.SetAttr(OPT_MailStoreDisable, (int)Disable->Value()); *t = Tag; } else { Tag = *t; int i = Tag.GetAsInt(OPT_MailStoreDisable); if (i >= 0) Disable->Value(i); } return true; } bool IsWebdav() { return Tag.GetAttr(OPT_MailStoreContactUrl) || Tag.GetAttr(OPT_MailStoreCalendarUrl); } void Edit() { - if (EditWebdav(GetList(), &Tag)) - Update(); + EditWebdav(GetList(), &Tag, [&](auto status) + { + if (status) + Update(); + }); } void OnMouseClick(LMouse &m) { bool Wd = IsWebdav(); if (m.IsContextMenu()) { if (Wd) { LSubMenu s; s.AppendItem(LLoadString(IDS_EDIT), IDM_EDIT); if (s.Float(GetList(), m) == IDM_EDIT) Edit(); } } else if (m.Left() && m.Double() && m.Down()) { int Col = GetList()->ColumnAtX(m.x); switch (Col) { case 0: { EditLabel(Col); break; } case 1: { if (Wd) Edit(); else EditLabel(Col); break; } } } LListItem::OnMouseClick(m); } }; LListItem *CreateStoreItem(void *User) { return new StoreItem((ScribeWnd*)User); } ManageMailStores::ManageMailStores(ScribeWnd *app) { Lst = 0; SetParent(App = app); if (LoadFromResource(IDD_MANAGE_FOLDERS)) { GetViewById(IDC_MAIL_STORES, Lst); MoveToCenter(); LXmlTag *Base = App->GetOptions()->LockTag(0, _FL); if (Base) { Options.Copy(*Base, false); LXmlTag *Ms = Base->GetChildTag(OPT_MailStores); if (Ms) { LXmlTag *t = Options.CreateTag(OPT_MailStores); if (t) { t->Copy(*Ms, true); } } App->GetOptions()->Unlock(); } Map(OPT_MailStores, IDC_MAIL_STORES, CreateStoreItem, OPT_MailStore, App); Map(OPT_StartInFolder, IDC_START_IN, GV_STRING); Convert(&Options, this, true); Lst->ResizeColumnsToContent(); OnItemSelect(); } } ManageMailStores::~ManageMailStores() { } void ManageMailStores::OnItemSelect() { LListItem *s = Lst->GetSelected(); int Ver = s ? GetFolderVersion(s->GetText(1)) : 0; SetCtrlEnabled(IDC_COMPACT_MS, Ver > 0); SetCtrlEnabled(IDC_CONVERT_MS, Ver == 3); SetCtrlEnabled(IDC_REPAIR_MS, true); } LMailStore *ManageMailStores::GetCurrentMailStore() { LListItem *s = Lst->GetSelected(); if (s) { LVariant p = FolderFullPath(s->GetText(1)); for (unsigned i=0; iGetStorageFolders().Length(); i++) { LMailStore &s = App->GetStorageFolders()[i]; if (s.Path && !_stricmp(s.Path, p.Str())) { if (s.Store) return &s; } } } return 0; } int ManageMailStores::OnNotify(LViewI *c, LNotification n) { if (!Lst) return 0; switch (c->GetId()) { case IDC_MAIL_STORES: { if (n.Type == LNotifyItemSelect) { OnItemSelect(); } break; } case IDC_OPEN_MS: { - LFileSelect s; - s.Parent(this); - s.InitialDir(LGetExePath()); - s.Type("Mail Folders", "*.mail3;*.sqlite"); - - if (s.Open()) + auto s = new LFileSelect(this); + s->InitialDir(LGetExePath()); + s->Type("Mail Folders", "*.mail3;*.sqlite"); + s->Open([this](auto dlg, auto status) { - StoreItem *Si = new StoreItem(App); - if (Si) + if (status) { - char b[MAX_PATH_LEN]; - strcpy_s(b, sizeof(b), s.Name()); - char *n = LGetExtension(b); - if (n && !_stricmp(n, "sqlite")) + StoreItem *Si = new StoreItem(App); + if (Si) { - n = strrchr(b, DIR_CHAR); - if (n) *n = 0; - } + char b[MAX_PATH_LEN]; + strcpy_s(b, sizeof(b), dlg->Name()); + char *n = LGetExtension(b); + if (n && !_stricmp(n, "sqlite")) + { + n = strrchr(b, DIR_CHAR); + if (n) *n = 0; + } - Si->Tag.SetAttr(OPT_MailStoreLocation, b); - Lst->Insert(Si); - Lst->ResizeColumnsToContent(); + Si->Tag.SetAttr(OPT_MailStoreLocation, b); + Lst->Insert(Si); + Lst->ResizeColumnsToContent(); + } } - } + delete dlg; + }); break; } case IDC_CLOSE_MS: { List s; Lst->GetSelection(s); s.DeleteObjects(); Lst->ResizeColumnsToContent(); break; } case IDC_CREATE_MS: { char Opts[MAX_PATH_LEN]; LMakePath(Opts, sizeof(Opts), App->GetOptions()->GetFile(), ".."); LView *btn; if (!GetViewById(IDC_CREATE_MS, btn)) break; LSubMenu s; s.AppendItem("Mail3 Local Folders", IDM_LOCAL_FOLDERS); s.AppendItem("Webdav Remote Folder", IDM_WEBDAV_FOLDER); LPoint pt(0, btn->Y()); btn->PointToScreen(pt); int Cmd = s.Float(this, pt.x, pt.y, LSubMenu::BtnLeft); if (Cmd == IDM_LOCAL_FOLDERS) { - LFileSelect s; - s.Parent(this); - s.InitialDir(Opts); - s.Name((char*)"Folders.mail3"); - if (s.Save()) + auto s = new LFileSelect(this); + s->InitialDir(Opts); + s->Name((char*)"Folders.mail3"); + s->Save([&](auto dlg, auto status) { - StoreItem *Si = new StoreItem(App); - if (Si) + if (status) { - auto Rel = LMakeRelativePath(Opts, s.Name()); + StoreItem *Si = new StoreItem(App); + if (Si) + { + auto Rel = LMakeRelativePath(Opts, s->Name()); - Si->Tag.SetAttr(OPT_MailStoreLocation, Rel ? Rel.Get() : s.Name()); + Si->Tag.SetAttr(OPT_MailStoreLocation, Rel ? Rel.Get() : s->Name()); - Lst->Insert(Si); - Lst->ResizeColumnsToContent(); + Lst->Insert(Si); + Lst->ResizeColumnsToContent(); + } } - } + delete dlg; + }); } else if (Cmd == IDM_WEBDAV_FOLDER) { StoreItem *Si = new StoreItem(App); if (!Si) break; - if (EditWebdav(this, &Si->Tag)) - { - Lst->Insert(Si); - Lst->ResizeColumnsToContent(); - } - else + + EditWebdav(this, &Si->Tag, [&](auto status) { - DeleteObj(Si); - } + if (status) + { + Lst->Insert(Si); + Lst->ResizeColumnsToContent(); + } + else DeleteObj(Si); + }); } break; } case IDC_COMPACT_MS: { LMailStore *ms = GetCurrentMailStore(); if (ms) { App->CompactFolders(*ms); } else { LgiMsg(this, LLoadString(IDS_ERROR_FOLDER_NOT_LOADED), AppName); } break; } case IDC_CONVERT_MS: { LMailStore *ms = GetCurrentMailStore(); if (ms) { - FmtDlg Dlg(this, (int)ms->Store->GetInt(FIELD_FORMAT)); - if (Dlg.DoModal()) + auto Dlg = new FmtDlg(this, (int)ms->Store->GetInt(FIELD_FORMAT)); + Dlg->DoModal([this, Dlg, ms](auto dlg, auto id) { - Store3Progress Prog(App, true); - Prog.SetInt(Store3UiNewFormat, Dlg.Ver); - if (!ms->Store->SetFormat(this, &Prog)) + if (id) { - LgiMsg(this, "Set format failed.", AppName); + Store3Progress Prog(App, true); + Prog.SetInt(Store3UiNewFormat, Dlg->Ver); + if (!ms->Store->SetFormat(this, &Prog)) + LgiMsg(this, "Set format failed.", AppName); } - } + delete dlg; + }); } break; } case IDC_REPAIR_MS: { LMailStore *ms = GetCurrentMailStore(); if (ms) { auto Prog = new Store3Progress(App, true); ms->Store->Repair(this, Prog, [this, Prog](auto status) { if (!status) { auto Err = Prog->GetStr(Store3UiError); LgiMsg( this, "Repair failed: %s", AppName, MB_OK, Err?Err:"Unsupported method."); } delete Prog; }); } else LgiMsg(this, "Error: No mail store selected.", AppName); break; } case IDC_SUB_FOLDERS: { - SubFolderDlg Dlg(this, App); + auto Dlg = new SubFolderDlg(this, App); + Dlg->DoModal(NULL); break; } case IDC_SET_START_IN: { - FolderDlg Dlg(this, App); - if (Dlg.DoModal()) - SetCtrlName(IDC_START_IN, Dlg.Get()); + auto Dlg = new FolderDlg(this, App); + Dlg->DoModal([this, Dlg](auto dlg, auto id) + { + if (id) + SetCtrlName(IDC_START_IN, Dlg->Get()); + delete dlg; + }); break; } case IDOK: { Convert(&Options, this, false); } case IDCANCEL: { EndModal(c->GetId() == IDOK); break; } } return 0; } diff --git a/Code/OptionsDlg.cpp b/Code/OptionsDlg.cpp --- a/Code/OptionsDlg.cpp +++ b/Code/OptionsDlg.cpp @@ -1,1274 +1,1282 @@ /* ** FILE: OptionsDlg.cpp ** AUTHOR: Matthew Allen ** DATE: 5/8/2011 ** DESCRIPTION: Scribe email options dialog ** ** Copyright (C) 2011, Matthew Allen ** fret@memecode.com */ #include #include #include #include "Scribe.h" #include "ScribePrivate.h" #include "lgi/common/Edit.h" #include "lgi/common/RadioGroup.h" #include "lgi/common/Combo.h" #include "lgi/common/Button.h" #include "lgi/common/TextView3.h" #include "lgi/common/TextLabel.h" #include "lgi/common/ControlTree.h" #include "lgi/common/TabView.h" #include "lgi/common/SpellCheck.h" #include "lgi/common/LgiRes.h" #include "lgi/common/RichTextEdit.h" #include "lgi/common/EventTargetThread.h" #include "lgi/common/TextView3.h" #include "lgi/common/FileSelect.h" #include "resdefs.h" #include "CalendarView.h" static char AutoInBrackets[] = "(auto)"; #ifndef IDC_TAB #define IDC_TAB 100 #endif #if defined WIN32 #define DLG_X 370 #define DLG_Y 420 #define TAB_X (DLG_X-26) #define TAB_Y (DLG_Y-74) #else #define DLG_X 380 #define DLG_Y 390 #define TAB_X (DLG_X-20) #define TAB_Y (DLG_Y-50) #endif static int AccountCmp(LListItem *a, LListItem *b, NativeInt Data) { return a->Compare(b); } class UtfEditor : public LWindow { ScribeWnd *App; LTabView *Tabs; LTabPage *TabText, *TabHtml; LButton *Ok, *Cancel; LTextView3 *Txt; LRichTextEdit *Html; const char *TextOpt, *HtmlOpt; public: UtfEditor(ScribeWnd *app, const char *txtopt, const char *htmlopt, const char *desc); void OnPosChange(); int OnNotify(LViewI *c, LNotification n); }; class AccountItem : public LListItem { OptionsDlg *Dlg; public: ScribeAccount *Account; LListItemCheckBox *Disable; AccountItem(OptionsDlg *d, ScribeAccount *a) { Dlg = d; Account = a; Account->Views.Add(this); Disable = 0; Disable = new LListItemCheckBox(this, 3, a->Send.Disabled() > 0); } ~AccountItem() { LAssert(Account->Views.HasItem(this)); Account->Views.Delete(this); } ScribeAccount *GetAccount() { return Account; } const char *GetText(int i); void OnMouseClick(LMouse &m) { - if (m.Double() && - Account && - Dlg->App->GetAccountSettingsAccess(Dlg, ScribeReadAccess)) + if (m.Double() && Account) { - Account->InitUI(Parent); - Update(); + Dlg->App->GetAccountSettingsAccess(Dlg, ScribeReadAccess, [&](auto Allow) + { + if (Allow) + { + Account->InitUI(Parent, 0, [&](auto status) + { + if (status) + Update(); + }); + } + }); } LListItem::OnMouseClick(m); } void OnColumnNotify(int Col, int64 Data) { if (Disable && Col == 3) { Dlg->OnAccountEnable(Account, !Data); } LListItem::OnColumnNotify(Col, Data); } int Compare(LListItem *To, ssize_t Field = 0) { AccountItem *ToItem = dynamic_cast(To); if (!ToItem) { printf("%s:%i - Not the right object.\n", _FL); return 0; } int a = Account->Identity.Sort(); int b = ToItem->Account->Identity.Sort(); // printf("%s:%i - %p = %i, %p = %i\n", _FL, Account, a, ToItem->Account, b); return a - b; } }; const char *AccountItem::GetText(int i) { if (Account) { switch (i) { case 0: { static char Buf[64]; LVariant Text = Account->Receive.Name(); if (!Text.Str()) { Text = Account->Receive.Server(); if (!Text.Str()) { Text = Account->Send.Server(); } } if (Text.Str()) { strcpy_s(Buf, sizeof(Buf), Text.Str()); return Buf; } break; } case 1: { return Account->Send.Server().Str() ? (char*)"yes" : 0; break; } case 2: { return Account->Receive.Server().Str() ? (char*)"yes" : 0; break; } } } return 0; } int LangCompare(LLanguage *a, LLanguage *b, NativeInt d) { return _stricmp(a->Name, b->Name); } /////////////////////////////////////////////////////////////////////////////////////////////////////// class RemoteContentDlg : public LDialog, public LXmlTreeUi { ScribeWnd *App; LOptionsFile *Opts; public: RemoteContentDlg(LView *Parent, ScribeWnd *app) : App(app) { SetParent(Parent); Opts = App->GetOptions(); if (LoadFromResource(IDD_REMOTE_CONTENT)) { Map(OPT_RemoteContentWhiteList, IDC_WHITELIST, GV_STRING); Map(OPT_RemoteContentBlackList, IDC_WHITELIST, GV_STRING); Convert(Opts, this, true); MoveSameScreen(Parent); } } int OnNotify(LViewI *Ctrl, LNotification n) { switch (Ctrl->GetId()) { case IDOK: Convert(Opts, this, false); App->RemoteContent_ClearCache(); // Fall thru case IDCANCEL: EndModal(Ctrl->GetId() == IDOK); break; } return 0; } }; /////////////////////////////////////////////////////////////////////////////////////////////////////// class OptionsDlgPrivate { public: LString::Array Langs; LString::Array Dictionaries; }; OptionsDlg::OptionsDlg(ScribeWnd *window) : TabDialog(IDC_TAB, IDC_LAUNCH_HELP) { d = new OptionsDlgPrivate; UiLang = 0; SetParent(App = window); SinkHnd = LEventSinkMap::Dispatch.AddSink(this); LRect r(0, 0, DLG_X, DLG_Y); SetPos(r); MoveSameScreen(App); if (!LoadFromResource(IDD_SETTINGS, window->GetUiTags())) { LgiMsg(window, "Options resource missing.", "Error"); return; } MoveToCenter(); LList *ACtrl; if (GetViewById(IDC_ACCOUNTS, ACtrl)) { for (auto a : *App->GetAccounts()) { AccountItem *i = new AccountItem(this, a); if (i) { ACtrl->Insert(i); if (ACtrl->Length() == 1) { i->Select(true); } } } LNotification note(LNotifyItemInsert); OnNotify(ACtrl, note); ACtrl->Sort(AccountCmp); } // Identity tab Map(OPT_UserName, IDC_NAME, GV_STRING); // Accounts tab Map(OPT_DefaultSendAccount, IDC_DEF_SEND, GV_INT32); Map(OPT_ExtraHeaders, IDC_EXTRA_HEADERS, GV_STRING); Map(OPT_HideId, IDC_HIDE_ID, GV_BOOL); // General tab Map(OPT_QuoteReply, IDC_QUOTE, GV_BOOL); Map(OPT_QuoteReplyStr, IDC_QUOTE_STR, GV_STRING); Map(OPT_ReplyWithSig, IDC_REPLYWITHSIG, GV_BOOL); Map(OPT_DefaultReplyAllSetting, IDC_DEF_REPLYALL_SETTING, GV_INT32); Map(OPT_MinimizeToTray, IDC_TRAY, GV_BOOL); Map(OPT_NewMailSoundFile, IDC_NEW_MAIL_SOUND, GV_STRING); #if WINNATIVE Map(OPT_CheckDefaultEmail, IDC_CHECK_DEFAULT, GV_BOOL); Map(OPT_RegisterWindowsClient, IDC_REGISTER_CLIENT, GV_BOOL); #endif Map(OPT_ConfirmDelete, IDC_CONFIRM_DEL, GV_BOOL); Map(OPT_DelDirection, IDC_MSG_DEL_ACTION, GV_INT32); Map(OPT_NewMailNotify, IDC_NEW_MAIL_NOTIFY, GV_BOOL); Map(OPT_RecipientFromClipboard, IDC_POP_RECIP, GV_BOOL); Map(OPT_MarkReadAfterPreview, IDC_PREVIEW_READ, GV_BOOL); Map(OPT_MarkReadAfterSeconds, IDC_PREVIEW_SECONDS, GV_INT32); Map(OPT_AutoDeleteExe, IDC_AUTO_DELETE_EXE, GV_BOOL); Map(OPT_BlinkNewMail, IDC_BLINK, GV_BOOL); // Connection Tab Map(OPT_UseSocks, IDC_SOCKS5, GV_BOOL); Map(OPT_Socks5Server, IDC_SOCKS5_SERVER, GV_STRING); Map(OPT_Socks5UserName, IDC_SOCKS5_USER, GV_STRING); Map(OPT_Socks5Password, IDC_SOCKS5_PASSWORD, GV_STRING); Map(OPT_Pop3OnStart, IDC_POP3_ON_START, GV_BOOL); Map(OPT_Pop3DefAction, IDC_DEF_ACTION, GV_INT32); Map(OPT_HttpProxy, IDC_HTTP_PROXY, GV_STRING); Map(OPT_CheckForDialUp, IDC_CHECKDIALUP, GV_BOOL); // Appearance/Look Tab Map(OPT_EditControl, IDC_EDIT_CONTROL, GV_INT32); Map(OPT_WordWrap, IDC_WRAP, GV_BOOL); Map(OPT_WrapAtColumn, IDC_WRAP_COLS, GV_INT32); Map(OPT_DefaultAlternative, IDC_DEFAULT_ALT, GV_INT32); Map(OPT_GridLines, IDC_GRID_LINES, GV_BOOL); Map(OPT_PreviewLines, IDC_PREVIEW_LINES, GV_BOOL); Map(OPT_ToolbarText, IDC_TOOLBAR_TEXT, GV_BOOL); Map(OPT_BoldUnread, IDC_BOLD_UNREAD, GV_BOOL); Map(OPT_DateFormat, IDC_DATE_FORMAT, GV_INT32); Map(OPT_UiFontSize, IDC_UI_FNT_SIZE, GV_INT32); Map(OPT_GlyphSub, IDC_GLYPH_SUB, GV_BOOL); Map(OPT_HtmlLoadImages, IDC_HTML_IMAGES, GV_BOOL); Map(OPT_ShowFolderTotals, IDC_SHOW_TOTALS, GV_BOOL); Map(OPT_Theme, IDC_THEME, GV_STRING); Map(OPT_CalendarFirstDayOfWeek, IDC_START_WEEK); // Debug tab Map("*", IDC_ADVANCED, GV_DOM); // Processed fields LControlTree *Ct; if (GetViewById(IDC_ADVANCED, Ct)) { Ct->SetPourLargest(true); LControlTree::Item *ci = Ct->Find(OPT_LogFormat); if (ci) { LAutoPtr Enum(new LControlTree::Item::EnumArr); if (Enum) { Enum->New().Set(LLoadString(IDS_NO_LOG), NET_LOG_NONE); Enum->New().Set(LLoadString(IDS_HEX_LOG), NET_LOG_HEX_DUMP); Enum->New().Set(LLoadString(IDS_BYTE_LOG), NET_LOG_ALL_BYTES); ci->SetEnum(Enum); } } LSpellCheck *SpellThread = App->GetSpellThread(true); if (SpellThread) { if (!SpellThread->EnumLanguages(SinkHnd)) LgiTrace("%s:%i - Failed to EnumLanguages.\n", _FL); LVariant Lang; if (App->GetOptions()->GetValue(OPT_SpellCheckLanguage, Lang)) SpellThread->EnumDictionaries(SinkHnd, Lang.Str()); else LgiTrace("%s:%i - Failed to EnumDictionaries.\n", _FL); } else LgiTrace("%s:%i - Failed to get spell thread.\n", _FL); if ((ci = Ct->Find(OPT_SoftwareUpdateTime))) { LAutoPtr Enum(new LControlTree::Item::EnumArr); if (Enum) { LControlTree::EnumValue *v = &Enum->New(); v->Name = (char*)LLoadString(IDS_WEEK); v->Value = 0; v = &Enum->New(); v->Name = (char*)LLoadString(IDS_MONTH); v->Value = 1; v = &Enum->New(); v->Name = (char*)LLoadString(IDS_YEAR); v->Value = 2; ci->SetEnum(Enum); } } } else LAssert(!"GetViewById failed."); LCombo *c; if (GetViewById(IDC_THEME, c)) { LVariant CurTheme; App->GetOptions()->GetValue(OPT_Theme, CurTheme); c->Insert(LLoadString(IDS_DEFAULT)); auto Paths = ScribeThemePaths(); for (auto p: Paths) { LDirectory d; for (auto b = d.First(p); b; b = d.Next()) { if (d.IsDir()) { c->Insert(d.GetName()); if (!Stricmp(d.GetName(), CurTheme.Str())) c->Value(c->Length()-1); } } } } // Load Convert(App->GetOptions(), this, true); // Get pointers to controls if (GetViewById(IDC_DEFAULT_ALT, c)) { c->Insert("text/plain"); c->Insert("text/html"); #ifdef WINDOWS c->Insert("application/internet-explorer"); #endif } else LAssert(!"GetViewById failed."); if (GetViewById(IDC_DEF_REPLYALL_SETTING, c)) { c->Insert("To"); c->Insert("Cc"); c->Insert("Bcc"); } else LAssert(!"GetViewById failed."); if (GetViewById(IDC_EDIT_CONTROL, c)) { c->Insert("text/plain"); c->Insert("text/html (experimental)"); } else LAssert(!"GetViewById failed."); LVariant SizeAdj; if (!App->GetOptions()->GetValue(OPT_UiFontSize, SizeAdj)) { App->GetOptions()->SetValue(OPT_UiFontSize, SizeAdj = 2); } if (GetViewById(IDC_UI_FNT_SIZE, c)) { c->Insert("-2pt"); c->Insert("-1pt"); c->Insert(AutoInBrackets); c->Insert("+1pt"); c->Insert("+2pt"); } else LAssert(!"IDC_UI_FNT_SIZE missing."); if (GetViewById(IDC_UI_LANG, UiLang)) { LResources *Res = LgiGetResObj(); if (Res && Res->GetLanguages()) { LArray *InLangs = Res->GetLanguages(); for (unsigned n=0; nLength(); n++) { LLanguage *Lang = LFindLang((*InLangs)[n]); if (Lang) { Langs.Insert(Lang); } } Langs.Sort(LangCompare); int n = 0; LVariant LangId; App->GetOptions()->GetValue(OPT_UiLanguage, LangId); if (LangId.Str()) { for (auto l: Langs) { char *LocalName = Res->LanguageNames.Find(l->Id); if (LocalName && _stricmp(l->Id, "en")) { char Txt[256]; sprintf_s(Txt, sizeof(Txt), "%s (%s)", LocalName, l->Name); UiLang->Insert(Txt); } else { UiLang->Insert(l->Name); } if (_stricmp(l->Id, LangId.Str()) == 0) { UiLang->Value(n); } n++; } } } } else LAssert(!"IDC_UI_LANG missing."); if (GetViewById(IDC_START_WEEK, c)) { for (int i=0; iInsert(LDateTime::WeekdaysLong[i]); } else LAssert(!"IDC_START_WEEK missing."); if (GetViewById(IDC_DATE_FORMAT, c)) { c->Insert(AutoInBrackets); char s[256]; const char *sDay = LLoadString(IDS_DAY); const char *sMonth = LLoadString(IDS_MONTH); const char *sYear = LLoadString(IDS_YEAR); sprintf_s(s, sizeof(s), "%s/%s/%s 12h", sDay, sMonth, sYear); c->Insert(s); sprintf_s(s, sizeof(s), "%s/%s/%s 12h", sMonth, sDay, sYear); c->Insert(s); sprintf_s(s, sizeof(s), "%s/%s/%s 12h", sYear, sMonth, sDay); c->Insert(s); sprintf_s(s, sizeof(s), "%s/%s/%s 24h", sDay, sMonth, sYear); c->Insert(s); sprintf_s(s, sizeof(s), "%s/%s/%s 24h", sMonth, sDay, sYear); c->Insert(s); sprintf_s(s, sizeof(s), "%s/%s/%s 24h", sYear, sMonth, sDay); c->Insert(s); } else LAssert(!"IDC_DATE_FORMAT missing."); EditorFont.Serialize(App->GetOptions(), OPT_EditorFont, false); HtmlFont.Serialize(App->GetOptions(), OPT_HtmlFont, false); UpdateFontDescription(); LNotification note(LNotifyValueChanged); OnNotify(FindControl(IDC_REGISTER_CLIENT), note); OnNotify(FindControl(IDC_SOCKS5), note); } OptionsDlg::~OptionsDlg() { LEventSinkMap::Dispatch.RemoveSink(this); DeleteObj(d); } void OptionsDlg::UpdateDefaultSendAccounts() { LCombo *c; if (GetViewById(IDC_DEF_SEND, c)) { bool ResetDefault = false; int64 CurIdx = c->Value(); while (c->Delete((size_t)0)); int FirstValid = -1; int i = 0; for (auto a: *App->GetAccounts()) { LVariant Server = a->Send.Server(); int Disabled = a->Send.Disabled(); if (Server.Str() && !Disabled) { LVariant v = a->Send.Name(); c->Insert(v.Str()); if (FirstValid < 0) FirstValid = i; } else { c->Insert("----"); if (i == CurIdx) ResetDefault = true; } i++; } if (ResetDefault) { if (FirstValid >= 0) c->Value(FirstValid); } else c->Value(CurIdx); } } void OptionsDlg::OnAccountEnable(ScribeAccount *Acc, bool Enable) { Acc->Send.Disabled(!Enable); UpdateDefaultSendAccounts(); } bool OptionsDlg::PasswordCtrlValue(int CtrlId, char *Option, bool ToWindow) { bool Status = false; LViewI *w = FindControl(CtrlId); LVariant v; if (w && Option && App->GetOptions()) { App->GetOptions()->GetValue(Option, v); if (ToWindow) { if (v.Str()) { // option -> window // Status = w->Name(v); } } else { // window -> option const char *s = w->Name(); if (ValidStr(s)) { // user has modified the string // encrypt and store GPassword p; p.Set(s); p.Serialize(App->GetOptions(), Option, true); Status = true; } } } return Status; } void RemoveCtrl(LView *p, int i) { LViewI *v = p->FindControl(i); DeleteObj(v); } void OptionsDlg::OnCreate() { TabDialog::OnCreate(); LList *ACtrl; if (GetViewById(IDC_ACCOUNTS, ACtrl)) { ACtrl->Select(ACtrl->ItemAt(0)); } } void OptionsDlg::UpdateFontDescription() { char Str[256] = ""; if (EditorFont.GetDescription(Str, sizeof(Str))) { SetCtrlName(IDC_FONT, Str); } if (HtmlFont.GetDescription(Str, sizeof(Str))) { SetCtrlName(IDC_HTML_FONT, Str); } } void OptionsDlg::WriteNativeText(LFile &f, char *t) { #ifdef WIN32 for (char *s=t; *s; s++) { if (*s == '\n') { f.Write("\r\n", 2); } else { f << *s; } } #else f.Write(t, (int)strlen(t)); #endif } int OptionsDlg::OnNotify(LViewI *Ctrl, LNotification n) { if (!Ctrl) return 0; switch (Ctrl->GetId()) { case IDC_BROWSE_THEMES: { auto pos = Ctrl->GetPos(); LPoint pt(0, pos.Y()); Ctrl->PointToScreen(pt); LSubMenu sub; auto Paths = ScribeThemePaths(); int n = 1; for (auto p: Paths) sub.AppendItem(p, n++, true); auto res = sub.Float(Ctrl->GetWindow(), pt.x, pt.y, LSubMenu::BtnLeft); if (res) { auto browse = Paths[res-1]; FileDev->CreateFolder(browse, true); LBrowseToFile(browse); } break; } case IDC_ADVANCED: { if (n.Type == IDC_SPELL_LANG) { // If the user changes the language we have to clear the dictionary LControlTree *Ct; if (GetViewById(IDC_ADVANCED, Ct)) { LControlTree::Item *ci; if ((ci = Ct->Find(OPT_SpellCheckDictionary))) { LVariant v; ci->SetValue(v); } } } break; } case IDC_REGISTER_CLIENT: { if (Ctrl->Value()) { SetCtrlEnabled(IDC_CHECK_DEFAULT, true); } else { SetCtrlEnabled(IDC_CHECK_DEFAULT, false); SetCtrlValue(IDC_CHECK_DEFAULT, false); } break; } case IDC_LAUNCH_HELP: { char Path[256] = "install.html"; switch (GetCtrlValue(IDC_TAB)) { case 0: // identity strcat_s(Path, sizeof(Path), "#id"); break; case 1: // accounts strcat_s(Path, sizeof(Path), "#accounts"); break; case 2: // general strcat_s(Path, sizeof(Path), "#general"); break; case 3: // connection strcat_s(Path, sizeof(Path), "#connect"); break; case 4: // appearence strcat_s(Path, sizeof(Path), "#appear"); break; case 5: // other strcat_s(Path, sizeof(Path), "#other"); break; } App->LaunchHelp(Path); break; } case IDC_SOCKS5: { bool SocksOn = Ctrl->Value() != 0; SetCtrlEnabled(IDC_SOCKS5_SERVER, SocksOn); SetCtrlEnabled(IDC_SOCKS5_USER, SocksOn); SetCtrlEnabled(IDC_SOCKS5_PASSWORD, SocksOn); break; } case IDC_EDIT_REPLY: { new UtfEditor(App, OPT_TextReplyFormat, OPT_HtmlReplyFormat, LLoadString(IDS_REPLY)); break; } case IDC_RESET_REPLY: { LVariant v; App->GetOptions()->SetValue(OPT_TextReplyFormat, v = DefaultTextReplyTemplate); App->GetOptions()->SetValue(OPT_HtmlReplyFormat, v = DefaultHtmlReplyTemplate); break; } case IDC_EDIT_FORWARD: { new UtfEditor(App, OPT_TextForwardFormat, OPT_HtmlForwardFormat, LLoadString(IDS_FORWARD)); break; } case IDC_RESET_FORWARD: { LVariant v; App->GetOptions()->SetValue(OPT_TextForwardFormat, v = DefaultTextReplyTemplate); App->GetOptions()->SetValue(OPT_HtmlForwardFormat, v = DefaultHtmlReplyTemplate); break; } case IDC_ACCOUNTS: { if (n.Type == LNotifyItemInsert || n.Type == LNotifyItemDelete) { UpdateDefaultSendAccounts(); } break; } case IDC_ADD: { - if (App->GetAccountSettingsAccess(this, ScribeWriteAccess)) + App->GetAccountSettingsAccess(this, ScribeWriteAccess, [&](auto Allow) { + if (!Allow) + return; List *AList = App->GetAccounts(); LList *ACtrl; if (AList && GetViewById(IDC_ACCOUNTS, ACtrl)) { int Items = (int)AList->Length(); LAutoPtr a(new ScribeAccount(App, Items)); if (a) { a->Create(); // Open the UI - if (a->InitUI(this)) + a->InitUI(this, 0, [&](auto status) { - ACtrl->Insert(new AccountItem(this, a)); - AList->Insert(a.Release()); - UpdateDefaultSendAccounts(); - } + if (status) + { + ACtrl->Insert(new AccountItem(this, a)); + AList->Insert(a.Release()); + UpdateDefaultSendAccounts(); + } + }); } } - } + }); break; } case IDC_DELETE: { - if (App->GetAccountSettingsAccess(this, ScribeWriteAccess)) + App->GetAccountSettingsAccess(this, ScribeWriteAccess, [&](auto Allow) { + if (!Allow) + return; List Sel; LList *ACtrl; if (GetViewById(IDC_ACCOUNTS, ACtrl) && ACtrl->GetSelection(Sel)) { List *AList = App->GetAccounts(); // For all selected for (auto i: Sel) { ScribeAccount *a = i->GetAccount(); if (a->IsOnline()) { LgiMsg( this, "You can't delete the account while it's still active.", AppName); } else { if (AList) { // Delete the applications reference AList->Delete(a); } a->Delete(); delete a; // delete actual account object } } { // Reindex remaining items so their are no gaps int i=0; for (auto a: *AList) { a->ReIndex(i++); } } } - } + }); break; } case IDC_UP: case IDC_DOWN: { - if (!App->GetAccountSettingsAccess(this, ScribeWriteAccess)) - break; + App->GetAccountSettingsAccess(this, ScribeWriteAccess, [&](auto Allow) + { + if (!Allow) + return; - List a; - LList *ACtrl; - if (!GetViewById(IDC_ACCOUNTS, ACtrl) || !ACtrl->GetAll(a)) - break; + List a; + LList *ACtrl; + if (!GetViewById(IDC_ACCOUNTS, ACtrl) || !ACtrl->GetAll(a)) + return; - AccountItem *Sel = NULL; - for (int i=0; iSelect()) + AccountItem *Sel = NULL; + for (int i=0; iSelect()) + { + Sel = a[i]; + break; + } } - } - if (!Sel) - break; + if (!Sel) + return; - bool IsUp = Ctrl->GetId() == IDC_UP; - int Idx = ACtrl->IndexOf(Sel); - int NewIdx = IsUp ? Idx - 1 : Idx + 1; - if (NewIdx < 0) - break; - ACtrl->Remove(Sel); - ACtrl->Insert(Sel, NewIdx); - Sel->Select(true); + bool IsUp = Ctrl->GetId() == IDC_UP; + int Idx = ACtrl->IndexOf(Sel); + int NewIdx = IsUp ? Idx - 1 : Idx + 1; + if (NewIdx < 0) + return; + ACtrl->Remove(Sel); + ACtrl->Insert(Sel, NewIdx); + Sel->Select(true); - // printf("%s:%i - index = %i -> %i, sel=%p\n", _FL, Idx, NewIdx, Sel->Account); - - ACtrl->GetAll(a); - for (int i=0; iGetAccount(); - if (Acc) + ACtrl->GetAll(a); + for (int i=0; iIdentity.Sort(i+1); - // printf("%s:%i - %p = %i\n", _FL, Acc, i+1); + ScribeAccount *Acc = a[i]->GetAccount(); + if (Acc) + Acc->Identity.Sort(i+1); } - } - ACtrl->Sort(AccountCmp); - - /* - for (unsigned i=0; iGetAccount(); - if (Acc) - { - printf("%s:%i - %p = %i\n", _FL, Acc, Acc->Identity.Sort()); - } - } - */ + ACtrl->Sort(AccountCmp); + }); break; } case IDC_PROPERTIES: { - if (App->GetAccountSettingsAccess(this, ScribeReadAccess)) + App->GetAccountSettingsAccess(this, ScribeReadAccess, [&](auto Allow) { + if (!Allow) + return; + List Sel; LList *ACtrl; if (GetViewById(IDC_ACCOUNTS, ACtrl) && ACtrl->GetSelection(Sel)) { AccountItem *i = dynamic_cast(Sel[0]); if (i) { - i->GetAccount()->InitUI(this); - i->Update(); - UpdateDefaultSendAccounts(); + i->GetAccount()->InitUI(this, 0, [&](auto status) + { + if (status) + { + i->Update(); + UpdateDefaultSendAccounts(); + } + }); } } else { LgiMsg(this, "No account selected.", AppName, MB_OK); } - } + }); break; } case IDC_SET_SOUND: { - LFileSelect Select; - - Select.Parent(this); - Select.Type("Sound", "*.wav"); - - if (Select.Open()) + auto Select = new LFileSelect(this); + Select->Type("Sound", "*.wav"); + Select->Open([this](auto dlg, auto status) { - LEdit *LogFile; - if (GetViewById(IDC_NEW_MAIL_SOUND, LogFile)) + if (status) { - LogFile->Name(Select.Name()); + LEdit *LogFile; + if (GetViewById(IDC_NEW_MAIL_SOUND, LogFile)) + LogFile->Name(dlg->Name()); } - } + delete dlg; + }); break; } case IDC_SET_FONT: { - if (EditorFont.DoUI(this)) + EditorFont.DoUI(this, [&](auto fontType) { UpdateFontDescription(); - } + }); break; } case IDC_SET_HTML_FONT: { - if (HtmlFont.DoUI(this)) + HtmlFont.DoUI(this, [&](auto fontType) { UpdateFontDescription(); - } + }); break; } case IDC_CONFIGURE_REMOTE_CONTENT: { - RemoteContentDlg Dlg(this, App); - Dlg.DoModal(); + auto Dlg = new RemoteContentDlg(this, App); + Dlg->DoModal(NULL); break; } case IDOK: { LOptionsFile *Opts = App->GetOptions(); // Font EditorFont.Serialize(Opts, OPT_EditorFont, true); HtmlFont.Serialize(Opts, OPT_HtmlFont, true); if (UiLang) { LLanguage *Lang = Langs[(int)UiLang->Value()]; if (Lang) { LVariant v; Opts->SetValue(OPT_UiLanguage, v = Lang->Id); } else { Opts->DeleteValue(OPT_UiLanguage); } } LList *ACtrl; if (GetViewById(IDC_ACCOUNTS, ACtrl)) { List a; ACtrl->GetAll(a); for (auto ai: a) { ai->Account->Send.Disabled(ai->Disable->Value() != 0); } } LVariant Cur[2], New[2], v; Opts->GetValue(OPT_SpellCheckLanguage, Cur[0]); Opts->GetValue(OPT_SpellCheckDictionary, Cur[1]); Convert(App->GetOptions(), this, false); Opts->GetValue(OPT_SpellCheckLanguage, New[0]); Opts->GetValue(OPT_SpellCheckDictionary, New[1]); if (Cur[0] != New[0] || Cur[1] != New[1]) { LgiTrace("%s:%i - OnSpellerSettingChange: Lang(%s), Dict(%s)\n", _FL, New[0].Str(), New[1].Str()); App->OnSpellerSettingChange(); } if (Opts->GetValue(OPT_SizeInKiB, v)) OptionSizeInKiB = v.CastInt32() != 0; if (Opts->GetValue(OPT_RelativeDates, v)) ShowRelativeDates = v.CastInt32() != 0; CalendarViewWnd::OnOptionsChange(); EndModal(1); break; } case IDCANCEL: { EndModal(0); break; } } return 0; } LMessage::Result OptionsDlg::OnEvent(LMessage *m) { switch (m->Msg()) { case M_ENUMERATE_LANGUAGES: { LControlTree *Ct; if (!GetViewById(IDC_ADVANCED, Ct)) { LgiTrace("%s:%i - No control tree.\n", _FL); break; } LAutoPtr< LArray > Langs((LArray*)m->A()); if (!Langs) { LgiTrace("%s:%i - Error: No dictionary list.\n", _FL); break; } LControlTree::Item *ci; if (!(ci = Ct->Find(OPT_SpellCheckLanguage))) { LgiTrace("%s:%i - Error: No OPT_SpellCheckLanguage leaf.\n", _FL); break; } LAutoPtr Enum(new LControlTree::Item::EnumArr); if (!Enum) { LgiTrace("%s:%i - Error: alloc failed.\n", _FL); break; } for (unsigned i=0; iLength(); i++) { LSpellCheck::LanguageId &p = (*Langs)[i]; LControlTree::EnumValue &e = Enum->New(); if (p.EnglishName && p.NativeName) { LString s; s.Printf("%s / %s", p.NativeName.Get(), p.EnglishName.Get()); e.Name = s; } else if (p.EnglishName) e.Name = p.EnglishName; else if (p.LangCode) e.Name = p.LangCode; else LAssert(!"Null object."); e.Value = p.EnglishName ? p.EnglishName : p.LangCode; } ci->SetEnum(Enum); break; } case M_ENUMERATE_DICTIONARIES: { LControlTree *Ct; if (!GetViewById(IDC_ADVANCED, Ct)) break; LAutoPtr< LArray > Dicts((LArray*)m->A()); if (!Dicts) { LgiTrace("%s:%i - Error: No dictionary list.\n", _FL); break; } LControlTree::Item *ci; if (!(ci = Ct->Find(OPT_SpellCheckDictionary))) { LgiTrace("%s:%i - Error: No OPT_SpellCheckDictionary leaf.\n", _FL); break; } LAutoPtr Enum(new LControlTree::Item::EnumArr); if (!Enum) { LgiTrace("%s:%i - Error: alloc failed.\n", _FL); break; } for (unsigned i=0; iLength(); i++) { LSpellCheck::DictionaryId &s = (*Dicts)[i]; LControlTree::EnumValue &e = Enum->New(); e.Name = s.Dict; e.Value = s.Dict; } ci->SetEnum(Enum); break; } } return TabDialog::OnEvent(m); } ///////////////////////////////////////////////////////////////////////////// UtfEditor::UtfEditor(ScribeWnd *app, const char *txtopt, const char *htmlopt, const char *desc) { Txt = NULL; Html = NULL; TabText = TabHtml = 0; LRect r(0, 0, 600, 500); SetPos(r); MoveToCenter(); App = app; TextOpt = txtopt; HtmlOpt = htmlopt; Cancel = Ok = 0; if (Attach(0)) { Children.Insert(new LTextLabel(-1, 5, 5, -1, -1, desc)); Children.Insert(Tabs = new LTabView(200, 0, 30, 300, 300)); if (Tabs) { Tabs->SetPourLargest(false); Tabs->SetPourChildren(true); if ((TabText = Tabs->Append("Text"))) { TabText->Append(Txt = new LTextView3(IDC_TEXT_VIEW, 0, 0, 100, 100)); Txt->SetPourLargest(true); Txt->Sunken(true); } if ((TabHtml = Tabs->Append("Html"))) { TabHtml->Append(Html = new LRichTextEdit(IDC_HTML_VIEW, 0, 0, 100, 100)); Html->SetPourLargest(true); Html->Sunken(true); } } Children.Insert(Ok = new LButton(IDOK, 5, 5, 75, 20, LLoadString(IDS_OK))); Children.Insert(Cancel = new LButton(IDCANCEL, 75, 5, 75, 20, LLoadString(IDS_CANCEL))); LVariant s; if (Txt && App->GetOptions()->GetValue(TextOpt, s)) Txt->Name(s.Str()); if (Html && App->GetOptions()->GetValue(HtmlOpt, s)) Html->Name(s.Str()); AttachChildren(); OnPosChange(); Visible(true); } } void UtfEditor::OnPosChange() { if (Cancel) { LRect c = GetClient(); LRect r = Cancel->GetPos(); r.Offset(c.X() - Cancel->X() - 5 - r.x1, 0); Cancel->SetPos(r); r = Ok->GetPos(); r.Offset(Cancel->GetPos().x1 - Ok->X() - 5 - r.x1, 0); Ok->SetPos(r); r = Tabs->GetPos(); r.x2 = c.x2; r.y2 = c.y2; Tabs->SetPos(r); } } int UtfEditor::OnNotify(LViewI *c, LNotification n) { switch (c->GetId()) { case IDOK: { LVariant v; LOptionsFile *Opts = App->GetOptions(); if (Txt) Opts->SetValue(TextOpt, v = Txt->Name()); if (Html) { const char *n = Html->Name(); Opts->SetValue(HtmlOpt, v = n); } // fall thru } case IDCANCEL: { Quit(); break; } } return 0; } diff --git a/Code/PrintContext.cpp b/Code/PrintContext.cpp --- a/Code/PrintContext.cpp +++ b/Code/PrintContext.cpp @@ -1,146 +1,160 @@ #include "lgi/common/Lgi.h" #include "Scribe.h" #include "lgi/common/Printer.h" #include "lgi/common/DrawListSurface.h" #include "ScribePageSetup.h" #include "PrintPreview.h" #include "PrintContext.h" ScribePrintContext::ScribePrintContext(ScribeWnd *app, Thing *object) : FontType("Courier New", 9) { App = app; Dpi = LScreenDpi(); Object = object; MarginPx.ZOff(0, 0); } ScribePrintContext::~ScribePrintContext() { Pages.DeleteObjects(); } LDrawListSurface *ScribePrintContext::NewPage() { LDrawListSurface *dls = new LDrawListSurface(PrintDC); if (dls) Pages.Add(dls); return dls; } -int ScribePrintContext::OnBeginPrint(LPrintDC *pdc) +#define PrintStatus(val) \ + { if (callback) callback(val); return; } + +void ScribePrintContext::OnBeginPrint(LPrintDC *pdc, std::function callback) { pDC = pdc; PrintDC = pdc; Dpi = PrintDC->GetDpi(); // read any options out.. LVariant v; #define GetMargin(opt, var) \ var(LCss::Len(LenCm, App->GetOptions()->GetValue(opt, v) ? (float)v.CastDouble() : 1.0f)); GetMargin(OPT_MarginX1, MarginLeft); GetMargin(OPT_MarginY1, MarginTop); GetMargin(OPT_MarginX2, MarginRight); GetMargin(OPT_MarginY2, MarginBottom); FontType.Serialize(App->GetOptions(), OPT_PrintFont, false); AppFont.Reset(FontType.Create(pdc)); Mail *mail = Object->IsMail(); if (mail && TestFlag(mail->GetFlags(), MAIL_FIXED_WIDTH_FONT)) FontType.GetSystemFont("Fixed"); MailFont.Reset(FontType.Create(pdc)); MailFont->Transparent(true); MailFont->Colour(L_TEXT, L_WORKSPACE); MailFont->Create(0, 0, pdc); LDisplayString dsSpace(MailFont, " "); MailFont->TabSize(dsSpace.X() * 8); AppFont->Transparent(true); AppFont->Colour(L_TEXT, L_WORKSPACE); AppFont->Create(0, 0, pdc); LDisplayString dsSpace2(AppFont, " "); AppFont->TabSize(dsSpace2.X() * 8); // int Line = AppFont->GetHeight(); CurrentY = MarginPx.x1 = MarginLeft().ToPx(PrintDC->X(), AppFont, Dpi.x); MarginPx.y1 = MarginTop().ToPx(PrintDC->Y(), AppFont, Dpi.y); MarginPx.x2 = pDC->X() - MarginRight().ToPx(PrintDC->X(), AppFont, Dpi.x); MarginPx.y2 = pDC->Y() - MarginBottom().ToPx(PrintDC->Y(), AppFont, Dpi.y); LVariant DefAlt; if (!App->GetOptions()->GetValue(OPT_DefaultAlternative, DefAlt)) DefAlt = 1; // HTML if (!AppFont || !MailFont) { LgiTrace("%s:%i - Error creating fonts for printing\n", _FL); - return OnBeginPrintError; + PrintStatus(OnBeginPrintError); } // There is always at least one page... Pages.DeleteObjects(); NewPage(); CurrentY = MarginPx.y1; // Check for HTML or text printing: bool HtmlPrinting = false; if (mail) { LAutoString HtmlContent(NewStr(mail->GetHtml())); HtmlPrinting = DefAlt.CastInt32() != 0 && ValidStr(HtmlContent); } if (HtmlPrinting) { - PrintPreview Dlg(App, mail, pdc); - if (!Dlg.DoModal()) - return OnBeginPrintCancel; + auto Dlg = new PrintPreview(App, mail, pdc); + Dlg->DoModal([this, Dlg, callback, mail](auto dlg, auto id) + { + if (!id) + { + delete dlg; + PrintStatus(OnBeginPrintCancel); + } - PageRanges.Reset(new LPrintPageRanges(Dlg.GetPageRanges())); - HtmlImg = Dlg.ReleaseImage(); - mail->OnPrintHeaders(*this); - mail->OnPrintHtml(*this, *PageRanges, HtmlImg); + PageRanges.Reset(new LPrintPageRanges(Dlg->GetPageRanges())); + HtmlImg = Dlg->ReleaseImage(); + mail->OnPrintHeaders(*this); + auto Pages = mail->OnPrintHtml(*this, *PageRanges, HtmlImg); + + delete dlg; + PrintStatus(Pages); + }); + + return; // Don't call callback yet, the UI lambda will do it. } else { PageRanges.Reset(new LPrintPageRanges(NULL)); Object->OnPrintHeaders(*this); Object->OnPrintText(*this, *PageRanges); } - return (int)Pages.Length(); + PrintStatus((int)Pages.Length()); } bool ScribePrintContext::OnPrintPage(LPrintDC *pdc, int PageIndex) { pDC = pdc; if (!pDC || PageIndex < 0 || PageIndex >= (int)Pages.Length()) return false; return Pages[PageIndex]->OnPaint(pDC); } LDisplayString *ScribePrintContext::Text(const char *str, int x) { LDrawListSurface *p = Pages.Last(); LFont *f = p->GetFont(); if (!f) p->SetFont(f = AppFont); if (CurrentY + f->GetHeight() >= MarginPx.Y()) { p = NewPage(); if (!p) return NULL; p->SetFont(f); CurrentY = MarginPx.y1; } LDisplayString *ds = p->Text(MarginPx.x1 + (x >= 0 ? x : 0), CurrentY, str); if (ds) CurrentY += ds->Y(); return ds; } diff --git a/Code/PrintContext.h b/Code/PrintContext.h --- a/Code/PrintContext.h +++ b/Code/PrintContext.h @@ -1,41 +1,38 @@ #ifndef _SCRIBE_PRINT_CONTEXT_H_ #define _SCRIBE_PRINT_CONTEXT_H_ #include "lgi/common/Printer.h" #include "lgi/common/DrawListSurface.h" struct ScribePrintContext : public LCss, public LPrintEvents { public: - constexpr static int OnBeginPrintError = -1; - constexpr static int OnBeginPrintCancel = 0; - ScribeWnd *App = NULL; LSurface *pDC = NULL; LPrintDC *PrintDC = NULL; LPoint Dpi; Thing *Object = NULL; LFontType FontType; LRect MarginPx; LAutoPtr AppFont, MailFont; int CurrentY = 0; LColour Fore = LColour::Black; LArray Pages; LAutoPtr HtmlImg; LAutoPtr PageRanges; ScribePrintContext(ScribeWnd *app, Thing *object); ~ScribePrintContext(); // Api LDrawListSurface *NewPage(); LDisplayString *Text(const char *str, int x = -1); // LPrintEvents impl - int OnBeginPrint(LPrintDC *pdc); + void OnBeginPrint(LPrintDC *pdc, std::function callback); bool OnPrintPage(LPrintDC *pdc, int PageIndex); LPrintPageRanges *GetPageRanges() { return PageRanges; } }; #endif \ No newline at end of file diff --git a/Code/PrintPreview.cpp b/Code/PrintPreview.cpp --- a/Code/PrintPreview.cpp +++ b/Code/PrintPreview.cpp @@ -1,259 +1,259 @@ #include "Scribe.h" #include "PrintPreview.h" #include "resdefs.h" #include "lgi/common/ZoomView.h" #include "lgi/common/TableLayout.h" #include "lgi/common/Slider.h" #include "lgi/common/FileSelect.h" #define DEFAULT_PAGE_SIZE 1000 struct PrintPreviewPriv { ScribeWnd *App = NULL; LPrintDC *PrintDC = NULL; Mail *m = NULL; PrintPreview *Wnd = NULL; LZoomView *Zoom = NULL; LAutoPtr Mem; LAutoPtr Ctrl; bool IsLoaded = false; int HeaderContentHeight = 0; LSlider *Slider = NULL; bool InUpdate = false; LString Ranges; PrintPreviewPriv(PrintPreview *w) { Wnd = w; if (Ctrl.Reset(new Html1::LHtml(IDC_HTML_IMAGES, 0, 0, DEFAULT_PAGE_SIZE, 4000))) { Ctrl->SetMaxPaintTime(5000); Ctrl->SetLoadImages(true); } } bool Render() { // Create a HTML control if (!Zoom) Wnd->GetViewById(IDC_PREVIEW, Zoom); if (!Ctrl || !Zoom) { LgiTrace("%s:%i - Failed to create controls.\n", _FL); return false; } // Set width int Width = (int)Wnd->GetCtrlValue(IDC_WIDTH); LRect p(0, 0, Width-1, 100000); Ctrl->SetPos(p); // Get the size of the HTML layout LPoint Size = Ctrl->Layout(); if (Size.x <= 0 || Size.y <= 0) { LgiTrace("%s:%i - No content to print.\n", _FL); return false; } // Create a memory context big enough for all the content if (!Mem || Mem->X() < Width || Mem->Y() < Size.y) { Zoom->SetSurface(NULL, true); if (!Mem.Reset(new LMemDC(Width, Size.y, System24BitColourSpace))) { LgiTrace("%s:%i - Can't create memory bitmap context (%ix%i).\n", _FL, Width, Size.y); return false; } } // Clear the page to white Mem->Colour(LColour::White); Mem->Rectangle(); // Ask the HTML control to paint itself into the memory context Ctrl->OnPaint(Mem); if (Ctrl->GetMaxPaintTimeout()) { LAssert(!"Max paint time reached."); } Zoom->SetDefaultZoomMode(LZoomView::ZoomFitX); Zoom->SetSurface(Mem, false); return true; } }; PrintPreview::PrintPreview(ScribeWnd *App, Mail *m, LPrintDC *PrintDC) { d = new PrintPreviewPriv(this); d->App = App; d->m = m; d->PrintDC = PrintDC; if (!LoadFromResource(IDD_PRINT_PREVIEW)) { LAssert(!"Resource missing."); return; } ThingUi *Ui = m->GetUI(); MoveSameScreen(Ui ? (LWindow*)Ui : App); SetCtrlValue(IDC_WIDTH, DEFAULT_PAGE_SIZE); SetCtrlValue(IDC_SLIDE, DEFAULT_PAGE_SIZE); SetCtrlName(IDC_STATUS, LLoadString(IDS_LOADING)); SetCtrlValue(IDC_PAGE_ALL, true); if (GetViewById(IDC_SLIDE, d->Slider)) d->Slider->SetRange(LRange(500, 1500)); // Give it the HTML to parse d->Ctrl->SetNotify(this); d->Ctrl->Visible(false); AddView(d->Ctrl); LAutoString HtmlContent(NewStr(m->GetHtml())); d->Ctrl->SetEnv(m); d->Ctrl->SetCharset(m->GetHtmlCharset()); d->Ctrl->Name(HtmlContent); d->Render(); SetPulse(300); } PrintPreview::~PrintPreview() { delete d; } void PrintPreview::OnPulse() { if (d->Ctrl) { // This is basically a hack to get images working in the HTML control. // As it's not connected to a View heirachy it can't send messages to // itself like normal. LMessage m(M_JOBS_LOADED); d->Ctrl->OnEvent(&m); } } LSurface *PrintPreview::GetImage() { return d->Mem; } LAutoPtr PrintPreview::ReleaseImage() { return d->Mem; } LString PrintPreview::GetPageRanges() { return d->Ranges; } void PrintPreview::OnPosChange() { auto it = Children.begin(); LLayout *t = dynamic_cast((LViewI*)it); if (t) { LRect r = GetClient(); r.Inset(LTableLayout::CellSpacing, LTableLayout::CellSpacing); t->SetPos(r); } } int PrintPreview::OnNotify(LViewI *Ctrl, LNotification n) { switch (Ctrl->GetId()) { case IDC_PAGE_RANGES: { bool HasContent = Strlen(Ctrl->Name()) > 0; SetCtrlValue(IDC_PAGE_PARTIAL, HasContent); SetCtrlValue(IDC_PAGE_ALL, !HasContent); break; } case IDC_SLIDE: { if (!d->InUpdate) { d->InUpdate = true; SetCtrlValue(IDC_WIDTH, Ctrl->Value()); d->InUpdate = false; } break; } case IDC_WIDTH: { if (!d->InUpdate) { d->InUpdate = true; SetCtrlValue(IDC_SLIDE, Ctrl->Value()); d->InUpdate = false; } break; } case IDC_UPDATE: { d->Zoom->SetSurface(NULL, false); d->Mem.Reset(); SetCtrlName(IDC_STATUS, LLoadString(IDS_LOADING)); - LYield(); d->Render(); SetCtrlName(IDC_STATUS, LLoadString(IDS_PREVIEW)); break; } case IDC_SAVE_IMG: { if (d->Mem) { - LFileSelect s; - s.Parent(this); - s.Type("JPEG", "*.jpg"); + auto s = new LFileSelect(this); + s->Type("JPEG", "*.jpg"); char p[MAX_PATH_LEN]; LGetSystemPath(LSP_USER_DOWNLOADS, p, sizeof(p)); LMakePath(p, sizeof(p), p, "print-preview.jpg"); - s.Name(p); + s->Name(p); - if (s.Save()) + s->Save([&](auto dlg, auto status) { - GdcD->Save(s.Name(), d->Mem); - } + if (status) + GdcD->Save(s->Name(), d->Mem); + delete dlg; + }); } else LgiMsg(this, "No image to save.", LLoadString(IDS_ERROR)); break; } case IDOK: { auto Partial = GetCtrlValue(IDC_PAGE_PARTIAL); d->Ranges = Partial ? GetCtrlName(IDC_PAGE_RANGES) : NULL; EndModal(1); break; } case IDCANCEL: { EndModal(0); break; } case IDC_HTML_IMAGES: { if (n.Type == LNotifyDocLoaded) { // Html control finished loading... d->IsLoaded = true; d->Render(); SetCtrlEnabled(IDOK, true); SetCtrlName(IDC_STATUS, LLoadString(IDS_PREVIEW)); } break; } } return 0; } diff --git a/Code/RemoteCalendarSource.cpp b/Code/RemoteCalendarSource.cpp --- a/Code/RemoteCalendarSource.cpp +++ b/Code/RemoteCalendarSource.cpp @@ -1,422 +1,426 @@ #include "Scribe.h" #include "Calendar.h" #include "CalendarView.h" #include "resdefs.h" #include "lgi/common/EventTargetThread.h" #include "lgi/common/Http.h" #include "lgi/common/vCard-vCal.h" enum Msgs { M_LOAD_URI = M_USER + 1000, M_LOADED }; struct RemoteCalendarSourcePriv : public LEventTargetThread { RemoteCalendarSource *Source; LString Uri; LString Name; bool Error = false; bool Loaded = false; LArray Events; RemoteCalendarSourcePriv(RemoteCalendarSource *src) : Source(src), LEventTargetThread("RemoteCalendarSourcePriv") { } ~RemoteCalendarSourcePriv() { for (auto c: Events) c->DecRef(); } void Post(int m, LMessage::Param a = 0, LMessage::Param b = 0) { auto app = Source->GetApp(); app->PostEvent( M_CALENDAR_SOURCE_EVENT, (LMessage::Param)Source, (LMessage::Param)new LMessage(m, a, b)); } LMessage::Result OnEvent(LMessage *Msg) { switch (Msg->Msg()) { case M_LOAD_URI: { LString err; LStringPipe out; auto r = LgiGetUri(this, &out, &err, Uri); if (r) { /* auto s = out.NewGStr(); LgiTrace("s='%s'\n", s.Get()); */ VCal imp; while (true) { Calendar *c = new Calendar(Source->GetApp()); // LgiTrace("outsize=" LPrintfInt64 "\n", out.GetSize()); if (imp.Import(c->GetObject(), &out)) Events.Add(c); else { c->DecRef(); break; } } Post(M_LOADED); } break; } } return 0; } }; RemoteCalendarSource::RemoteCalendarSource(ScribeWnd *a, const char *id) { d = new RemoteCalendarSourcePriv(this); App = a; Id = id; } RemoteCalendarSource::~RemoteCalendarSource() { DeleteObj(d); } const char *RemoteCalendarSource::GetUri() { return d->Uri; } void RemoteCalendarSource::SetUri(const char *uri) { d->Uri = uri; Write(); OnChange(false); } bool RemoteCalendarSource::Read() { if (!Id) return false; LString k = GetKey(); LXmlTag *t = App->GetOptions()->LockTag(k, _FL); if (!t) return false; char *Col = t->GetAttr("Colour"); if (Col) Colour.Set((uint32_t)atoi64(Col), 32); else Colour.Empty(); d->Uri = t->GetAttr(CalendarSource::OptUri); auto c = Atoi(t->GetAttr(CalendarSource::OptColour)); if (c >= 0) Colour.c32((uint32_t)c); else Colour.Empty(); Display = t->GetAsInt(CalendarSource::OptDisplay); App->GetOptions()->Unlock(); OnChange(false); return true; } bool RemoteCalendarSource::Write() { LVariant v; if (!Id) { LXmlTag *t = App->GetOptions()->LockTag(OPT_CalendarSources, _FL); if (t) { LString Key; for (int i=0; i<100; i++) { Key.Printf("Source-%i", LRand(10000)); if (!t->GetChildTag(Key)) { Id = Key; break; } } App->GetOptions()->Unlock(); } } if (!Id) return false; auto Key = GetKey(); auto t = App->GetOptions()->LockTag(Key, _FL); if (!t) { App->GetOptions()->CreateTag(Key); t = App->GetOptions()->LockTag(Key, _FL); } if (!t) return false; SaveAttr(t, CalendarSource::OptUri, d->Uri); t->SetAttr(CalendarSource::OptColour, (int64_t) Colour.c32()); t->SetAttr(CalendarSource::OptDisplay, Display); t->SetAttr(CalendarSource::OptObject, GetClass()); App->GetOptions()->Unlock(); return true; } bool RemoteCalendarSource::Delete() { LString k = GetKey(); bool r = App->GetOptions()->DeleteTag(k); if (r) App->SaveOptions(); else LAssert(!"Delete failed."); return r; } Calendar *RemoteCalendarSource::NewEvent() { // Can't create remote events... read only feed. return NULL; } bool RemoteCalendarSource::Match(char *Email) { return false; } bool RemoteCalendarSource::GetEvents(LDateTime &StartTs, LDateTime &EndTs, LArray &Events) { if (!Display) return false; if (!d->Loaded) { d->Loaded = true; d->PostEvent(M_LOAD_URI); return true; } LDateTime Start = StartTs; Start.ToUtc(); LDateTime End = EndTs; End.ToUtc(); for (auto c: d->Events) { LDateTime s, e; if (c->GetCalType() == CalEvent && c->GetField(FIELD_CAL_START_UTC, s)) { int Recur = 0; c->GetField(FIELD_CAL_RECUR, Recur); const char *Sub = NULL; c->GetField(FIELD_CAL_SUBJECT, Sub); if (Recur) { LArray Times; if (c->GetTimes(Start, End, Times)) { SetCalendarsSource(c); for (auto &t: Times) { t.src = this; Events.Add(t); } } } else { if (!c->GetField(FIELD_CAL_END_UTC, e)) { e = s; e.AddHours(1); } #if 0 printf("%s: %s > %s, %s < %s\n", Sub, s.Get().Get(), End.Get().Get(), e.Get().Get(), Start.Get().Get()); #endif if (s > End || e < Start) { // Is before/after the range } else { TimePeriod &tp = Events.New(); tp.src = this; tp.c = c; tp.s = s; tp.e = e; tp.ToLocal(); SetCalendarsSource(c); } } } } return false; } void RemoteCalendarSource::EditPath(LView *parent, CalendarView *cv) { - LInput Dlg(parent, d->Uri); - if (!Dlg.DoModal()) - return; - - SetUri(Dlg.GetStr()); - if (cv) - cv->OnContentsChanged(this); + auto Dlg = new LInput(parent, d->Uri); + Dlg->DoModal([this, Dlg, cv](auto dlg, auto id) + { + if (id) + { + SetUri(Dlg->GetStr()); + if (cv) + cv->OnContentsChanged(this); + } + delete dlg; + }); } LColour RemoteCalendarSource::GetColour() { return Colour; } void RemoteCalendarSource::SetColour(LColour c) { Colour = c; } const char *RemoteCalendarSource::GetName() { return d->Name; } void RemoteCalendarSource::OnMouseClick(LMouse &m) { if (m.IsContextMenu()) { } else if (m.Down() && m.Left() && Parent) { int Col = Parent->ColumnAtX(m.x); if (Col == 0) { Display = !Display; Update(); Parent->SendNotify(LNotifyValueChanged); } else if (Col > 0) { SetCreateIn(this); } } } void RemoteCalendarSource::OnPaintColumn(LItem::ItemPaintCtx &Ctx, int i, LItemColumn *c) { if (i == 0) { LRect r = Ctx; Ctx.pDC->Colour(Ctx.Back); for (int i=0; i<4; i++) { Ctx.pDC->Box(&r); r.Inset(1, 1); } Ctx.pDC->Colour(Colour); if (Display) Ctx.pDC->Rectangle(&r); else { Ctx.pDC->Box(&r); r.Inset(1, 1); Ctx.pDC->Colour(Ctx.Back); Ctx.pDC->Rectangle(&r); } } else { bool PathErr = (i == 1 && d->Error); if (PathErr) Ctx.Fore = LColour::Red; LListItem::OnPaintColumn(Ctx, i, c); if (PathErr) { Ctx.pDC->Colour(Ctx.Fore); int Cy = Ctx.y1 + (Ctx.Y() >> 1) + 1; Ctx.pDC->Line(Ctx.x1, Cy, Ctx.x2, Cy); } } } const char *RemoteCalendarSource::GetText(int i) { if (i == 1) return d->Uri; return NULL; } void RemoteCalendarSource::OnFolderDelete(ScribeFolder *f) { } void RemoteCalendarSource::OnPulse() { } void RemoteCalendarSource::OnChange(bool IsDelete) { Update(); if (!GetList()) return; auto w = GetList()->GetWindow(); if (!w) return; CalendarView *cv = NULL; if (!w->GetViewById(IDC_CALENDAR, cv)) return; if (IsDelete) cv->OnSourceDelete(this); else cv->OnContentsChanged(this); } LMessage::Result RemoteCalendarSource::OnEvent(LMessage *Msg) { switch (Msg->Msg()) { case M_LOADED: { OnChange(false); break; } } return 0; } diff --git a/Code/ReplicateDlg.cpp b/Code/ReplicateDlg.cpp --- a/Code/ReplicateDlg.cpp +++ b/Code/ReplicateDlg.cpp @@ -1,1447 +1,1447 @@ #include "Scribe.h" #include "ReplicateDlg.h" #include "lgi/common/Combo.h" #include "lgi/common/List.h" #include "resdefs.h" #include "lgi/common/ListItemCheckBox.h" #include "lgi/common/ProgressDlg.h" #include "lgi/common/LgiRes.h" #define SECONDS * 1000 #define REPLICATE_TIMEOUT (30 SECONDS) #define REPLICATE_TRANS_LENGTH 10 #define MAX_DELAYED_UNITS 10 #define DEBUG_LOGGING 1 enum WorkType { RNull, RCountFinish, RCountSource, // Uses 'Folder1' RDeleteFolder, // Uses 'Folder1' RCopySubFolder, // Uses 'Folder2' RCopyFolder, // Uses 'Folder2' RCreateFolder, // Uses 'Folder2' RCopyData, // Uses 'Data' RSaveData, // Uses 'Data' RReloadFolders, // Tells 'App' to load folders.. ROpenMailStore, // Uses 'Open' RCopyStore, // Expects 'SrcStore' and 'DstStore' as arguments REndTransaction, }; const char *ToString(WorkType t) { switch (t) { case RNull: return "RNull"; case RCountSource: return "RCountSource"; case RCountFinish: return "RCountFinish"; case RCopyFolder: return "RCopyFolder"; case RCopySubFolder: return "RCopySubFolder"; case RCreateFolder: return "RCreateFolder"; case RCopyData: return "RCopyData"; case RSaveData: return "RSaveData"; case RReloadFolders: return "RReloadFolders"; case ROpenMailStore: return "ROpenMailStore"; case RCopyStore: return "RCopyStore"; case REndTransaction: return "REndTransaction"; case RDeleteFolder: return "RDeleteFolder"; } return "#error"; } static LString ObjToString(LDataI *d) { LString s; switch ((unsigned)d->Type()) { case MAGIC_MAIL: s = d->GetStr(FIELD_MESSAGE_ID); break; case MAGIC_CONTACT: s.Printf("%s %s", d->GetStr(FIELD_FIRST_NAME), d->GetStr(FIELD_LAST_NAME)); break; case MAGIC_FILTER: s = d->GetStr(FIELD_FILTER_NAME); break; case MAGIC_CALENDAR: s = d->GetStr(FIELD_CAL_SUBJECT); break; case MAGIC_GROUP: s = d->GetStr(FIELD_GROUP_NAME); break; default: s.Printf("Error: Unknown type '%x'", d->Type()); break; } return s; } struct ScribeReplicator : public LProgressDlg, public LDataEventsI { bool CanRunConcurrent(WorkType t) { return t == RCopyData || t == RSaveData; } struct CopyStatus { int Total; int Errors; int Ok; CopyStatus() { Total = 0; Errors = 0; Ok = 0; } }; LHashTbl, CopyStatus*> Status; struct WorkUnit { WorkType Type; uint64 Ts; bool Delayed; LArray Deferred; union { struct { LDataFolderI *Src, *Dst; } Folder2; // Valid if Type == RCopyFolder struct { LDataFolderI *Folder; } Folder1; // Valid if Type == RCountSource struct { LDataFolderI *DstFld; LDataFolderI *SrcFld; LDataI *Src; } Data; // Valid if Type == RCopyData struct { ReplicateDlg::AccountSpec *Spec; LAutoPtr *Store; } Open; // Valid if Type == ROpenMailStore }; LString ToString() { LString s; switch (Type) { // Folder2 users case RCopyFolder: case RCopySubFolder: case RCreateFolder: s.Printf("%s: %s <- %s", ::ToString(Type), Folder2.Dst->GetStr(FIELD_FOLDER_NAME), Folder2.Src->GetStr(FIELD_FOLDER_NAME)); break; // Folder1 users case RCountSource: case RDeleteFolder: s.Printf("%s: %s", ::ToString(Type), Folder1.Folder->GetStr(FIELD_FOLDER_NAME)); break; // Data users case RCopyData: case RSaveData: s.Printf("%s: %s <- %s", ::ToString(Type), Data.DstFld->GetStr(FIELD_FOLDER_NAME), ObjToString(Data.Src).Get()); break; default: s = ::ToString(Type); break; } return s; } }; ScribeWnd *App; int Folders, Items; bool Types[MAGIC_MAX-MAGIC_BASE]; int Copied[MAGIC_MAX-MAGIC_BASE]; bool Recurse; bool DeleteSourceOnSuccess; LArray Work; LAutoPtr SrcStore, DstStore; ReplicateDlg::AccountSpec SrcSpec, DstSpec; LDataStoreI::StoreTrans Trans; int TransLen; bool RestartOnPulse; bool PulseStarted; uint64 LastEvent; int UnitsTimedOut; LString OverviewMsg; LString StatusMsg; // Error handling.. int FailedWork; LStringPipe ErrorLog; ScribeReplicator(ScribeWnd *app) : LProgressDlg(app) { App = app; Recurse = true; DeleteSourceOnSuccess = false; LastEvent = 0; Folders = 0; Items = 0; UnitsTimedOut = 0; PulseStarted = false; RestartOnPulse = false; FailedWork = 0; ZeroObj(Copied); SetDescription("Loading..."); App->OnFolderTask(this, true); App->AddStore3EventHandler(this); SetAlwaysOnTop(true); } ~ScribeReplicator() { App->OnFolderTask(this, false); App->RemoveStore3EventHandler(this); Status.DeleteObjects(); } LMessage::Result OnEvent(LMessage *Msg) { switch (Msg->Msg()) { case M_REPLICATE_NEXT: return DoNext(); case M_STORAGE_EVENT: { LDataStoreI *Store = (LDataStoreI*)Msg->A(); if (Store) Store->OnEvent((void*)Msg->B()); break; } } return LProgressDlg::OnEvent(Msg); } LString ToString() { LString sep("\n"); LString::Array s; for (unsigned i=0; iGetStore()->StartTransaction(); TransLen = 0; } // This is called after there is a valid object to save. // It may end up with a deferred save. int SaveObject(WorkUnit *w) { LDataFolderI *df = w->Data.DstFld; LDataFolderI *sf = w->Data.SrcFld; LDataI *s = w->Data.Src; LDataI *d = df->GetStore()->Create(s->Type()); if (d) { d->CopyProps(*s); Store3Status Result = d->Save(df); if (Result == Store3Error) { ErrorLog.Print("%s:%i - Failed to save item.\n", _FL); } else if (Result == Store3Delayed) { w->Type = RSaveData; w->Data.Src = d; w->Delayed = true; w->Ts = LCurrentTime(); return true; } CopyStatus *Cs = Status.Find(sf); if (Cs) Cs->Ok++; TransLen++; } else ErrorLog.Print("%s:%i - Failed to create object of type %i\n", _FL, s->Type()); Pop(w); Value(Value() + 1); if (TransLen >= REPLICATE_TRANS_LENGTH) { Trans.Reset(); } return true; } void Pop(WorkUnit *w) { if (Work.PtrCheck(w)) { ptrdiff_t Idx = w - &Work.First(); Work.DeleteAt((int)Idx, true); } else LAssert(0); } int DoNext() { if (Work.Length() == 0) return OnFinish(); if (!PulseStarted) { SetPulse(60); PulseStarted = true; } uint64 Start = LCurrentTime(); LastEvent = Start; WorkUnit *w = NULL; int DelayedCopies = 0, DelayedSaves = 0, DelayedCreate = 0; for (unsigned i=0; iDelayed) { if (w->Type == RCopyData) DelayedCopies++; else if (w->Type == RSaveData) DelayedSaves++; else if (w->Type == RCreateFolder) DelayedCreate++; } } if (DelayedCopies + DelayedSaves >= MAX_DELAYED_UNITS || DelayedCreate > 0) { RestartOnPulse = true; return true; } for (ssize_t i=(ssize_t)Work.Length()-1; i>=0; i--) { w = &Work[i]; if (CanRunConcurrent(w->Type)) { if (w->Delayed) continue; // Find the last undelayed unit // else do this unit } else { if (w->Delayed) w = NULL; // We wait for it to complete.. // else do this unit } break; } if (!w) { RestartOnPulse = true; #if DEBUG_LOGGING LgiTrace("%s:%i - DoNext all remaining delayed (%i/%i)\n", _FL, DelayedCopies, DelayedSaves); #endif return true; } #if DEBUG_LOGGING LString Str = w->ToString(); LgiTrace("Do: %s\n", Str.Get()); #endif switch (w->Type) { case RCreateFolder: { // Just wait for it to be created... LAssert(w->Delayed); RestartOnPulse = true; return true; } case RDeleteFolder: { LDataFolderI *f = w->Folder1.Folder; if (!f) { ErrorLog.Print("%s:%i - Invalid folder.\n", _FL); Pop(w); break; } // Get the copy status CopyStatus *Cs = Status.Find(f); if (!Cs) { ErrorLog.Print("%s:%i - No copy status for '%s'.\n", _FL, f->GetStr(FIELD_FOLDER_NAME)); Pop(w); break; } // Check that all the items have been copied across. if (Cs->Ok + Cs->Errors < Cs->Total) { // Go into wait mode... RestartOnPulse = true; return true; } if (Cs->Total != Cs->Ok) { // Error out if not ErrorLog.Print("%s:%i - Delete '%s' skipped: %i of %i ok (%i errors).\n", _FL, f->GetStr(FIELD_FOLDER_NAME), Cs->Ok, Cs->Total, Cs->Errors); Pop(w); break; } Store3Status s = f->Delete(); if (s == Store3Error) { ErrorLog.Print("%s:%i - Failed to delete folder.\n", _FL); } else if (s == Store3Delayed) { w->Delayed = true; // Leave the work unit on the stack... break; } Pop(w); break; } case RCountSource: { LDataFolderI *f = w->Folder1.Folder; LAssert(f != NULL); if (!f->GetInt(FIELD_IS_ONLINE)) { w->Delayed = true; w->Ts = LCurrentTime(); StatusMsg.Printf("Waiting folder %s", f->GetStr(FIELD_FOLDER_NAME)); UpdateMsg(); RestartOnPulse = true; return true; } Folders++; Pop(w); for (LDataI *i = f->Children().First(); i && !IsCancelled(); i = f->Children().Next()) { int Type = i->Type(); if (Type && Types[Type-MAGIC_BASE]) Items++; } for (LDataFolderI *c = f->SubFolders().First(); c && !IsCancelled(); c = f->SubFolders().Next()) { WorkUnit &wu = Work.New(); wu.Type = RCountSource; wu.Folder1.Folder = c; } break; } case RCountFinish: { // Update the UI Pop(w); OverviewMsg.Printf("%i items in %i folder(s)...", Items, Folders); UpdateMsg(); SetRange(Items); break; } case REndTransaction: { Pop(w); Trans.Reset(); break; } case RCopyFolder: { // Iterate over all the items and push work units onto the stack... LDataFolderI *d = w->Folder2.Dst; LDataFolderI *s = w->Folder2.Src; LAssert(d != NULL && d != NULL); Pop(w); auto Name = s->GetStr(FIELD_FOLDER_NAME); int64 Type = s->GetInt(FIELD_FOLDER_TYPE); StatusMsg.Printf("Copying folder '%s'", Name); UpdateMsg(); CopyStatus *Cs = NULL; if (DeleteSourceOnSuccess) { // Setup a copy status structure.. Cs = new CopyStatus; if (Cs) Status.Add(s, Cs); else LAssert(0); // Create a delete if needed WorkUnit &del = Work.New(); del.Type = RDeleteFolder; del.Folder1.Folder = s; } // Setup an end transaction to commit outstanding data.. Work.New().Type = REndTransaction; if (Type && Types[Type-MAGIC_BASE]) { int ExistsInDestination = 0; switch (Type) { case MAGIC_MAIL: { // Scan existing items for UID's LHashTbl,LDataI*> MsgMap; for (LDataI *e=d->Children().First(); e && !IsCancelled(); e=d->Children().Next()) { auto MsgId = e->GetStr(FIELD_MESSAGE_ID); if (MsgId) MsgMap.Add(MsgId, e); } // Start replicate process for (LDataI *in=s->Children().First(); in && !IsCancelled(); in=s->Children().Next()) { // Check if we have an existing item... auto SrcMsgId = in->GetStr(FIELD_MESSAGE_ID); if (!MsgMap.Find(SrcMsgId)) { // Create a work unit to copy the data... WorkUnit &wu = Work.New(); wu.Type = RCopyData; wu.Data.DstFld = d; wu.Data.SrcFld = s; wu.Data.Src = in; if (Cs) Cs->Total++; } else { ExistsInDestination++; } } break; } case MAGIC_CONTACT: { // Scan for UID's LHashTbl,LDataI*> Uid, Email; const char *c; for (LDataI *e=d->Children().First(); e && !IsCancelled(); e=d->Children().Next()) { if ((c = e->GetStr(FIELD_UID))) Uid.Add(c, e); if ((c = e->GetStr(FIELD_EMAIL))) Email.Add(c, e); } // Start replicate process for (LDataI *in=s->Children().First(); in && !IsCancelled(); in=s->Children().Next()) { // Check if we have an existing item... if (!Uid.Find(in->GetStr(FIELD_UID)) && !Email.Find(in->GetStr(FIELD_EMAIL))) { // Create a work unit to copy the data... WorkUnit &wu = Work.New(); wu.Type = RCopyData; wu.Data.DstFld = d; wu.Data.SrcFld = s; wu.Data.Src = in; if (Cs) Cs->Total++; } else { ExistsInDestination++; } } break; } case MAGIC_FILTER: { // Scan for names LHashTbl,LDataI*> Name; const char *c; for (LDataI *e=d->Children().First(); e && !IsCancelled(); e=d->Children().Next()) { if ((c = e->GetStr(FIELD_FILTER_NAME))) Name.Add(c, e); } // Start replicate process for (LDataI *in=s->Children().First(); in && !IsCancelled(); in=s->Children().Next()) { // Check if we have an existing item... if (!Name.Find(in->GetStr(FIELD_FILTER_NAME))) { // Create a work unit to copy the data... WorkUnit &wu = Work.New(); wu.Type = RCopyData; wu.Data.DstFld = d; wu.Data.SrcFld = s; wu.Data.Src = in; if (Cs) Cs->Total++; } else { ExistsInDestination++; } } break; } } if (ExistsInDestination) { Value(Value() + ExistsInDestination); } } if (Recurse) { // Create a work unit to copy the child folders... // // By doing this at the end things like deletes get done // on child folders first. E.g. for a "move" operation, // which is broken down to copy + delete. WorkUnit &wu = Work.New(); wu.Type = RCopySubFolder; wu.Folder2.Dst = d; wu.Folder2.Src = s; } break; } case RCopySubFolder: { // Iterate over all the items and push work units onto the stack... LDataFolderI *d = w->Folder2.Dst; LDataFolderI *s = w->Folder2.Src; LAssert(d != NULL && d != NULL); Pop(w); LHashTbl,LDataFolderI*> DestMap; for (LDataFolderI *dc = d->SubFolders().First(); dc; dc=d->SubFolders().Next()) { auto DstName = dc->GetStr(FIELD_FOLDER_NAME); if (DstName) DestMap.Add(DstName, dc); } // Replicate sub-folders for (LDataFolderI *sc=s->SubFolders().First(); sc && !IsCancelled(); sc=s->SubFolders().Next()) { auto SrcName = sc->GetStr(FIELD_FOLDER_NAME); if (SrcName) { Store3Status Status = Store3Success; LArray *Tasks = &Work; // Find matching dest folder... LDataFolderI *dc = DestMap.Find(SrcName); if (!dc) { // Not found, so create new destination sub-folder if ((dc = dynamic_cast(d->GetStore()->Create(MAGIC_FOLDER)))) { dc->CopyProps(*sc); Status = dc->Save(d); if (Status == Store3Error) { ErrorLog.Print("%s:%i - Failed to create folder '%s'\n", _FL, SrcName); } else if (Status == Store3Delayed) { WorkUnit &wu = Work.New(); wu.Type = RCreateFolder; wu.Folder2.Dst = d; wu.Folder2.Src = dc; wu.Delayed = true; Tasks = &wu.Deferred; } } } if (dc) { WorkUnit &wu = Tasks->New(); wu.Type = RCopyFolder; wu.Folder2.Dst = dc; wu.Folder2.Src = sc; } } } break; } case RCopyData: { LDataFolderI *d = w->Data.DstFld; LDataI *s = w->Data.Src; LAssert(d != NULL && d != NULL); if (IsCancelled()) { Pop(w); break; } if (!Trans) StartTransaction(d); Store3State State = (Store3State)s->GetInt(FIELD_LOADED); if (State != Store3Loaded) { // Ask for the object to load itself.. s->GetStr(FIELD_TEXT); // Now check again to see what it's doing... State = (Store3State)s->GetInt(FIELD_LOADED); if (State == Store3Loading) { // We should get an OnChange event when it loads... w->Delayed = true; w->Ts = LCurrentTime(); RestartOnPulse = true; return true; } else if (State != Store3Loaded) { // The error case... kill the task FailedWork++; Pop(w); break; } } SaveObject(w); break; } case RReloadFolders: { Pop(w); - App->LoadFolders(); + App->LoadFolders(NULL); break; } case ROpenMailStore: { Store3Status s = Open(w->Open.Store, *w->Open.Spec); if (s == Store3Error) { LString a = w->Open.Spec->Uri.ToString(); ErrorLog.Print("%s:%i - Failed to open data store '%s'\n", _FL, a.Get()); } else if (s == Store3Delayed) { w->Delayed = true; w->Ts = LCurrentTime(); StatusMsg.Printf("Waiting for IMAP connection"); UpdateMsg(); RestartOnPulse = true; return true; } Pop(w); break; } case RCopyStore: { Pop(w); if (SrcStore && DstStore) { LDataFolderI *SrcRoot = SrcStore->GetRoot(); LDataFolderI *DstRoot = DstStore->GetRoot(); if (SrcRoot && DstRoot) { // Setup a job to copy the root sub-folders w = &Work.New(); w->Type = RCopySubFolder; w->Folder2.Dst = DstRoot; w->Folder2.Src = SrcRoot; // Update the UI with the count results... Work.New().Type = RCountFinish; // Setup a copy store task w = &Work.New(); w->Type = RCountSource; w->Folder1.Folder = SrcRoot; } else { ErrorLog.Print("%s:%i - Get roots failed %p/%p\n", _FL, SrcRoot, DstRoot); } } else { ErrorLog.Print("%s:%i - Copy store failed %p/%p\n", _FL, SrcStore.Get(), DstStore.Get()); } break; } default: { LAssert(!"Invalid type."); break; } } uint64 Length = LCurrentTime() - Start; #if DEBUG_LOGGING if (Length > 20) LgiTrace("Work %i took " LPrintfInt64 "\n", w->Type, Length); #endif if (Length >= 50) { // This leaves a air gap for messages to be processed normally. RestartOnPulse = true; return true; } return PostEvent(M_REPLICATE_NEXT); } void UpdateMsg() { LString s, m; if (OverviewMsg) s.Printf("%s\n", OverviewMsg.Get()); s += StatusMsg; if (UnitsTimedOut > 0) { m.Printf(" (%i timed out)", UnitsTimedOut); s += m; } else s += "..."; SetDescription(s); } void OnPulse() { // Check for timeouts... int Prev = UnitsTimedOut; UnitsTimedOut = 0; uint64 Now = LCurrentTime(); for (unsigned i=0; iDelayed) { if (Now - w->Ts > REPLICATE_TIMEOUT) { if (IsCancelled()) { // Kill the work unit... user wants out. Work.DeleteAt(i--); } else { // Show the user there is an issue... UnitsTimedOut++; } } switch (w->Type) { case ROpenMailStore: { if (IsCancelled()) { Work.DeleteAt(i--); } else { // Waiting for IMAP connection... int64 Online = (*w->Open.Store)->GetInt(FIELD_IS_ONLINE); if (Online) { Pop(w); } else { int64 Ms = LCurrentTime() - w->Ts; StatusMsg.Printf("Waiting %.1fsec for IMAP connection", ((double)Ms / 1000.0)); UpdateMsg(); } } break; } case RCountSource: { if (IsCancelled()) Work.DeleteAt(i--); break; } default: break; } } } if (UnitsTimedOut != Prev) UpdateMsg(); if (RestartOnPulse) { RestartOnPulse = false; PostEvent(M_REPLICATE_NEXT); } } bool StartProcess(LDataFolderI *Dst, LDataFolderI *Src, bool recurse, bool deleteSourceOnSuccess, LArray *types) { if (!Dst) { ErrorLog.Print("%s:%i - No destination folder.\n", _FL); return false; } if (!Src) { ErrorLog.Print("%s:%i - No source folder.\n", _FL); return false; } Recurse = recurse; DeleteSourceOnSuccess = deleteSourceOnSuccess; if (types) { ZeroObj(Types); for (unsigned i=0; iLength(); i++) Types[(*types)[i]-MAGIC_BASE] = true; } else { memset(Types, 1, sizeof(Types)); } // Setup a task to copy the folders... WorkUnit *w = &Work.New(); w->Type = RCopyFolder; w->Folder2.Src = Src; w->Folder2.Dst = Dst; // Update the UI with the count results... Work.New().Type = RCountFinish; // Setup a copy store task w = &Work.New(); w->Type = RCountSource; w->Folder1.Folder = Src; // Start the event cycle return PostEvent(M_REPLICATE_NEXT); } bool StartProcess(ReplicateDlg::ReplicateSettings *Settings) { if (!Settings) { ErrorLog.Print("%s:%i - No settings.\n", _FL); return false; } // Set copy type flags ZeroObj(Types); for (unsigned i=0; iTypes.Length(); i++) Types[Settings->Types[i]-MAGIC_BASE] = true; // Setup a task to reload the Scribe folders at the end... WorkUnit *w = &Work.New(); w->Type = RReloadFolders; // Setup a copy store task w = &Work.New(); w->Type = RCopyStore; // Setup load tasks SrcSpec = Settings->Src; DstSpec = Settings->Dst; w = &Work.New(); w->Type = ROpenMailStore; w->Open.Store = &SrcStore; w->Open.Spec = &SrcSpec; w = &Work.New(); w->Type = ROpenMailStore; w->Open.Store = &DstStore; w->Open.Spec = &DstSpec; // Start the event cycle return PostEvent(M_REPLICATE_NEXT); } Store3Status Open(LAutoPtr *Out, ReplicateDlg::AccountSpec &Acc) { if (!Out) return Store3Error; if (Acc.Uri.sProtocol && !_stricmp(Acc.Uri.sProtocol, "imap")) { MailProtocolProgress *prog[2] = { NULL, NULL }; LAutoPtr SettingStore; LDataStoreI *Store = OpenImap( Acc.Uri.sHost, Acc.Uri.Port, Acc.Uri.sUser, Acc.Uri.sPass, Acc.SslFlags, this, 0, prog, 0, 0, SettingStore); if (!Store) return Store3Error; Out->Reset(Store); return Store3Delayed; } else if (Acc.Uri.sProtocol && !_stricmp(Acc.Uri.sProtocol, "file")) { char Path[MAX_PATH_LEN]; if (LIsRelativePath(Acc.Uri.sPath)) { LMakePath(Path, sizeof(Path), App->GetOptions()->GetFile(), ".."); LMakePath(Path, sizeof(Path), Path, Acc.Uri.sPath); } else { strcpy_s(Path, sizeof(Path), Acc.Uri.sPath); } char *Ext = LGetExtension(Path); if (LDirExists(Path)) { if (Ext && !_stricmp(Ext, "mail3")) { LDataStoreI *Store = OpenMail3(Path, this); if (!Store) return Store3Error; Out->Reset(Store); } } else LAssert(!"Unknown store type."); } else LAssert(!"Unknown protocol."); return Store3Success; } ////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////// // LDataEventsI impl ////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////// void Post(LDataStoreI *store, void *Param) { PostEvent(M_STORAGE_EVENT, (LMessage::Param)store, (LMessage::Param)Param); } bool GetSystemPath(int Folder, LVariant &Path) { return App->GetSystemPath(Folder, Path); } LOptionsFile *GetOptions(bool Create = false) { return App->GetOptions(); } void OnNew(LDataFolderI *parent, LArray &items, int pos, bool is_new) { #if DEBUG_LOGGING LgiTrace("%s:%i - OnNew %i\n", _FL, items.Length()); #endif for (ssize_t i=(ssize_t)Work.Length()-1; i>=0; i--) { WorkUnit &w = Work[i]; if (!w.Delayed) continue; switch (w.Type) { case RCreateFolder: { if (items.HasItem( (LDataI*)w.Folder2.Src )) { #if DEBUG_LOGGING LString Str = w.ToString(); LgiTrace("OnNew.%s\n", Str.Get()); #endif Work.DeleteAt(i); } break; } case RCopyData: case RSaveData: { if (items.HasItem( (LDataI*)w.Data.Src )) { CopyStatus *Cs = Status.Find(w.Data.SrcFld); if (Cs) Cs->Ok++; #if DEBUG_LOGGING LString Str = w.ToString(); LgiTrace("OnNew.%s (Cs=%p)\n", Str.Get(), Cs); #endif Work.DeleteAt(i); Value(Value() + 1); } break; } default: break; } } } bool OnDelete(LDataFolderI *parent, LArray &items) { for (ssize_t i=(ssize_t)Work.Length()-1; i>=0; i--) { WorkUnit *w = &Work[i]; if (!w->Delayed) continue; if (w->Type == RDeleteFolder) { if (items.HasItem((LDataI*)w->Folder1.Folder)) { // Finished the delete... #if DEBUG_LOGGING LString Str = w->ToString(); LgiTrace("OnDelete.%s\n", Str.Get()); #endif Pop(w); } } } return true; } bool OnMove(LDataFolderI *new_parent, LDataFolderI *old_parent, LArray &item) { return true; } bool OnChange(LArray &items, int FieldHint) { #if DEBUG_LOGGING LgiTrace("%s:%i - OnChange %i\n", _FL, items.Length()); #endif for (ssize_t i=(ssize_t)Work.Length()-1; i>=0; i--) { WorkUnit &w = Work[i]; if (!w.Delayed) continue; switch (w.Type) { case RCopyData: { // This event fires when the delayed open has completed... if (items.HasItem(w.Data.Src)) { // Finish the save... SaveObject(&w); } break; } default: break; } } return true; } }; struct ReplicateDlgPriv { ScribeWnd *App; LCombo *Src, *Dst; LList *Lst; LArray Paths; LAutoPtr Settings; LAutoString Msg; ReplicateDlgPriv() { App = 0; Src = Dst = 0; Lst = 0; } }; class LType : public LListItem { LListItemCheckBox *Chk; public: int Type; LType(int t, int s) { Type = t; SetText((char*)LLoadString(s), 1); Chk = new LListItemCheckBox(this, 0, true); } bool Value() { return Chk->Value() != 0; } }; ////////////////////////////////////////////////////////////////////////////////////////////////////////// ReplicateDlg::ReplicateDlg(ScribeWnd *app) { d = new ReplicateDlgPriv; SetParent(d->App = app); if (LoadFromResource(IDD_REPLICATE)) { MoveToCenter(); if (GetViewById(IDC_SRC, d->Src) && GetViewById(IDC_DST, d->Dst)) { // Scan for file based mail stores... LXmlTag *Ms = d->App->GetOptions()->LockTag(OPT_MailStores, _FL); if (Ms) { for (auto c: Ms->Children) { char b[256]; sprintf_s(b, sizeof(b), "%s (%s)", c->GetAttr(OPT_MailStoreName), c->GetAttr(OPT_MailStoreLocation)); d->Src->Insert(b); d->Dst->Insert(b); ReplicateDlg::AccountSpec &a = d->Paths.New(); a.Uri.sProtocol = "file"; a.Uri.sPath = c->GetAttr(OPT_MailStoreLocation); } d->App->GetOptions()->Unlock(); } // Scan for IMAP stores... for (auto a : *d->App->GetAccounts()) { LVariant Proto = a->Receive.Protocol(); if (Proto.Str() && !_stricmp(Proto.Str(), PROTOCOL_IMAP4) && !a->Receive.Disabled()) { LVariant Host = a->Receive.Server(); LVariant User = a->Receive.UserName(); LVariant Port = a->Receive.Port(); char b[256]; int Ch = sprintf_s(b, sizeof(b), "imap://%s@%s", User.Str(), Host.Str()); if (Port.CastInt32()) sprintf_s(b+Ch, sizeof(b)-Ch, ":%i", Port.CastInt32()); d->Src->Insert(b); d->Dst->Insert(b); ReplicateDlg::AccountSpec &as = d->Paths.New(); as.SslFlags = MakeOpenFlags(a, false); as.Uri.Empty(); GPassword Pass; if (a->Receive.GetPassword(&Pass)) { char Ps[256] = ""; Pass.Get(Ps); as.Uri.sPass = Ps; } as.Uri.sProtocol = "imap"; as.Uri.sUser = User.Str(); as.Uri.sHost = Host.Str(); } } } if (GetViewById(IDC_TYPES, d->Lst)) { d->Lst->Insert(new LType(MAGIC_MAIL, IDS_EMAIL)); d->Lst->Insert(new LType(MAGIC_CONTACT, IDS_CONTACT)); d->Lst->Insert(new LType(MAGIC_GROUP, IDC_GROUP)); d->Lst->Insert(new LType(MAGIC_CALENDAR, IDS_CALENDAR)); d->Lst->Insert(new LType(MAGIC_FILTER, IDS_FILTER)); d->Lst->ResizeColumnsToContent(); } OnFolderChange(); } } ReplicateDlg::~ReplicateDlg() { DeleteObj(d); } void ReplicateDlg::OnFolderChange() { SetCtrlEnabled(IDOK, GetCtrlValue(IDC_SRC) != GetCtrlValue(IDC_DST)); } int ReplicateDlg::OnNotify(LViewI *c, LNotification n) { switch (c->GetId()) { case IDC_SRC: case IDC_DST: { OnFolderChange(); break; } case IDOK: { d->Settings.Reset(new ReplicateSettings); List Types; if (d->Lst->GetAll(Types)) { for (auto t: Types) { if (t->Value()) { d->Settings->Types.Add(t->Type); } } } ReplicateDlg::AccountSpec &a = d->Paths[(int32_t)d->Dst->Value()]; d->Settings->Dst = a; a = d->Paths[(uint32_t)d->Src->Value()]; d->Settings->Src = a; // Fall through to EndModal } case IDCANCEL: { EndModal(c->GetId() == IDOK); break; } } return 0; } bool ReplicateDlg::StartProcess() { ScribeReplicator *Rep = new ScribeReplicator(d->App); if (!Rep) return false; return Rep->StartProcess(d->Settings); } Store3Status Store3ReplicateFolders( ScribeWnd *App, LDataFolderI *Dst, LDataFolderI *Src, bool Recurse, bool DeleteSourceOnSuccess, LArray *Types) { if (!Dst || !Src) return Store3Error; ScribeReplicator *Rep = new ScribeReplicator(App); if (!Rep) return Store3Error; if (!Rep->StartProcess(Dst, Src, Recurse, DeleteSourceOnSuccess, Types)) return Store3Error; return Store3Delayed; } diff --git a/Code/Scribe.h b/Code/Scribe.h --- a/Code/Scribe.h +++ b/Code/Scribe.h @@ -1,2531 +1,2544 @@ /*hdr ** FILE: Scribe.h ** AUTHOR: Matthew Allen ** DATE: 22/10/97 ** DESCRIPTION: Scribe email application ** ** Copyright (C) 1998-2003 Matthew Allen ** fret@memecode.com */ // Includes #include #include #include "lgi/common/Lgi.h" #include "lgi/common/DragAndDrop.h" #include "lgi/common/DateTime.h" #include "lgi/common/Password.h" #include "lgi/common/vCard-vCal.h" #include "lgi/common/WordStore.h" #include "lgi/common/SharedMemory.h" #include "lgi/common/XmlTreeUi.h" #include "lgi/common/Mime.h" #include "lgi/common/OptionsFile.h" #include "lgi/common/TextLog.h" #include "lgi/common/Menu.h" #include "lgi/common/ToolBar.h" #include "lgi/common/Combo.h" #include "lgi/common/Printer.h" // Gui controls #include "lgi/common/Panel.h" #include "lgi/common/DocView.h" #include "lgi/common/List.h" #include "lgi/common/Tree.h" #include "lgi/common/ListItemCheckBox.h" // Storage #include "lgi/common/Store3.h" // App Includes #include "ScribeInc.h" #include "ScribeUtils.h" #include "ScribeDefs.h" #include "DomType.h" class ListAddr; // The field definition type struct ItemFieldDef { const char *DisplayText; ScribeDomType Dom; LVariantType Type; int FieldId; // Was 'Id' int CtrlId; const char *Option; bool UtcConvert; }; //////////////////////////////////////////////////////////////////////////////////////////// // Classes class MailTree; class LMailStore; class ScribeWnd; class Thing; class Mail; class Contact; class ThingUi; class MailUi; class ScribeFolder; class ContactUi; class FolderPropertiesDlg; class ScribeAccount; class Filter; class Attachment; class Calendar; class CalendarSource; class AttachmentList; struct ItemFieldDef; class FolderDlg; class Filter; class ContactGroup; class ScribeBehaviour; class AccountletThread; class ThingList; +class LSpellCheck; //////////////////////////////////////////////////////////////////////// // Scripting support #include "lgi/common/Scripting.h" /// Script callback types. See 'api.html' in the Scripts folder for more details. enum LScriptCallbackType { LCallbackNull, LToolsMenu, LThingContextMenu, LThingUiToolbar, LApplicationToolbar, LMailOnBeforeSend, // "OnBeforeMailSend" LMailOnAfterReceive, LBeforeInstallBar, LInstallComponent, LFolderContextMenu, LOnTimer, LRenderMail, LOnLoad }; struct LScript; struct LScriptCallback { LScriptCallbackType Type = LCallbackNull; LScript *Script = NULL; LFunctionInfo *Func = NULL; int Param = 0; double fParam = 0.0; LVariant Data; uint64 PrevTs = 0; bool OnSecond = false; }; struct LScript { LAutoPtr Code; LArray Callbacks; }; typedef void (*ConsoleClosingCallback)(class LScriptConsole *Console, void *user_data); class LScriptConsole : public LWindow { ScribeWnd *App; LTextLog *Txt; ConsoleClosingCallback Callback; void *CallbackData; bool OnViewKey(LView *v, LKey &k); public: LScriptConsole(ScribeWnd *app, ConsoleClosingCallback callback, void *callback_data); ~LScriptConsole(); void Write(const char *s, int64 Len); bool OnRequestClose(bool OsShuttingDown); }; /// This class is a wrapper around a user interface element used for /// Scripting. The script engine needs to be able to store information /// pertaining to the menu item's callbacks along with the sub menu. class LScriptUi : public LDom { public: LScriptUi *Parent; LSubMenu *Sub; LToolBar *Toolbar; LArray Callbacks; LArray Subs; LScriptUi() { Parent = 0; Sub = 0; Toolbar = 0; } LScriptUi(LSubMenu *s) { Parent = 0; Toolbar = 0; Sub = s; } LScriptUi(LToolBar *t) { Parent = 0; Toolbar = t; Sub = 0; } ~LScriptUi() { Subs.DeleteObjects(); } bool GetVariant(const char *Name, LVariant &Value, const char *Arr = NULL) override { if (Sub) return Sub->GetVariant(Name, Value, Arr); LDomProperty Method = LStringToDomProp(Name); if (Method == ObjLength) { if (Toolbar) Value = (int64)Toolbar->Length(); } else return false; return true; } bool CallMethod(const char *MethodName, LVariant *ReturnValue, LArray &Args) override { if (Sub) return Sub->CallMethod(MethodName, ReturnValue, Args); return false; } bool SetupCallbacks(ScribeWnd *App, ThingUi *Parent, Thing *t, LScriptCallbackType Type); bool ExecuteCallbacks(ScribeWnd *App, ThingUi *Parent, Thing *t, int Cmd); }; class LScribeScript : public LScriptContext { LScriptEngine *Eng; struct LScribeScriptPriv *d; public: ScribeWnd *App; static LScribeScript *Inst; LScribeScript(ScribeWnd *app); ~LScribeScript(); void ShowScriptingWindow(bool show); LAutoString GetDataFolder(); LStream *GetLog(); GHostFunc *GetCommands(); char *GetIncludeFile(char *FileName); // System void SetEngine(LScriptEngine *eng); bool MsgBox(LScriptArguments &Args); // Paths bool GetSystemPath(LScriptArguments &Args); bool GetScribeTempPath(LScriptArguments &Args); bool JoinPath(LScriptArguments &Args); // Folders bool GetFolder(LScriptArguments &Args); bool GetSourceFolders(LScriptArguments &Args); bool CreateSubFolder(LScriptArguments &Args); bool LoadFolder(LScriptArguments &Args); bool FolderSelect(LScriptArguments &Args); bool BrowseFolder(LScriptArguments &Args); // Things bool CreateThing(LScriptArguments &Args); bool MoveThing(LScriptArguments &Args); bool SaveThing(LScriptArguments &Args); bool DeleteThing(LScriptArguments &Args); bool ShowThingWindow(LScriptArguments &Args); bool FilterDoActions(LScriptArguments &Args); bool LookupContact(LScriptArguments &Args); // Callbacks bool AddToolsMenuItem(LScriptArguments &Args); bool AddCallback(LScriptArguments &Args); // UI bool MenuAddItem(LScriptArguments &Args); bool MenuAddSubmenu(LScriptArguments &Args); bool ToolbarAddItem(LScriptArguments &Args); }; //////////////////////////////////////////////////////////////////////// class ScribePassword { class ScribePasswordPrivate *d; public: ScribePassword(LOptionsFile *p, const char *opt, int check, int pwd, int confirm); ~ScribePassword(); bool IsOk(); bool Load(LView *dlg); bool Save(); void OnNotify(LViewI *c, LNotification &n); }; class ChooseFolderDlg : public LDialog { ScribeWnd *App; - // LTextLabel *Message; LEdit *Folder; int Type; bool Export; LList *Lst; void InsertFile(const char *f); public: - char *DestFolder; - List SrcFiles; + LString DestFolder; + LString::Array SrcFiles; ChooseFolderDlg ( ScribeWnd *parent, bool IsExport, const char *Title, const char *Msg, - char *DefFolder = 0, + char *DefFolder = NULL, int FolderType = MAGIC_MAIL, - LArray *Files = 0 + LString::Array *Files = NULL ); - ~ChooseFolderDlg(); + int OnNotify(LViewI *Ctrl, LNotification n); }; #define IoProgressImplArgs LAutoPtr stream, const char *mimeType, IoProgressCallback cb #define IoProgressFnArgs IoProgressImplArgs = NULL #define IoProgressError(err) \ { \ IoProgress p(Store3Error, err); \ if (cb) cb(&p, stream); \ return p; \ } #define IoProgressSuccess() \ { \ IoProgress p(Store3Success); \ if (cb) cb(&p, stream); \ return p; \ } #define IoProgressNotImpl() \ { \ IoProgress p(Store3NotImpl); \ if (cb) cb(&p, stream); \ return p; \ } class ScribeClass ThingType : public LDom, public LDataUserI { bool Dirty = false; bool WillDirty = true; bool Loaded = false; protected: bool GetDirty() { return Dirty; } bool OnError(const char *File, int Line) { _lgi_assert(false, "Object Missing", File, Line); return false; } // Callbacks struct ThingEventInfo { const char *File = NULL; int Line = 0; std::function Callback; }; LArray OnLoadCallbacks; public: struct IoProgress; typedef std::function IoProgressCallback; struct IoProgress { // This is the main result to look at: // Store3NotImpl - typically means the mime type is wrong. // Store3Error - an error occured. // Store3Delayed - means the operation will take a long time. // However progress is report via 'prog' if not NULL. // And the 'onComplete' handler will be called at the end. // Store3Success - the operation successfully completed. Store3Status status = Store3NotImpl; // Optional progress for the operation. Really only relevant for // status == Store3Delayed. Progress *prog = NULL; // Optional error message for the operation. Relevant if // status == Store3Error. LString errMsg; IoProgress(Store3Status s, const char *err = NULL) { status = s; if (err) errMsg = err; } operator bool() { return status > Store3Error; } }; template LAutoPtr AutoCast(LAutoPtr ap) { return LAutoPtr(ap.Release()); } static LArray DirtyThings; ScribeWnd *App = NULL; ThingType(); virtual ~ThingType(); virtual Store3ItemTypes Type() { return MAGIC_NONE; } virtual bool SetDirty(bool b = true); void SetWillDirty(bool c) { WillDirty = c; } virtual bool Save(ScribeFolder *Into) { return false; } virtual void OnProperties(int Tab = -1) {} virtual ScribeFolder *GetFolder() = 0; virtual Store3Status SetFolder(ScribeFolder *f, int Param = -1) = 0; virtual bool IsPlaceHolder() { return false; } // Events void WhenLoaded(const char *file, int line, std::function Callback, int index = -1); bool IsLoaded(int Set = -1); // Printing virtual void OnPrintHeaders(struct ScribePrintContext &Context) { LAssert(!"Impl me."); } virtual void OnPrintText(ScribePrintContext &Context, LPrintPageRanges &Pages) { LAssert(!"Impl me."); } - virtual void OnPrintHtml(ScribePrintContext &Context, LPrintPageRanges &Pages, LSurface *RenderedHtml) { LAssert(!"Impl me."); } + virtual int OnPrintHtml(ScribePrintContext &Context, LPrintPageRanges &Pages, LSurface *RenderedHtml) { LAssert(!"Impl me."); return 0; } }; class MailContainerIter; class ScribeClass MailContainer { friend class MailContainerIter; List Iters; public: virtual ~MailContainer(); virtual size_t Length() { return 0; } virtual ssize_t IndexOf(Mail *m) { return -1; } virtual Mail *operator [](size_t i) { return NULL; } }; class ScribeClass MailContainerIter { friend class MailContainer; protected: MailContainer *Container; public: MailContainerIter(); ~MailContainerIter(); void SetContainer(MailContainer *c); }; class ScribeClass ThingStorage // External storage information { public: int Data; ThingStorage() { Data = 0; } virtual ~ThingStorage() {} }; class ThingUi; class ScribeClass Thing : public ThingType, public LListItem, public LDragDropSource, public LRefCount { friend class ScribeWnd; friend class ScribeFolder; ScribeFolder *_ParentFolder = NULL; protected: LArray FieldArray; LAutoString DropFileName; // This structure allows the app to move objects between // mail stores. After a delayed write to the new mail store // the old item needs to be removed. This keeps track of // where that old item is. Don't assume that the Obj pointer // is valid... check in the folder's items first. struct ThingReference { LString Path; Thing *Obj; ThingReference() { Obj = NULL; } } DeleteOnAdd; public: ThingStorage *Data = NULL; Thing(ScribeWnd *app, LDataI *object = 0); ~Thing(); // Dom bool CallMethod(const char *MethodName, LVariant *ReturnValue, LArray &Args) override; // D'n'd bool GetData(LArray &Data) override; bool GetFormats(LDragFormats &Formats) override; bool OnBeginDrag(LMouse &m) override; // Import / Export virtual bool GetFormats(bool Export, LString::Array &MimeTypes) { return false; } // Anything implementing 2 functions should mostly be using one of these // to "return" an IoProgress and process any callback: // IoProgressError(msg) // IoProgressNotImpl() // IoProgressSuccess() virtual IoProgress Import(IoProgressFnArgs) = 0; virtual IoProgress Export(IoProgressFnArgs) = 0; /// This exports all the selected items - bool ExportAll(LViewI *Parent, const char *ExportMimeType); + void ExportAll(LViewI *Parent, const char *ExportMimeType, std::function Callback); // UI bool OnKey(LKey &k) override; // Thing ScribeFolder *GetFolder() override { return _ParentFolder; } void SetParentFolder(ScribeFolder *f); Store3Status SetFolder(ScribeFolder *f, int Param = -1) override; LDataI *DefaultObject(LDataI *arg = 0); virtual ThingUi *DoUI(MailContainer *c = 0) { return NULL; } virtual ThingUi *GetUI() { return 0; } virtual bool SetUI(ThingUi *ui = 0) { return false; } virtual uint32_t GetFlags() { return 0; } virtual void OnCreate() override; virtual bool OnDelete(); virtual int *GetDefaultFields() { return 0; } virtual const char *GetFieldText(int Field) { return 0; } virtual void DoContextMenu(LMouse &m, LView *Parent = 0) {} virtual Thing &operator =(Thing &c) { LAssert(0); return *this; } virtual char *GetDropFileName() = 0; virtual bool GetDropFiles(LString::Array &Files) { return false; } virtual void OnSerialize(bool Write) {} // Interfaces virtual Mail *IsMail() { return 0; } virtual Contact *IsContact() { return 0; } virtual ContactGroup *IsGroup() { return 0; } virtual Filter *IsFilter() { return 0; } virtual Attachment *IsAttachment() { return 0; } virtual Calendar *IsCalendar() { return 0; } void SetFieldArray(LArray &i) { FieldArray = i; } void OnMove(); bool SetField(int Field, int n); bool SetField(int Field, double n); bool SetField(int Field, char *n); bool SetField(int Field, LDateTime &n); bool GetField(int Field, int &n); bool GetField(int Field, double &n); bool GetField(int Field, const char *&n); bool GetField(int Field, LDateTime &n); bool DeleteField(int Field); }; class ThingUi : public LWindow { friend class MailUiGpg; bool _Dirty; char *_Name; protected: Thing *_Item; bool _Running; void SetItem(Thing *i) { _Item = i; } public: ScribeWnd *App; static LArray All; ThingUi(Thing *item, const char *name); ~ThingUi(); virtual bool SetDirty(bool d, bool ui = true); bool IsDirty() { return _Dirty; } bool OnRequestClose(bool OsShuttingDown); bool OnViewKey(LView *v, LKey &k); virtual void OnDirty(bool Dirty) {} virtual void OnLoad() = 0; virtual void OnSave() = 0; virtual void OnChange() {} virtual AttachmentList *GetAttachments() { return 0; } virtual bool AddRecipient(AddressDescriptor *Addr) { return false; } }; class ThingFilter { public: virtual bool TestThing(Thing *Thing) = 0; }; class ScribeClass Attachment : public Thing { friend class Mail; protected: Mail *Msg; Mail *Owner; bool IsResizing; LString Buf; // D'n'd LAutoString DropSourceFile; bool GetFormats(LDragFormats &Formats) override; bool GetData(LArray &Data) override; void _New(LDataI *object); public: enum Encoding { OCTET_STREAM, PLAIN_TEXT, BASE64, QUOTED_PRINTABLE, }; Attachment(ScribeWnd *App, Attachment *import = 0); Attachment(ScribeWnd *App, LDataI *object, const char *import = 0); ~Attachment(); bool ImportFile(const char *FileName); bool ImportStream(const char *FileName, const char *MimeType, LAutoStreamI Stream); Thing &operator =(Thing &c) override; LDATA_INT64_PROP(Size, FIELD_SIZE); LDATA_STR_PROP(Name, FIELD_NAME); LDATA_STR_PROP(MimeType, FIELD_MIME_TYPE); LDATA_STR_PROP(ContentId, FIELD_CONTENT_ID); LDATA_STR_PROP(Charset, FIELD_CHARSET); LDATA_STR_PROP(InternetHeaders, FIELD_INTERNET_HEADER); // LDom support bool GetVariant(const char *Name, LVariant &Value, const char *Array = NULL) override; bool CallMethod(const char *MethodName, LVariant *Ret, LArray &Args) override; void OnOpen(LView *Parent, char *Dest = 0); void OnDeleteAttachment(LView *Parent, bool Ask); void OnSaveAs(LView *Parent); void OnMouseClick(LMouse &m) override; bool OnKey(LKey &k) override; Store3ItemTypes Type() override { return MAGIC_ATTACHMENT; } bool Get(char **ptr, ssize_t *size); bool Set(char *ptr, ssize_t size); bool Set(LAutoStreamI Stream); Attachment *IsAttachment() override { return this; } LAutoString MakeFileName(); bool GetIsResizing(); void SetIsResizing(bool b); bool IsMailMessage(); bool IsVCalendar(); bool IsVCard(); // The owner is the mail that this is attached to Mail *GetOwner() { return Owner; } void SetOwner(Mail *msg); // The msg is the mail that this message/rfc822 attachment is rendered into Mail *GetMsg(); void SetMsg(Mail *m); LStreamI *GotoObject(const char *file, int line); int Sizeof(); bool Serialize(LFile &f, bool Write); IoProgress Import(IoProgressFnArgs) override { return Store3Error; } IoProgress Export(IoProgressFnArgs) override { return Store3Error; } bool SaveTo(char *FileName, bool Quite = false, LView *Parent = 0); const char *GetText(int i) override; char *GetDropFileName() override; bool GetDropFiles(LString::Array &Files) override; }; class ScribeClass Contact : public Thing { friend class ContactUi; protected: class ContactPriv *d = NULL; ContactUi *Ui = NULL; public: static List Everyone; static Contact *LookupEmail(const char *Email); static LHashTbl, int> PropMap; static int DefaultContactFields[]; Contact(ScribeWnd *app, LDataI *object = 0); ~Contact(); LDATA_STR_PROP(First, FIELD_FIRST_NAME); LDATA_STR_PROP(Last, FIELD_LAST_NAME); LDATA_STR_PROP(Email, FIELD_EMAIL); bool Get(const char *Opt, const char *&Value); bool Set(const char *Opt, const char *Value); bool Get(const char *Opt, int &Value); bool Set(const char *Opt, int Value); // operators Thing &operator =(Thing &c) override; Contact *IsContact() override { return this; } // Dom bool GetVariant(const char *Name, LVariant &Value, const char *Array = 0) override; bool SetVariant(const char *Name, LVariant &Value, const char *Array = 0) override; bool CallMethod(const char *MethodName, LVariant *ReturnValue, LArray &Args) override; // Events void OnMouseClick(LMouse &m) override; // Printing void OnPrintHeaders(struct ScribePrintContext &Context) override; void OnPrintText(ScribePrintContext &Context, LPrintPageRanges &Pages) override; // Misc Store3ItemTypes Type() override { return MAGIC_CONTACT; } ThingUi *DoUI(MailContainer *c = 0) override; int Compare(LListItem *Arg, ssize_t Field) override; bool IsAssociatedWith(char *PluginName); char *GetLocalTime(const char *TimeZone = 0); // Email address int GetAddrCount(); LString::Array GetEmails(); LString GetAddrAt(int i); bool HasEmail(LString email); // Serialization size_t SizeofField(const char *Name); size_t Sizeof(); bool Serialize(LFile &f, bool Write); bool Save(ScribeFolder *Into = 0) override; // ListItem const char *GetText(int i) override; int *GetDefaultFields() override; const char *GetFieldText(int Field) override; int GetImage(int Flags = 0) override { return ICON_CONTACT; } // Import/Export bool GetFormats(bool Export, LString::Array &MimeTypes) override; IoProgress Import(IoProgressFnArgs) override; IoProgress Export(IoProgressFnArgs) override; char *GetDropFileName() override; bool GetDropFiles(LString::Array &Files) override; }; #define ContactGroupName "Name" #define ContactGroupList "List" #define ContactGroupDateModified "DateModified" extern ItemFieldDef GroupFieldDefs[]; class ContactGroup : public Thing { friend class GroupUi; class GroupUi *Ui; class ContactGroupPrivate *d; LString DateCache; public: LDateTime UsedTs; LDATA_STR_PROP(Name, FIELD_GROUP_NAME); ContactGroup(ScribeWnd *app, LDataI *object = 0); ~ContactGroup(); // operators Thing &operator =(Thing &c) override; ContactGroup *IsGroup() override { return this; } // Dom bool GetVariant(const char *Name, LVariant &Value, const char *Array = NULL) override; bool CallMethod(const char *MethodName, LVariant *ReturnValue, LArray &Args) override; // Events void OnMouseClick(LMouse &m) override; void OnSerialize(bool Write) override; // Misc Store3ItemTypes Type() override { return MAGIC_GROUP; } ThingUi *DoUI(MailContainer *c = 0) override; int Compare(LListItem *Arg, ssize_t Field) override; IoProgress Import(IoProgressFnArgs) override { return Store3NotImpl; } IoProgress Export(IoProgressFnArgs) override { return Store3NotImpl; } bool GetAddresses(List &a); LString::Array GetAddresses(); char *GetDropFileName() override; bool GetDropFiles(LString::Array &Files) override; // Serialization bool Save(ScribeFolder *Into = 0) override; // ListItem const char *GetText(int i) override; int *GetDefaultFields() override; const char *GetFieldText(int Field) override; int GetImage(int Flags = 0) override { return ICON_CONTACT_GROUP; } }; struct LGroupMapArray : public LArray { LString toString() { LString::Array a; for (auto i: *this) a.Add(i->GetName()); return LString(",").Join(a); } }; class LGroupMap : public LHashTbl,LGroupMapArray*> { ScribeWnd *App; void Index(ContactGroup *grp); public: LGroupMap(ScribeWnd *app); ~LGroupMap(); }; ///////////////////////////////////////////////////////////// // Mail threading // // See: http://www.jwz.org/doc/threading.html struct ThingSortParams { int SortAscend; int SortField; }; class MContainer { int Lines; int Depth; bool Open; bool Next; void Dump(LStream &s, int Depth = 0); public: typedef LHashTbl, MContainer*> ContainerHash; // Container data Mail *Message; MContainer *Parent; LArray Children; List Refs; int Index; // Cache data List RefCache; // Debug #ifdef _DEBUG LAutoString MsgId; #endif // Methods MContainer(const char *Id, Mail *m = 0); ~MContainer(); void SetMail(Mail *m); Mail *GetTop(); bool HasChild(MContainer *m); void AddChild(MContainer *m); void RemoveChild(MContainer *m); int CountMessages(); void Pour(int &index, int depth, int tree, bool next, ThingSortParams *folder); void OnPaint(LSurface *pDC, LRect &r, LItemColumn *c, LColour Fore, LColour Back, LFont *Font, const char *Txt); static void Prune(int &ParentIndex, LArray &L); static void Thread(List &In, LArray &Out); }; extern int ListItemCompare(LListItem *a, LListItem *b, NativeInt Data); extern int ContainerIndexer(Thing *a, Thing *b, NativeInt Data); extern const char *Store3ItemTypeName(Store3ItemTypes t); extern int GetFolderVersion(const char *Path); extern bool CreateMailHeaders(ScribeWnd *App, LStream &Out, LDataI *Mail, MailProtocol *Protocol); //////////////////////////////////////////////////////////// // Thing sorting // The old way extern int ContainerCompare(MContainer **a, MContainer **b); extern int ThingCompare(Thing *a, Thing *b, NativeInt Data); // The new way extern int ContainerSorter(MContainer *&a, MContainer *&b, ThingSortParams *Params); extern int ThingSorter(Thing *a, Thing *b, ThingSortParams *Data); ///////////////////////////////////////////////////////////// class MailViewOwner : public LCapabilityTarget { public: virtual LDocView *GetDoc(const char *MimeType) = 0; virtual bool SetDoc(LDocView *v, const char *MimeType) = 0; }; ///////////////////////////////////////////////////////////// // The core mail object struct MailPrintContext; class ScribeClass Mail : public Thing, public MailContainer, public LDefaultDocumentEnv { friend class MailUi; friend class MailPropDlg; friend class ScribeFolder; friend class Attachment; friend class ScribeWnd; private: class MailPrivate *d; static LHashTbl,Mail*> MessageIdMap; // List item preview int PreviewCacheX; List PreviewCache; int64_t TotalSizeCache; int64_t FlagsCache; MailUi *Ui; int Cursor; // Stores the cursor position in reply/forward format until the UI needs it Attachment *ParentFile; List Attachments; Mail *PreviousMail; // the mail we are replying to / forwarding void _New(); void _Delete(); bool _GetListItems(List &l, bool All); // All=false is just the selected items void SetListRead(bool Read); void SetFlagsCache(int64_t NewFlags, bool IgnoreReceipt, bool UpdateScreen); // LDocumentEnv impl List Actions; bool OnNavigate(LDocView *Parent, const char *Uri) override; bool AppendItems(LSubMenu *Menu, const char *Param, int Base = 1000) override; bool OnMenu(LDocView *View, int Id, void *Context) override; LoadType GetContent(LoadJob *&j) override; public: static bool PreviewLines; static bool AdjustDateTz; static int DefaultMailFields[]; static bool RunMailPipes; static List NewMailLst; constexpr static float MarkColourMix = 0.9f; uint8_t SendAttempts; enum NewEmailState { NewEmailNone, NewEmailLoading, NewEmailFilter, NewEmailBayes, NewEmailGrowl, NewEmailTray }; NewEmailState NewEmail; Mail(ScribeWnd *app, LDataI *object = 0); ~Mail(); bool SetObject(LDataI *o, bool InDataDestuctor, const char *File, int Line) override; LDATA_STR_PROP(Label, FIELD_LABEL); LDATA_STR_PROP(FwdMsgId, FIELD_FWD_MSG_ID); LDATA_STR_PROP(BounceMsgId, FIELD_BOUNCE_MSG_ID); LDATA_STR_PROP(Subject, FIELD_SUBJECT); LDATA_STR_PROP(Body, FIELD_TEXT); LDATA_STR_PROP(BodyCharset, FIELD_CHARSET); LDATA_STR_PROP(Html, FIELD_ALTERNATE_HTML); LDATA_STR_PROP(HtmlCharset, FIELD_HTML_CHARSET); LDATA_STR_PROP(InternetHeader, FIELD_INTERNET_HEADER); LDATA_INT_TYPE_PROP(EmailPriority, Priority, FIELD_PRIORITY, MAIL_PRIORITY_NORMAL); LDATA_INT32_PROP(AccountId, FIELD_ACCOUNT_ID); LDATA_INT64_PROP(MarkColour, FIELD_COLOUR); LDATA_STR_PROP(References, FIELD_REFERENCES); LDATA_DATE_PROP(DateReceived, FIELD_DATE_RECEIVED); LDATA_DATE_PROP(DateSent, FIELD_DATE_SENT); LDATA_INT_TYPE_PROP(Store3State, Loaded, FIELD_LOADED, Store3Loaded); LVariant GetServerUid(); bool SetServerUid(LVariant &v); const char *GetFromStr(int id) { LDataPropI *From = GetObject() ? GetObject()->GetObj(FIELD_FROM) : 0; return From ? From->GetStr(id) : NULL; } LDataPropI *GetFrom() { return GetObject() ? GetObject()->GetObj(FIELD_FROM) : 0; } LDataPropI *GetReply() { return GetObject() ? GetObject()->GetObj(FIELD_REPLY) : 0; } GDataIt GetTo() { return GetObject() ? GetObject()->GetList(FIELD_TO) : 0; } bool GetAttachmentObjs(LArray &Objs); LDataI *GetFileAttachPoint(); // Operators Mail *IsMail() override { return this; } Thing &operator =(Thing &c) override; OsView Handle(); ThingUi *GetUI() override; bool SetUI(ThingUi *ui) override; // References and ID's MContainer *Container; const char *GetMessageId(bool Create = false); bool SetMessageId(const char *val); LAutoString GetThreadIndex(int TruncateChars = 0); static Mail *GetMailFromId(const char *Id); bool MailMessageIdMap(bool Add = true); bool GetReferences(List &Ids); void GetThread(List &Thread); LString GetMailRef(); bool ResizeImage(Attachment *a); // MailContainer size_t Length() override; ssize_t IndexOf(Mail *m) override; Mail *operator [](size_t i) override; // Dom bool GetVariant(const char *Name, LVariant &Value, const char *Array = NULL) override; bool SetVariant(const char *Name, LVariant &Value, const char *Array = NULL) override; bool CallMethod(const char *MethodName, LVariant *ReturnValue, LArray &Args) override; // Events void OnCreate() override; bool OnBeforeSend(struct ScribeEnvelope *Out); void OnAfterSend(); bool OnBeforeReceive(); bool OnAfterReceive(LStreamI *Msg); void OnMouseClick(LMouse &m) override; void OnProperties(int Tab = -1) override; void OnInspect(); void OnReply(Mail *m, bool All, bool MarkOriginal); bool OnForward(Mail *m, bool MarkOriginal, int WithAttachments = -1); bool OnBounce(Mail *m, bool MarkOriginal, int WithAttachments = -1); void OnReceipt(Mail *m); int OnNotify(LViewI *Ctrl, LNotification n) override; // Printing void OnPrintHeaders(ScribePrintContext &Context) override; void OnPrintText(ScribePrintContext &Context, LPrintPageRanges &Pages) override; - void OnPrintHtml(ScribePrintContext &Context, LPrintPageRanges &Pages, LSurface *RenderedHtml) override; + int OnPrintHtml(ScribePrintContext &Context, LPrintPageRanges &Pages, LSurface *RenderedHtml) override; // Misc uint32_t GetFlags() override; void SetFlags(ulong i, bool IgnoreReceipt = false, bool Update = true); void DeleteAsSpam(LView *View); const char *GetFieldText(int Field) override; LAutoString GetCharSet(); char *GetNewText(int Max = 64 << 10, const char *AsCp = "utf-8"); int *GetDefaultFields() override; Store3ItemTypes Type() override { return MAGIC_MAIL; } void DoContextMenu(LMouse &m, LView *Parent = 0) override; int Compare(LListItem *Arg, ssize_t Field) override; char *GetDropFileName() override; bool GetDropFiles(LString::Array &Files) override; LAutoString GetSig(bool HtmlVersion, ScribeAccount *Account = 0); bool LoadFromFile(char *File); void PrepSend(); void NewRecipient(char *Email, char *Name = 0); void ClearCachedItems(); bool Send(bool Now); void CreateMailHeaders(); bool AddCalendarEvent(LViewI *Parent, bool AddPopupReminder, LString *Msg); LArray GetCalendarAttachments(); // UI LDocView *CreateView(MailViewOwner *Owner, LString MimeType, bool Sunken, size_t MaxBytes, bool NoEdit = false); ThingUi *DoUI(MailContainer *c = 0) override; // Alt HTML bool HasAlternateHtml(Attachment **Attach = 0); char *GetAlternateHtml(List *Refs = 0); // dynamically allocated ptr bool WriteAlternateHtml(char *File = NULL, int FileLen = 0); // defaults to TEMP dir // Account stuff void ProcessTextForResponse(Mail *From, LOptionsFile *Options, ScribeAccount *Account); void WrapAndQuote(LStringPipe &Pipe, const char *QuoteStr, int WrapAt = -1); ScribeAccount *GetAccountSentTo(); // Access int64 TotalSizeof(); bool Save(ScribeFolder *Into = 0) override; // Attachments Attachment *AttachFile(LView *Parent, const char *FileName); bool AttachFile(Attachment *File); bool DeleteAttachment(Attachment *File); bool GetAttachments(List *Attachments); bool HasAttachments() { return Attachments.Length() > 0; } bool UnloadAttachments(); // Import / Export bool GetFormats(bool Export, LString::Array &MimeTypes) override; IoProgress Import(IoProgressFnArgs) override; IoProgress Export(IoProgressFnArgs) override; // ListItem void Update() override; const char *GetText(int i) override; int GetImage(int SelFlags = 0) override; void OnMeasure(LPoint *Info) override; void OnPaintColumn(LItem::ItemPaintCtx &Ctx, int i, LItemColumn *c) override; void OnPaint(LItem::ItemPaintCtx &Ctx) override; }; inline const char *toString(Mail::NewEmailState s) { #define _(s) case Mail::s: return #s; switch (s) { _(NewEmailNone) _(NewEmailLoading) _(NewEmailFilter) _(NewEmailBayes) _(NewEmailGrowl) _(NewEmailTray) } #undef _ LAssert(0); return "#invalidNewEmailState"; } // this is where the items reside // and it forms a leaf on the mail box tree // to the user it looks like a folder class ScribeClass ScribeFolder : public ThingType, public LTreeItem, public LDragDropSource, public MailContainer { friend class MailTree; friend class FolderPropertiesDlg; friend class ThingList; friend class ScribeWnd; protected: class ThingContainerPriv *d; ThingList *View(); LString DropFileName; LString GetDropFileName(); // UI cache LAutoString NameCache; int ChildUnRead = 0; LTreeItem *LoadOnDemand = NULL; LAutoPtr Loading; LArray FieldArray; void SerializeFieldWidths(bool Write = false); void EmptyFieldList(); void SetLoadFolder(Thing *t) { if (t) t->SetParentFolder(this); } bool HasFieldId(int Id); // Tree item stuff void _PourText(LPoint &Size) override; void _PaintText(LItem::ItemPaintCtx &Ctx) override; int _UnreadChildren(); void UpdateOsUnread(); // Debugging state enum FolderState { FldState_Idle, FldState_Loading, FldState_Populating, } CurState = FldState_Idle; public: List Items; ScribeFolder(); ~ScribeFolder(); // Object LArray &GetFieldArray() { return FieldArray; } LDataFolderI *GetFldObj() { return dynamic_cast(GetObject()); } bool SetObject(LDataI *o, bool InDataDestuctor, const char *File, int Line) override; // ThingType Store3ItemTypes Type() override { return GetObject() ? (Store3ItemTypes)GetObject()->Type() : MAGIC_NONE; } ScribeFolder *GetFolder() override { return dynamic_cast(LTreeItem::GetParent()); } ScribeFolder *GetChildFolder() { return dynamic_cast(LTreeItem::GetChild()); } ScribeFolder *GetNextFolder() { return dynamic_cast(LTreeItem::GetNext()); } Store3Status SetFolder(ScribeFolder *f, int Param = -1) override; ScribeFolder *IsFolder() { return this; } Store3Status CopyTo(ScribeFolder *NewParent, int NewIndex = -1); // MailContainer size_t Length() override; ssize_t IndexOf(Mail *m) override; Mail *operator [](size_t i) override; /// Update the unread count void OnUpdateUnRead ( /// Increments the count, or zero if a child folder is changing. int Offset, /// Re-scan the folder bool ScanItems ); // Methods LDATA_INT32_PROP(UnRead, FIELD_UNREAD); LDATA_INT_TYPE_PROP(Store3ItemTypes, ItemType, FIELD_FOLDER_TYPE, MAGIC_MAIL); LDATA_INT32_PROP(Open, FIELD_FOLDER_OPEN); LDATA_INT32_PROP(SortIndex, FIELD_FOLDER_INDEX); LDATA_INT64_PROP(Items, FIELD_FOLDER_ITEMS); // Cached item count LDATA_INT_TYPE_PROP(ScribePerm, ReadAccess, FIELD_FOLDER_PERM_READ, PermRequireNone); LDATA_INT_TYPE_PROP(ScribePerm, WriteAccess, FIELD_FOLDER_PERM_WRITE, PermRequireNone); LDATA_ENUM_PROP(SystemFolderType, FIELD_SYSTEM_FOLDER, Store3SystemFolder); void SetSort(int Col, bool Ascend, bool CanDirty = true); int GetSortAscend() { return GetObject()->GetInt(FIELD_SORT) > 0; } int GetSortCol() { return abs((int)GetObject()->GetInt(FIELD_SORT)) - 1; } int GetSortField(); void ReSort(); bool Save(ScribeFolder *Into = 0) override; bool ReindexField(int OldIndex, int NewIndex); void CollectSubFolderMail(ScribeFolder *To = 0); bool InsertThing(Thing *Item); bool MoveTo(LArray &Items, bool CopyOnly = false, LArray *Status = NULL); bool Delete(LArray &Items, bool ToTrash); void SetDefaultFields(bool Force = false); bool Thread(); ScribePerm GetFolderPerms(ScribeAccessType Access); - bool SetFolderPerms(LView *Parent, ScribeAccessType Access, ScribePerm Perm); + void SetFolderPerms(LView *Parent, ScribeAccessType Access, ScribePerm Perm, std::function Callback); bool GetThreaded(); void SetThreaded(bool t); // void Update(); - Mail *GetMessageById(char *Id); + void GetMessageById(const char *Id, std::function Callback); void SetLoadOnDemand(); void SortSubfolders(); void DoContextMenu(LMouse &m); void OnItemType(); bool IsInTrash(); bool SortItems(); - // Virtuals - virtual Store3Status WriteThing(Thing *t); - virtual bool DeleteThing(Thing *t); - virtual bool DeleteAllThings(); - virtual bool LoadFolders(); - virtual Store3State LoadThings(LViewI *Parent = 0); - virtual bool UnloadThings(); - virtual bool IsWriteable() { return true; } - virtual bool IsPublicFolders() { return false; } + // Virtuals: + /// + /// These methods can be used in a synchronous or asynchronous manner: + /// sync: Call with 'Callback=NULL' and use the return value. + /// If the function needs to show a dialog (like to get permissions from + /// the user) then it'll return Store3Delayed immediately. + /// async: Call with a valid callback, and the method will possibly wait + /// for the user and then either return Store3Error or Store3Success. + virtual Store3Status LoadThings(LViewI *Parent = NULL, std::function Callback = NULL); + virtual Store3Status WriteThing(Thing *t, std::function Callback = NULL); + virtual Store3Status DeleteThing(Thing *t, std::function Callback = NULL); + virtual Store3Status DeleteAllThings( std::function Callback = NULL); + virtual bool LoadFolders(); + virtual bool UnloadThings(); + virtual bool IsWriteable() { return true; } + virtual bool IsPublicFolders() { return false; } - virtual void OnProperties(int Tab = -1) override; - virtual ScribeFolder *CreateSubDirectory(const char *Name, int Type); - virtual void OnRename(char *NewName); - virtual void OnDelete(); - virtual LString GetPath(); - virtual ScribeFolder *GetSubFolder(const char *Path); - virtual void Populate(ThingList *List); - virtual bool CanHaveSubFolders(Store3ItemTypes Type = MAGIC_MAIL) { return GetItemType() != MAGIC_ANY; } - virtual void OnRethread(); + virtual void OnProperties(int Tab = -1) override; + virtual ScribeFolder *CreateSubDirectory(const char *Name, int Type); + virtual void OnRename(char *NewName); + virtual void OnDelete(); + virtual LString GetPath(); + virtual ScribeFolder *GetSubFolder(const char *Path); + virtual void Populate(ThingList *List); + virtual bool CanHaveSubFolders(Store3ItemTypes Type = MAGIC_MAIL) { return GetItemType() != MAGIC_ANY; } + virtual void OnRethread(); // Name void SetName(const char *Name, bool Encode); LString GetName(bool Decode); // Serialization int Sizeof(); bool Serialize(LFile &f, bool Write); // Tree Item const char *GetText(int i=0) override; int GetImage(int Flags = 0) override; void OnExpand(bool b) override; bool OnKey(LKey &k) override; void Update() override; // Drag'n'drop bool GetFormats(LDragFormats &Formats) override; bool OnBeginDrag(LMouse &m) override; void OnEndData() override; bool GetData(LArray &Data) override; void OnReceiveFiles(LArray &Files); // Import/Export bool GetFormats(bool Export, LString::Array &MimeTypes); IoProgress Import(IoProgressFnArgs); IoProgress Export(IoProgressFnArgs); + bool Import(LStreamI &f, const char *MimeType); + void Export(LStreamI &f, const char *MimeType, std::function Callback = NULL); + void ExportAsync(LAutoPtr f, const char *MimeType, std::function Callback = NULL); const char *GetStorageMimeType(); // Dom bool GetVariant(const char *Name, LVariant &Value, const char *Array = NULL) override; bool SetVariant(const char *Name, LVariant &Value, const char *Array = NULL) override; bool CallMethod(const char *MethodName, LVariant *ReturnValue, LArray &Args) override; }; ////////////////////////////////////////////////////////////// class Filter; class FilterCondition { protected: bool TestData(Filter *F, LVariant &v, LStream *Log); public: // Data LAutoString Source; // Data Source (used to be "int Field") LAutoString Value; // Constant char Op; uint8_t Not; // Methods FilterCondition(); bool Set(class LXmlTag *t); // Test condition against email bool Test(Filter *F, Mail *m, LStream *Log); FilterCondition &operator =(FilterCondition &c); // Object ThingUi *DoUI(MailContainer *c = 0); }; class FilterAction : public LListItem, public LDataPropI { LCombo *TypeCbo; LEdit *ArgEdit; LButton *Btn; public: // Data FilterActionTypes Type; LAutoString Arg1; // Methods FilterAction(LDataStoreI *Store); ~FilterAction(); bool Set(LXmlTag *t); bool Get(LXmlTag *t); bool Do(Filter *F, ScribeWnd *App, Mail *&m, LStream *log); void Browse(ScribeWnd *App, LView *Parent); void DescribeHtml(Filter *Flt, LStream &s); LDataPropI &operator =(LDataPropI &p); // List item const char *GetText(int Col = 0); void OnMeasure(LPoint *Info); bool Select(); void Select(bool b); void OnPaintColumn(LItem::ItemPaintCtx &Ctx, int i, LItemColumn *c); int OnNotify(LViewI *c, LNotification n); // Object ThingUi *DoUI(MailContainer *c = 0); // bool Serialize(ObjProperties &f, bool Write); }; class ScribeClass Filter : public Thing { friend class FilterUi; protected: class FilterUi *Ui; class FilterPrivate *d; static int MaxIndex; // Ui LListItemCheckBox *ChkIncoming; LListItemCheckBox *ChkOutgoing; LListItemCheckBox *ChkInternal; bool IgnoreCheckEvents; // Current Mail **Current; // XML I/O LAutoPtr ConditionsCache; LAutoPtr Parse(bool Actions); // Methods bool EvaluateTree(LXmlTag *n, Mail *m, bool &Stop, LStream *Log); bool EvaluateXml(Mail *m, bool &Stop, LStream *Log); public: Filter(ScribeWnd *app, LDataI *object = 0); ~Filter(); LDATA_STR_PROP(Name, FIELD_FILTER_NAME); LDATA_STR_PROP(ConditionsXml, FIELD_FILTER_CONDITIONS_XML); LDATA_STR_PROP(ActionsXml, FIELD_FILTER_ACTIONS_XML); LDATA_STR_PROP(Script, FIELD_FILTER_SCRIPT); LDATA_INT32_PROP(Index, FIELD_FILTER_INDEX); LDATA_INT32_PROP(StopFiltering, FIELD_STOP_FILTERING); LDATA_INT32_PROP(Incoming, FIELD_FILTER_INCOMING); LDATA_INT32_PROP(Outgoing, FIELD_FILTER_OUTGOING); LDATA_INT32_PROP(Internal, FIELD_FILTER_INTERNAL); int Compare(LListItem *Arg, ssize_t Field) override; Thing &operator =(Thing &c) override; Filter *IsFilter() override { return this; } static void Reindex(ScribeFolder *Folder); int *GetDefaultFields() override; const char *GetFieldText(int Field) override; // Methods void Empty(); LAutoString DescribeHtml(); // Import / Export char *GetDropFileName() override; bool GetDropFiles(LString::Array &Files) override; bool GetFormats(bool Export, LString::Array &MimeTypes) override; IoProgress Import(IoProgressFnArgs) override; IoProgress Export(IoProgressFnArgs) override; // Dom bool Evaluate(char *s, LVariant &v); bool SetVariant(const char *Name, LVariant &Value, const char *Array = NULL) override; bool GetVariant(const char *Name, LVariant &Value, const char *Array = NULL) override; bool CallMethod(const char *MethodName, LVariant *ReturnValue, LArray &Args) override; // Filter bool Test(Mail *m, bool &Stop, LStream *Log = 0); bool DoActions(Mail *&m, bool &Stop, LStream *Log = 0); Mail *GetCurrent() { return Current?*Current:0; } /// This filters all the mail in 'Email'. Anything that is handled by a filter /// is removed from the list, leaving just the unfiltered mail. static int ApplyFilters ( /// [In] The window of the filtering caller LView *Parent, /// [In] List of all the filter to test and/or apply List &Filters, /// [In/Out] The email to filter. After the call anything that has been /// acted on by a filter will be removed from the list. List &Email ); // Object Store3ItemTypes Type() override { return MAGIC_FILTER; } ThingUi *DoUI(MailContainer *c = 0) override; bool Serialize(LFile &f, bool Write); bool Save(ScribeFolder *Into = 0) override; void OnMouseClick(LMouse &m) override; void AddAction(FilterAction *a); // List item const char *GetText(int i) override; int GetImage(int Flags) override; void OnPaint(ItemPaintCtx &Ctx) override; void OnColumnNotify(int Col, int64 Data) override; // Index Filter *GetFilterAt(int Index); }; ////////////////////////////////////////////////////////////////////// class Accountlet; enum AccountThreadState { ThreadIdle, ThreadSetup, ThreadConnecting, ThreadTransfer, ThreadWaiting, ThreadDeleting, ThreadDone, ThreadCancel, ThreadError }; enum ReceiveAction { MailNoop, MailDelete, MailDownloadAndDelete, MailDownload, MailUpload, MailHeaders, }; enum ReceiveStatus { MailReceivedNone, MailReceivedWaiting, // This has been given to the main thread MailReceivedOk, // and one of "Ok" or "Error" has to be MailReceivedError, // set to continue. MailReceivedMax, }; ScribeFunc const char *AccountThreadStateName(AccountThreadState i); ScribeFunc const char *ReceiveActionName(ReceiveAction i); ScribeFunc const char *ReceiveStatusName(ReceiveStatus i); class LScribeMime : public LMime { public: LScribeMime() : LMime(ScribeTempPath()) { } }; class LMimeStream : public LTempStream, public LScribeMime { public: LMimeStream(); bool Parse(); }; class MailTransferEvent { public: // Message LAutoPtr Rfc822Msg; ReceiveAction Action = MailNoop; ReceiveStatus Status = MailReceivedNone; int Index = 0; bool Explicit = false; LString Uid; int64 Size = 0; int64 StartWait = 0; // Header Listing class AccountMessage *Msg = NULL; LList *GetList(); // Sending ScribeEnvelope *Send = NULL; LString OutgoingHeaders; // Other class Accountlet *Account = NULL; }; #define RoProp(Type, Name) \ protected: \ Type Name; \ public: \ Type Get##Name() { return Name; } class AccountThread : public LThread, public LCancel { protected: Accountlet *Acc; void OnAfterMain(); public: // Object AccountThread(Accountlet *acc); ~AccountThread(); // Api Accountlet *GetAccountlet() { return Acc; } // 1st phase virtual void Disconnect(); // 2nd phase virtual bool Kill(); }; #define AccStrOption(func, opt) \ LVariant func(const char *Set = 0) { LVariant v; StrOption(opt, v, Set); return v; } #define AccIntOption(name, opt) \ int name(int Set = -1) { LVariant v; IntOption(opt, v, Set); return v.CastInt32(); } class ScribeClass Accountlet : public LStream { friend class ScribeAccount; friend class AccountletThread; friend class AccountThread; public: struct AccountletPriv { LArray Log; }; class AccountletLock { LMutex *l; public: bool Locked; AccountletPriv *d; AccountletLock(AccountletPriv *data, LMutex *lck, const char *file, int line) { d = data; l = lck; Locked = lck->Lock(file, line); } ~AccountletLock() { if (Locked) l->Unlock(); } }; typedef LAutoPtr I; private: AccountletPriv d; LMutex PrivLock; protected: // Data ScribeAccount *Account; LAutoPtr Thread; bool ConnectionStatus; MailProtocol *Client; uint64 LastOnline; - char *TempPsw; + LString TempPsw; bool Quiet; LView *Parent; // Pointers ScribeFolder *Root; LDataStoreI *DataStore; LMailStore *MailStore; // this memory is owned by ScribeWnd // Options const char *OptPassword; // Members LSocketI *CreateSocket(bool Sending, LCapabilityClient *Caps, bool RawLFCheck); bool WaitForTransfers(List &Files); void StrOption(const char *Opt, LVariant &v, const char *Set); void IntOption(const char *Opt, LVariant &v, int Set); // LStringPipe Line; ssize_t Write(const void *buf, ssize_t size, int flags); public: MailProtocolProgress Group; MailProtocolProgress Item; Accountlet(ScribeAccount *a); ~Accountlet(); Accountlet &operator =(const Accountlet &a) { LAssert(0); return *this; } I Lock(const char *File, int Line) { I a(new AccountletLock(&d, &PrivLock, File, Line)); if (!a->Locked) a.Reset(); return a; } // Methods bool Connect(LView *Parent, bool Quiet); bool Lock(); void Unlock(); virtual bool IsConfigured() { return false; } bool GetStatus() { return ConnectionStatus; } uint64 GetLastOnline() { return LastOnline; } ScribeAccount* GetAccount() { return Account; } bool IsCancelled(); void IsCancelled(bool b); ScribeWnd* GetApp(); const char* GetStateName(); ScribeFolder* GetRootFolder() { return Root; } RoProp(AccountThreadState, State); bool IsOnline() { if (DataStore) return DataStore->GetInt(FIELD_IS_ONLINE) != 0; return Thread != 0; } void OnEndSession() { if (DataStore) DataStore->SetInt(FIELD_IS_ONLINE, false); } // Commands void Disconnect(); void Kill(); // Data char *OptionName(const char *Opt, char *Dest, int DestLen); void Delete(); LThread *GetThread() { return Thread; } LMailStore *GetMailStore() { return MailStore; } LDataStoreI *GetDataStore() { return DataStore; } // General options virtual int UseSSL(int Set = -1) = 0; AccStrOption(Name, OPT_AccountName); AccIntOption(Disabled, OPT_AccountDisabled); AccIntOption(Id, OPT_AccountUID); AccIntOption(Expanded, OPT_AccountExpanded); bool GetPassword(GPassword *p); void SetPassword(GPassword *p); bool IsCheckDialup(); // Events void OnThreadDone(); void OnOnlineChange(bool Online); virtual void OnBeforeDelete(); // LDom impl bool GetVariant(const char *Name, LVariant &Value, const char *Array = NULL); bool SetVariant(const char *Name, LVariant &Value, const char *Array = NULL); // Virtuals virtual void Main(AccountletThread *Thread) = 0; virtual LVariant Server(const char *Set = 0) = 0; virtual LVariant UserName(const char *Set = 0) = 0; virtual void Enabled(bool b) = 0; virtual void OnPulse(char *s, int s_len) {} virtual bool IsReceive() { return false; } virtual bool InitMenus() { return false; } virtual void CreateMaps() = 0; virtual ScribeAccountletStatusIcon GetStatusIcon() { return STATUS_ERROR; } }; #undef RoProp class AccountletThread; class AccountIdentity : public Accountlet { public: AccountIdentity(ScribeAccount *a); AccStrOption(Name, OPT_AccIdentName); AccStrOption(Email, OPT_AccIdentEmail); AccStrOption(ReplyTo, OPT_AccIdentReply); AccStrOption(TextSig, OPT_AccIdentTextSig); AccStrOption(HtmlSig, OPT_AccIdentHtmlSig); AccIntOption(Sort, OPT_AccountSort); int UseSSL(int Set = -1) { return 0; } void Main(AccountletThread *Thread) {} LVariant Server(const char *Set = 0) { return LVariant(); } LVariant UserName(const char *Set = 0) { return LVariant(); } void Enabled(bool b) {} void CreateMaps(); bool IsValid(); bool GetVariant(const char *Name, LVariant &Value, const char *Array = NULL); bool SetVariant(const char *Name, LVariant &Value, const char *Array = NULL); }; struct ScribeEnvelope { LString MsgId; LString SourceFolder; LString From; LArray To; LString References; LString FwdMsgId; LString BounceMsgId; LString Rfc822; }; class SendAccountlet : public Accountlet { friend class ScribeAccount; friend class ScribeWnd; LMenuItem *SendItem; public: LArray Outbox; SendAccountlet(ScribeAccount *a); ~SendAccountlet(); // Methods void Main(AccountletThread *Thread); void Enabled(bool b); bool InitMenus(); void CreateMaps(); ScribeAccountletStatusIcon GetStatusIcon(); // Sending options AccStrOption(Server, OPT_SmtpServer); AccIntOption(Port, OPT_SmtpPort); AccStrOption(Domain, OPT_SmtpDomain); AccStrOption(UserName, OPT_SmtpName); AccIntOption(RequireAuthentication, OPT_SmtpAuth); AccIntOption(AuthType, OPT_SmtpAuthType); AccStrOption(PrefCharset1, OPT_SendCharset1); AccStrOption(PrefCharset2, OPT_SendCharset2); AccStrOption(HotFolder, OPT_SendHotFolder); AccIntOption(OnlySendThroughThisAccount, OPT_OnlySendThroughThis); /// Get/Set the SSL mode /// \sa #SSL_NONE, #SSL_STARTTLS or #SSL_DIRECT AccIntOption(UseSSL, OPT_SmtpSSL); bool IsConfigured() { LVariant hot = HotFolder(); if (hot.Str()) { bool Exists = LDirExists(hot.Str()); printf("%s:%i - '%s' exists = %i\n", _FL, hot.Str(), Exists); return Exists; } return ValidStr(Server().Str()); } bool GetVariant(const char *Name, LVariant &Value, const char *Array = NULL); bool SetVariant(const char *Name, LVariant &Value, const char *Array = NULL); }; typedef LHashTbl, LXmlTag*> MsgListHash; class MsgList : protected MsgListHash { LOptionsFile *Opts; LString Tag; bool Loaded; bool Load(); LXmlTag *LockId(const char *id, const char *file, int line); void Unlock(); public: typedef MsgListHash Parent; bool Dirty; MsgList(LOptionsFile *Opts, char *Tag); ~MsgList(); // Access methods bool Add(const char *id); bool Delete(const char *id); int Length(); bool Find(const char *id); void Empty(); LString::Array CopyKeys(); // Dates bool SetDate(char *id, LDateTime *dt); bool GetDate(char *id, LDateTime *dt); }; class ReceiveAccountlet : public Accountlet { friend class ScribeAccount; friend class ScribeWnd; friend class ImapThread; friend class ScpThread; LArray Actions; LList *Items; int SecondsTillOnline; LMenuItem *ReceiveItem; LMenuItem *PreviewItem; List *IdTemp; LAutoPtr SettingStore; LAutoPtr Msgs; LAutoPtr Spam; public: ReceiveAccountlet(ScribeAccount *a); ~ReceiveAccountlet(); // Props LList *GetItems() { return Items; } bool SetItems(LList *l); bool SetActions(LArray *a = NULL); bool IsReceive() { return true; } bool IsPersistant(); // Methods void Main(AccountletThread *Thread); void OnPulse(char *s, int s_len); bool OnIdle(); void Enabled(bool b); bool InitMenus(); int GetCheckTimeout(); void CreateMaps(); ScribeAccountletStatusIcon GetStatusIcon(); // Message list bool HasMsg(const char *Id); void AddMsg(const char *Id); void RemoveMsg(const char *Id); void RemoveAllMsgs(); int GetMsgs(); // Spam list void DeleteAsSpam(const char *Id); bool RemoveFromSpamIds(const char *Id); bool IsSpamId(const char *Id, bool Delete = false); // Receive options AccStrOption(Protocol, OPT_Pop3Protocol); ScribeProtocol ProtocolType() { return ProtocolStrToEnum(Protocol().Str()); } AccStrOption(Server, OPT_Pop3Server); AccIntOption(Port, OPT_Pop3Port); AccStrOption(UserName, OPT_Pop3Name); AccStrOption(DestinationFolder, OPT_Pop3Folder); AccIntOption(AutoReceive, OPT_Pop3AutoReceive); AccStrOption(CheckTimeout, OPT_Pop3CheckEvery); AccIntOption(LeaveOnServer, OPT_Pop3LeaveOnServer); AccIntOption(DeleteAfter, OPT_DeleteAfter); AccIntOption(DeleteDays, OPT_DeleteDays); AccIntOption(DeleteLarger, OPT_DeleteIfLarger); AccIntOption(DeleteSize, OPT_DeleteIfLargerSize); AccIntOption(DownloadLimit, OPT_MaxEmailSize); AccStrOption(Assume8BitCharset, OPT_Receive8BitCs); AccStrOption(AssumeAsciiCharset, OPT_ReceiveAsciiCs); AccIntOption(AuthType, OPT_ReceiveAuthType); AccStrOption(HotFolder, OPT_ReceiveHotFolder); AccIntOption(SecureAuth, OPT_ReceiveSecAuth); /// Get/Set the SSL mode /// \sa #SSL_NONE, #SSL_STARTTLS or #SSL_DIRECT AccIntOption(UseSSL, OPT_Pop3SSL); bool IsConfigured() { LVariant hot = HotFolder(); if (hot.Str() && LDirExists(hot.Str())) { return true; } LVariant v = Server(); bool s = ValidStr(v.Str()); v = Protocol(); if (!v.Str() || _stricmp(v.Str(), PROTOCOL_POP_OVER_HTTP) != 0) { v = UserName(); s &= ValidStr(v.Str()); } return s; } bool GetVariant(const char *Name, LVariant &Value, const char *Array = NULL); bool SetVariant(const char *Name, LVariant &Value, const char *Array = NULL); }; class ScribeAccount : public LDom, public LXmlTreeUi, public LCapabilityClient { friend class ScribeWnd; friend class ScribePopViewer; friend class AccountStatusPanel; friend class Accountlet; friend class SendAccountlet; friend class ReceiveAccountlet; protected: class ScribeAccountPrivate *d; ScribeWnd *Parent; ScribeFolder *&GetRoot(); void SetIndex(int i); public: // Data AccountIdentity Identity; SendAccountlet Send; ReceiveAccountlet Receive; LArray Views; // Object ScribeAccount(ScribeWnd *parent, int index); ~ScribeAccount(); // Lifespan bool IsValid(); bool Create(); bool Delete(); // Properties ScribeWnd *GetApp() { return Parent; } int GetIndex(); bool IsOnline(); void SetCheck(bool c); LMenuItem *GetMenuItem(); void SetMenuItem(LMenuItem *i); void OnEndSession() { Send.OnEndSession(); Receive.OnEndSession(); } // Commands void Stop(); bool Disconnect(); void Kill(); void SetDefaults(); // User interface - bool InitUI(LView *Parent, int Tab = 0); + void InitUI(LView *Parent, int Tab, std::function callback); bool InitMenus(); void SerializeUi(LView *Wnd, bool Load); int OnNotify(LViewI *Ctrl, LNotification &n); // Worker void OnPulse(char *s = NULL, int s_len = 0); void ReIndex(int i); void CreateMaps(); // LDom interface bool GetVariant(const char *Name, LVariant &Value, const char *Array = NULL) override; bool SetVariant(const char *Name, LVariant &Value, const char *Array = NULL) override; bool CallMethod(const char *MethodName, LVariant *ReturnValue, LArray &Args) override; }; ////////////////////////////////////////////////////////////////////// class ScribeClass ScribeDom : public LDom { ScribeWnd *App; public: Mail *Email; Contact *Con; ContactGroup *Grp; Calendar *Cal; Filter *Fil; ScribeDom(ScribeWnd *a); bool GetVariant(const char *Name, LVariant &Value, const char *Array = 0); }; ////////////////////////////////////////////////////////////////////// #include "BayesianFilter.h" #include "Components.h" class LMailStore { public: bool Default, Expanded; LString Name; LString Path; LDataStoreI *Store; ScribeFolder *Root; LMailStore() { Expanded = true; Default = false; Store = NULL; Root = NULL; } bool IsOk() { return Store != NULL && Root != NULL; } int Priority() { int Ver = Store ? (int)Store->GetInt(FIELD_VERSION) : 0; return Ver; } LMailStore &operator =(LMailStore &a) { LAssert(0); return *this; } }; struct OptionsInfo { LString File; char *Leaf; int Score; uint64 Mod; bool Usual; OptionsInfo(); OptionsInfo &operator =(char *p); LOptionsFile *Load(); }; class ScribeClass ScribeWnd : public LWindow, public LDom, public LDataEventsI, public BayesianFilter, public CapabilityInstaller, public LCapabilityTarget { friend class ScribeAccount; friend class Accountlet; friend class SendAccountlet; friend class ReceiveAccountlet; friend class AccountStatusPanel; friend class ScribeFolder; friend class OptionsDlg; friend class LoadWordStoreThread; friend struct ScribeReplicator; public: enum LayoutMode { OptionsLayout = 0, ///-------------------- /// | | /// Folders | List | /// | | /// |---------| /// | Preview | ///-------------------- FoldersListAndPreview = 1, ///----------------- /// | | /// Folders | List | /// | | ///----------------- /// Preview | ///----------------- PreviewOnBottom, ///----------------- /// | | /// Folders | List | /// | | ///----------------- FoldersAndList, ///--------------------------- /// | | | /// Folders | List | Preview | /// | | | ///--------------------------- ThreeColumn, }; enum AppState { - ScribeInitializing, - ScribeRunning, + ScribeConstructing, // 1) In ScribeWnd constructor OR one of it's dialogs. + ScribeInitializing, // 2) In the ScribeWnd::OnCreate event. + ScribeRunning, // 3) Normal fully initialized runtime. ScribeExiting, ScribeLoadingFolders, ScribeUnloadingFolders, }; AppState GetScribeState() { return ScribeState; } protected: class ScribeWndPrivate *d = NULL; LTrayIcon TrayIcon; // Ipc LSharedMemory *ScribeIpc = NULL; class ScribeIpcInstance *ThisInst = NULL; bool ShutdownIpc(); // Accounts List Accounts; // New Mail stuff class LNewMailDlg *NewMailDlg = NULL; static AppState ScribeState; DoEvery Ticker; int64 LastDrop = 0; // Static LSubMenu *File = NULL; LSubMenu *ContactsMenu = NULL; LSubMenu *Edit = NULL; LSubMenu *Help = NULL; LToolBar *Commands = NULL; // Dynamic LSubMenu *IdentityMenu = NULL; LMenuItem *DefaultIdentityItem = NULL; LSubMenu *MailMenu = NULL; LSubMenu *SendMenu = NULL, *ReceiveMenu = NULL, *PreviewMenu = NULL; LMenuItem *SendItem = NULL, *ReceiveItem = NULL, *PreviewItem = NULL; LSubMenu *NewTemplateMenu = NULL; LMenuItem *WorkOffline = NULL; // Commands LCommand CmdSend; LCommand CmdReceive; LCommand CmdPreview; // Storage LArray Folders; LArray FolderTasks; List PostValidateFree; // Main view LAutoPtr ImageList; LAutoPtr ToolbarImgs; class LBox *Splitter = NULL; ThingList *MailList = NULL; class DynamicHtml *TitlePage = NULL; class LSearchView *SearchView = NULL; MailTree *Tree = NULL; class LPreviewPanel *PreviewPanel = NULL; class AccountStatusPanel *StatusPanel = NULL; // Security ScribePerm CurrentAuthLevel = PermRequireNone; // Methods void SetupUi(); void SetupAccounts(); int AdjustAllObjectSizes(LDataI *Item); bool CleanFolders(ScribeFolder *f); LDataStoreI *CreateDataStore(char *Full, bool CreateIfMissing); - bool LoadFolders(); + void LoadFolders(std::function Callback); bool LoadMailStores(); + bool ProcessFolder(LDataStoreI *&Store, int StoreIdx, char *StoreName); bool UnLoadFolders(); void AddFolderToMru(char *FileName); void AddContactsToMenu(LSubMenu *Menu); bool FindWordDb(char *Out, int OutSize, char *Name); void OnFolderChanged(LDataFolderI *folder); bool ValidateFolder(LMailStore *s, int Id); void GrowlOnMail(Mail *m); void GrowlInfo(LString title, LString text); bool OnTransfer(); ScribeDomType StrToDom(const char *Var) { return ::StrToDom(Var); } const char* DomToStr(ScribeDomType p) { return ::DomToStr(p); } void LoadImageResources(); void DoOnTimer(LScriptCallback *c); public: ScribeWnd(); ~ScribeWnd(); static bool IsUnitTest; const char *GetClass() override { return "ScribeWnd"; } void DoDebug(char *s); void Validate(LMailStore *s); // Dom bool GetVariant(const char *Name, LVariant &Value, const char *Array = NULL) override; bool CallMethod(const char *MethodName, LVariant *ReturnValue, LArray &Args) override; // --------------------------------------------------------------------- // Methods LAutoString GetDataFolder(); Thing *CreateThingOfType(Store3ItemTypes Type, LDataI *obj = 0); Thing *CreateItem(int Type, ScribeFolder *Folder = 0, bool Ui = true); Mail *CreateMail(Contact *c = 0, const char *Email = 0, const char *Name = 0); Mail *LookupMailRef(const char *MsgRef, bool TraceAllUids = false); bool CreateFolders(LAutoString &FileName); bool CompactFolders(LMailStore &Store, bool Interactive = true); void Send(int Which = -1, bool Quiet = false); void Receive(int Which); void Preview(int Which); void OnBeforeConnect(ScribeAccount *Account, bool Receive); void OnAfterConnect(ScribeAccount *Account, bool Receive); bool NeedsCapability(const char *Name, const char *Param = NULL) override; void OnInstall(CapsHash *Caps, bool Status) override; void OnCloseInstaller() override; bool HasFolderTasks() { return FolderTasks.Length() > 0; } int GetEventHandle(); void Update(int What = 0); void UpdateUnRead(ScribeFolder *Folder, int Delta); - bool ThingPrint(ThingType *m, LPrinter *Info = 0, LView *Parent = 0, int MaxPage = -1); + void ThingPrint(std::function Callback, ThingType *m, LPrinter *Info = NULL, LView *Parent = NULL, int MaxPage = -1); bool OpenAMail(ScribeFolder *Folder); void BuildDynMenus(); LDocView *CreateTextControl(int Id, const char *MimeType, bool Editor, Mail *m = 0); void SetLastDrop() { LastDrop = LCurrentTime(); } void SetListPane(LView *v); void SetLayout(LayoutMode Mode = OptionsLayout); bool IsMyEmail(const char *Email); bool SetItemPreview(LView *v); LOptionsFile::PortableType GetPortableType(); ScribeRemoteContent RemoteContent_GetSenderStatus(const char *Addr); void RemoteContent_ClearCache(); void RemoteContent_AddSender(const char *Addr, bool WhiteList); void SetDefaultHandler(); + void OnSetDefaultHandler(bool Error, bool OldAssert); void SetCurrentIdentity(int i=-1); int GetCurrentIdentity(); bool MailReplyTo(Mail *m, bool All = false); bool MailForward(Mail *m); bool MailBounce(Mail *m); void MailMerge(LArray &Recip, const char *FileName, Mail *Source); void OnNewMail(List *NewMailObjs, bool Add = true); void OnNewMailSound(); void OnCommandLine(); void OnTrayClick(LMouse &m) override; void OnTrayMenu(LSubMenu &m) override; void OnTrayMenuResult(int MenuId) override; void OnFolderSelect(ScribeFolder *f); void AddThingSrc(ScribeFolder *src); void RemoveThingSrc(ScribeFolder *src); LArray GetThingSources(Store3ItemTypes Type); bool GetContacts(List &Contacts, ScribeFolder *f = 0, bool Deep = true); List *GetEveryone(); void HashContacts(LHashTbl,Contact*> &Contacts, ScribeFolder *Folder = 0, bool Deep = true); // CapabilityInstaller impl LAutoString GetHttpProxy() override; InstallProgress *StartAction(MissingCapsBar *Bar, LCapabilityTarget::CapsHash *Components, const char *Action) override; class HttpImageThread *GetImageLoader(); int GetMaxPages(); ScribeWnd::LayoutMode GetEffectiveLayoutMode(); ThingList *GetMailList() { return MailList; } // Gets the matching mail store for a given identity LMailStore *GetMailStoreForIdentity ( /// If this is NULL, assume the current identity const char *IdEmail = NULL ); ScribeFolder *GetFolder(int Id, LMailStore *s = NULL, bool Quiet = false); ScribeFolder *GetFolder(int Id, LDataI *s); ScribeFolder *GetFolder(const char *Name, LMailStore *s = NULL); ScribeFolder *GetCurrentFolder(); int GetFolderType(ScribeFolder *f); LImageList *GetIconImgList() { return ImageList; } LAutoPtr &GetToolbarImgList() { return ToolbarImgs; } LString GetResourceFile(SribeResourceType Type); DoEvery *GetTicker() { return &Ticker; } ScribeAccount *GetSendAccount(); ScribeAccount *GetCurrentAccount(); ThingFilter *GetThingFilter(); List *GetAccounts() { return &Accounts; } ScribeAccount *GetAccountById(int Id); ScribeAccount *GetAccountByEmail(const char *Email); LPrinter *GetPrinter(); int GetActiveThreads(); int GetToolbarHeight(); void GetFilters(List &Filters, bool JustIn, bool JustOut, bool JustInternal); bool OnFolderTask(LEventTargetI *Ptr, bool Add); LArray &GetStorageFolders() { return Folders; } LMailStore *GetDefaultMailStore(); LMailStore *GetMailStoreForPath(const char *Path); bool OnMailStore(LMailStore **MailStore, bool Add); ThingList *GetItemList() { return MailList; } LColour GetColour(int i); LMutex *GetLock(); LFont *GetPreviewFont(); LFont *GetBoldFont() { return LSysBold; } LToolBar *LoadToolbar(LViewI *Parent, const char *File, LAutoPtr &Img); class LVmDebuggerCallback *GetDebuggerCallback(); class GpgConnector *GetGpgConnector(); - LString GetUserInput(LView *Parent, LString Msg, bool Password = false); + void GetUserInput(LView *Parent, LString Msg, bool Password, std::function Callback); int GetCalendarSources(LArray &Sources); - bool GetAccessLevel(LViewI *Parent, ScribePerm Required, const char *ResourceName); - bool GetAccountSettingsAccess(LViewI *Parent, ScribeAccessType AccessType); + Store3Status GetAccessLevel(LViewI *Parent, ScribePerm Required, const char *ResourceName, std::function Callback); + void GetAccountSettingsAccess(LViewI *Parent, ScribeAccessType AccessType, std::function Callback); const char* EditCtrlMimeType(); LAutoString GetReplyXml(const char *MimeType); LAutoString GetForwardXml(const char *MimeType); bool GetHelpFilesPath(char *Path, int PathSize); bool LaunchHelp(const char *File); LAutoString ProcessSig(Mail *m, char *Xml, const char *MimeType); LString ProcessReplyForwardTemplate(Mail *m, Mail *r, char *Xml, int &Cursor, const char *MimeType); bool LogFilterActivity(); ScribeFolder *FindContainer(LDataFolderI *f); bool SaveDirtyObjects(int TimeLimitMs = 100); class LSpellCheck *CreateSpellObject(); class LSpellCheck *GetSpellThread(bool OverrideOpt = false); bool SetSpellThreadParams(LSpellCheck *Thread); void OnSpellerSettingChange(); bool OnMailTransferEvent(MailTransferEvent *e); LViewI *GetView() { return this; } char *GetUiTags(); struct MailStoreUpgradeParams { LAutoString OldFolders; LAutoString NewFolders; bool Quiet; LStream *Log; MailStoreUpgradeParams() { Quiet = false; Log = 0; } }; // Scripting support bool GetScriptCallbacks(LScriptCallbackType Type, LArray &Callbacks); - LScriptCallback GetCallback(char *CallbackMethodName); + LScriptCallback GetCallback(const char *CallbackMethodName); bool RegisterCallback(LScriptCallbackType Type, LScriptArguments &Args); LStream *ShowScriptingConsole(); bool ExecuteScriptCallback(LScriptCallback &c, LScriptArguments &Args, bool ReturnArgs = false); LScriptEngine *GetScriptEngine(); // Options LOptionsFile *GetOptions(bool Create = false) override; bool ScanForOptionsFiles(LArray &Inf, LSystemPath PathType); bool LoadOptions(); bool SaveOptions(); bool IsSending() { return false; } bool ShowToolbarText(); bool IsValid(); // Data events from storage back ends bool GetSystemPath(int Folder, LVariant &Path) override; void OnNew(LDataFolderI *parent, LArray &new_items, int pos, bool is_new) override; bool OnDelete(LDataFolderI *parent, LArray &items) override; bool OnMove(LDataFolderI *new_parent, LDataFolderI *old_parent, LArray &Items) override; void SetContext(const char *file, int line) override; bool OnChange(LArray &items, int FieldHint) override; void Post(LDataStoreI *store, void *Param) override { PostEvent(M_STORAGE_EVENT, store->Id, (LMessage::Param)Param); } void OnPropChange(LDataStoreI *store, int Prop, LVariantType Type) override; bool Match(LDataStoreI *store, LDataPropI *Addr, int Type, LArray &Matches) override; ContactGroup *FindGroup(char *Name); bool AddStore3EventHandler(LDataEventsI *callback); bool RemoveStore3EventHandler(LDataEventsI *callback); // --------------------------------------------------------------------- // Events void OnDelete(); int OnNotify(LViewI *Ctrl, LNotification n) override; void OnPaint(LSurface *pDC) override; LMessage::Result OnEvent(LMessage *Msg) override; int OnCommand(int Cmd, int Event, OsView Handle) override; void OnPulse() override; void OnPulseSecond(); bool OnRequestClose(bool OsShuttingDown) override; void OnSelect(List *l = 0, bool ChangeEvent = false); void OnReceiveFiles(LArray &Files) override; void OnUrl(const char *Url) override; void OnZoom(LWindowZoom Action) override; void OnCreate() override; void OnMinute(); void OnHour(); bool OnIdle(); void OnBayesAnalyse(const char *Msg, const char *WhiteListEmail) override; /// \returns true if spam bool OnBayesResult(const char *MailRef, double Rating) override; /// \returns true if spam bool OnBayesResult(Mail *m, double Rating); void OnScriptCompileError(const char *Source, Filter *f); }; //////////////////////////////////////////////////////////////////////////////////// #ifdef SCRIBE_APP #include "ScribePrivate.h" #endif diff --git a/Code/ScribeAccount.cpp b/Code/ScribeAccount.cpp --- a/Code/ScribeAccount.cpp +++ b/Code/ScribeAccount.cpp @@ -1,560 +1,567 @@ /* ** FILE: ScribeAccount.cpp ** AUTHOR: Matthew Allen ** DATE: 19/10/99 ** DESCRIPTION: Scribe account ** ** Copyright (C) 2002, Matthew Allen ** fret@memecode.com */ // Includes #include #include #include #include #include "Scribe.h" #include "lgi/common/Edit.h" #include "ScribeAccountUI.h" #include "../Resources/resdefs.h" #include "lgi/common/LgiRes.h" #include "ScribeFolderSelect.h" //////////////////////////////////////////////////////////////////////////// class ScribeAccountPrivate { public: // Data int Index; LMenuItem *IdentityItem; ScribeAccountPrivate(ScribeWnd *App, int i) { IdentityItem = 0; Index = i; } }; //////////////////////////////////////////////////////////////////////////// ScribeAccount::ScribeAccount(ScribeWnd *App, int Index) : d(new ScribeAccountPrivate(App, Index)), Parent(App), Identity(this), Send(this), Receive(this) { CreateMaps(); } ScribeAccount::~ScribeAccount() { Receive.OnBeforeDelete(); Send.OnBeforeDelete(); Identity.OnBeforeDelete(); DeleteObj(d); } bool ScribeAccount::GetVariant(const char *Name, LVariant &Value, const char *Array) { ScribeDomType f = StrToDom(Name); char k[128]; LOptionsFile *Opts = GetApp()->GetOptions(); switch (f) { case SdIdentity: // Type: Accountlet.AccountIdentity Value = (LDom*)&Identity; break; case SdSend: // Type: Accountlet.SendAccountlet Value = (LDom*)&Send; break; case SdReceive: // Type: Accountlet.ReceiveAccountlet Value = (LDom*)&Receive; break; case SdIsValid: // Type: Bool Value = IsValid(); break; case SdIsOnline: // Type: Bool Value = IsOnline(); break; case SdScribe: // Type: ScribeWnd Value = (LDom*)Parent; break; case SdName: // Type: String return Opts->GetValue(Identity.OptionName(OPT_AccountName, k, sizeof(k)), Value); case SdId: // Type: String return Opts->GetValue(Identity.OptionName(OPT_AccountUID, k, sizeof(k)), Value); case SdDisable: // Type: Bool return Opts->GetValue(Identity.OptionName(OPT_AccountDisabled, k, sizeof(k)), Value); case SdAccountExpanded: // Type: Bool return Opts->GetValue(Identity.OptionName(OPT_AccountExpanded, k, sizeof(k)), Value); default: return false; } return true; } bool ScribeAccount::SetVariant(const char *Name, LVariant &Value, const char *Array) { ScribeDomType f = StrToDom(Name); char k[128]; LOptionsFile *Opts = GetApp()->GetOptions(); switch (f) { case SdName: // Type: String return Opts->SetValue(Identity.OptionName(OPT_AccountName, k, sizeof(k)), Value); case SdId: // Type: String return Opts->SetValue(Identity.OptionName(OPT_AccountUID, k, sizeof(k)), Value); case SdDisable: // Type: Bool return Opts->SetValue(Identity.OptionName(OPT_AccountDisabled, k, sizeof(k)), Value); case SdAccountExpanded: // Type: Bool return Opts->SetValue(Identity.OptionName(OPT_AccountExpanded, k, sizeof(k)), Value); default: return false; } return true; } bool ScribeAccount::CallMethod(const char *MethodName, LVariant *ReturnValue, LArray &Args) { ScribeDomType m = StrToDom(MethodName); switch (m) { case SdStop: // Type: () Stop(); break; case SdDisconnect: // Type: () Disconnect(); break; case SdKill: // Type: () Kill(); break; case SdEndSession: // Type: () OnEndSession(); break; default: return false; } return true; } bool ScribeAccount::IsValid() { char Key[128]; Receive.OptionName(OPT_AccountUID, Key, sizeof(Key)); LVariant v; if (Parent->GetOptions()->GetValue(Key, v)) { return v.CastInt32() > 0; } return false; } bool ScribeAccount::Create() { LVariant v; bool Status = false; char Key[128]; Receive.OptionName(0, Key, sizeof(Key)); // Check our tag exists LXmlTag *Tag = Parent->GetOptions()->LockTag(Key, _FL); if (!Tag) { Parent->GetOptions()->CreateTag(Key); Tag = Parent->GetOptions()->LockTag(Key, _FL); } if (Tag) { // Check our ID exists if (!Tag->GetAttr(OPT_AccountUID)) { int NewId; do { NewId = LRand() & 0x7fffffff; } while (Parent->GetAccountById(NewId)); Tag->SetAttr(OPT_AccountUID, NewId); } // Create our child tags, if missing Tag->GetChildTag("Identity", true); Tag->GetChildTag("Send", true); Tag->GetChildTag("Receive", true); Status = true; Parent->GetOptions()->Unlock(); // Check for the defaults v = Receive.Name(); if (!v.Str()) { Receive.Name("My ISP"); } v = Send.PrefCharset1(); if (!v.Str()) { SetDefaults(); } } return Status; } bool ScribeAccount::Delete() { ReIndex(-1); while (Views.Length()) { LListItem *i = Views[0]; DeleteObj(i); } return true; } void ScribeAccount::SetDefaults() { // Set the default charsets... const char *DefCharset = LLoadString(IDS_DEFAULT_CHARSET_SEND); if (DefCharset) { LToken t(DefCharset, ","); for (unsigned i=0; iIndex = i; } ScribeFolder *&ScribeAccount::GetRoot() { return Receive.Root; } LMenuItem *ScribeAccount::GetMenuItem() { return d->IdentityItem; } void ScribeAccount::SetMenuItem(LMenuItem *i) { d->IdentityItem = i; } void ScribeAccount::SetCheck(bool c) { if (d->IdentityItem) { d->IdentityItem->Checked(c); } } void ScribeAccount::CreateMaps() { char Name[256] = ""; Map(Send.OptionName(OPT_AccountName, Name, sizeof(Name)), IDC_ACCOUNT_NAME, GV_STRING); Identity.CreateMaps(); Send.CreateMaps(); Receive.CreateMaps(); } void ScribeAccount::ReIndex(int i) { char Key[128]; Receive.OptionName(0, Key, sizeof(Key)); EmptyMaps(); if (i >= 0) { LXmlTag *Tag = GetApp()->GetOptions()->LockTag(Key, _FL); if (Tag) { sprintf_s(Key, sizeof(Key), "Account-%i", i); Tag->SetTag(Key); d->Index = i; GetApp()->GetOptions()->Unlock(); } CreateMaps(); } else { GetApp()->GetOptions()->DeleteTag(Key); } } int ScribeAccount::GetIndex() { LAssert(d != NULL); return d ? d->Index : -1; } bool ScribeAccount::IsOnline() { return Send.IsOnline() || Receive.IsOnline(); } bool ScribeAccount::InitMenus() { bool Status = Send.InitMenus(); return Receive.InitMenus() && Status; } void ScribeAccount::Stop() { if (Send.IsOnline()) { Send.Disconnect(); } if (Receive.IsOnline()) { Receive.Disconnect(); } } void ScribeAccount::Kill() { if (Send.IsOnline()) { Send.Kill(); } if (Receive.IsOnline()) { Receive.Kill(); } } void ScribeAccount::OnPulse(char *s, int s_len) { Send.OnPulse(s, s_len); Receive.OnPulse(s, s_len); } void ScribeAccount::SerializeUi(LView *Wnd, bool Load) { if (Wnd) { LVariant v; // Special Fields LEdit *Pop3Folder; if (Wnd->GetViewById(IDC_FOLDER, Pop3Folder)) { Pop3Folder->Enabled(false); } GPassword s, r; char Buf[128]; if (Load) { LEdit *e; bool HasPass; HasPass = s.Serialize(Parent->GetOptions(), Receive.OptionName(OPT_EncryptedSmtpPassword, Buf, sizeof(Buf)), false); Wnd->SetCtrlValue(IDC_SMTP_AUTH, HasPass); if (HasPass && Wnd->GetViewById(IDC_SMTP_PASSWORD, e)) e->SetEmptyText(LLoadString(IDS_PASSWORD_SAVED)); HasPass = r.Serialize(Parent->GetOptions(), Receive.OptionName(OPT_EncryptedPop3Password, Buf, sizeof(Buf)), false); Wnd->SetCtrlValue(IDC_REMEMBER_PSW, HasPass); Wnd->SetCtrlEnabled(IDC_REC_PASSWORD, HasPass); if (HasPass && Wnd->GetViewById(IDC_REC_PASSWORD, e)) e->SetEmptyText(LLoadString(IDS_PASSWORD_SAVED)); } else { if (Wnd->GetCtrlValue(IDC_REMEMBER_PSW)) { const char *p = Wnd->GetCtrlName(IDC_REC_PASSWORD); if (ValidStr(p)) { r.Set(p); r.Serialize(Parent->GetOptions(), Receive.OptionName(OPT_EncryptedPop3Password, Buf, sizeof(Buf)), true); } } else r.Delete(Parent->GetOptions(), Receive.OptionName(OPT_EncryptedPop3Password, Buf, sizeof(Buf))); if (Wnd->GetCtrlValue(IDC_SMTP_AUTH)) { const char *p = Wnd->GetCtrlName(IDC_SMTP_PASSWORD); if (ValidStr(p)) { s.Set(p); s.Serialize(Parent->GetOptions(), Send.OptionName(OPT_EncryptedSmtpPassword, Buf, sizeof(Buf)), true); } } else s.Delete(Parent->GetOptions(), Send.OptionName(OPT_EncryptedSmtpPassword, Buf, sizeof(Buf))); } // Normal Fields Convert(Parent->GetOptions(), Wnd, Load); } } -bool ScribeAccount::InitUI(LView *Parent, int Tab) +void ScribeAccount::InitUI(LView *Parent, int Tab, std::function callback) { - AccountDlg Dlg(Parent, this->Parent, this, Tab); - return Dlg.DoModal() == IDOK; + auto Dlg = new AccountDlg(Parent, this->Parent, this, Tab); + Dlg->DoModal([callback](auto dlg, auto id) + { + if (callback) + callback(id); + delete dlg; + }); } int ScribeAccount::OnNotify(LViewI *Ctrl, LNotification &n) { LWindow *Wnd = Ctrl->GetWindow(); if (!Wnd) return 0; switch (Ctrl->GetId()) { case IDC_REMEMBER_PSW: { Wnd->SetCtrlEnabled(IDC_REC_PASSWORD, Ctrl->Value() != 0); if (Ctrl->Value()) { LViewI *c = Wnd->FindControl(IDC_REC_PASSWORD); if (c && c->IsAttached()) { c->Focus(true); } } else { Wnd->SetCtrlName(IDC_REC_PASSWORD, 0); } break; } case IDC_SMTP_AUTH: { Wnd->SetCtrlEnabled(IDC_SMTP_PASSWORD, Ctrl->Value() != 0); break; } case IDC_PICK_FOLDER: { - FolderDlg Dlg(Parent, Parent); - if (Dlg.DoModal()) + auto Dlg = new FolderDlg(Parent, Parent); + Dlg->DoModal([this, Dlg, Ctrl](auto dlg, auto id) { - Ctrl->GetWindow()->SetCtrlName(IDC_FOLDER, Dlg.Get()); - } + if (id) + Ctrl->GetWindow()->SetCtrlName(IDC_FOLDER, Dlg->Get()); + delete dlg; + }); break; } } return 0; } bool ScribeAccount::Disconnect() { if (Send.IsOnline()) { Send.Disconnect(); } if (Receive.IsOnline()) { Receive.Disconnect(); } return true; } //////////////////////////////////////////////////////////////////////////// AccountThread::AccountThread(Accountlet *acc) : LThread("AccountThread") { DeleteOnExit = false; Acc = acc; } AccountThread::~AccountThread() { if (!LAppInst->InThread()) { LAssert(0); } if (!IsExited()) Cancel(true); uint64 Start = LCurrentTime(); while (!IsExited()) { // Give the thread a tiny bit of time to exit // It's just got to return after posting the message LSleep(100); if (LCurrentTime() - Start > 1000) { // Took too long... is it really exiting? LAssert(0); } if (LCurrentTime() - Start > 30000) { // Broken... cut out losses and exit. Terminate(); break; } } } void AccountThread::OnAfterMain() { if (Acc && Acc->GetApp()) { Acc->GetApp()->PostEvent(M_SCRIBE_THREAD_DONE, (LMessage::Param)this, (LMessage::Param)Acc); } else { LAssert(0); } } void AccountThread::Disconnect() { Cancel(true); } bool AccountThread::Kill() { Cancel(true); Terminate(); return true; } diff --git a/Code/ScribeAccountUI.cpp b/Code/ScribeAccountUI.cpp --- a/Code/ScribeAccountUI.cpp +++ b/Code/ScribeAccountUI.cpp @@ -1,345 +1,345 @@ #include "Scribe.h" #include "ScribePrivate.h" #include "ScribeAccountUI.h" #include "resdefs.h" #include "lgi/common/ListItemCheckBox.h" #include "lgi/common/Combo.h" #include "lgi/common/Edit.h" #include "lgi/common/TabView.h" #include "lgi/common/TableLayout.h" #include "lgi/common/Button.h" #include "lgi/common/LgiRes.h" #include "lgi/common/RichTextEdit.h" #include "lgi/common/Charset.h" #define DefaultPortValue LLoadString(IDS_DEFAULT) void PopulateTypes(LCombo *c) { if (c) { c->Insert(PROTOCOL_POP3); c->Insert(PROTOCOL_IMAP4_FETCH); c->Insert(PROTOCOL_IMAP4); c->Insert(PROTOCOL_CALENDAR); c->Insert(PROTOCOL_POP_OVER_HTTP); #ifdef WIN32 c->Insert(PROTOCOL_MAPI); #endif // Old order: // Pop, Imap(fetch), Mapi, Cal, Http } } void FillWithCharsets(LCombo *c, bool All) { if (c) { c->Value(-1); c->Sort(true); c->Sub(GV_STRING); for (LCharset *Cs = LGetCsInfo("us-ascii"); Cs->Charset; Cs++) { if (All || Cs->Type == CpMapped || Cs->Type == CpUtf8) { c->Insert(Cs->Charset); } } } } AccountDlg::AccountDlg(LView *p, ScribeWnd *app, ScribeAccount *a, int Tab) : TabDialog(IDC_TAB, IDC_LAUNCH_HELP) { SetParent(p); App = app; Account = a; Plugins = 0; SendServer = NULL; SendPort = NULL; ReceiveServer = NULL; ReceivePort = NULL; if (LoadFromResource(IDD_ACCOUNT, App->GetUiTags())) { MoveToCenter(); LRichTextEdit *Rte; if (GetViewById(IDC_SIG_HTML, Rte)) Rte->SetStylePrefix("Sig"); PopulateTypes(dynamic_cast(FindControl(IDC_REC_TYPE))); FillWithCharsets(dynamic_cast(FindControl(IDC_SEND_CHARSET1)), false); FillWithCharsets(dynamic_cast(FindControl(IDC_SEND_CHARSET2)), false); FillWithCharsets(dynamic_cast(FindControl(IDC_REC_8BIT_CS)), true); FillWithCharsets(dynamic_cast(FindControl(IDC_REC_ASCII_CP)), true); LCombo *c; if (GetViewById(IDC_SEND_AUTH_TYPE, c)) { c->Insert(LLoadString(IDS_ALL)); c->Insert("PLAIN"); c->Insert("LOGIN"); c->Insert("CRAM-MD5"); c->Insert("OAUTH2"); } if (GetViewById(IDC_RECEIVE_AUTH_TYPE, c)) { c->Insert(LLoadString(IDS_ALL)); c->Insert("PLAIN"); c->Insert("LOGIN"); c->Insert("NTLM"); c->Insert("OAUTH2"); } const char *DefServerTxt = "i.e. mail.isp.com"; if (GetViewById(IDC_SMTP_SERVER, SendServer)) SendServer->SetEmptyText(DefServerTxt); if (GetViewById(IDC_REC_SERVER, ReceiveServer)) ReceiveServer->SetEmptyText(DefServerTxt); GetViewById(IDC_SMTP_PORT, SendPort); GetViewById(IDC_REC_PORT, ReceivePort); Account->SerializeUi(this, true); SetCtrlEnabled(IDC_SMTP_PASSWORD, GetCtrlValue(IDC_SMTP_AUTH) != 0); LNotification note(LNotifyValueChanged); OnNotify(FindControl(IDC_SMTP_SERVER), note); SetCtrlValue(IDC_TAB, Tab); SetCtrlEnabled(IDC_REC_CHECK, GetCtrlValue(IDC_CHECK_EVERY) != 0); LViewI *v = FindControl(IDC_POP3_LEAVE); if (v) OnNotify(v, note); v = FindControl(IDC_REC_TYPE); if (v) OnNotify(v, note); LLayout *t; if (GetViewById(IDC_SIG, t)) { t->SetPourLargest(true); t->Sunken(true); } if (GetViewById(IDC_SIG_HTML, t)) { t->SetPourLargest(true); t->Sunken(true); } } } void AccountDlg::OnCreate() { LTabView *t; if (GetViewById(IDC_SIGNATURE_TAB, t)) { t->SetPourChildren(true); } TabDialog::OnCreate(); } void AccountDlg::UpdateDefaultPort(bool Send) { if (Send) { int DefPort = SMTP_PORT; int64 Ssl = GetCtrlValue(IDC_SEND_SSL); if (Ssl == 2) DefPort = SMTP_SSL_PORT; char s[256]; sprintf_s(s, sizeof(s), "%s: %i", LLoadString(IDC_DEFAULT), DefPort); if (SendPort) { if (SendPort->Value() == 0) SendPort->Name(NULL); SendPort->SetEmptyText(s); } } else { int DefPort = 0; const char *Type = GetCtrlName(IDC_REC_TYPE); int64 Ssl = GetCtrlValue(IDC_RECEIVE_SSL); if (Type) { if (!_stricmp(Type, PROTOCOL_POP3)) { DefPort = (Ssl == 2) ? POP3_SSL_PORT : POP3_PORT; } else if (!_stricmp(Type, PROTOCOL_IMAP4) || !_stricmp(Type, PROTOCOL_IMAP4_FETCH)) { DefPort = (Ssl == 2) ? IMAP_SSL_PORT : IMAP_PORT; } else if (!_stricmp(Type, PROTOCOL_POP_OVER_HTTP)) { DefPort = (Ssl == 2) ? HTTPS_PORT : HTTP_PORT; } } if (DefPort) { char s[256]; sprintf_s(s, sizeof(s), "%s: %i", LLoadString(IDC_DEFAULT), DefPort); if (ReceivePort) { if (ReceivePort->Value() == 0) ReceivePort->Name(NULL); ReceivePort->SetEmptyText(s); } } } } int AccountDlg::OnNotify(LViewI *c, LNotification n) { if (!c) return 0; switch (c->GetId()) { default: { Account->OnNotify(c, n); break; } case IDC_POP3_LEAVE: { bool Leave = c->Value() != 0; SetCtrlEnabled(IDC_DELETE_AFTER, Leave); SetCtrlEnabled(IDC_DELETE_LARGER, Leave); bool Delete = GetCtrlValue(IDC_DELETE_AFTER) != 0; SetCtrlEnabled(IDC_DELETE_DAYS, Leave && Delete); SetCtrlEnabled(IDC_TXT_DAYS, Leave && Delete); Delete = GetCtrlValue(IDC_DELETE_LARGER) != 0; SetCtrlEnabled(IDC_DELETE_SIZE, Leave && Delete); SetCtrlEnabled(IDC_TXT_SIZE, Leave && Delete); break; } case IDC_DELETE_AFTER: { bool Delete = c->Value() != 0; SetCtrlEnabled(IDC_DELETE_DAYS, Delete); SetCtrlEnabled(IDC_TXT_DAYS, Delete); break; } case IDC_DELETE_LARGER: { bool Delete = c->Value() != 0; SetCtrlEnabled(IDC_DELETE_SIZE, Delete); SetCtrlEnabled(IDC_TXT_SIZE, Delete); break; } case IDC_CHECK_EVERY: { SetCtrlEnabled(IDC_REC_CHECK, GetCtrlValue(IDC_CHECK_EVERY) != 0); break; } case IDC_SMTP_SERVER: { bool HasSend = ValidStr(GetCtrlName(IDC_SMTP_SERVER)); SetCtrlEnabled(IDC_ONLY_SEND_THIS, HasSend); if (!HasSend) SetCtrlValue(IDC_ONLY_SEND_THIS, false); break; } case IDC_LAUNCH_HELP: { char Page[256] = "install.html"; switch (GetCtrlValue(IDC_TAB)) { case 0: strcat_s(Page, sizeof(Page), "#acc_id"); break; case 1: strcat_s(Page, sizeof(Page), "#acc_send"); break; case 2: strcat_s(Page, sizeof(Page), "#acc_receive"); break; case 3: strcat_s(Page, sizeof(Page), "#acc_connect"); break; } App->LaunchHelp(Page); break; } case IDC_TAB: { if (c->Value() == 1) { UpdateDefaultPort(true); } else if (c->Value() == 2) { UpdateDefaultPort(false); } break; } case IDC_REC_TYPE: { int64 Type = c->Value(); bool PopType = Type == 0 || Type == 1; SetCtrlEnabled(IDC_POP3_LEAVE, PopType); SetCtrlEnabled(IDC_DELETE_AFTER, PopType); SetCtrlEnabled(IDC_TXT_DAYS, PopType); SetCtrlEnabled(IDC_DELETE_DAYS, PopType); SetCtrlEnabled(IDC_DELETE_LARGER, PopType); SetCtrlEnabled(IDC_DELETE_SIZE, PopType); SetCtrlEnabled(IDC_TXT_MIN, PopType); SetCtrlEnabled(IDC_TXT_KBS, PopType); SetCtrlEnabled(IDC_TXT_DOWNLOAD, PopType); SetCtrlEnabled(IDC_CHECK_EVERY, PopType); SetCtrlEnabled(IDC_REC_CHECK, PopType); SetCtrlEnabled(IDC_MAX_SIZE, PopType); if (!PopType) { SetCtrlValue(IDC_POP3_LEAVE, 0); SetCtrlValue(IDC_DELETE_AFTER, 0); SetCtrlValue(IDC_DELETE_LARGER, 0); } // fall through } case IDC_RECEIVE_SSL: { UpdateDefaultPort(false); break; } case IDC_SEND_SSL: { UpdateDefaultPort(true); break; } case IDOK: { - if (App->GetAccountSettingsAccess(this, ScribeWriteAccess)) + App->GetAccountSettingsAccess(this, ScribeWriteAccess, [&](auto Allow) { - if (!ValidStr(GetCtrlName(IDC_ACCOUNT_NAME))) + if (Allow) { - SetCtrlName(IDC_ACCOUNT_NAME, "My ISP"); + if (!ValidStr(GetCtrlName(IDC_ACCOUNT_NAME))) + SetCtrlName(IDC_ACCOUNT_NAME, "My ISP"); + Account->SerializeUi(this, false); + + EndModal(c->GetId()); } - - Account->SerializeUi(this, false); - } - else break; - - // fall through + }); + break; } case IDCANCEL: { EndModal(c->GetId()); break; } } return 0; } diff --git a/Code/ScribeApp.cpp b/Code/ScribeApp.cpp --- a/Code/ScribeApp.cpp +++ b/Code/ScribeApp.cpp @@ -1,12470 +1,12701 @@ /* ** FILE: ScribeApp.cpp ** AUTHOR: Matthew Allen ** DATE: 22/10/1998 ** DESCRIPTION: Scribe email application ** ** Copyright (C) 1998, Matthew Allen ** fret@memecode.com */ // Debug defines // #define PRINT_OUT_STORAGE_TREE // #define TEST_OBJECT_SIZE #define USE_SPELLCHECKER 1 #define USE_INTERNAL_BROWSER 1 // for help #define RUN_STARTUP_SCRIPTS 1 #define PROFILE_ON_PULSE 0 #define TRAY_CONTACT_BASE 1000 #define TRAY_MAIL_BASE 10000 // Includes #include #include #include #include +#include #include "Scribe.h" #include "lgi/common/StoreConvert1To2.h" #include "lgi/common/NetTools.h" #include "lgi/common/ProgressDlg.h" #include "lgi/common/TextLabel.h" #include "lgi/common/Button.h" #include "lgi/common/CheckBox.h" #include "lgi/common/OpenSSLSocket.h" #include "lgi/common/SoftwareUpdate.h" #include "lgi/common/Html.h" #include "lgi/common/TextView3.h" #include "lgi/common/RichTextEdit.h" #include "lgi/common/Browser.h" #include "lgi/common/ClipBoard.h" #include "lgi/common/Store3.h" #include "lgi/common/Growl.h" #include "lgi/common/Edit.h" #include "lgi/common/Box.h" #include "lgi/common/LgiRes.h" #include "lgi/common/SpellCheck.h" #include "lgi/common/SubProcess.h" #include "lgi/common/CssTools.h" #include "lgi/common/Map.h" #include "lgi/common/Charset.h" +#include "lgi/common/RefCount.h" #include "ScribePrivate.h" #include "PreviewPanel.h" #include "ScribeStatusPanel.h" #include "ScribeFolderDlg.h" #include "ScribePageSetup.h" #include "Calendar.h" #include "CalendarView.h" #include "ScribeSpellCheck.h" #include "Store3Common.h" #include "PrintContext.h" #include "resource.h" #include "ManageMailStores.h" #include "ReplicateDlg.h" #include "ScribeAccountPreview.h" #include "Encryption/GnuPG.h" #include "Store3Webdav/WebdavStore.h" #include "../Resources/resdefs.h" #include "../UnitTests/UnitTest.h" #include "../src/common/Coding/ScriptingPriv.h" #define DEBUG_STORE_EVENTS 0 #if DEBUG_STORE_EVENTS #define LOG_STORE(...) LgiTrace(__VA_ARGS__) #else #define LOG_STORE(...) #endif #define IDM_LOAD_MSG 2000 #define RAISED_LOOK 0 #define SUNKEN_LOOK false #ifdef MAC #define SUNKEN_CTRL false #else #define SUNKEN_CTRL true #endif #if LGI_CARBON #define TRAY_ICON_NONE -1 #define TRAY_ICON_NORMAL -1 #define TRAY_ICON_MAIL 0 #define TRAY_ICON_ERROR 1 #elif defined(WIN32) #define TRAY_ICON_NORMAL 0 #define TRAY_ICON_ERROR 1 #define TRAY_ICON_MAIL 2 #define TRAY_ICON_NONE 3 #else #define TRAY_ICON_NONE -1 #define TRAY_ICON_NORMAL 0 #define TRAY_ICON_ERROR 1 #define TRAY_ICON_MAIL 2 #endif char ScribeThingList[] = "com.memecode.ThingList"; ScribeClipboardFmt *ScribeClipboardFmt::Alloc(bool ForFolders, size_t Size) { ScribeClipboardFmt *obj = (ScribeClipboardFmt*) calloc(sizeof(ScribeClipboardFmt)+((Size-1)*sizeof(Thing*)), 1); if (obj) { memcpy(obj->Magic, ForFolders ? ScribeFolderMagic : ScribeThingMagic, sizeof(obj->Magic)); obj->ProcessId = LAppInst->GetProcessId(); obj->Len = (uint32_t)Size; } return obj; } ScribeClipboardFmt *ScribeClipboardFmt::Alloc(List &Lst) { ScribeClipboardFmt *Fmt = Alloc(false, Lst.Length()); for (unsigned i=0; iThingAt(i, Lst[i]); return Fmt; } ScribeClipboardFmt *ScribeClipboardFmt::Alloc(LArray &Arr) { ScribeClipboardFmt *Fmt = Alloc(false, Arr.Length()); for (unsigned i=0; iThingAt(i, Arr[i]); return Fmt; } bool ScribeClipboardFmt::Is(const char *Type, void *Ptr, size_t Bytes) { // Do we have the minimum bytes for the structure? if (Bytes >= sizeof(ScribeClipboardFmt) && Ptr != NULL) { ScribeClipboardFmt *This = (ScribeClipboardFmt*)Ptr; // Check the magic is the right value if (memcmp(This->Magic, Type, 4) != 0) return false; // Check it's from this process if (This->ProcessId != LAppInst->GetProcessId()) return false; return true; } return false; } Thing *ScribeClipboardFmt::ThingAt(size_t Idx, Thing *Set) { if (memcmp(Magic, ScribeThingMagic, 4)) return NULL; if (Idx >= Len) return NULL; if (Set) Things[Idx] = Set; return Things[Idx]; } ScribeFolder *ScribeClipboardFmt::FolderAt(size_t Idx, ScribeFolder *Set) { if (memcmp(Magic, ScribeFolderMagic, 4)) return NULL; if (Idx >= Len) return NULL; if (Set) Folders[Idx] = Set; return Folders[Idx]; } size_t ScribeClipboardFmt::Sizeof() { return sizeof(*this) + ((Len - 1) * sizeof(Thing*)); } bool OptionSizeInKiB = false; bool ShowRelativeDates = false; const char *MailAddressDelimiters = "\t\r\n;,"; char16 SpellDelim[] = { ' ', '\t', '\r', '\n', ',', ',', '.', ':', ';', '{', '}', '[', ']', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '-', '+', '=', '|', '\\', '/', '?', '\"', 0 }; const char *DefaultRfXml = "---------- %s ----------\n" "%s: ()\n" "%s: ()\n" "%s: \n" "%s: \n" "\n" "\n" "\n" "\n"; uchar DateTimeFormats[] = { GDTF_DEFAULT, GDTF_DAY_MONTH_YEAR | GDTF_12HOUR, GDTF_MONTH_DAY_YEAR | GDTF_12HOUR, GDTF_YEAR_MONTH_DAY | GDTF_12HOUR, GDTF_DAY_MONTH_YEAR | GDTF_24HOUR, GDTF_MONTH_DAY_YEAR | GDTF_24HOUR, GDTF_YEAR_MONTH_DAY | GDTF_24HOUR }; SystemFolderInfo SystemFolders[] = { {FOLDER_INBOX, OPT_Inbox, NULL}, {FOLDER_OUTBOX, OPT_Outbox, NULL}, {FOLDER_SENT, OPT_Sent, NULL}, {FOLDER_CONTACTS, OPT_Contacts, NULL}, {FOLDER_TRASH, OPT_Trash, NULL}, {FOLDER_CALENDAR, OPT_Calendar, OPT_HasCalendar}, {FOLDER_TEMPLATES, OPT_Templates, OPT_HasTemplates}, {FOLDER_FILTERS, OPT_Filters, OPT_HasFilters}, {FOLDER_GROUPS, OPT_Groups, OPT_HasGroups}, {FOLDER_SPAM, OPT_SpamFolder, OPT_HasSpam}, {-1, 0, 0} }; ScribeBehaviour *ScribeBehaviour::New(ScribeWnd *app) { return 0; } void ScribeOptionsDefaults(LOptionsFile *f) { - if (f) - { - f->CreateTag("Accounts"); - f->CreateTag("CalendarUI"); - f->CreateTag("CalendarUI.Sources"); - f->CreateTag("MailUI"); - f->CreateTag("ScribeUI"); - f->CreateTag("Plugins"); - f->CreateTag("Print"); - } + if (!f) + return; + + f->CreateTag("Accounts"); + f->CreateTag("CalendarUI"); + f->CreateTag("CalendarUI.Sources"); + f->CreateTag("MailUI"); + f->CreateTag("ScribeUI"); + f->CreateTag("Plugins"); + f->CreateTag("Print"); + + #define DefaultIntOption(opt, def) { LVariant v; if (!f->GetValue(opt, v)) \ + f->SetValue(opt, v = (int)def); } + #define DefaultStrOption(opt, def) { LVariant v; if (!f->GetValue(opt, v)) \ + f->SetValue(opt, v = def); } + DefaultIntOption(OPT_DefaultAlternative, 1); + DefaultIntOption(OPT_BoldUnread, 1); + DefaultIntOption(OPT_PreviewLines, 1); + DefaultIntOption(OPT_AutoDeleteExe, 1); + DefaultIntOption(OPT_DefaultReplyAllSetting, MAIL_ADDR_BCC); + DefaultIntOption(OPT_BlinkNewMail, 1); + DefaultIntOption(OPT_MarkReadAfterSeconds, 5); + DefaultStrOption(OPT_BayesThreshold, "0.9"); + DefaultIntOption(OPT_SoftwareUpdate, 1); + DefaultIntOption(OPT_ResizeImgAttachments, false); + DefaultIntOption(OPT_ResizeJpegQual, 80); + DefaultIntOption(OPT_ResizeMaxPx, 1024); + DefaultIntOption(OPT_ResizeMaxKb, 200); + DefaultIntOption(OPT_RegisterWindowsClient, 1); + DefaultIntOption(OPT_HasTemplates, 0); + DefaultIntOption(OPT_HasCalendar, 1); + DefaultIntOption(OPT_HasGroups, 1); + DefaultIntOption(OPT_HasFilters, 1); + DefaultIntOption(OPT_HasSpam, 0); } const char *Store3ItemTypeName(Store3ItemTypes t) { switch (t) { case MAGIC_NONE: return "MAGIC_NONE"; case MAGIC_BASE: return "MAGIC_BASE"; case MAGIC_MAIL: return "MAGIC_MAIL"; case MAGIC_CONTACT: return "MAGIC_CONTACT"; // case MAGIC_FOLDER: return "MAGIC_FOLDER"; case MAGIC_MAILBOX: return "MAGIC_MAILBOX"; case MAGIC_ATTACHMENT: return "MAGIC_ATTACHMENT"; case MAGIC_ANY: return "MAGIC_ANY"; case MAGIC_FILTER: return "MAGIC_FILTER"; case MAGIC_FOLDER: return "MAGIC_FOLDER"; case MAGIC_CONDITION: return "MAGIC_CONDITION"; case MAGIC_ACTION: return "MAGIC_ACTION"; case MAGIC_CALENDAR: return "MAGIC_CALENDAR"; case MAGIC_ATTENDEE: return "MAGIC_ATTENDEE"; case MAGIC_GROUP: return "MAGIC_GROUP"; default: LAssert(0); break; } return "(error)"; } void SetRecipients(ScribeWnd *App, char *Start, GDataIt l, EmailAddressType CC) { while (Start && *Start) { LString Str; char *End = strchr(Start, ','); if (End) { Str.Set(Start, End-Start); Start = End + 1; } else { Str = Start; Start = 0; } if (Str) { ListAddr *a = new ListAddr(App); if (a) { a->CC = CC; if (_strnicmp(Str, "mailto:", 7) == 0) a->sAddr = Str(7,-1); else a->sAddr = Str; l->Insert(a); } } } } static char SoftwareUpdateUri[] = "http://www.memecode.com/update.php"; enum SoftwareStatus { SwError, SwCancel, SwOutOfDate, SwUpToDate }; static LString ExtractVer(const char *s) { char Buf[256], *Out = Buf; for (const char *In = s; *In && Out < Buf + sizeof(Buf) - 1; In++) { if (*In == ' ') break; if (IsDigit(*In) || *In == '.') *Out++ = *In; } *Out++ = 0; return LString(Buf); } void IsSoftwareUpToDate(LSoftwareUpdate::UpdateInfo &Info, ScribeWnd *Parent, bool WithUI, bool IncBetas, std::function callback) { // Software update? LAutoString Proxy = Parent->GetHttpProxy(); LSoftwareUpdate Update(AppName, SoftwareUpdateUri, Proxy); Update.CheckForUpdate( Info, WithUI?Parent:0, IncBetas, [Info, WithUI, Parent, callback](auto status, auto errorMsg) { if (status) { auto LocalVer = LString(ScribeVer).SplitDelimit("."); LString BuildVer = ExtractVer(Info.Build); LToken OnlineVer(BuildVer, "."); if (OnlineVer.Length() != LocalVer.Length()) { LgiTrace("%s:%i - Invalid online version number \"%s\"\n", _FL, Info.Version.Get()); if (callback) callback(SwError); return; } unsigned i; for (i=0; i o) { if (callback) callback(SwUpToDate); return; } } LDateTime Compile; LToken Date(__DATE__, " "); Compile.Month(LDateTime::MonthFromName(Date[0])); Compile.Day(atoi(Date[1])); Compile.Year(atoi(Date[2])); Compile.SetTime(__TIME__); bool DateGreaterThenCompile = Info.Date > Compile; if (callback) callback(DateGreaterThenCompile ? SwOutOfDate : SwUpToDate); return; } else if (WithUI) { if (Info.Cancel) { if (callback) callback(SwCancel); return; } LgiMsg(Parent, LLoadString(IDS_ERROR_SOFTWARE_UPDATE), AppName, MB_OK, errorMsg); } if (callback) callback(SwError); }); } bool UpgradeSoftware(const LSoftwareUpdate::UpdateInfo &Info, ScribeWnd *Parent, bool WithUI) { bool DownloadUpdate = true; if (WithUI) { char Ds[64]; Info.Date.Get(Ds, sizeof(Ds)); DownloadUpdate = LgiMsg(Parent, LLoadString(IDS_SOFTWARE_UPDATE_DOWNLOAD), AppName, MB_YESNO, (char*)Info.Build, (char*)Info.Uri, Ds) == IDYES; } if (!DownloadUpdate) return false; LAutoString Proxy = Parent->GetHttpProxy(); LSoftwareUpdate Update(AppName, SoftwareUpdateUri, Proxy, ScribeTempPath()); return Update.ApplyUpdate(Info, false, Parent); } void SoftwareUpdate(ScribeWnd *Parent, bool WithUI, bool IncBetas, std::function callback) { // Software update? LSoftwareUpdate::UpdateInfo Info; IsSoftwareUpToDate(Info, Parent, WithUI, IncBetas, [WithUI, Parent, Info, callback](auto s) { if (s == SwUpToDate) { if (WithUI) LgiMsg(Parent, LLoadString(IDS_SOFTWARE_CURRENT), AppName, MB_OK); if (callback) callback(false); // we're up to date } else if (s == SwOutOfDate) { auto status = UpgradeSoftware(Info, Parent, WithUI); if (callback) callback(status); // update is going to happen } }); } const char *AppName = "Scribe"; char HelpFile[] = "index.html"; const char OptionsFileName[] = "ScribeOptions"; const char AuthorEmailAddr[] = "fret@memecode.com"; const char AuthorHomepage[] = "http://www.memecode.com"; const char ApplicationHomepage[] = "http://www.memecode.com/scribe.php"; const char CommercialHomepage[] = "http://www.memecode.com/inscribe.php"; const char FaqHomepage[] = "http://www.memecode.com/scribe/faq.php"; const char *DefaultFolderNames[16]; Store3ItemTypes DefaultFolderTypes[] = { MAGIC_MAIL, // Inbox MAGIC_MAIL, // Outbox MAGIC_MAIL, // Sent MAGIC_ANY, // Trash MAGIC_CONTACT, // Contacts MAGIC_MAIL, // Templates MAGIC_FILTER, // Filters MAGIC_CALENDAR, // Calendar Events MAGIC_GROUP, // Groups MAGIC_MAIL, // Spam MAGIC_NONE, MAGIC_NONE, MAGIC_NONE, MAGIC_NONE }; extern void Log(char *File, char *Str, ...); ////////////////////////////////////////////////////////////////////////////// void LogMsg(char *str, ...) { #ifdef _DEBUG char f[256]; LMakePath(f, sizeof(f), LGetExePath(), "log.txt"); if (str) { char buffer[256]; va_list arg; va_start(arg ,str); vsprintf_s(buffer, sizeof(buffer), str, arg); va_end(arg); LFile File; while (!File.Open(f, O_WRITE)) { LSleep(5); } File.Seek(File.GetSize(), SEEK_SET); File.Write(buffer, strlen(buffer)); } else { FileDev->Delete(f, false); } #endif } LString GetFullAppName(bool Platform) { LString Ret = AppName; if (Platform) { LString s; const char *Build = #ifndef _DEBUG "Release"; #else "Debug"; #endif LArray Ver; int Os = LGetOs(&Ver); const char *OsName = LGetOsName(); if (Os == LGI_OS_WIN9X) { switch (Ver[1]) { case 0: OsName = "Win95"; break; case 10: OsName = "Win98"; break; case 90: OsName = "WinME"; break; } } else if (Os == LGI_OS_WIN32 || Os == LGI_OS_WIN64) { if (Ver[0] < 5) { OsName = "WinNT"; } else if (Ver[0] == 5) { if (Ver[1] == 0) OsName = "Win2k"; else OsName = "WinXP"; } else if (Ver[0] == 6) { if (Ver[1] == 0) OsName = "Vista"; else if (Ver[1] == 1) OsName = "Win7"; else if (Ver[1] == 2) OsName = "Win8"; else if (Ver[1] == 3) OsName = "Win8.1"; } else if (Ver[0] == 10) { OsName = "Win10"; } else if (Ver[0] == 11) { // What's the chances eh? OsName = "Win11"; } } s.Printf(" v%s (%s v", ScribeVer, OsName); Ret += s; for (unsigned i=0; iId); Ret += s; } s.Printf(")"); Ret += s; } return Ret; } bool MatchWord(char *Str, char *Word) { bool Status = false; if (Str && Word) { #define IsWord(c) ( IsDigit(c) || IsAlpha(c) ) for (char *s=stristr(Str, Word); s; s=stristr(s+1, Word)) { char *e = s + strlen(Word); if ( (s<=Str || !IsWord(s[-1]) ) && (e[0] == 0 || !IsWord(e[0])) ) { return true; } } } return Status; } ////////////////////////////////////////////////////////////////////////////// ScribePanel::ScribePanel(ScribeWnd *app, const char *name, int size, bool open) : LPanel(name, size, open) { App = app; } bool ScribePanel::Pour(LRegion &r) { if (App) { SetClosedSize(App->GetToolbarHeight()); } return LPanel::Pour(r); } ////////////////////////////////////////////////////////////////////////////// class NoContactType : public Contact { LString NoFace80Path; LString NoFace160Path; public: NoContactType(ScribeWnd *wnd) : Contact(wnd) { } Thing &operator =(Thing &c) override { return *this; } bool GetVariant(const char *Name, LVariant &Value, const char *Array) override { ScribeDomType Fld = StrToDom(Name); int Px = Array ? atoi(Array) : 80; LString &Str = Px == 160 ? NoFace160Path : NoFace80Path; if (!Str) { LString f; f.Printf("NoFace%i.png", Px); Str = LFindFile(f); LAssert(Str != NULL); // This should always resolve. } if (!Str) return false; if (Fld == SdImageHtml) { LString html; html.Printf("\n", Str.Get()); Value = html; return true; } else if (Fld == SdImage) { Value = Str; return true; } return false; } }; class ScribeWndPrivate : public LBrowser::LBrowserEvents, public LVmDebuggerCallback, public LHtmlStaticInst { LOptionsFile::PortableType InstallMode = LOptionsFile::UnknownMode; public: ScribeWnd *App; uint64 LastTs = 0; int ClipboardFormat = 0; LFont *PreviewFont = NULL; int PrintMaxPages = -1; int NewMailTimeout = -1; bool SendAfterReceive = false; bool IngoreOnClose = false; LAutoString UiTags; LAutoPtr Growl; LArray TrayMenuContacts; bool ExitAfterSend = false; LToolButton *ShowConsoleBtn = NULL; LString MulPassword; LBox *SubSplit = NULL, *SearchSplit = NULL; LArray ThingSources; int LastLayout = 0; LMenuItem *DisableUserFilters = NULL; LOptionsFile *Options = NULL; HttpImageThread *ImageLoader = NULL; int LastMinute = -1, LastHour = -1; LArray Store3EventCallbacks; LAutoPtr PrintOptions; LHashTbl, LString> ResFiles; // These are for the LDataEventsI callbacks to store source context // Mainly for debugging where various events came from. const char *CtxFile = NULL; int CtxLine = 0; // Contact no face images LAutoRefPtr NoContact; // Remote content white/blacklists bool RemoteContent_Init = false; LString::Array RemoteWhiteLst, RemoteBlackLst; // Spell checking int AppWndHnd; LAutoPtr SpellerThread; // Missing caps LCapabilityTarget::CapsHash MissingCaps; MissingCapsBar *Bar = NULL; LString ErrSource; // Script file that has an error. Filter *ErrFilter = NULL; // Filter that has scripting error. // Load state bool FoldersLoaded = false; // Bayesian filter LStringPipe BayesLog; // Thread item processing LArray Transfers; // Scripting... LAutoPtr Engine; LArray Scripts; LArray CurrentScripts; LScript *CurrentScript() { return CurrentScripts.Length() ? CurrentScripts.Last() : NULL; } int NextToolMenuId = IDM_TOOL_SCRIPT_BASE; LAutoPtr ScriptToolbar; LArray OnSecondTimerCallbacks; // Encryption LAutoPtr GpgInst; // Unit tests LAutoPtr UnitTestServer; class ScribeTextControlFactory : public LViewFactory { ScribeWnd *Wnd; LView *NewView(const char *Class, LRect *Pos, const char *Text) { if (!_stricmp(Class, "ScribeTextView")) return Wnd->CreateTextControl(-1, 0, true); return NULL; } public: ScribeTextControlFactory(ScribeWnd *wnd) { Wnd = wnd; } } TextControlFactory; ScribeWndPrivate(ScribeWnd *app) : App(app), TextControlFactory(app) { NoContact = new NoContactType(app); NoContact->DecRef(); // 2->1 AppWndHnd = LEventSinkMap::Dispatch.AddSink(App); #ifdef WIN32 ClipboardFormat = RegisterClipboardFormat( #ifdef UNICODE L"Scribe.Item" #else "Scribe.Item" #endif ); #endif LScribeScript::Inst = new LScribeScript(App); if (Engine.Reset(new LScriptEngine(App, LScribeScript::Inst, this))) Engine->SetConsole(LScribeScript::Inst->GetLog()); } ~ScribeWndPrivate() { // Why do we need this? ~LView will take care of it? // LEventSinkMap::Dispatch.RemoveSink(App); DeleteObj(Options); Scripts.DeleteObjects(); DeleteObj(ImageLoader); Engine.Reset(); DeleteObj(LScribeScript::Inst); } LGrowl *GetGrowl() { if (!Growl && Growl.Reset(new LGrowl)) { LAutoPtr r(new LGrowl::LRegister); r->App = "Scribe"; r->IconUrl = "http://memecode.com/images/scribe/growl-app.png"; LGrowl::LNotifyType &NewMail = r->Types.New(); NewMail.Name = "new-mail"; NewMail.IconUrl = "http://memecode.com/images/scribe/growl-new-mail.png"; NewMail.Enabled = true; LGrowl::LNotifyType &Cal = r->Types.New(); Cal.Name = "calendar"; Cal.IconUrl = "http://memecode.com/images/scribe/growl-calendar.png"; Cal.Enabled = true; LGrowl::LNotifyType &Debug = r->Types.New(); Debug.Name = "debug"; Debug.IconUrl = "http://memecode.com/images/scribe/growl-bug.png"; Debug.Enabled = false; LGrowl::LNotifyType &Info = r->Types.New(); Info.IconUrl = "http://memecode.com/images/scribe/growl-note.png"; Info.Name = "info"; Info.Enabled = true; Growl->Register(r); } return Growl; } LVmDebugger *AttachVm(LVirtualMachine *Vm, LCompiledCode *Code, const char *Assembly) { LVariant v; if (Options) Options->GetValue(OPT_ScriptDebugger, v); if (v.CastInt32()) return new LVmDebuggerWnd(App, this, Vm, Code, NULL); return NULL; } bool CompileScript(LAutoPtr &Output, const char *FileName, const char *Source) { LCompiler c; return c.Compile(Output, Engine->GetSystemContext(), LScribeScript::Inst, FileName, Source, NULL); } bool OnSearch(LBrowser *br, const char *txt) { char Path[256]; if (!App->GetHelpFilesPath(Path, sizeof(Path))) return false; LToken Terms(txt, ", "); LStringPipe p; p.Print("\n

Search Results

\n
    \n"); LDirectory Dir; for (int b = Dir.First(Path, "*.html"); b; b = Dir.Next()) { if (!Dir.IsDir()) { char Path[256]; Dir.Path(Path, sizeof(Path)); LFile f; if (f.Open(Path, O_READ)) { LXmlTree t(GXT_NO_DOM); LXmlTag r; if (t.Read(&r, &f)) { char *PrevName = 0; char PrevUri[256] = ""; for (auto c: r.Children) { if (c->IsTag("a")) { char *Name = c->GetAttr("name"); if (Name) { PrevName = Name; } } else if (c->GetContent()) { bool Hit = false; for (unsigned i=0; !Hit && iGetContent(), Terms[i]) != 0; } if (Hit) { LStringPipe Uri(256); char *Leaf = strrchr(Path, DIR_CHAR); Leaf = Leaf ? Leaf + 1 : Path; Uri.Print("file://%s", Path); if (PrevName) Uri.Print("#%s", PrevName); LAutoString UriStr(Uri.NewStr()); if (_stricmp(UriStr, PrevUri)) { p.Print("
  • %s", UriStr.Get(), Leaf); if (PrevName) p.Print("#%s", PrevName); p.Print("\n"); strcpy_s(PrevUri, sizeof(PrevUri), UriStr); } } } } } } } } p.Print("
\n\n\n"); LAutoString Html(p.NewStr()); br->SetHtml(Html); return true; } + void AskUserForInstallMode(std::function callback) + { + auto Dlg = new LAlert(App, + AppName, + LLoadString(IDS_PORTABLE_Q), + LLoadString(IDS_HELP), + LLoadString(IDS_DESKTOP), + LLoadString(IDS_PORTABLE)); + + Dlg->SetButtonCallback(1, [&](auto idx) + { + App->LaunchHelp("install.html"); + }); + + Dlg->DoModal([callback](auto dlg, auto Btn) + { + if (Btn == 1) + { + // Help + LAssert(!"Help btn should use callback."); + } + else if (Btn == 2) + { + // Desktop + if (callback) + callback(LOptionsFile::DesktopMode); + } + else if (Btn == 3) + { + // Portable + if (callback) + callback(LOptionsFile::PortableMode); + } + else + { + delete dlg; + LAppInst->Exit(1); + } + + delete dlg; + }); + } + LOptionsFile::PortableType GetInstallMode() { if (InstallMode == LOptionsFile::UnknownMode) { if (LAppInst->GetOption("portable")) { InstallMode = LOptionsFile::PortableMode; LgiTrace("Selecting portable mode based on -portable switch.\n"); } else if (LAppInst->GetOption("desktop")) { InstallMode = LOptionsFile::DesktopMode; LgiTrace("Selecting portable mode based on -desktop switch.\n"); } } if (InstallMode == LOptionsFile::UnknownMode) { bool PortableIsPossible = true; char Inst[MAX_PATH_LEN] = ""; LGetSystemPath(LSP_APP_INSTALL, Inst, sizeof(Inst)); // Do write check char Wr[MAX_PATH_LEN]; LMakePath(Wr, sizeof(Wr), Inst, "_write_test.txt"); LFile f; if (f.Open(Wr, O_WRITE)) { // Clean up f.Close(); FileDev->Delete(Wr, false); } else { // No write perms PortableIsPossible = false; } if (PortableIsPossible && LAppInst->IsElevated()) { // Check if the install is in some read only location: // e.g. c:\Program Files char Pm[MAX_PATH_LEN]; if (LGetSystemPath(LSP_USER_APPS, Pm, sizeof(Pm))) { size_t n = strlen(Pm); PortableIsPossible = _strnicmp(Pm, Inst, n) != 0; // LgiMsg(App, "%i\n%s\n%s", AppName, MB_OK, PortableIsPossible, Pm, Inst); } else LgiTrace("%s:%i - Failed to get paths.", _FL); } if (PortableIsPossible) { - while (InstallMode == LOptionsFile::UnknownMode) - { - LAlert Dlg( App, - AppName, - LLoadString(IDS_PORTABLE_Q), - LLoadString(IDS_HELP), - LLoadString(IDS_DESKTOP), - LLoadString(IDS_PORTABLE)); - int Btn = Dlg.DoModal(); - if (Btn == 1) - { - // Help - App->LaunchHelp("install.html"); - } - else if (Btn == 2) - { - // Desktop - InstallMode = LOptionsFile::DesktopMode; - } - else if (Btn == 3) - { - // Portable - InstallMode = LOptionsFile::PortableMode; - } - else - { - LAppInst->Exit(1); - break; - } - } - - /* - InstallMode = LgiMsg(App, - LLoadString(IDS_PORTABLE_Q), - AppName, - MB_YESNO) == IDYES - ? LOptionsFile::PortableMode - : LOptionsFile::DesktopMode; - */ - - - LgiTrace("Selecting %s mode based on querying user.\n", InstallMode == LOptionsFile::PortableMode ? "portable" : "desktop"); + // Basically "ask the user" here... + return LOptionsFile::UnknownMode; } else { InstallMode = LOptionsFile::DesktopMode; LgiTrace("Selecting Desktop based on lack of write permissions to install folder.\n"); } } return InstallMode; } void SetInstallMode(LOptionsFile::PortableType t) { InstallMode = t; } void DeleteCallbacks(LArray &Callbacks) { for (unsigned i=0; iGetMenu()->FindItem(Callbacks[i].Param); if (it) { it->Remove(); DeleteObj(it); } } } } }; ////////////////////////////////////////////////////////////////////////////// void UpgradeRfOption(ScribeWnd *App, const char *New, const char *Old, const char *Default) { LVariant v; /* App->GetOptions()->GetValue(New, v); if (v.Str()) { ScribePath *Path = new ScribePath(App, Old); if (Path) { char *Xml = LReadTextFile(*Path); if (Xml) { App->GetOptions()->SetValue(New, v = Xml); DeleteArray(Xml); } App->GetOptions()->DeleteValue(Old); DeleteObj(Path); } } */ if (Default && !App->GetOptions()->GetValue(New, v)) { App->GetOptions()->SetValue(New, v = Default); } } //////////////////////////////////////////////////////////////////////////// -ScribeWnd::AppState ScribeWnd::ScribeState = ScribeInitializing; - +ScribeWnd::AppState ScribeWnd::ScribeState = ScribeConstructing; + + +/* + * This constructor is a little convoluted, but the basic idea is this: + * + * - Do some basic init. + * - Attempt to load the options (could make portable/desktop mode clear) + * - If the portable/desktop mode is unclear ask the user. + * - Call AppConstruct1. + * - If the UI language is not known, ask the user. + * - Call AppConstruct2. + * + * Each time a dialog is needed the rest of the code needs to be in a callable function. + * + * It's important to note that the ScribeWnd::OnCreate method needs to be called after + * the system Handle() is created, and after any dialogs in the ScribeWnd::ScribeWnd + * constructor have finished. + */ ScribeWnd::ScribeWnd() : BayesianFilter(this), CapabilityInstaller("Scribe", ScribeVer, "http://memecode.com/components/lookup.php", ScribeTempPath()), TrayIcon(this) { if (_Lock) _Lock->SetName("ScribeWnd"); // init some variables LApp::ObjInstance()->AppWnd = this; LCharsetSystem::Inst()->DetectCharset = ::DetectCharset; d = new ScribeWndPrivate(this); ScribeIpc = new LSharedMemory("Scribe", SCRIBE_INSTANCE_MAX * sizeof(ScribeIpcInstance)); if (ScribeIpc && ScribeIpc->GetPtr()) { ScribeIpcInstance *InstLst = (ScribeIpcInstance*) ScribeIpc->GetPtr(); for (int i=0; iMagic = SCRIBE_INSTANCE_MAGIC; ThisInst->Pid = LProcessId(); // LgiTrace("Install Scribe pid=%i to pos=%i\n", LProcessId(), i); } } } - else - { - DeleteObj(ScribeIpc); - } + else DeleteObj(ScribeIpc); #ifndef WIN32 LString App = GetFullAppName(true); printf("%s\n", App.Get()); #endif - if (!LoadOptions()) - { - ScribeState = ScribeExiting; - return; - } - ScribeOptionsDefaults(d->Options); - - #define DefaultIntOption(opt, def) { LVariant v; if (!GetOptions()->GetValue(opt, v)) \ - GetOptions()->SetValue(opt, v = (int)def); } - #define DefaultStrOption(opt, def) { LVariant v; if (!GetOptions()->GetValue(opt, v)) \ - GetOptions()->SetValue(opt, v = def); } - DefaultIntOption(OPT_DefaultAlternative, 1); - DefaultIntOption(OPT_BoldUnread, 1); - DefaultIntOption(OPT_PreviewLines, 1); - DefaultIntOption(OPT_AutoDeleteExe, 1); - DefaultIntOption(OPT_DefaultReplyAllSetting, MAIL_ADDR_BCC); - DefaultIntOption(OPT_BlinkNewMail, 1); - DefaultIntOption(OPT_MarkReadAfterSeconds, 5); - DefaultStrOption(OPT_BayesThreshold, "0.9"); - DefaultIntOption(OPT_SoftwareUpdate, 1); - DefaultIntOption(OPT_ResizeImgAttachments, false); - DefaultIntOption(OPT_ResizeJpegQual, 80); - DefaultIntOption(OPT_ResizeMaxPx, 1024); - DefaultIntOption(OPT_ResizeMaxKb, 200); - DefaultIntOption(OPT_RegisterWindowsClient, 1); - DefaultIntOption(OPT_HasTemplates, 0); - DefaultIntOption(OPT_HasCalendar, 1); - DefaultIntOption(OPT_HasGroups, 1); - DefaultIntOption(OPT_HasFilters, 1); - DefaultIntOption(OPT_HasSpam, 0); - - LVariant GlyphSub; - if (GetOptions()->GetValue(OPT_GlyphSub, GlyphSub)) - { - bool UseGlyphSub = GlyphSub.CastInt32() != 0; - LSysFont->SubGlyphs(UseGlyphSub); - LSysBold->SubGlyphs(UseGlyphSub); - LFontSystem::Inst()->SetDefaultGlyphSub(UseGlyphSub); - } - else - { - GetOptions()->SetValue(OPT_GlyphSub, GlyphSub = LFontSystem::Inst()->GetDefaultGlyphSub()); - } - - { - // Limit the size of the 'Scribe.txt' log file - char p[MAX_PATH_LEN]; - if (LgiTraceGetFilePath(p, sizeof(p))) - { - int64 Sz = LFileSize(p); - #define MiB * 1024 * 1024 - if (Sz > (3 MiB)) - FileDev->Delete(p); - } - } - - // Process pre-UI options - LVariant SizeAdj; - int SzAdj = SizeAdj.CastInt32(); - if (GetOptions()->GetValue(OPT_UiFontSize, SizeAdj) && - (SzAdj = SizeAdj.CastInt32()) >= 0 && - SzAdj < 5) - { - SzAdj -= 2; - if (SzAdj) - { - int Pt = LSysFont->PointSize(); + LgiTrace("const this=%p\n", this); + + auto AppConstruct1 = [this]() + { + if (!d->Options && !LoadOptions()) + { + ScribeState = ScribeExiting; + return; + } + ScribeOptionsDefaults(d->Options); + + LVariant GlyphSub; + if (GetOptions()->GetValue(OPT_GlyphSub, GlyphSub)) + { + bool UseGlyphSub = GlyphSub.CastInt32() != 0; + LSysFont->SubGlyphs(UseGlyphSub); + LSysBold->SubGlyphs(UseGlyphSub); + LFontSystem::Inst()->SetDefaultGlyphSub(UseGlyphSub); + } + else + { + GetOptions()->SetValue(OPT_GlyphSub, GlyphSub = LFontSystem::Inst()->GetDefaultGlyphSub()); + } + + { + // Limit the size of the 'Scribe.txt' log file + char p[MAX_PATH_LEN]; + if (LgiTraceGetFilePath(p, sizeof(p))) + { + int64 Sz = LFileSize(p); + #define MiB * 1024 * 1024 + if (Sz > (3 MiB)) + FileDev->Delete(p); + } + } + + // Process pre-UI options + LVariant SizeAdj; + int SzAdj = SizeAdj.CastInt32(); + if (GetOptions()->GetValue(OPT_UiFontSize, SizeAdj) && + (SzAdj = SizeAdj.CastInt32()) >= 0 && + SzAdj < 5) + { + SzAdj -= 2; + if (SzAdj) + { + int Pt = LSysFont->PointSize(); - LSysFont->PointSize(Pt + SzAdj); - LSysFont->Create(); - - LSysBold->PointSize(Pt + SzAdj); - LSysBold->Create(); + LSysFont->PointSize(Pt + SzAdj); + LSysFont->Create(); + + LSysBold->PointSize(Pt + SzAdj); + LSysBold->Create(); - LFont *m = LMenu::GetFont(); - if (m) - { - m->PointSize(m->PointSize() + SzAdj); - m->Create(); - } - } - } - else - { - GetOptions()->SetValue(OPT_UiFontSize, SizeAdj = 2); - } - - // Resources and languages - SetLanguage: - LVariant LangId; - if (GetOptions()->GetValue(OPT_UiLanguage, LangId)) - { - // Set the language to load... - LAppInst->SetConfig("Language", LangId.Str()); - } - LResources::SetLoadStyles(true); - - // Load the resources (with the current lang) - if (!LgiGetResObj(true, "Scribe")) - { - LgiMsg(0, "The resource file 'Scribe.lr8' is missing.", AppName); - ScribeState = ScribeExiting; - return; - } - - // If no language set... - if (!GetOptions()->GetValue(OPT_UiLanguage, LangId)) - { - // Ask the user... - LanguageDlg Dlg(this); - if (Dlg.Ok && - Dlg.DoModal()) - { - // Set the language in the options file - GetOptions()->SetValue(OPT_UiLanguage, LangId = (char*)Dlg.Lang); + LFont *m = LMenu::GetFont(); + if (m) + { + m->PointSize(m->PointSize() + SzAdj); + m->Create(); + } + } + } + else + { + GetOptions()->SetValue(OPT_UiFontSize, SizeAdj = 2); + } + + // Resources and languages + auto SetLanguage = [this]() + { + LVariant LangId; + if (GetOptions()->GetValue(OPT_UiLanguage, LangId)) + { + // Set the language to load... + LAppInst->SetConfig("Language", LangId.Str()); + } + LResources::SetLoadStyles(true); + + // Load the resources (with the current lang) + if (!LgiGetResObj(true, "Scribe")) + { + LgiMsg(NULL, "The resource file 'Scribe.lr8' is missing.", AppName); + ScribeState = ScribeExiting; + LCloseApp(); + } + }; + + SetLanguage(); + + auto AppConstruct2 = [this]() + { + #if 1 + auto CurRes = LgiGetResObj(false); + LVariant Theme; + if (CurRes && GetOptions()->GetValue(OPT_Theme, Theme)) + { + auto Paths = ScribeThemePaths(); + auto NoTheme = LLoadString(IDS_DEFAULT); + if (Theme.Str() && + Stricmp(NoTheme, Theme.Str())) + { + for (auto p: Paths) + { + LFile::Path Inst(p); + Inst += Theme.Str(); + if (Inst.Exists()) + { + CurRes->SetThemeFolder(Inst); + d->Static->OnSystemColourChange(); + break; + } + } + } + } + #endif + + LoadCalendarStringTable(); + + ZeroObj(DefaultFolderNames); + DefaultFolderNames[FOLDER_INBOX] = LLoadString(IDS_FOLDER_INBOX, "Inbox"); + DefaultFolderNames[FOLDER_OUTBOX] = LLoadString(IDS_FOLDER_OUTBOX, "Outbox"); + DefaultFolderNames[FOLDER_SENT] = LLoadString(IDS_FOLDER_SENT, "Sent"); + DefaultFolderNames[FOLDER_TRASH] = LLoadString(IDS_FOLDER_TRASH, "Trash"); + DefaultFolderNames[FOLDER_CONTACTS] = LLoadString(IDS_FOLDER_CONTACTS, "Contacts"); + DefaultFolderNames[FOLDER_TEMPLATES] = LLoadString(IDS_FOLDER_TEMPLATES, "Templates"); + DefaultFolderNames[FOLDER_FILTERS] = LLoadString(IDS_FOLDER_FILTERS, "Filters"); + DefaultFolderNames[FOLDER_CALENDAR] = LLoadString(IDS_FOLDER_CALENDAR, "Calendar"); + DefaultFolderNames[FOLDER_GROUPS] = LLoadString(IDS_FOLDER_GROUPS, "Groups"); + DefaultFolderNames[FOLDER_SPAM] = LLoadString(IDS_SPAM, "Spam"); + + LStringPipe RfXml; + RfXml.Print(DefaultRfXml, + LLoadString(IDS_ORIGINAL_MESSAGE), + LLoadString(FIELD_TO), + LLoadString(FIELD_FROM), + LLoadString(FIELD_SUBJECT), + LLoadString(IDS_DATE)); + { + LAutoString Xml(RfXml.NewStr()); + UpgradeRfOption(this, OPT_TextReplyFormat, "ReplyXml", Xml); + UpgradeRfOption(this, OPT_TextForwardFormat, "ForwardXml", Xml); + } + + LFontType t; + if (t.GetSystemFont("small")) + { + d->PreviewFont = t.Create(); + if (d->PreviewFont) + { + #if defined WIN32 + d->PreviewFont->PointSize(8); + #endif + } + } + + MoveOnScreen(); + + // Load global graphics + LoadImageResources(); + + // Load time threads + // Window name + Name(AppName); + SetSnapToEdge(true); + ClearTempPath(); + + #if WINNATIVE + SetStyle(GetStyle() & ~WS_VISIBLE); + SetExStyle(GetExStyle() & ~WS_EX_ACCEPTFILES); + CreateClassW32(AppName, LoadIcon(LProcessInst(), MAKEINTRESOURCE(IDI_APP))); + #endif + + #if defined LINUX + SetIcon("About64px.png"); + LFinishXWindowsStartup(this); + #endif + + ScribeState = ScribeInitializing; + #if LGI_VIEW_HANDLE + if (Handle()) + #endif + OnCreate(); + }; + + // If no language set... + LVariant LangId; + if (!GetOptions()->GetValue(OPT_UiLanguage, LangId)) + { + // Ask the user... + auto Dlg = new LanguageDlg(this); + if (!Dlg->Ok) + { + delete Dlg; + LgiMsg(this, "Failed to create language selection dialog.", "Scribe Error"); + ScribeState = ScribeExiting; + LCloseApp(); + } + else + { + Dlg->DoModal([this, Dlg, SetLanguage, AppConstruct2](auto dlg, auto id) + { + if (id) + { + // Set the language in the options file + LVariant v; + GetOptions()->SetValue(OPT_UiLanguage, v = Dlg->Lang.Get()); - // Reload the resource file... to get the new lang. - LResources *Cur = LgiGetResObj(false); - DeleteObj(Cur); - goto SetLanguage; - } - } - - #if 1 - auto CurRes = LgiGetResObj(false); - LVariant Theme; - if (CurRes && GetOptions()->GetValue(OPT_Theme, Theme)) - { - auto Paths = ScribeThemePaths(); - auto NoTheme = LLoadString(IDS_DEFAULT); - if (Theme.Str() && - Stricmp(NoTheme, Theme.Str())) - { - for (auto p: Paths) - { - LFile::Path Inst(p); - Inst += Theme.Str(); - if (Inst.Exists()) - { - CurRes->SetThemeFolder(Inst); - d->Static->OnSystemColourChange(); - break; - } - } - } - } - #endif - - LoadCalendarStringTable(); - - ZeroObj(DefaultFolderNames); - DefaultFolderNames[FOLDER_INBOX] = LLoadString(IDS_FOLDER_INBOX, "Inbox"); - DefaultFolderNames[FOLDER_OUTBOX] = LLoadString(IDS_FOLDER_OUTBOX, "Outbox"); - DefaultFolderNames[FOLDER_SENT] = LLoadString(IDS_FOLDER_SENT, "Sent"); - DefaultFolderNames[FOLDER_TRASH] = LLoadString(IDS_FOLDER_TRASH, "Trash"); - DefaultFolderNames[FOLDER_CONTACTS] = LLoadString(IDS_FOLDER_CONTACTS, "Contacts"); - DefaultFolderNames[FOLDER_TEMPLATES] = LLoadString(IDS_FOLDER_TEMPLATES, "Templates"); - DefaultFolderNames[FOLDER_FILTERS] = LLoadString(IDS_FOLDER_FILTERS, "Filters"); - DefaultFolderNames[FOLDER_CALENDAR] = LLoadString(IDS_FOLDER_CALENDAR, "Calendar"); - DefaultFolderNames[FOLDER_GROUPS] = LLoadString(IDS_FOLDER_GROUPS, "Groups"); - DefaultFolderNames[FOLDER_SPAM] = LLoadString(IDS_SPAM, "Spam"); - - LStringPipe RfXml; - RfXml.Print(DefaultRfXml, - LLoadString(IDS_ORIGINAL_MESSAGE), - LLoadString(FIELD_TO), - LLoadString(FIELD_FROM), - LLoadString(FIELD_SUBJECT), - LLoadString(IDS_DATE)); - { - LAutoString Xml(RfXml.NewStr()); - UpgradeRfOption(this, OPT_TextReplyFormat, "ReplyXml", Xml); - UpgradeRfOption(this, OPT_TextForwardFormat, "ForwardXml", Xml); - } - - LFontType t; - if (t.GetSystemFont("small")) - { - d->PreviewFont = t.Create(); - if (d->PreviewFont) - { - #if defined WIN32 - d->PreviewFont->PointSize(8); - #endif - } - } - - MoveOnScreen(); - - // Load global graphics - LoadImageResources(); - - // Load time threads - // Window name - Name(AppName); - SetSnapToEdge(true); - ClearTempPath(); - - #if WINNATIVE - SetStyle(GetStyle() & ~WS_VISIBLE); - SetExStyle(GetExStyle() & ~WS_EX_ACCEPTFILES); - CreateClassW32(AppName, LoadIcon(LProcessInst(), MAKEINTRESOURCE(IDI_APP))); - #endif - - #if defined LINUX - SetIcon("About64px.png"); - LFinishXWindowsStartup(this); - #endif + // Reload the resource file... to get the new lang. + LResources *Cur = LgiGetResObj(false); + DeleteObj(Cur); + + SetLanguage(); + AppConstruct2(); + } + else // User canceled + { + ScribeState = ScribeExiting; + LCloseApp(); + } + delete dlg; + }); + } + } + else AppConstruct2(); + }; + + auto Type = d->GetInstallMode(); + + if (Type == LOptionsFile::UnknownMode) + { + LoadOptions(); // This may make the mode more clear... + Type = d->GetInstallMode(); + } + + if (Type == LOptionsFile::UnknownMode) + { + d->AskUserForInstallMode([this, AppConstruct1](auto selectedMode) + { + d->SetInstallMode(selectedMode); + AppConstruct1(); + }); + } + else AppConstruct1(); } int StrSort(char *a, char *b, int d) { return _stricmp(a, b); } ScribeWnd::~ScribeWnd() { LAppInst->AppWnd = 0; SearchView = NULL; ScribeState = ScribeExiting; LScribeScript::Inst->ShowScriptingWindow(false); // Other cleanup... ClearTempPath(); ShutdownIpc(); SetPulse(); // Save anything thats still dirty in the folders... // just in case we crash during the shutdown phase. ScribeFolder *Cur = GetCurrentFolder(); if (Cur) Cur->SerializeFieldWidths(); SaveDirtyObjects(5000); // Tell the UI not to reference anything in the folders if (PreviewPanel) { PreviewPanel->OnThing(0, false); } Mail::NewMailLst.Empty(); // ~AccountStatusItem references the account list... must be before we // delete the accounts. DeleteObj(StatusPanel); // ~Accountlet needs to reference the root container... so // it has to go before unloading of folders. Accounts.DeleteObjects(); UnLoadFolders(); DeleteObj(PreviewPanel); SaveOptions(); DeleteObj(Commands); DeleteObj(d->PreviewFont); DeleteObj(d->SubSplit); DeleteObj(Splitter); MailList = NULL; CmdSend.ToolButton = NULL; CmdReceive.ToolButton = NULL; CmdPreview.ToolButton = NULL; CmdSend.MenuItem = NULL; CmdReceive.MenuItem = NULL; CmdPreview.MenuItem = NULL; // This could be using the OpenSSL library for HTTPS connections. So // close it before calling EndSSL. DeleteObj(d->ImageLoader); // This has to be after we close all the accounts... otherwise // they might still be using SSL functions, e.g. an IMAP/SSL connect. EndSSL(); DeleteObj(d); } LString ScribeWnd::GetResourceFile(SribeResourceType Type) { auto File = d->ResFiles.Find(Type); if (!File) LgiTrace("%s:%i - No file for resource type %i\n", _FL, Type); return File; } void ScribeWnd::LoadImageResources() { auto Res = LgiGetResObj(); LString::Array Folders; if (Res) { auto p = Res->GetThemeFolder(); if (p) Folders.Add(p); } Folders.Add(ScribeResourcePath()); for (auto p: Folders) { LDirectory Dir; LgiTrace("%s:%i - Loading resource folder '%s'\n", _FL, p.Get()); for (auto b = Dir.First(p); b; b = Dir.Next()) { if (Dir.IsDir()) continue; auto Name = Dir.GetName(); if (MatchStr("Toolbar-*.png", Name)) { if (!d->ResFiles.Find(ResToolbarFile)) d->ResFiles.Add(ResToolbarFile, Dir.FullPath()); } else if (MatchStr("xgate-icons-*.png", Name)) d->ResFiles.Add(ResToolbarFile, Dir.FullPath()); else if (MatchStr("Icons-*.png", Name)) d->ResFiles.Add(ResIconsFile, Dir.FullPath()); } } ToolbarImgs.Reset(LLoadImageList(GetResourceFile(ResToolbarFile))); ImageList.Reset(LLoadImageList(GetResourceFile(ResIconsFile))); if (!ImageList) LgiTrace("%s:%i - Failed to load toolbar image ('xgate-icons-32.png' or 'Toolbar-24.png')\n", _FL); } int ScribeWnd::GetEventHandle() { return d->AppWndHnd; } void ScribeWnd::OnCloseInstaller() { d->Bar = NULL; if (InThread()) { PourAll(); } else LAssert(0); } void ScribeWnd::OnInstall(CapsHash *Caps, bool Status) { } bool ScribeWnd::NeedsCapability(const char *Name, const char *Param) { #if DEBUG_CAPABILITIES LgiTrace("ScribeWnd::NeedsCapability(%s, %s)\n", Name, Param); #endif if (!InThread()) { #if DEBUG_CAPABILITIES LgiTrace("%s:%i - Posting M_NEEDS_CAP\n", _FL); #endif PostEvent(M_NEEDS_CAP, (LMessage::Param)NewStr(Name), (LMessage::Param)NewStr(Param)); } else { if (!Name) return false; if (d->MissingCaps.Find(Name)) { #if DEBUG_CAPABILITIES LgiTrace("%s:%i - Already in MissingCaps\n", _FL); #endif return true; } d->MissingCaps.Add(Name, true); LStringPipe MsgBuf(256); int i = 0; // const char *k; // for (bool b=d->MissingCaps.First(&k); b; b=d->MissingCaps.Next(&k), i++) for (auto k : d->MissingCaps) { MsgBuf.Print("%s%s", i?", ":"", k.key); } LVariant Actions; if (stristr(Name, "OpenSSL")) { MsgBuf.Print(LLoadString(IDS_ERROR_SERVER_CONNECT)); if (Param) MsgBuf.Print("\n%s", Param); Actions.Add(new LVariant(LLoadString(IDS_INSTALL))); } else if (stristr(Name, "Registry")) { MsgBuf.Print(LLoadString(IDS_ERROR_REG_WRITE)); Actions.Add(new LVariant(LLoadString(IDS_DONT_SHOW_AGAIN))); } else if (stristr(Name, "SpellingDictionary")) { MsgBuf.Print(LLoadString(IDS_ERROR_NEED_INSTALL), Param); Actions.Add(new LVariant(LLoadString(IDS_DOWNLOAD))); } Actions.Add(new LVariant(LLoadString(IDS_OK))); #if DEBUG_CAPABILITIES LgiTrace("%s:%i - Actions.Length()=%i, Bar=%p\n", _FL, Actions.Length(), d->Bar); #endif if (Actions.Length()) { LAutoString Msg(MsgBuf.NewStr()); // Check the script hook here... bool ShowInstallBar = true; LArray Callbacks; if (GetScriptCallbacks(LBeforeInstallBar, Callbacks)) { for (unsigned i=0; iCastInt32()) ShowInstallBar = false; else Msg.Reset(TheMsg.ReleaseStr()); } } } // Now create the capability install bar... if (!d->Bar && ShowInstallBar && Actions.Type == GV_LIST) { // FYI Capabilities are handled in ScribeWnd::StartAction. LArray Act; for (auto v : *Actions.Value.Lst) Act.Add(v->Str()); d->Bar = new MissingCapsBar(this, &d->MissingCaps, Msg, this, Act); AddView(d->Bar, 2); AttachChildren(); OnPosChange(); } } } return true; } LAutoString ScribeWnd::GetDataFolder() { LVariant v; GetOptions()->GetValue(OPT_IsPortableInstall, v); char p[MAX_PATH_LEN]; if (LGetSystemPath(v.CastInt32() ? LSP_APP_INSTALL : LSP_APP_ROOT, p, sizeof(p))) { if (!LDirExists(p)) FileDev->CreateFolder(p); return LAutoString(NewStr(p)); } else LgiTrace("%s:%i - LgiGetSystemPath failed (portable=%i).\n", _FL, v.CastInt32()); return LAutoString(); } LScriptEngine *ScribeWnd::GetScriptEngine() { return d->Engine; } -LScriptCallback ScribeWnd::GetCallback(char *CallbackMethodName) +LScriptCallback ScribeWnd::GetCallback(const char *CallbackMethodName) { LScriptCallback Cb; auto Cur = d->CurrentScript(); if (Cur && Cur->Code) { Cb.Script = Cur; Cb.Func = Cur->Code->GetMethod(CallbackMethodName); } if (!Cb.Func) { for (auto s: d->Scripts) { Cb.Script = s; if ((Cb.Func = s->Code->GetMethod(CallbackMethodName))) break; } } return Cb; } bool ScribeWnd::RegisterCallback(LScriptCallbackType Type, LScriptArguments &Args) { if (!d->CurrentScript()) { LgiTrace("%s:%i - No current script.\n", _FL); return false; } char *Fn = Args[1]->Str(); LScriptCallback Cb = GetCallback(Fn); if (!Cb.Func) { LgiTrace("%s:%i - No callback '%s'.\n", _FL, Fn); return false; } switch (Type) { case LToolsMenu: { char *Menu = Args[0]->Str(); auto Cur = d->CurrentScript(); if (!Menu || !Fn || !Cur) { LgiTrace("%s:%i - menu=%s, fn=%s.\n", _FL, Menu, Fn); return false; } LScriptCallback &c = Cur->Callbacks.New(); c = Cb; c.Type = Type; c.Param = d->NextToolMenuId; LMenuItem *Tools = GetMenu()->FindItem(IDM_TOOLS_MENU); auto ToolSub = Tools ? Tools->Sub() : 0; if (ToolSub) { if (d->NextToolMenuId == IDM_TOOL_SCRIPT_BASE) { ToolSub->AppendSeparator(); } ToolSub->AppendItem(Menu, c.Param, true); d->NextToolMenuId++; } break; } case LThingContextMenu: case LFolderContextMenu: case LThingUiToolbar: case LMailOnBeforeSend: case LMailOnAfterReceive: case LApplicationToolbar: case LBeforeInstallBar: case LInstallComponent: case LOnTimer: case LRenderMail: case LOnLoad: { auto Cur = d->CurrentScript(); LAssert(d->Scripts.HasItem(Cur)); LScriptCallback &c = Cur->Callbacks.New(); c = Cb; c.Type = Type; if (Args.Length() > 2) c.Data = *Args[2]; break; } default: { LAssert(!"Not a known callback type"); return false; } } return true; } bool ScribeWnd::GetScriptCallbacks(LScriptCallbackType Type, LArray &Callbacks) { for (auto s: d->Scripts) { for (auto &c: s->Callbacks) { if (c.Type == Type) Callbacks.Add(&c); } } return Callbacks.Length() > 0; } bool ScribeWnd::ExecuteScriptCallback(LScriptCallback &c, LScriptArguments &Args, bool ReturnArgs) { if (!c.Func || !c.Script) return false; // Setup LVirtualMachine Vm(d); d->CurrentScripts.Add(c.Script); // Call the method bool Status = Vm.ExecuteFunction( c.Script->Code, c.Func, Args, LScribeScript::Inst->GetLog(), ReturnArgs ? &Args : NULL) != ScriptError; // Cleanup d->CurrentScripts.PopLast(); return Status; } LStream *ScribeWnd::ShowScriptingConsole() { auto Item = Menu->FindItem(IDM_SCRIPTING_CONSOLE); if (Item) { Item->Checked(!Item->Checked()); LScribeScript::Inst->ShowScriptingWindow(Item->Checked()); LVariant v; GetOptions()->SetValue(OPT_ShowScriptConsole, v = Item->Checked()); } return LScribeScript::Inst->GetLog(); } LOptionsFile::PortableType ScribeWnd::GetPortableType() { return d->GetInstallMode(); } void ScribeWnd::RemoteContent_AddSender(const char *Addr, bool WhiteList) { if (!Addr) return; auto Opt = WhiteList ? OPT_RemoteContentWhiteList : OPT_RemoteContentBlackList; LVariant v; GetOptions()->GetValue(Opt, v); // Not an error if not there... auto existing = LString(v.Str()).SplitDelimit(" ,\r\n"); for (auto p: existing) { if (MatchStr(p, Addr)) { LgiTrace("%s:%i - '%s' is already in '%s'\n", _FL, Addr, Opt); return; // Already in list... } } existing.SetFixedLength(false); existing.Add(Addr); auto updated = LString("\n").Join(existing); GetOptions()->SetValue(Opt, v = updated.Get()); LgiTrace("%s:%i - Added '%s' to '%s'\n", _FL, Addr, Opt); d->RemoteContent_Init = false; } ScribeRemoteContent ScribeWnd::RemoteContent_GetSenderStatus(const char *Addr) { if (!d->RemoteContent_Init) { LVariant v; if (GetOptions()->GetValue(OPT_RemoteContentWhiteList, v)) d->RemoteWhiteLst = LString(v.Str()).SplitDelimit(" ,\r\n"); if (GetOptions()->GetValue(OPT_RemoteContentBlackList, v)) d->RemoteBlackLst = LString(v.Str()).SplitDelimit(" ,\r\n"); d->RemoteContent_Init = true; } for (auto p: d->RemoteWhiteLst) if (MatchStr(p, Addr)) return RemoteAlwaysLoad; for (auto p: d->RemoteBlackLst) if (MatchStr(p, Addr)) return RemoteNeverLoad; return RemoteDefault; } void ScribeWnd::RemoteContent_ClearCache() { d->RemoteWhiteLst.Empty(); d->RemoteBlackLst.Empty(); d->RemoteContent_Init = false; } void ScribeWnd::OnSpellerSettingChange() { // Kill the current thread d->SpellerThread.Reset(); // Setup the new thread LSpellCheck *t = GetSpellThread(); if (t) { // Trigger an install if needed t->Check(d->AppWndHnd, "thisisamispeltword", 0, 18); } } bool ScribeWnd::SetSpellThreadParams(LSpellCheck *Thread) { if (!Thread) return false; LVariant Lang, Dict; GetOptions()->GetValue(OPT_SpellCheckLanguage, Lang); GetOptions()->GetValue(OPT_SpellCheckDictionary, Dict); LAutoPtr Params(new LSpellCheck::Params); if (!Params) return false; Params->IsPortable = GetPortableType(); Params->OptionsPath = GetOptions()->GetFile(); Params->Lang = Lang.Str(); Params->Dict = Dict.Str(); Params->CapTarget = this; Thread->SetParams(Params); return true; } LSpellCheck *ScribeWnd::CreateSpellObject() { LVariant PrefAspell; GetOptions()->GetValue(OPT_PreferAspell, PrefAspell); LAutoPtr Obj; if (PrefAspell.CastInt32()) Obj = CreateAspellObject(); #if defined(MAC) if (!Obj) Obj = CreateAppleSpellCheck(); #elif defined(WINDOWS) LArray Ver; int Os = LGetOs(&Ver); if ( !Obj && (Os == LGI_OS_WIN32 || Os == LGI_OS_WIN64) && ( Ver.Length() > 1 && ( Ver[0] > 6 || (Ver[0] == 6 && Ver[1] > 1) ) ) ) Obj = CreateWindowsSpellCheck(); #endif if (!Obj) Obj = CreateAspellObject(); SetSpellThreadParams(Obj); return Obj.Release(); } LSpellCheck *ScribeWnd::GetSpellThread(bool OverrideOpt) { LVariant Use; if (OverrideOpt) Use = true; else GetOptions()->GetValue(OPT_SpellCheck, Use); #if USE_SPELLCHECKER if ((Use.CastInt32() != 0) ^ (d->SpellerThread.Get() != 0)) d->SpellerThread.Reset(Use.CastInt32() ? CreateSpellObject() : NULL); #endif return d->SpellerThread; } LAutoString ScribeWnd::GetHttpProxy() { LAutoString Proxy; LVariant v; if (GetOptions()->GetValue(OPT_HttpProxy, v) && ValidStr(v.Str())) { Proxy.Reset(v.ReleaseStr()); } else { LProxyUri p; if (p.sHost) Proxy.Reset(NewStr(p.ToString())); } return Proxy; } InstallProgress *ScribeWnd::StartAction(MissingCapsBar *Bar, LCapabilityTarget::CapsHash *Components, const char *ActionParam) { if (!ActionParam) { LgiTrace("%s:%i - No action supplied.\n", _FL); return NULL; } LArray Callbacks; LVariant Action(ActionParam); if (GetScriptCallbacks(LInstallComponent, Callbacks)) { bool StartInstall = true; for (unsigned i=0; iCastInt32()) StartInstall = false; } } if (!Action.Str()) { LgiTrace("%s:%i - GInstallComponent removed action name.\n", _FL); return NULL; } if (!StartInstall) { LgiTrace("%s:%i - GInstallComponent script canceled install of '%s'.\n", _FL, ActionParam); return NULL; } } if (!_stricmp(Action.Str(), LLoadString(IDS_OK))) { // Do nothing d->MissingCaps.Empty(); } else if (!_stricmp(Action.Str(), LLoadString(IDS_DONT_SHOW_AGAIN))) { // Turn off registering as a client. LVariant No(false); GetOptions()->SetValue(OPT_RegisterWindowsClient, No); GetOptions()->SetValue(OPT_CheckDefaultEmail, No); } else if (!_stricmp(Action.Str(), LLoadString(IDS_INSTALL))) { #ifdef WINDOWS bool IsSsl = false; for (auto c: *Components) { if (!_stricmp(c.key, "openssl")) { IsSsl = true; break; } } if (IsSsl) { LString s; s.Printf(LLoadString(IDS_WINDOWS_SSL_INSTALL), LGetOsName()); - LAlert q(this, AppName, s, "Open Website", LLoadString(IDS_CANCEL)); - switch (q.DoModal()) - { - case 1: - LExecute("https://slproweb.com/products/Win32OpenSSL.html"); - break; - default: - break; - } + auto q = new LAlert(this, AppName, s, "Open Website", LLoadString(IDS_CANCEL)); + q->DoModal([this, q](auto dlg, auto id) + { + switch (id) + { + case 1: + LExecute("https://slproweb.com/products/Win32OpenSSL.html"); + break; + default: + break; + } + delete dlg; + }); return NULL; } #endif return CapabilityInstaller::StartAction(Bar, Components, Action.Str()); } else if (!_stricmp(Action.Str(), LLoadString(IDS_SHOW_CONSOLE))) { ShowScriptingConsole(); } else if (!_stricmp(Action.Str(), LLoadString(IDS_OPEN_SOURCE))) { if (d->ErrSource) LExecute(d->ErrSource); else if (d->ErrFilter) d->ErrFilter->DoUI(); d->ErrSource.Empty(); d->ErrFilter = NULL; } else if ( !Stricmp(Action.Str(), LLoadString(IDS_SHOW_REMOTE_CONTENT)) || !Stricmp(Action.Str(), LLoadString(IDS_ALWAYS_SHOW_REMOTE_CONTENT))) { auto c = Components->begin(); LWindow *w = Bar->GetWindow(); if ((*c).key && !Stricmp((*c).key, "RemoteContent") && w) { LVariant Ret, Always(!Stricmp(Action.Str(), LLoadString(IDS_ALWAYS_SHOW_REMOTE_CONTENT))); LArray Args; Args[0] = &Always; w->CallMethod(DomToStr(SdShowRemoteContent), &Ret, Args); } } else if (!_stricmp(Action.Str(), LLoadString(IDS_DOWNLOAD))) { - LSpellCheck *t = GetSpellThread(); + auto t = GetSpellThread(); if (t) t->InstallDictionary(); else LgiTrace("%s:%i - No spell thread.\n", _FL); } else LAssert(!"Unknown action."); return NULL; } HttpImageThread *ScribeWnd::GetImageLoader() { if (!d->ImageLoader) { LAutoString Proxy = GetHttpProxy(); d->ImageLoader = new HttpImageThread(this, Proxy, 0); } return d->ImageLoader; } char *ScribeWnd::GetUiTags() { if (!d->UiTags) { char UiTags[256] = "inscribe" #if defined WIN32 " win32" #elif defined LINUX " linux" #elif defined MAC " mac" #endif ; LVariant Tags; - if (GetOptions()->GetValue("tags", Tags)) + if (!GetOptions()) + { + LAssert(!"Where is the options?"); + } + else if (GetOptions()->GetValue("tags", Tags)) { size_t Len = strlen(UiTags); sprintf_s(UiTags+Len, sizeof(UiTags)-Len, " %s", Tags.Str()); } d->UiTags.Reset(NewStr(UiTags)); } return d->UiTags; } void ScribeWnd::OnCreate() { + if (ScribeState == ScribeConstructing) + { + // Constructor is still running, probably showing some UI. + // Don't complete setup at this point. + return; + } + // Load the styles LResources::StyleElement(this); // Main menu Menu = new LMenu(AppName); if (Menu) { Menu->Attach(this); if (Menu->Load(this, "ID_MENU", GetUiTags())) { LAssert(ImageList != NULL); Menu->SetImageList(ImageList, false); auto IdentityItem = Menu->FindItem(IDM_NO_IDENTITIES); if (IdentityItem) { IdentityMenu = IdentityItem->GetParent(); } CmdSend.MenuItem = Menu->FindItem(IDM_SEND_MAIL); auto NewMailMenu = Menu->FindItem(IDM_NEW_EMAIL); if (NewMailMenu) { MailMenu = NewMailMenu->GetParent(); } LVariant v; WorkOffline = Menu->FindItem(IDM_WORK_OFFLINE); if (WorkOffline && GetOptions()->GetValue(OPT_WorkOffline, v)) { WorkOffline->Checked(v.CastInt32() != 0); } if ((d->DisableUserFilters = Menu->FindItem(IDM_FILTERS_DISABLE))) { if (GetOptions()->GetValue(OPT_DisableUserFilters, v)) { d->DisableUserFilters->Checked(v.CastInt32() != 0); } } #if RUN_STARTUP_SCRIPTS // Run scripts in './Scripts' folder char s[MAX_PATH_LEN]; LMakePath(s, sizeof(s), ScribeResourcePath(), "Scripts"); if (!LDirExists(s)) LMakePath(s, sizeof(s), LGetSystemPath(LSP_APP_INSTALL), #if defined(LINUX) || defined(WINDOWS) "..\\" #endif "Scripts"); if (!LDirExists(s)) LgiTrace("%s:%i - Error: the scripts folder '%s' doesn't exist.\n", _FL, s); else { bool ErrorDsp = false; LDirectory Dir; for (int b = Dir.First(s); b; b = Dir.Next()) { if (Dir.IsDir()) continue; char *Ext = LGetExtension(Dir.GetName()); if (!Ext || _stricmp(Ext, "script") != 0) continue; Dir.Path(s, sizeof(s)); LStringPipe Log; char *Source = LReadTextFile(s); if (Source) { LScript *Cur = new LScript; if (Cur) { char Msg[256]; d->CurrentScripts.Add(Cur); LScribeScript::Inst->GetLog()->Write(Msg, sprintf_s(Msg, sizeof(Msg), "Compiling '%s'...\n", Dir.GetName())); LCompiler c; if (c.Compile( Cur->Code, d->Engine->GetSystemContext(), LScribeScript::Inst, s, Source, NULL)) { LFunctionInfo *Main = Cur->Code->GetMethod("Main"); if (Main) { LVirtualMachine Vm(d); LScriptArguments Args(&Vm); Args.New() = new LVariant((LDom*)this); d->Scripts.Add(Cur); if (Vm.ExecuteFunction( Cur->Code, Main, Args, LScribeScript::Inst->GetLog()) && Args.GetReturn()->CastInt32()) { d->CurrentScripts.Delete(Cur, true); Cur = NULL; } else { LgiTrace("Error: Script's main failed (%s)\n", Cur->Code->GetFileName()); if (Cur->Callbacks.Length()) d->DeleteCallbacks(Cur->Callbacks); d->Scripts.Delete(Cur); } Args.DeleteObjects(); } } else if (!ErrorDsp) { ErrorDsp = true; OnScriptCompileError(Source, NULL); } if (Cur) { d->CurrentScripts.Delete(Cur, true); DeleteObj(Cur); } } DeleteArray(Source); } } } #endif #define EnableItem(id, en) { auto i = Menu->FindItem(id); if (i) i->Enabled(en); } #define SetMenuIcon(id, ico) { auto i = Menu->FindItem(id); if (i) i->Icon(ico); } EnableItem(IDM_IMPORT_OUTLOOK_ITEMS, true); // SetMenuIcon(IDM_OPEN_FOLDERS, ICON_OPEN_FOLDER); SetMenuIcon(IDM_OPTIONS, ICON_OPTIONS); SetMenuIcon(IDM_SECURITY, ICON_LOCK); SetMenuIcon(IDM_CUT, ICON_CUT); SetMenuIcon(IDM_COPY, ICON_COPY); SetMenuIcon(IDM_PASTE, ICON_PASTE); SetMenuIcon(IDM_LAYOUT1, ICON_LAYOUT1); SetMenuIcon(IDM_LAYOUT2, ICON_LAYOUT2); SetMenuIcon(IDM_LAYOUT3, ICON_LAYOUT3); SetMenuIcon(IDM_LAYOUT4, ICON_LAYOUT4); SetMenuIcon(IDM_NEW_EMAIL, ICON_UNSENT_MAIL); SetMenuIcon(IDM_SET_READ, ICON_READ_MAIL); SetMenuIcon(IDM_SET_UNREAD, ICON_UNREAD_MAIL); SetMenuIcon(IDM_NEW_CONTACT, ICON_CONTACT); SetMenuIcon(IDM_NEW_GROUP, ICON_CONTACT_GROUP); SetMenuIcon(IDM_REPLY, ICON_FLAGS_REPLY); SetMenuIcon(IDM_REPLY_ALL, ICON_FLAGS_REPLY); SetMenuIcon(IDM_FORWARD, ICON_FLAGS_FORWARD); SetMenuIcon(IDM_BOUNCE, ICON_FLAGS_BOUNCE); SetMenuIcon(IDM_NEW_FILTER, ICON_FILTER); SetMenuIcon(IDM_FILTER_CURRENT_FOLDER, ICON_FOLDER_FILTERS); SetMenuIcon(IDM_MEMECODE, ICON_LINK); SetMenuIcon(IDM_HOMEPAGE, ICON_LINK); SetMenuIcon(IDM_SCRIBE_FAQ, ICON_LINK); SetMenuIcon(IDM_INSCRIBE_LINK, ICON_LINK); SetMenuIcon(IDM_VERSION_HISTORY, ICON_LINK); SetMenuIcon(IDM_DEBUG_INFO, ICON_LINK); SetMenuIcon(IDM_TUTORIALS, ICON_LINK); SetMenuIcon(IDM_FEEDBACK, ICON_UNREAD_MAIL); SetMenuIcon(IDM_HELP, ICON_HELP); LMenuItem *mi; if ( GetOptions()->GetValue(OPT_EditControl, v) && (mi = Menu->FindItem(IDM_HTML_EDITOR)) ) mi->Checked(v.CastInt32() != 0); Menu->SetPrefAndAboutItems(IDM_OPTIONS, IDM_ABOUT); } } // Initialize user interface SetupUi(); // Get some of the base submenu pointers. These are needed // for SetupAccounts to work correctly, e.g. populate the // send/receive/preview submenus. Folders need to be loaded // before this for the templates folder BuildDynMenus(); // Load accounts SetupAccounts(); // Recursively load folder tree - LoadFolders(); - - // Redo it for the templates... now that load folders has completed. - BuildDynMenus(); - - if (ScribeState == ScribeExiting) - return; - - // Process command line - OnCommandLine(); - - // Update the templates sub-menu now that the folders are loaded - BuildDynMenus(); - - // Check registry settings - SetDefaultHandler(); - - // Run on load scripts... - LArray OnLoadCallbacks; - if (GetScriptCallbacks(LOnLoad, OnLoadCallbacks)) - { - for (auto r: OnLoadCallbacks) - { - LVirtualMachine Vm; - LScriptArguments Args(&Vm); - Args.New() = new LVariant(this); - ExecuteScriptCallback(*r, Args); - Args.DeleteObjects(); - } - } - - ScribeState = ScribeRunning; + LoadFolders([&](auto status) + { + // Redo it for the templates... now that load folders has completed. + BuildDynMenus(); + + if (ScribeState == ScribeExiting) + return; + + // Process command line + OnCommandLine(); + + // Update the templates sub-menu now that the folders are loaded + BuildDynMenus(); + + // Check registry settings + SetDefaultHandler(); + + // Run on load scripts... + LArray OnLoadCallbacks; + if (GetScriptCallbacks(LOnLoad, OnLoadCallbacks)) + { + for (auto r: OnLoadCallbacks) + { + LVirtualMachine Vm; + LScriptArguments Args(&Vm); + Args.New() = new LVariant(this); + ExecuteScriptCallback(*r, Args); + Args.DeleteObjects(); + } + } + + ScribeState = ScribeRunning; + }); } ScribeAccount *ScribeWnd::GetAccountByEmail(const char *Email) { if (!Email) return NULL; for (auto a : *GetAccounts()) { LVariant e = a->Identity.Email(); if (e.Str() && !_stricmp(e.Str(), Email)) { return a; } } return 0; } ScribeAccount *ScribeWnd::GetAccountById(int Id) { for (auto a : *GetAccounts()) { if (a->Receive.Id() == Id) { return a; } } return 0; } const char *ScribeWnd::EditCtrlMimeType() { LVariant Html; GetOptions()->GetValue(OPT_EditControl, Html); return Html.CastInt32() ? sTextHtml : sTextPlain; } LAutoString ScribeWnd::GetReplyXml(const char *MimeType) { bool IsHtml = MimeType && !_stricmp(MimeType, sTextHtml); LVariant s; GetOptions()->GetValue(IsHtml ? OPT_HtmlReplyFormat : OPT_TextReplyFormat, s); return LAutoString(s.ReleaseStr()); } LAutoString ScribeWnd::GetForwardXml(const char *MimeType) { bool IsHtml = MimeType && !_stricmp(MimeType, sTextHtml); LVariant s; GetOptions()->GetValue(IsHtml ? OPT_HtmlForwardFormat : OPT_TextForwardFormat, s); return LAutoString(s.ReleaseStr()); } LVmDebuggerCallback *ScribeWnd::GetDebuggerCallback() { return d; } GpgConnector *ScribeWnd::GetGpgConnector() { if (!d->GpgInst) { if (!GpgConnector::IsInstalled()) return NULL; d->GpgInst.Reset(new GpgConnector()); } return d->GpgInst; } LFont *ScribeWnd::GetPreviewFont() { return d->PreviewFont; } bool ScribeWnd::IsValid() { #if 0 try { for (ScribeAccount *a = Accounts.First(); a; a = Accounts.Next()) { } } catch(...) { return false; } #endif return true; } bool ScribeWnd::ShutdownIpc() { // Remove our instance from the shared memory if (ScribeIpc && ScribeIpc->GetPtr()) { ScribeIpcInstance *InstLst = (ScribeIpcInstance*) ScribeIpc->GetPtr(); if (ThisInst) memset(ThisInst, 0, sizeof(*ThisInst)); int c = 0; for (int i=0; iDestroy(); } ThisInst = 0; DeleteObj(ScribeIpc); return true; } bool ScribeWnd::GetVariant(const char *Name, LVariant &Value, const char *Array) { ScribeDomType Fld = StrToDom(Name); switch (Fld) { case SdQuote: // Type: String { return GetOptions()->GetValue(OPT_QuoteReplyStr, Value); } case SdName: // Type: String { Value = AppName; break; } case SdHome: // Type: String { Value = LGetExePath().Get(); break; } case SdNow: // Type: String { LDateTime Now; Now.SetNow(); char s[64]; Now.Get(s, sizeof(s)); Value = s; break; } case SdFolder: // Type: ScribeFolder[] // Pass system folder index or string as array parameter. { ScribeFolder *f = 0; if (!Array) return false; if (IsDigit(*Array)) { f = GetFolder(atoi(Array)); } else { f = GetFolder(Array); } if (!f) return false; Value = (LDom*)f; break; } case SdAppName: // Type: String { Value = AppName; break; } case SdCalendarToday: // Type: String { return Calendar::SummaryOfToday(this, Value); } case SdInboxSummary: { LStringPipe p; // Iterate through the mail stores for (auto m: Folders) { if (!m.Store) continue; auto Inbox = m.Store->GetObj(FIELD_INBOX); if (!Inbox) continue; auto Unread = Inbox->GetInt(FIELD_UNREAD); auto Name = Inbox->GetStr(FIELD_FOLDER_NAME); LString Path; Path.Printf("/%s/%s", m.Name.Get(), Name); if (Unread) p.Print("%s: %i unread
", m.Name.Get(), Path.Get(), Unread); else p.Print("%s: 0 unread
", m.Name.Get()); } // And also the IMAP full folders for (auto a: Accounts) { ScribeProtocol Protocol = a->Receive.ProtocolType(); if (Protocol == ProtocolImapFull) { LDataStoreI *Store = a->Receive.GetDataStore(); if (Store) { auto Inbox = Store->GetObj(FIELD_INBOX); if (Inbox) { auto Unread = Inbox->GetInt(FIELD_UNREAD); auto Name = Inbox->GetStr(FIELD_FOLDER_NAME); auto m = a->Receive.Name(); if (m.Str()) { LString Path; Path.Printf("/%s/%s", m.Str(), Name); if (Unread) p.Print("%s: %i unread
", m.Str(), Path.Get(), Unread); else p.Print("%s: 0 unread
", m.Str()); } } } } } Value = p.NewGStr().Get(); break; } case SdExecute: // Type: String { if (!Array) return false; const char *s = Array; char *Exe = LTokStr(s); if (!Exe) return false; while (*s && *s == ' ') s++; LStringPipe Out; LSubProcess p(Exe, (char*)s); if (p.Start()) { p.Communicate(&Out); LAutoString o(Out.NewStr()); LAutoString t(TrimStr(o)); Value = t; } DeleteArray(Exe); break; } case SdBuildType: // Type: String { #ifdef _DEBUG Value = "Debug"; #else Value = "Release"; #endif break; } case SdPlatform: // Type: String { LArray Ver; LGetOs(&Ver); LString::Array Va; for (auto i: Ver) Va.New().Printf("%i", i); #if defined __GTK_H__ auto Api = "GTK3"; #elif LGI_SDL auto Api = "SDL"; #elif LGI_COCOA auto Api = "Cocoa"; #elif LGI_CARBON auto Api = "Carbon"; #elif defined WIN32 auto Api = "WinApi"; #else #error "Impl me." auto Api = "#err"; #endif LString s; s.Printf("%s, v%s, %s", LGetOsName(), LString(".").Join(Va).Get(), Api); Value = s.Get(); break; } case SdVersion: // Type: String { char Ver[32]; sprintf_s(Ver, sizeof(Ver), "v%s", ScribeVer); Value = Ver; break; } case SdBuild: // Type: String { char s[128]; sprintf_s(s, sizeof(s), "%s, %s, %ibit", __DATE__, __TIME__, (int)(sizeof(NativeInt)*8)); Value = s; break; } case SdLanguage: // Type: String { LLanguage *l = LGetLanguageId(); if (!l) return false; char s[256]; sprintf_s(s, sizeof(s), "%s \"%s\"", l->Name, l->Id); Value = s; break; } case SdString: // Type: String { if (!Array) { LAssert(!"Missing string ID"); return false; } int Id = atoi(Array); Value = LLoadString(Id); break; } case SdCurrentFolder: // Type: ScribeFolder { Value = (LDom*) GetCurrentFolder(); break; } case SdView: // Type: LView { Value = (LView*)this; break; } case SdNoContact: // Type: Contact { Value = (NoContactType*)d->NoContact; break; } case SdAccounts: { if (Array) { if (IsDigit(*Array)) { auto i = atoi(Array); if (i >= 0 && i < (ssize_t)Accounts.Length()) { Value = (LDom*)Accounts[i]; } else return false; } else { for (auto a : Accounts) { LVariant nm = a->Send.Name(); if (nm.Str() && !_stricmp(nm.Str(), Array)) { Value = (LDom*)a; break; } } } } else { Value = (int32)Accounts.Length(); } break; } case SdOptions: { Value = GetOptions(); break; } case SdMailStorePaths: { if (!Value.SetList()) return false; for (auto Ms : Folders) Value.Add(new LVariant(Ms.Path)); break; } case SdRootFolders: { if (!Value.SetList() || !Tree) return false; for (auto *i = Tree->GetChild(); i; i = i->GetNext()) { ScribeFolder *c = dynamic_cast(i); if (c) { auto p = c->GetPath(); Value.Add(new LVariant(p)); } } break; } default: { return false; } } return true; } bool ScribeWnd::CallMethod(const char *MethodName, LVariant *ReturnValue, LArray &Args) { ScribeDomType m = StrToDom(MethodName); switch (m) { case SdGrowlOnMail: // Type: (Mail Obj) { if (Args.Length() != 1) { LgiTrace("%s:%i - Wrong arg count: %i.\n", _FL, (int)Args.Length()); return false; } Mail *m = dynamic_cast(Args[0]->CastDom()); if (!m) { LgiTrace("%s:%i - Invalid object.\n", _FL); return false; } GrowlOnMail(m); break; } case SdGrowlInfo: { auto Title = Args.Length() > 0 ? Args[0] : NULL; auto Text = Args.Length() > 1 ? Args[1] : NULL; GrowlInfo(Title ? Title->Str() : NULL, Text ? Text->Str() : NULL); break; } case SdGetClipboardText: // Type: () { LClipBoard c(this); *ReturnValue = c.Text(); break; } case SdSetClipboardText: // Type: (String Text) { if (Args.Length() != 1) { LgiTrace("%s:%i - Wrong arg count: %i.\n", _FL, (int)Args.Length()); return false; } char *Str = Args[0]->CastString(); LClipBoard c(this); if (ValidStr(Str)) *ReturnValue = c.Text(Str); else *ReturnValue = c.Empty(); break; } case SdLookupContactGroup: // Type: (String GroupName) { if (Args.Length() != 1) { LgiTrace("%s:%i - Wrong arg count: %i.\n", _FL, (int)Args.Length()); return false; } ContactGroup *Grp = LookupContactGroup(this, Args[0]->Str()); *ReturnValue = dynamic_cast(Grp); break; } case SdGetUserString: // Type: (LView ParentView, String PromptMessage[, Bool ObsurePassword[, String DefaultValue]]) { if (Args.Length() < 2) return false; LView *View = Args[0]->CastView(); char *Prompt = Args[1]->CastString(); bool Pass = Args.Length() > 2 ? Args[2]->CastInt32() != 0 : false; char *Default = Args.Length() > 3 ? Args[3]->CastString() : NULL; - LInput i(View ? View : this, Default, Prompt, AppName, Pass); - if (!i.DoModal()) - return false; + LString Result; + bool Loop = true; + auto i = new LInput(View ? View : this, Default, Prompt, AppName, Pass); + i->DoModal([&Result, &Loop, i](auto dlg, auto id) + { + if (id) + Result = i->GetStr(); + delete dlg; + Loop = false; + }); + + // This is obviously not ideal, but I don't want to implement a scripting language callback for + // something that should be a simple modal dialog that waits for user input. + while (Loop) + { + LSleep(10); + LYield(); + } if (ReturnValue) - *ReturnValue = i.GetStr(); + *ReturnValue = Result; break; } case SdCreateAccount: // Type: () { ScribeAccount *a = new ScribeAccount(this, (int)Accounts.Length()); if (a) { if (ReturnValue) *ReturnValue = (LDom*)a; Accounts.Insert(a); a->Create(); } else return false; break; } case SdDeleteAccount: // Type: (ScribeAccount AccountToDelete) { if (Args.Length() != 1) return false; ScribeAccount *a = dynamic_cast(Args[0]->CastDom()); if (!a) { if (ReturnValue) *ReturnValue = false; } else { int Idx = (int)Accounts.IndexOf(a); if (Idx < 0 || a->IsOnline()) { if (ReturnValue) *ReturnValue = false; } else { Accounts.Delete(a); a->Delete(); delete a; // delete actual account object // Reindex remaining items so their are no gaps int i=0; auto it = Accounts.begin(); for (a = *it; a; a = *++it, i++) { a->ReIndex(i); } } } break; } case SdShowRemoteContent: { if (PreviewPanel) return PreviewPanel->CallMethod(MethodName, ReturnValue, Args); else return false; break; } case SdSearchHtml: // Type(Html, SearchExp, ResultExp) { if (Args.Length() != 3) { LgiTrace("%s:%i - SearchHtml requires 3 arguments.\n", _FL); *ReturnValue = false; return true; } auto Html = Args[0]->Str(); auto SearchExp = Args[1]->Str(); auto ResultExp = Args[2]->Str(); if (!Html || !SearchExp || !ResultExp) { LgiTrace("%s:%i - SearchHtml got non-string argument.\n", _FL); *ReturnValue = false; return true; } SearchHtml(ReturnValue, Html, SearchExp, ResultExp); return true; } case SdGetUri: // Type(UriToDownload, CallbackName) { if (Args.Length() < 2) { LgiTrace("%s:%i - GetUri requires at least 2 arguments.\n", _FL); *ReturnValue = false; return true; } auto Uri = Args[0]->Str(); auto Callback = Args[1]->Str(); LVariant *UserData = Args.Length() > 2 ? Args[2] : NULL; new ScriptDownloadContentThread(this, Uri, Callback, UserData); *ReturnValue = true; return true; } default: { LAssert(!"Unsupported method."); return false; } } return true; } LOptionsFile *ScribeWnd::GetOptions(bool Create) { if (!d->Options && Create) { - d->Options = new LOptionsFile(d->GetInstallMode(), OptionsFileName); - ScribeOptionsDefaults(d->Options); + LAssert(!"Not here... do it in LoadOptions."); + return NULL; } return d->Options; } int OptionsFileCmp(OptionsInfo *a, OptionsInfo *b) { int64 Diff = b->Mod - a->Mod; return Diff < 0 ? -1 : (Diff > 0 ? 1 : 0); } OptionsInfo::OptionsInfo() { Score = 0; Mod = 0; Leaf = NULL; Usual = false; } OptionsInfo &OptionsInfo::operator =(char *p) { File = p; Leaf = LGetLeaf(File); if (Leaf) { char n[64]; sprintf_s(n, sizeof(n), "%s.xml", OptionsFileName); Usual = !_stricmp(n, Leaf); } return *this; } LOptionsFile *OptionsInfo::Load() { // Read the file... LAutoPtr Of(new LOptionsFile(File)); if (!Of) return NULL; if (!Of->SerializeFile(false)) return NULL; // Sanity check the options... LXmlTag *Acc = Of->LockTag(OPT_Accounts, _FL); if (!Acc) return NULL; Of->Unlock(); LXmlTag *Stores = Of->LockTag(OPT_MailStores, _FL); if (!Stores) return NULL; auto Count = Stores->Children.Length(); Of->Unlock(); if (Count == 0) Of.Reset(); return Of.Release(); } #define DEBUG_OPTS_SCAN 0 bool ScribeWnd::ScanForOptionsFiles(LArray &Files, LSystemPath PathType) { LString Root = LGetSystemPath(PathType); LDirectory Dir; char p[MAX_PATH_LEN]; if (IsUnitTest) Root += ".unittest"; #if DEBUG_OPTS_SCAN LgiTrace("%s:%i - Root='%s'\n", _FL, Root.Get()); #endif for (int b = Dir.First(Root); b; b = Dir.Next()) { if ( !Dir.IsDir() && Dir.Path(p, sizeof(p)) ) { LResolveShortcut(p, p, sizeof(p)); char *Ext = LGetExtension(Dir.GetName()); if (stristr(Dir.GetName(), OptionsFileName) != NULL && Ext && (!_stricmp(Ext, "xml") || !_stricmp(Ext, "bak"))) { OptionsInfo &i = Files.New(); i = p; i.Mod = Dir.GetLastWriteTime(); #if DEBUG_OPTS_SCAN LgiTrace("%s:%i - File='%s'\n", _FL, p); #endif } } } Files.Sort(OptionsFileCmp); // Scan through the results and pick out the normal file for (unsigned i=0; iOptions; i++) { if (Files[i].Usual) { d->Options = Files[i].Load(); #if DEBUG_OPTS_SCAN LgiTrace("%s:%i - Attempt '%s' = %p\n", _FL, Files[i].File.Get(), d->Options); #endif } } if (!d->Options) { // Scan through the alternative files and look for something // we can use. #if DEBUG_OPTS_SCAN LgiTrace("%s:%i - Scanning backups\n", _FL); #endif for (unsigned i=0; iOptions; i++) { if (!Files[i].Usual) { d->Options = Files[i].Load(); if (d->Options) { // Lets rename this baby back to the real filename LString Xml = OptionsFileName; Xml += ".xml"; LFile::Path Normal(Root, Xml); if (LFileExists(Normal)) FileDev->Delete(Normal); d->Options->SetFile(Normal); d->Options->SerializeFile(true); // sets to clean after changing filename. } } } } if (d->Options) { // Load OK: Clear out any old options files... #if DEBUG_OPTS_SCAN LgiTrace("%s:%i - Files.len=" LPrintfSizeT "\n", _FL, Files.Length()); #endif while (Files.Length() > 6) { auto Idx = Files.Length() - 1; auto &f = Files[Idx]; #if DEBUG_OPTS_SCAN LgiTrace("%s:%i - Delete '%s'\n", _FL, f.File.Get()); #endif FileDev->Delete(f.File); Files.DeleteAt(Idx); } } return d->Options != NULL; } bool ScribeWnd::IsUnitTest = false; bool ScribeWnd::LoadOptions() { bool Load = false; // Check if we are running unit tests... if ((IsUnitTest = LAppInst->GetOption("unittest"))) { d->UnitTestServer.Reset(new LUnitTestServer(this)); } // Look in the XGate folder #if WINNATIVE && !defined(_DEBUG) LRegKey xgate(false, "HKEY_CURRENT_USER\\Software\\XGate"); if (xgate.IsOk()) { char *Spool = xgate.GetStr("SpoolDir"); if (LDirExists(Spool)) { char File[MAX_PATH_LEN]; LMakePath(File, sizeof(File), Spool, "spool"); LMakePath(File, sizeof(File), File, OptionsFileName); strcat_s(File, sizeof(File), ".xml"); if (LFileExists(File)) { d->SetInstallMode(LOptionsFile::DesktopMode); LgiTrace("Selecting xgate mode based on options file path.\n"); d->Options = new LOptionsFile(File); } } } #endif // Now look in the application install folder LArray Files; if (!d->Options && ScanForOptionsFiles(Files, LSP_APP_INSTALL)) { // File is in the install folder... d->SetInstallMode(LOptionsFile::PortableMode); LgiTrace("Selecting portable mode based on options file path.\n"); } // Look in the app root if ( !d->Options && ScanForOptionsFiles(Files, LSP_APP_ROOT) ) { // Desktop mode d->SetInstallMode(LOptionsFile::DesktopMode); LgiTrace("Selecting desktop mode based on options file path.\n"); } // Do multi-instance stuff if (d->Options && d->Options->GetFile()) { // Search for other instances of Scribe if (ScribeIpc) { int i; ScribeIpcInstance *InstLst = (ScribeIpcInstance*) ScribeIpc->GetPtr(); ScribeIpcInstance *Mul = 0; for (i=0; iMulPassword = Mul->Password; break; } } for (i=0; iIsScribe(), InstLst->Magic, InstLst->Pid); if (InstLst->IsScribe() && InstLst != ThisInst) { // LgiTrace("%s, %s\n", InstLst->OptionsPath, d->Options->GetFile()); if (Mul || _stricmp(InstLst->OptionsPath, d->Options->GetFile()) == 0) { int Pid = InstLst->Pid; OsAppArguments *Args = LAppInst->GetAppArgs(); if (!LIsProcess(Pid)) { continue; } char *Utf8 = 0; #if WINNATIVE Utf8 = WideToUtf8(Args->lpCmdLine); #else LStringPipe p; for (int i=1; iArgs; i++) { if (i > 1) p.Push(" "); char *Sp = strchr(Args->Arg[i], ' '); if (Sp) p.Push("\""); p.Push(Args->Arg[i]); if (Sp) p.Push("\""); } Utf8 = p.NewStr(); #endif if (Utf8) { size_t Len = strlen(Utf8); if (Len > 255) { InstLst->Flags |= SCRIBE_IPC_LONG_ARGS; InstLst->Flags &= ~SCRIBE_IPC_CONTINUE_ARGS; for (char *u = Utf8; Len > 0; u += 255) { ssize_t Part = MIN(sizeof(InstLst->Args)-1, Len); memcpy(InstLst->Args, u, Part); Len -= Part; int64 Start = LCurrentTime(); while (LCurrentTime() - Start < 60000) { if (TestFlag(InstLst->Flags, SCRIBE_IPC_CONTINUE_ARGS) && InstLst->Args[0] == 0) { Start = 0; break; } LSleep(10); } if (Start) { LgiTrace("%s:%i - SendLA timed out.\n", _FL); break; } } InstLst->Flags &= ~(SCRIBE_IPC_CONTINUE_ARGS | SCRIBE_IPC_LONG_ARGS); } else { strcpy_s(InstLst->Args, sizeof(InstLst->Args), Utf8); } DeleteArray(Utf8); ShutdownIpc(); LgiTrace("Passed args to the other running instance of Scribe (pid=%i)\n", Pid); LCloseApp(); return false; } else LgiTrace("%s:%i - No arguments to pass.\n", _FL); } } } if (Mul && LFileExists(Mul->OptionsPath)) { // No instance of Scribe is running, but MUL may be keeping // the previously run instance around. So we should run that // by using the options file and password from MUL's instance // record. DeleteObj(d->Options); d->Options = new LOptionsFile(Mul->OptionsPath); } } // Insert ourselves into the instance list if (ThisInst) { strcpy_s(ThisInst->OptionsPath, sizeof(ThisInst->OptionsPath), d->Options->GetFile()); } } // Open file and load.. if (!Load && d->Options) { LOptionsFile *Opts = GetOptions(); Load = Opts->SerializeFile(false); if (Load) { LVariant v = d->GetInstallMode() == LOptionsFile::PortableMode; GetOptions()->SetValue(OPT_IsPortableInstall, v); } else { auto err = GetOptions()->GetError(); LgiMsg( this, LLoadString(IDS_ERROR_LR8_FAILURE), AppName, MB_OK, err); } } if (!d->Options) { d->Options = new LOptionsFile(d->GetInstallMode(), OptionsFileName); } if (d->Options) { LVariant v; if (!d->Options->GetValue(OPT_IsPortableInstall, v) && d->GetInstallMode() != LOptionsFile::UnknownMode) { v = d->GetInstallMode() == LOptionsFile::PortableMode; d->Options->SetValue(OPT_IsPortableInstall, v); } ScribeOptionsDefaults(d->Options); if (Load) { if (GetOptions()->GetValue(OPT_PrintSettings, v)) { auto *p = GetPrinter(); if (p) { LString s = v.Str(); p->Serialize(s, false); } } } - if (GetOptions()->GetValue(OPT_PreviewLines, v)) + if (d->Options->GetValue(OPT_PreviewLines, v)) { Mail::PreviewLines = v.CastInt32() != 0; } // upgrade smtp password const char *Pw = "SmtpPsw"; if (!GetOptions()->GetValue(OPT_EncryptedSmtpPassword, v)) { // no encrypted password, look for unencrypted password if (GetOptions()->GetValue(Pw, v)) { GPassword p; p.Set(v.Str()); p.Serialize(GetOptions(), OPT_EncryptedSmtpPassword, true); } } // if old un-encrypted password exists... // delete the key, we are now storing an encrypted // password if (GetOptions()->GetValue(Pw, v)) GetOptions()->DeleteValue(Pw); if (GetOptions()->GetValue(OPT_AdjustDateTz, v)) Mail::AdjustDateTz = !v.CastInt32(); if (!GetOptions()->GetValue(OPT_ConfirmDelete, v)) GetOptions()->SetValue(OPT_ConfirmDelete, v = true); if (!GetOptions()->GetValue(OPT_DelDirection, v)) GetOptions()->SetValue(OPT_DelDirection, v = DeleteActionPrev); if (GetOptions()->GetValue(OPT_SizeInKiB, v)) OptionSizeInKiB = v.CastInt32() != 0; if (GetOptions()->GetValue(OPT_RelativeDates, v)) ShowRelativeDates = v.CastInt32() != 0; // date format if (GetOptions()->GetValue(OPT_DateFormat, v)) { int Idx = v.CastInt32(); if (Idx >= 0 && Idx < CountOf(DateTimeFormats)) LDateTime::SetDefaultFormat(DateTimeFormats[Idx]); } // SSL debug logging if (GetOptions()->GetValue(OPT_DebugSSL, v)) SslSocket::DebugLogging = v.CastInt32() != 0; // Growl if (GetOptions()->GetValue(OPT_GrowlEnabled, v) && v.CastInt32()) { LVariant Ver, Bld; GetVariant(DomToStr(SdVersion), Ver); GetVariant("Build", Bld); LString n; n.Printf("%s\n%s", Ver.Str(), Bld.Str()); GrowlInfo("Scribe has started up...", n); } } #if LGI_EXCEPTIONS try { #endif // Default the font settings to the system font // if they don't already exist const char *OptFont[] = { OPT_EditorFont, OPT_PrintFont, OPT_HtmlFont, 0 }; int Index = 0; for (const char **Opt=OptFont; *Opt; Opt++, Index++) { LVariant v; if (!GetOptions()->GetValue(*Opt, v)) { LFontType Type; if (Type.GetSystemFont("System")) { if (Index == 2) { int Pt = Type.GetPointSize(); Type.SetPointSize(Pt+3); } Type.Serialize(GetOptions(), *Opt, true); } } } #if LGI_EXCEPTIONS } catch (...) { LgiMsg( this, LLoadString(IDS_ERROR_FONT_SETTINGS), AppName, MB_OK); } #endif return true; } bool ScribeWnd::SaveOptions() { LStringPipe Log(256); bool Status = false; bool WriteFailed = false; bool WndStateSet = false; RestartSave: if (d->Options && !d->Options->GetFile()) { bool PortableOk = true; char Path[MAX_PATH_LEN]; char Leaf[32]; sprintf_s(Leaf, sizeof(Leaf), "%s.xml", OptionsFileName); Log.Print("No current path for '%s', creating...\n", Leaf); LVariant v; GetOptions()->GetValue(OPT_IsPortableInstall, v); if (v.CastInt32()) { if (!LGetSystemPath(LSP_APP_INSTALL, Path, sizeof(Path))) { PortableOk = false; Log.Print("Error: LgiGetSystemPath(LSP_APP_INSTALL) failed.\n"); } else { LMakePath(Path, sizeof(Path), Path, Leaf); // Do write test to confirm we are good to go LFile f; if (f.Open(Path, O_WRITE)) { f.Close(); FileDev->Delete(Path, false); d->Options->SetFile(Path); } else { PortableOk = false; Log.Print("Warning: '%s' is not writable.\n", Path); } } } if (!v.CastInt32() || !PortableOk) { // Desktop mode then. if (v.CastInt32()) { const char *Msg = "Switching to desktop mode because the install folder is not writable."; Log.Print("%s\n", Msg); LgiMsg(this, Msg, AppName, MB_OK); GetOptions()->SetValue(OPT_IsPortableInstall, v = false); } if (!LGetSystemPath(LSP_APP_ROOT, Path, sizeof(Path))) { Log.Print("Error: LgiGetSystemPath(LSP_APP_ROOT) failed.\n"); } else { LMakePath(Path, sizeof(Path), Path, LAppInst->LBase::Name()); if (!LDirExists(Path)) { if (!FileDev->CreateFolder(Path)) { Log.Print("Error: CreateFolder('%s') failed.\n", Path); } } LMakePath(Path, sizeof(Path), Path, Leaf); // Do write test to confirm we are good to go LFile f; if (f.Open(Path, O_WRITE)) { f.Close(); FileDev->Delete(Path, false); d->Options->SetFile(Path); } else { Log.Print("Error: '%s' is not writable.\n", Path); } } } } if (d->Options && d->Options->GetFile() && d->Options->IsValid()) { // Backup options file char Backup[MAX_PATH_LEN]; strcpy_s(Backup, sizeof(Backup), d->Options->GetFile()); char *Ext = LGetExtension(Backup); if (Ext) { *--Ext = 0; LString s; for (int i=1; i<100; i++) { s.Printf("%s_%i.bak", Backup, i); if (!LFileExists(s)) break; } if (!LFileExists(s)) FileDev->Move(d->Options->GetFile(), s); } // Update some settings... #if LGI_VIEW_HANDLE if (Handle()) #endif WndStateSet = SerializeState(GetOptions(), OPT_ScribeWndPos, false); LVariant v; if (Splitter) GetOptions()->SetValue(OPT_SplitterPos, v = (int)Splitter->Value()); if (d->SubSplit) { auto First = d->SubSplit->GetViewAt(0); if (First == (LViewI*)SearchView) { auto Lst = (SearchView) ? d->SubSplit->GetViewAt(1) : NULL; if (Lst) GetOptions()->SetValue(OPT_SubSplitPos, v = (int)Lst->GetPos().Y()); } else GetOptions()->SetValue(OPT_SubSplitPos, v = (int)d->SubSplit->Value()); } // Write them... if (GetOptions()->SerializeFile(true)) { Status = true; } else { // We probably don't have write permissions to the install folder... Log.Print("Error: Options.Serialize failed.\n"); if (!WriteFailed) { // This blocks any possibility of an infinite loop WriteFailed = true; d->Options->SetFile(NULL); // Set desktop mode explicitly LVariant v; GetOptions()->GetValue(OPT_IsPortableInstall, v = false); Log.Print("Restarting save after setting desktop mode...\n"); goto RestartSave; } } } if (!Status) { LString a = Log.NewGStr(); LgiMsg(this, "Saving options failed:\n%s", AppName, MB_OK, a.Get()); } if (!WndStateSet) { LRect r(10, 10, 790, 590); SetPos(r); MoveToCenter(); } return Status; } // // Command Line Options: // // -m, -t : To recipient(s) // -f : The filename of the attachment // -b : Attach as a binary // -c : CC'd recipient(s) // -s : Subject for the email // -n : Send now... else UI is shown // -p : Print the file // -upgrade_folders : trigger a folder upgrade // -o : Load the following options file // -u : Load the following URL/file // void ScribeWnd::OnCommandLine() { // check command line args LString Str, File; bool CreateMail = false; CreateMail = LAppInst->GetOption("m", Str); if (!CreateMail) CreateMail = LAppInst->GetOption("t", Str); bool HasFile = LAppInst->GetOption("f", File); if (!CreateMail) CreateMail = HasFile; LString OpenArg; if (LAppInst->GetOption("u", OpenArg)) { LUri u(OpenArg); if (u.sProtocol) { OnUrl(OpenArg); } else if (LFileExists(OpenArg)) { LArray Files; Files.Add(OpenArg); OnReceiveFiles(Files); } } Mail *NewEmail = 0; if (CreateMail && Str) { // strip off quotes if needed char *In = Str, *Out = Str; for (; In && *In; In++) { if (!strchr("\'\"", *In)) { *Out++ = *In; } } *Out++ = 0; // create object NewEmail = dynamic_cast(CreateItem(MAGIC_MAIL, NULL, false)); if (NewEmail) { Mailto mt(this, Str); mt.Apply(NewEmail); // cc's? if (LAppInst->GetOption("c", Str)) { SetRecipients(this, Str, NewEmail->GetObject()->GetList(FIELD_TO), MAIL_ADDR_CC); } // attach a file? if (File) { if (LAppInst->GetOption("b")) { // attach as a binary file NewEmail->AttachFile(this, &File[0]); } else { // insert as the body LAutoString b(LReadTextFile(&File[0])); if (b) { NewEmail->SetBody(b); } } } // subject? if (LAppInst->GetOption("s", Str)) { NewEmail->SetSubject(Str); } // Send now or later? if (LAppInst->GetOption("n")) { // Check for exit after send option d->ExitAfterSend = LAppInst->GetOption("exit"); // now NewEmail->SetFlags(MAIL_CREATED | MAIL_READY_TO_SEND | NewEmail->GetFlags()); NewEmail->Save(); OnCommand(IDM_SEND_MAIL, 0, #ifndef __GTK_H__ Handle() #else NULL #endif ); } else { // later NewEmail->DoUI(); } } } // Pop3 on startup option LVariant n; if (GetOptions()->GetValue(OPT_Pop3OnStart, n) && n.CastInt32()) { OnCommand(IDM_RECEIVE_MAIL, 0, NULL); } } void ScribeWnd::SetCurrentIdentity(int i) { LVariant v = i; GetOptions()->SetValue(OPT_CurrentIdentity, v); if (DefaultIdentityItem) DefaultIdentityItem->Checked(i < 0); for (auto a: Accounts) { a->SetCheck(i == a->GetIndex()); } } ScribeAccount *ScribeWnd::GetCurrentAccount() { auto Idx = GetCurrentIdentity(); ScribeAccount *a = (Idx >= 0 && Idx < (ssize_t)Accounts.Length()) ? Accounts.ItemAt(Idx) : NULL; bool ValidId = a != NULL && a->IsValid(); if (!ValidId) { LAssert(!"No current identity?"); // Find a valid account to be the identity... for (auto a : Accounts) { if (!a->Send.Disabled() && a->Identity.IsValid()) { break; } } } return a; } int ScribeWnd::GetCurrentIdentity() { LVariant i; if (GetOptions()->GetValue(OPT_CurrentIdentity, i)) return i.CastInt32(); else if (ScribeState != ScribeInitializing) LgiTrace("%s:%i - No OPT_CurrentIdentity set.\n", _FL); return -1; } void ScribeWnd::SetupAccounts() { int i, CurrentIdentity = GetCurrentIdentity(); if (StatusPanel) { StatusPanel->Empty(); } #if !defined(COCOA) // FIXME LAssert(ReceiveMenu && PreviewMenu); #endif if (SendMenu) SendMenu->Empty(); if (ReceiveMenu) ReceiveMenu->Empty(); if (PreviewMenu) PreviewMenu->Empty(); if (IdentityMenu) { IdentityMenu->Empty(); } static bool Startup = true; bool ResetDefault = false; LArray Enabled; for (i=0; true; i++) { // char *s = 0; ScribeAccount *a = Startup ? new ScribeAccount(this, i) : Accounts[i]; if (a) { if (i == 0) { a->Create(); } a->Register(this); LVariant ReceiveName = a->Receive.Name(); LVariant ReceiveServer = a->Receive.Server(); LVariant SendServer = a->Send.Server(); if (i == 0 || ValidStr(ReceiveName.Str()) || ValidStr(ReceiveServer.Str()) || ValidStr(SendServer.Str()) ) { a->Send.SendItem = SendItem; a->Receive.ReceiveItem = ReceiveItem; a->Receive.PreviewItem = PreviewItem; if (!Accounts.HasItem(a)) { Accounts.Insert(a); } if (i) a->Create(); a->InitMenus(); // Identity Menu Item LVariant IdEmail = a->Identity.Email(); LVariant IdName = a->Identity.Name(); if (IdentityMenu && ValidStr(IdEmail.Str())) { char s[256]; if (IdName.Str()) sprintf_s(s, sizeof(s), "%s <%s>", IdName.Str(), IdEmail.Str()); else sprintf_s(s, sizeof(s), "<%s>", IdEmail.Str()); a->SetMenuItem(IdentityMenu->AppendItem(s, IDM_IDENTITY_BASE+i+1, !a->Send.Disabled())); if (a->Send.Disabled()) { a->SetCheck(false); if (i == CurrentIdentity) ResetDefault = true; } else { a->SetCheck(i == CurrentIdentity); Enabled[i] = a; } } } else { Accounts.Delete(a); DeleteObj(a); } } if (!a) break; } if ((ResetDefault || CurrentIdentity < 0) && Enabled.Length()) { for (unsigned i=0; iSetCheck(true); LVariant v; GetOptions()->SetValue(OPT_CurrentIdentity, v = (int)i); break; } } } Startup = false; if (ReceiveMenu && i == 0) { ReceiveMenu->AppendItem(LLoadString(IDS_NO_ITEMS), 0, false); } if (StatusPanel) { StatusPanel->OnAccountListChange(); } SetPulse(100); } ////////////////////////////////////////////////////////////////////////////// class LShutdown : public LDialog { LTextLabel *Msg; LButton *KillBtn; LButton *CancelBtn; bool Disconnected; public: ScribeAccount *Wait; List *Accounts; LShutdown(List *accounts) { Wait = 0; Disconnected = false; Accounts = accounts; LRect r( 0, 0, 320 + LAppInst->GetMetric(LGI_MET_DECOR_X), 70 + LAppInst->GetMetric(LGI_MET_DECOR_Y)); SetPos(r); MoveToCenter(); char Str[256]; sprintf_s(Str, sizeof(Str), "%s exiting...", AppName); LView::Name(Str); AddView(Msg = new LTextLabel(-1, 10, 10, 300, -1, "None")); AddView(KillBtn = new LButton(IDC_KILL, 70, 35, 60, 20, "Kill")); AddView(CancelBtn = new LButton(IDCANCEL, 140, 35, 60, 20, "Cancel")); if (KillBtn) { KillBtn->Enabled(false); } } void OnCreate() { SetPulse(100); } void OnPulse() { if (Accounts) { if (!Wait) { Wait = (*Accounts)[0]; if (Wait) { Disconnected = false; char s[256]; LVariant v = Wait->Receive.Name(); sprintf_s(s, sizeof(s), "Waiting for '%s'", v.Str() ? v.Str() : (char*)"Untitled..."); Msg->Name(s); Accounts->Delete(Wait); Wait->Stop(); KillBtn->Enabled(true); } else { SetPulse(); EndModal(true); } } if (Wait && !Wait->IsOnline()) { Wait = 0; Msg->Name("None"); KillBtn->Enabled(false); } } else { SetPulse(); EndModal(false); } } int OnNotify(LViewI *Ctrl, LNotification n) { switch (Ctrl->GetId()) { case IDC_KILL: { if (Wait) { if (!Disconnected) { Disconnected = true; Wait->Disconnect(); } else { Wait->Kill(); } } break; } case IDCANCEL: { EndModal(false); break; } } return 0; } }; bool ScribeWnd::OnRequestClose(bool OsShuttingDown) { if (FolderTasks.Length() > 0) { LgiTrace("%s:%i - %i folder tasks still busy...\n", _FL, FolderTasks.Length()); return false; } LString OnClose = LAppInst->GetConfig("Scribe.OnClose"); if (!d->IngoreOnClose && !OsShuttingDown && !Stricmp(OnClose.Get(), "minimize")) { SetZoom(LZoomMin); return false; } Visible(false); if (ScribeState != ScribeRunning) { // Inside a folder load/unload or initialization // Tell the loader to quit out... ScribeState = ScribeExiting; // Leave now, we can exit when we're ready return false; } else if (IsSending() || GetActiveThreads() > 0) { // whack up a shutdown window List Online; for (auto i: Accounts) { i->OnEndSession(); if (i->IsOnline()) { Online.Insert(i); } } - LShutdown Dlg(&Online); - if (Dlg.DoModal()) - { - ScribeState = ScribeExiting; - } - else - { - ScribeState = ScribeRunning; - Visible(true); - return false; - } + auto Dlg = new LShutdown(&Online); + Dlg->DoModal([this, Dlg](auto dlg, auto id) + { + if (id) + { + ScribeState = ScribeExiting; + LCloseApp(); + } + else + { + ScribeState = ScribeRunning; + Visible(true); + } + }); + return false; // At the very minimum the app has to wait for the user to respond. } else { // End all sessions if any... for (auto i: Accounts) { i->OnEndSession(); } } // close all the other top level windows while (ThingUi::All.Length() > 0) { ThingUi *Ui = ThingUi::All.First(); if (!Ui->OnRequestClose(OsShuttingDown)) { ScribeState = ScribeRunning; Visible(true); return false; } size_t Start = ThingUi::All.Length(); Ui->Quit(); if (ThingUi::All.Length() >= Start) { LAssert(0); break; } } SerializeState(GetOptions(), OPT_ScribeWndPos, false); LCloseApp(); return LWindow::OnRequestClose(OsShuttingDown); } void ScribeWnd::DoOnTimer(LScriptCallback *c) { if (!c) return; auto Now = LCurrentTime(); if (c->PrevTs) { auto Since = Now - c->PrevTs; double Sec = (double)Since / 1000.0; if (Sec >= c->fParam) { // Call the function c->PrevTs = Now; LVirtualMachine Vm; LScriptArguments Args(&Vm); LVariant This((LDom*)this); Args.Add(&This); ExecuteScriptCallback(*c, Args); } } else { c->PrevTs = Now; } } void ScribeWnd::OnMinute() { if (Folders.Length() == 0) return; // Check for calendar event alarms... Calendar::CheckReminders(); // Check for any outgoing email that should be re-attempted... ScribeFolder *Outbox = GetFolder(FOLDER_OUTBOX); if (Outbox) { bool Resend = false; for (auto t : Outbox->Items) { Mail *m = t->IsMail(); if (m && !TestFlag(m->GetFlags(), MAIL_SENT) && TestFlag(m->GetFlags(), MAIL_READY_TO_SEND) && m->SendAttempts > 0) { Resend = true; break; } } if (Resend) Send(); } LArray Cb; if (GetScriptCallbacks(LOnTimer, Cb)) { for (auto c: Cb) { if (!c->Func) continue; if (c->fParam == 0.0) { // Work out the period from 'Data' char *s = c->Data.Str(); while (*s && IsWhiteSpace(*s)) s++; char *u = s; while (*u && !IsAlpha(*u)) u++; double v = atof(s); switch (*u) { case 's': case 'S': // seconds c->fParam = v; break; case 'm': case 'M': // mins c->fParam = v * LDateTime::MinuteLength; break; case 'h': case 'H': // hours c->fParam = v * LDateTime::HourLength; break; case 'd': case 'D': // days c->fParam = v * LDateTime::DayLength; break; default: { LgiTrace("%s:%i - Couldn't understand period '%s'\n", _FL, c->Data.Str()); c->Data.Empty(); break; } } if ((c->OnSecond = c->fParam < 60.0)) { d->OnSecondTimerCallbacks.Add(c); } } if (!c->OnSecond) DoOnTimer(c); } } } void ScribeWnd::OnHour() { // Force time zone update in case of daylight savings change. LDateTime::SystemTimeZone(true); // Check if we need should be doing a software update check static bool InSoftwareCheck = false; if (!InSoftwareCheck) { char s[64]; InSoftwareCheck = true; LVariant v; if (GetOptions()->GetValue(OPT_SoftwareUpdate, v) && v.CastInt32()) { LDateTime Now, Last; Now.SetFormat(GDTF_YEAR_MONTH_DAY); Last.SetFormat(Now.GetFormat()); Now.SetNow(); if (!GetOptions()->GetValue(OPT_SoftwareUpdateLast, v) || !Last.Set(v.Str())) { // Record now as the last check point Now.Get(s, sizeof(s)); GetOptions()->SetValue(OPT_SoftwareUpdateLast, v = s); } else if (GetOptions()->GetValue(OPT_SoftwareUpdateTime, v)) { // Valid last check date/time. switch (v.CastInt32()) { case 0: // Week Last.AddDays(7); break; case 1: // Month Last.AddMonths(1); break; case 2: // Year Last.AddMonths(12); break; default: LgiTrace("%s:%i - The option '%s' is not valid\n", _FL, OPT_SoftwareUpdateTime); return; } if (Last < Now) { // Save the last date for next time... Now.Get(s, sizeof(s)); GetOptions()->SetValue(OPT_SoftwareUpdateLast, v = s); // Check for update now... LSoftwareUpdate::UpdateInfo Info; GetOptions()->GetValue(OPT_SoftwareUpdateIncBeta, v); IsSoftwareUpToDate(Info, this, false, v.CastInt32() != 0, [Info, this](auto s) { if (s == SwOutOfDate) if (UpgradeSoftware(Info, this, true)) LCloseApp(); }); } } } InSoftwareCheck = false; } } bool ScribeWnd::SaveDirtyObjects(int TimeLimitMs) { bool Status = false; if (Thing::DirtyThings.Length() > 0) { static bool SavingObjects = false; if (!SavingObjects) { SavingObjects = true; LArray WriteTimes; // ssize_t StartDirty = Thing::DirtyThings.Length(); uint64 Start = LCurrentTime(); for (unsigned i=0; iSave(NULL)) { WriteTimes.Add((int) (LCurrentTime() - WriteStart)); LAssert(!ThingType::DirtyThings.HasItem(t)); Status = true; } else { LgiTrace("Failed to save thing type 0x%x\n", t->Type()); FailedWrites++; if (FailedWrites > 2) { while (ThingType::DirtyThings.Length()) ThingType::DirtyThings[0]->SetDirty(false); FailedWrites = 0; } } } } SavingObjects = false; /* if (Status) { LStringPipe p; p.Print("WriteTimes: "); for (unsigned i=0; iLastTs >= 1000) { d->LastTs = Now; OnPulseSecond(); } } } void ScribeWnd::OnPulseSecond() { #if PROFILE_ON_PULSE LProfile Prof("NewMailLst handling"); Prof.HideResultsIfBelow(50); #endif if (Mail::NewMailLst.Length() > 0) { LVariant Blink; if (GetOptions()->GetValue(OPT_BlinkNewMail, Blink) && Blink.CastInt32()) { TrayIcon.Value((TrayIcon.Value() == TRAY_ICON_MAIL) ? TRAY_ICON_NONE : TRAY_ICON_MAIL); } } else { bool Err = false; for (auto a: Accounts) { if (!a->Receive.GetStatus() || !a->Send.GetStatus()) { Err = true; } } TrayIcon.Value(Err ? TRAY_ICON_ERROR : TRAY_ICON_NORMAL); } #if PROFILE_ON_PULSE Prof.Add("StatusPanel handling"); #endif if (StatusPanel) { StatusPanel->OnPulse(); } #if PROFILE_ON_PULSE Prof.Add("OnXXXX handling"); #endif LDateTime Now; Now.SetNow(); if (d->LastMinute != Now.Minutes()) // Check every minute... { d->LastMinute = Now.Minutes(); OnMinute(); } if (d->LastHour != Now.Hours()) // Check every hour... { d->LastHour = Now.Hours(); OnHour(); } { // These timers need to be checked every second... for (auto c: d->OnSecondTimerCallbacks) DoOnTimer(c); } #if PROFILE_ON_PULSE Prof.Add("Instance handling"); #endif if (ThisInst && ValidStr(ThisInst->Args)) { LStringPipe p; p.Push(ThisInst->Args); if (ThisInst->Flags & SCRIBE_IPC_LONG_ARGS) { ThisInst->Flags |= SCRIBE_IPC_CONTINUE_ARGS; int64 Start = LCurrentTime(); while ( TestFlag(ThisInst->Flags, SCRIBE_IPC_LONG_ARGS) && LCurrentTime() - Start < 60000) { ZeroObj(ThisInst->Args); while ( TestFlag(ThisInst->Flags, SCRIBE_IPC_LONG_ARGS) && !ThisInst->Args[0] && LCurrentTime() - Start < 60000) { LSleep(10); } p.Push(ThisInst->Args); } } ZeroObj(ThisInst->Args); LAutoString Arg(p.NewStr()); if (Arg) { OsAppArguments AppArgs(0, 0); LgiTrace("Received cmd line: %s\n", Arg.Get()); AppArgs.Set(Arg); LAppInst->SetAppArgs(AppArgs); if (LAppInst->GetOption("m") && LAppInst->GetOption("f")) ; else LAppInst->OnCommandLine(); OnCommandLine(); if (GetZoom() == LZoomMin) SetZoom(LZoomNormal); Visible(true); } } #if PROFILE_ON_PULSE Prof.Add("PreviewPanel handling"); #endif if (PreviewPanel) { PreviewPanel->OnPulse(); } } void ScribeWnd::AddFolderToMru(char *FileName) { if (FileName) { // read MRU List Files; int i; for (i=0; i<10; i++) { char Key[32]; LVariant f; sprintf_s(Key, sizeof(Key), "FolderMru.%i", i); if (GetOptions()->GetValue(Key, f)) { Files.Insert(NewStr(f.Str())); GetOptions()->DeleteValue(Key); } } // remove FileName if present for (auto f: Files) { if (_stricmp(f, FileName) == 0) { Files.Delete(f); DeleteArray(f); break; } } // insert FileName at the start of the list Files.Insert(NewStr(FileName)); // write MRU for (i=0; i<10; i++) { char *n = Files.ItemAt(i); if (n) { char Key[32]; sprintf_s(Key, sizeof(Key), "FolderMru.%i", i); LVariant f; GetOptions()->SetValue(Key, f = n); } else break; } // Clean up Files.DeleteArrays(); } } bool ScribeWnd::CleanFolders(ScribeFolder *f) { if (!f) return false; if (f->Select()) { f->SerializeFieldWidths(); } for (ScribeFolder *c = f->GetChildFolder(); c; c = c->GetNextFolder()) { CleanFolders(c); } return true; } void ScribeWnd::OnFolderChanged(LDataFolderI *folder) { } bool ScribeWnd::OnFolderTask(LEventTargetI *Ptr, bool Add) { if (Add) { if (FolderTasks.HasItem(Ptr)) { LAssert(!"Can't add task twice."); return false; } FolderTasks.Add(Ptr); return true; } else { if (!FolderTasks.HasItem(Ptr)) { LAssert(!"Item not part of task list."); return false; } FolderTasks.Delete(Ptr); return true; } } LMailStore *ScribeWnd::GetDefaultMailStore() { LMailStore *Def = 0; for (unsigned i=0; i Def->Priority()) { Def = &Folders[i]; } } } } return Def; } bool HasMailStore(LXmlTag *MailStores, char *Name) { for (auto t : MailStores->Children) { char *StoreName = t->GetAttr(OPT_MailStoreName); if (StoreName && Name && !_stricmp(StoreName, Name)) return true; } return false; } LDataStoreI *ScribeWnd::CreateDataStore(char *Full, bool CreateIfMissing) { char *Ext = LGetExtension(Full); if (Ext) { if (!_stricmp(Ext, "mail2")) { LgiMsg(this, LLoadString(IDS_MAIL2_DEPRECATED), AppName, MB_OK, Full); } else if (!_stricmp(Ext, "mail3")) { return OpenMail3(Full, this, CreateIfMissing); } else if (!_stricmp(Ext, "sqlite")) { LTrimDir(Full); return OpenMail3(Full, this, CreateIfMissing); } else { LgiTrace("%s:%i - Not a valid mail store extension: %s\n", _FL, Full); LAssert(!"Not a valid mail store extension."); } } else LgiTrace("%s:%i - No extension for CreateDataStore: %s\n", _FL, Full); return NULL; } class MailStoreUpgrade : public LProgressDlg, public LDataPropI { public: ScribeWnd *App = NULL; LDataStoreI *Ds = NULL; int Status = -1; LString Error; MailStoreUpgrade(ScribeWnd *app, LDataStoreI *ds) { App = app; Ds = ds; SetCanCancel(false); SetDescription("Upgrading mail store..."); Ds->Upgrade(this, this, [this](auto status) { Status = status; }); } ~MailStoreUpgrade() { } void OnPulse() override { if (Status >= 0) { EndModal(0); return; } return LProgressDlg::OnPulse(); } LDataPropI &operator =(LDataPropI &p) { LAssert(0); return *this; } Store3Status SetStr(int id, const char *str) override { switch (id) { case Store3UiError: Error = str; break; default: LAssert(!"Impl me."); return Store3Error; break; } return Store3Success; } }; +bool ScribeWnd::ProcessFolder(LDataStoreI *&Store, int StoreIdx, char *StoreName) +{ + if (Store->GetInt(FIELD_VERSION) == 0) + { + // version error + LgiMsg(this, LLoadString(IDS_ERROR_FOLDERS_VERSION), AppName, MB_OK, 0, Store->GetInt(FIELD_VERSION)); + return false; + } + + if (Store->GetInt(FIELD_READONLY)) + { + LgiMsg(this, LLoadString(IDS_ERROR_READONLY_FOLDERS), AppName); + } + + // get root item + LDataFolderI *Root = Store->GetRoot(); + if (!Root) + return false; + + ScribeFolder *&Mailbox = Folders[StoreIdx].Root; + Mailbox = new ScribeFolder; + if (Mailbox) + { + Mailbox->App = this; + Mailbox->SetObject(Root, _FL); + + Root->SetStr(FIELD_FOLDER_NAME, StoreName); + Root->SetInt(FIELD_FOLDER_TYPE, MAGIC_NONE); + } + + #ifdef TEST_OBJECT_SIZE + // debug/repair code + if (Root->StoreSize != Root->Sizeof()) + { + SizeErrors[0]++; + Root->StoreSize = Root->Sizeof(); + if (Root->Object) + { + Root->Object->StoreDirty = true; + } + } + #endif + + // Insert the root object and then... + Tree->Insert(Mailbox); + + // Recursively load the rest of the tree + { + LProfile p("Loadfolders"); + Mailbox->LoadFolders(); + } + + // This forces a re-pour to re-order the folders according to their + // sort settings. + Tree->UpdateAllItems(); + + if (ScribeState != ScribeExiting) + { + // Show the tree + Mailbox->Expanded(Folders[StoreIdx].Expanded); + + // Checks the folders for a number of required objects + // and creates them if required + auto StoreType = Store->GetInt(FIELD_STORE_TYPE); + if (StoreType == Store3Sqlite) + Validate(&Folders[StoreIdx]); + else if (StoreType < 0) + LAssert(!"Make sure you impl the FIELD_STORE_TYPE field in the store."); + + + // FIXME + // AddFolderToMru(Full); + } + + return true; +} + bool ScribeWnd::LoadMailStores() { bool Status = false; LXmlTag *MailStores = GetOptions()->LockTag(OPT_MailStores, _FL); if (!MailStores) return false; bool OptionsDirty = false; int StoreIdx = 0; for (auto MailStore: MailStores->Children) { if (!MailStore->IsTag(OPT_MailStore)) continue; // Read the folders.. auto Path = MailStore->GetAttr(OPT_MailStoreLocation); auto ContactUrl = MailStore->GetAttr(OPT_MailStoreContactUrl); auto CalUrl = MailStore->GetAttr(OPT_MailStoreCalendarUrl); if (!Path && !ContactUrl && !CalUrl) { LgiTrace("%s:%i - No mail store path (%i).\n", _FL, StoreIdx); continue; } char *StoreName = MailStore->GetAttr(OPT_MailStoreName); if (!StoreName) { char Tmp[256]; for (int i=1; true; i++) { sprintf_s(Tmp, sizeof(Tmp), "Folders%i", i); if (!HasMailStore(MailStores, Tmp)) break; } MailStore->SetAttr(OPT_MailStoreName, Tmp); StoreName = MailStore->GetAttr(OPT_MailStoreName); OptionsDirty = true; } Folders[StoreIdx].Name = StoreName; if (MailStore->GetAsInt(OPT_MailStoreDisable) > 0) { // LgiTrace("%s:%i - Mail store '%i' is disabled.\n", _FL, StoreIdx); continue; } if (Path) { // Mail3 folders on disk... char Full[MAX_PATH_LEN]; if (LIsRelativePath(Path)) { LMakePath(Full, sizeof(Full), GetOptions()->GetFile(), ".."); LMakePath(Full, sizeof(Full), Full, Path); } else { strcpy_s(Full, sizeof(Full), Path); } LVariant CreateFoldersIfMissing; GetOptions()->GetValue(OPT_CreateFoldersIfMissing, CreateFoldersIfMissing); // Sniff type... char *Ext = LGetExtension(Full); if (!Ext) continue; if (!Folders[StoreIdx].Store) Folders[StoreIdx].Store = CreateDataStore(Full, CreateFoldersIfMissing.CastInt32() != 0); if (!Folders[StoreIdx].Store) { LgiTrace("%s:%i - Failed to create data store for '%s'\n", _FL, Full); continue; } Folders[StoreIdx].Path = Full; } else if (ContactUrl || CalUrl) { // Remove Webdav folders... Folders[StoreIdx].Store = new WebdavStore(this, this, StoreName); } else break; LDataStoreI *&Store = Folders[StoreIdx].Store; auto ex = MailStore->GetAsInt(OPT_MailStoreExpanded); if (ex >= 0) Folders[StoreIdx].Expanded = ex != 0; // check if the mail store requires upgrading... Store3Status MsState = (Store3Status)Store->GetInt(FIELD_STATUS); if (MsState == Store3UpgradeRequired) { const char *Details = Store->GetStr(FIELD_STATUS); if (LgiMsg(this, LLoadString(IDS_MAILSTORE_UPGRADE_Q), AppName, MB_YESNO, Folders[StoreIdx].Path.Get(), ValidStr(Details)?Details:"n/a") == IDYES) { MailStoreUpgrade Prog(this, Store); Prog.DoModal(); } else { continue; } } else if (MsState == Store3Error) { auto ErrMsg = Store->GetStr(FIELD_ERROR); - LAlert a(this, - AppName, ErrMsg ? ErrMsg : LLoadString(IDS_ERROR_FOLDERS_STATUS), - LLoadString(IDS_EDIT_MAIL_STORES), - LLoadString(IDS_OK)); - int Btn = a.DoModal(); - if (Btn == 1) - { - PostEvent(M_COMMAND, IDM_MANAGE_MAIL_STORES); - break; - } + auto a = new LAlert(this, + AppName, ErrMsg ? ErrMsg : LLoadString(IDS_ERROR_FOLDERS_STATUS), + LLoadString(IDS_EDIT_MAIL_STORES), + LLoadString(IDS_OK)); + a->DoModal([this](auto dlg, auto Btn) + { + if (Btn == 1) + PostEvent(M_COMMAND, IDM_MANAGE_MAIL_STORES); + delete dlg; + }); continue; } + // check password - const char *FolderPsw; + LString FolderPsw; if ((FolderPsw = Store->GetStr(FIELD_STORE_PASSWORD))) { bool Verified = false; if (ValidStr(d->MulPassword)) { Verified = d->MulPassword.Equals(FolderPsw, false); d->MulPassword.Empty(); } if (!Verified) { - LInput Dlg(this, "", LLoadString(IDS_ASK_FOLDER_PASS), AppName, true); - if (Dlg.DoModal() == IDOK) - { - GPassword User; - User.Set(Dlg.GetStr()); - Verified = strcmp(Dlg.GetStr(), FolderPsw) == 0; - } - } - - if (!Verified) - { - DeleteObj(Store); - } - } - - // process folders - if (Store->GetInt(FIELD_VERSION) == 0) - { - // version error - LgiMsg(this, LLoadString(IDS_ERROR_FOLDERS_VERSION), AppName, MB_OK, 0, Store->GetInt(FIELD_VERSION)); - } - else - { - if (Store->GetInt(FIELD_READONLY)) - { - LgiMsg(this, LLoadString(IDS_ERROR_READONLY_FOLDERS), AppName); - } - - // get root item - LDataFolderI *Root = Store->GetRoot(); - if (Root) - { - ScribeFolder *&Mailbox = Folders[StoreIdx].Root; - Mailbox = new ScribeFolder; - if (Mailbox) - { - Mailbox->App = this; - Mailbox->SetObject(Root, false, _FL); - - Root->SetStr(FIELD_FOLDER_NAME, StoreName); - Root->SetInt(FIELD_FOLDER_TYPE, MAGIC_NONE); - } - - #ifdef TEST_OBJECT_SIZE - // debug/repair code - if (Root->StoreSize != Root->Sizeof()) - { - SizeErrors[0]++; - Root->StoreSize = Root->Sizeof(); - if (Root->Object) - { - Root->Object->StoreDirty = true; - } - } - #endif - - // Insert the root object and then... - Tree->Insert(Mailbox); - - // Recursively load the rest of the tree - { - LProfile p("Loadfolders"); - Mailbox->LoadFolders(); - } - - // This forces a re-pour to re-order the folders according to their - // sort settings. - Tree->UpdateAllItems(); - - if (ScribeState != ScribeExiting) - { - // Show the tree - Mailbox->Expanded(Folders[StoreIdx].Expanded); - - // Checks the folders for a number of required objects - // and creates them if required - auto StoreType = Store->GetInt(FIELD_STORE_TYPE); - if (StoreType == Store3Sqlite) - Validate(&Folders[StoreIdx]); - else if (StoreType < 0) - LAssert(!"Make sure you impl the FIELD_STORE_TYPE field in the store."); - - - // FIXME - // AddFolderToMru(Full); - } - - // LYield(); - Status = true; - } - } + auto Dlg = new LInput(this, "", LLoadString(IDS_ASK_FOLDER_PASS), AppName, true); + Dlg->DoModal([this, Dlg, FolderPsw, &Store, StoreIdx, StoreName](auto dlg, auto id) + { + if (id == IDOK) + { + GPassword User; + User.Set(Dlg->GetStr()); + if (Dlg->GetStr() == FolderPsw) + ProcessFolder(Store, StoreIdx, StoreName); + else + DeleteObj(Store); + } + delete dlg; + }); + } + } + else ProcessFolder(Store, StoreIdx, StoreName); + StoreIdx++; } if (Status) { // Force load some folders... ScribeFolder *Folder = GetFolder(FOLDER_CALENDAR); if (Folder) Folder->LoadThings(); Folder = GetFolder(FOLDER_FILTERS); if (Folder) Folder->LoadThings(); for (auto ms: Folders) { if (!ms.Root) continue; for (auto c = ms.Root->GetChildFolder(); c; c = c->GetNextFolder()) { if (c->GetItemType() == MAGIC_CONTACT || c->GetItemType() == MAGIC_FILTER) c->LoadThings(); } } List c; GetContacts(c); // Set selected folder to inbox by default // if the user hasn't selected a folder already if (ScribeState != ScribeExiting && Tree && !Tree->Selection()) { LVariant StartInFolder; GetOptions()->GetValue(OPT_StartInFolder, StartInFolder); ScribeFolder *Start = 0; if (ValidStr(StartInFolder.Str())) { Start = GetFolder(StartInFolder.Str()); } if (!Start) { Start = GetFolder(FOLDER_INBOX); } if (Start && Tree) { Tree->Select(Start); } } } GetOptions()->DeleteValue(OPT_CreateFoldersIfMissing); if (OptionsDirty) SaveOptions(); GetOptions()->Unlock(); // Set system folders ScribeFolder *f = GetFolder(FOLDER_INBOX); if (f) f->SetSystemFolderType(Store3SystemInbox); f = GetFolder(FOLDER_OUTBOX); if (f) f->SetSystemFolderType(Store3SystemOutbox); f = GetFolder(FOLDER_SENT); if (f) f->SetSystemFolderType(Store3SystemSent); f = GetFolder(FOLDER_SPAM); if (f) f->SetSystemFolderType(Store3SystemSpam); return Status; } -bool ScribeWnd::LoadFolders() -{ - bool Status = false; +void ScribeWnd::LoadFolders(std::function Callback) +{ AppState PrevState = ScribeState; ScribeState = ScribeLoadingFolders; // Setup Mailstores tag { LXmlTag *MailStores = GetOptions()->LockTag(OPT_MailStores, _FL); if (!MailStores) { // Check if we can upgrade the old folder tag char n[32]; sprintf_s(n, sizeof(n), "%s-Folders", LGetOsName()); LVariant OldFolders; GetOptions()->GetValue(n, OldFolders); // Create mail store element.. GetOptions()->CreateTag(OPT_MailStores); if ((MailStores = GetOptions()->LockTag(OPT_MailStores, _FL))) { if (OldFolders.Str()) { LXmlTag *Store = MailStores->CreateTag(OPT_MailStore); if (Store) { char Opts[MAX_PATH_LEN]; LMakePath(Opts, sizeof(Opts), GetOptions()->GetFile(), ".."); auto Rel = LMakeRelativePath(Opts, OldFolders.Str()); Store->SetAttr(OPT_MailStoreLocation, Rel ? Rel.Get() : OldFolders.Str()); // No need to ask the user for a store name, it'll be // asked later in this method anyway... // Leave the old folder tag in the xml in case the user // downgrades to v1.xx } } } } + + GetOptions()->Unlock(); if (!MailStores) - return false; - GetOptions()->Unlock(); + { + if (Callback) + Callback(false); + return; + } } // Set loading flags CmdSend.Enabled(false); CmdReceive.Enabled(false); CmdPreview.Enabled(false); - Status = LoadMailStores(); + bool Status = LoadMailStores(); if (Tree) { for (auto a: Accounts) { if (!a->Receive.Disabled() && a->Receive.IsPersistant()) a->Receive.Connect(0, false); } } - if (Folders.Length() == 0) - { - ScribeFolderDlg Dlg(this); - if (Dlg.DoModal() == IDOK) - { - bool CreateMailStore = false; - - if (Dlg.Create) - { - // create folders - if (LFileExists(Dlg.FolderFile)) - { - if (LgiMsg(this, LLoadString(IDS_ERROR_FOLDERS_ALREADY_EXIST), AppName, MB_YESNO) == IDYES) - { - CreateMailStore = true; - } - else - { - LgiMsg(this, LLoadString(IDS_ERROR_WONT_OVERWRITE_FOLDERS), AppName); - } - } - else if ((Status = CreateFolders(Dlg.FolderFile))) - { - CreateMailStore = true; - } + using BoolFn = std::function; + auto FinishLoad = new BoolFn + ( + [this, PrevState, Callback](bool Status) + { + if (ScribeState == ScribeExiting) + { + LCloseApp(); } else { - CreateMailStore = true; - } - - if (CreateMailStore) - { - LXmlTag *MailStores = GetOptions()->LockTag(OPT_MailStores, _FL); - if (MailStores) - { - LXmlTag *Store = MailStores->CreateTag(OPT_MailStore); - if (Store) - { - char p[MAX_PATH_LEN]; - LMakePath(p, sizeof(p), GetOptions()->GetFile(), ".."); - auto RelPath = LMakeRelativePath(p, Dlg.FolderFile); - Store->SetAttr(OPT_MailStoreLocation, RelPath ? RelPath.Get() : Dlg.FolderFile.Get()); - } - GetOptions()->Unlock(); - - LoadMailStores(); - } - } - } - } - - if (ScribeState == ScribeExiting) - { - LCloseApp(); + d->FoldersLoaded = true; + PostEvent(M_SCRIBE_LOADED); + } + + if (ScribeState == ScribeExiting) + LCloseApp(); + ScribeState = PrevState; + + if (Callback) + Callback(Status); + } + ); + + if (Folders.Length() == 0) + { + auto Dlg = new ScribeFolderDlg(this); + Dlg->DoModal([this, Dlg, FinishLoad, Callback, &Status](auto dlg, auto id) + { + if (id == IDOK) + { + bool CreateMailStore = false; + + if (Dlg->Create) + { + // create folders + if (LFileExists(Dlg->FolderFile)) + { + if (LgiMsg(this, LLoadString(IDS_ERROR_FOLDERS_ALREADY_EXIST), AppName, MB_YESNO) == IDYES) + CreateMailStore = true; + else + LgiMsg(this, LLoadString(IDS_ERROR_WONT_OVERWRITE_FOLDERS), AppName); + } + else if ((Status = CreateFolders(Dlg->FolderFile))) + CreateMailStore = true; + } + else + CreateMailStore = true; + + if (CreateMailStore) + { + LXmlTag *MailStores = GetOptions()->LockTag(OPT_MailStores, _FL); + if (MailStores) + { + LXmlTag *Store = MailStores->CreateTag(OPT_MailStore); + if (Store) + { + char p[MAX_PATH_LEN]; + LMakePath(p, sizeof(p), GetOptions()->GetFile(), ".."); + auto RelPath = LMakeRelativePath(p, Dlg->FolderFile); + Store->SetAttr(OPT_MailStoreLocation, RelPath ? RelPath.Get() : Dlg->FolderFile.Get()); + } + GetOptions()->Unlock(); + + LoadMailStores(); + } + } + } + + if (id) + (*FinishLoad)(Status); + else if (Callback) + Callback(false); + delete FinishLoad; + delete dlg; + }); } else { - d->FoldersLoaded = true; - PostEvent(M_SCRIBE_LOADED); - } - - if (ScribeState == ScribeExiting) - LCloseApp(); - ScribeState = PrevState; - - return Status; + (*FinishLoad)(Status); + delete FinishLoad; + } } bool ScribeWnd::UnLoadFolders() { if (FolderTasks.Length() > 0 || ScribeState == ScribeLoadingFolders) { // Um, we can't unload folders right now // something is already happening... return false; } AppState PrevState = ScribeState; ScribeState = ScribeUnloadingFolders; OnSelect(); if (MailList) { ScribeFolder *Container = MailList->GetContainer(); if (Container) { // save folder settings Container->SerializeFieldWidths(); } MailList->SetContainer(NULL); MailList->RemoveAll(); } int Error = 0; while (Thing::DirtyThings.Length() > 0) { if (!SaveDirtyObjects()) { Error++; LgiTrace("%s:%i - SaveDirtyObjects failed.\n", _FL); if (Error > 100) { // I think we're stuck... return false; } } } // Unload IMAP folders... for (auto a: Accounts) { if (!a->Receive.Disabled() && a->Receive.IsPersistant()) { a->Receive.Disconnect(); } } - // Unload local folders... - LXmlTag *MailStores = GetOptions()->LockTag(OPT_MailStores, _FL); - - for (size_t i=0; iExpanded(); - - for (auto ms: MailStores->Children) - { - char *StoreName = ms->GetAttr(OPT_MailStoreName); - if (Folders[i].Name.Equals(StoreName)) - { - ms->SetAttr(OPT_MailStoreExpanded, Expanded); - break; - } - } - } - - DeleteObj(Folders[i].Root); - DeleteObj(Folders[i].Store); - } - - if (MailStores) - { - GetOptions()->Unlock(); - MailStores = NULL; + if (GetOptions()) + { + // Unload local folders... + LXmlTag *MailStores = GetOptions()->LockTag(OPT_MailStores, _FL); + + for (size_t i=0; iExpanded(); + + for (auto ms: MailStores->Children) + { + char *StoreName = ms->GetAttr(OPT_MailStoreName); + if (Folders[i].Name.Equals(StoreName)) + { + ms->SetAttr(OPT_MailStoreExpanded, Expanded); + break; + } + } + } + + DeleteObj(Folders[i].Root); + DeleteObj(Folders[i].Store); + } + + if (MailStores) + { + GetOptions()->Unlock(); + MailStores = NULL; + } } Folders.Length(0); d->FoldersLoaded = false; if (ScribeState == ScribeExiting) LCloseApp(); ScribeState = PrevState; return true; } void ScribeWnd::BuildDynMenus() { if (MailMenu) { LString SendMail = LLoadString(IDS_SEND_MAIL); LString ReceiveMail = LLoadString(IDS_RECEIVE_MAIL); LString PreviewMail = LLoadString(IDS_PREVIEW_ON_SERVER); auto ReceiveAll = LLoadString(IDS_RECEIVE_ALL_ACCOUNTS); if (!CmdReceive.MenuItem && ReceiveAll) CmdReceive.MenuItem = MailMenu->AppendItem(ReceiveAll, IDM_RECEIVE_ALL, true); if (!SendMenu && SendMail) { auto s = SendMail.SplitDelimit("\t"); SendMenu = MailMenu->AppendSub(s[0]); } if (!ReceiveMenu && ReceiveMail) { auto s = ReceiveMail.SplitDelimit("\t"); ReceiveMenu = MailMenu->AppendSub(s[0]); } if (!PreviewMenu && PreviewMail) { auto s = PreviewMail.SplitDelimit("\t"); PreviewMenu = MailMenu->AppendSub(s[0]); } } if (!NewTemplateMenu) { auto i = Menu->FindItem(IDM_NO_TEMPLATES); if (i) { NewTemplateMenu = i->GetParent(); } } if (NewTemplateMenu) { NewTemplateMenu->Empty(); int d = 0; ScribeFolder *Templates = GetFolder(FOLDER_TEMPLATES, NULL, true); if (Templates) { Templates->LoadThings(); for (auto i: Templates->Items) { Mail *m = i->IsMail(); if (m) { NewTemplateMenu->AppendItem(m->GetSubject() ? m->GetSubject() : (char*)"(no subject)", IDM_NEW_FROM_TEMPLATE + d, true); d++; } } if (d == 0) { NewTemplateMenu->AppendItem(LLoadString(IDS_NO_ITEMS_IN_FOLDER), -1, false); } } else { NewTemplateMenu->AppendItem(LLoadString(IDS_NO_TEMPLATES), -1, false); } } } int ScribeWnd::GetToolbarHeight() { return (Commands) ? MAX(Commands->Y()-1, 20) : 20; } LToolBar *ScribeWnd::LoadToolbar(LViewI *Parent, const char *File, LAutoPtr &Img) { if (!Img) Img.Reset(LLoadImageList(File)); if (!Img) { LAssert(!"Missing image resource."); return NULL; } LToolBar *Tools = NULL; if (Img) { Tools = new LToolBar; if (Tools) Tools->SetImageList(Img, Img->TileX(), Img->TileY(), false); } else { Tools = LgiLoadToolbar(Parent, File); } if (Tools) { LVariant SizeAdj; LFont *f = Tools->GetFont(); if (f) { if (GetOptions()->GetValue(OPT_UiFontSize, SizeAdj)) { SizeAdj.Cast(GV_INT32); SizeAdj.Value.Int -= 2; f->PointSize(f->PointSize()+SizeAdj.Value.Int); } } Tools->GetCss(true)->BorderSpacing(LCss::Len(LCss::LenPx, SCRIBE_TOOLBAR_BORDER_SPACING_PX)); Tools->TextLabels(ShowToolbarText()); } return Tools; } void ScribeWnd::SetListPane(LView *v) { ThingList *ThingLst = dynamic_cast(v); DynamicHtml *Html = dynamic_cast(v); if (!ThingLst) { DeleteObj(SearchView); if (MailList) MailList->RemoveAll(); } v->Sunken(SUNKEN_CTRL); if ((MailList = ThingLst)) { DeleteObj(TitlePage); if (GetCtrlValue(IDM_ITEM_FILTER)) { OnCommand(IDM_ITEM_FILTER, 0, NULL); } } else { TitlePage = Html; } SetLayout(); } bool ScribeWnd::SetItemPreview(LView *v) { v->Sunken(SUNKEN_CTRL); if (d->SubSplit->IsAttached()) { if (d->LastLayout == 2) { Splitter->SetViewAt(1, v); } else { d->SubSplit->SetViewAt(1, v); } return true; } return false; } ScribeWnd::LayoutMode ScribeWnd::GetEffectiveLayoutMode() { LVariant Mode; GetOptions()->GetValue(OPT_LayoutMode, Mode); ScribeFolder *Cur = GetCurrentFolder(); if (Cur && Cur->IsRoot()) { Mode = FoldersAndList; } if (Mode.CastInt32() == 0) { Mode = FoldersListAndPreview; } return (LayoutMode) Mode.CastInt32(); } void ScribeWnd::SetLayout(LayoutMode Mode) { // int TreeWidth = Tree ? Tree->X() : 200; // int PreviewHt = PreviewPanel ? PreviewPanel->Y() : 200; if (Mode > 0) { LVariant v; GetOptions()->SetValue(OPT_LayoutMode, v = (int)Mode); } Mode = GetEffectiveLayoutMode(); if (!Splitter) return; bool JustPreviewPane = (Mode == FoldersAndList && d->LastLayout == FoldersListAndPreview) || (Mode == FoldersListAndPreview && d->LastLayout == FoldersAndList); LView *Content = NULL; if (TitlePage) Content = TitlePage; else if (MailList) Content = MailList; if (JustPreviewPane) { // Optimized path for hide/show the preview pane that doesn't destroy the tree // control and cause it to lose focus... otherwise we can't set it's focus due // to some weird windows interaction. switch (Mode) { default: case FoldersListAndPreview: { if (Content) Content->Sunken(SUNKEN_CTRL); if (PreviewPanel) PreviewPanel->Sunken(SUNKEN_CTRL); Splitter->SetViewAt(1, d->SubSplit); d->SubSplit->SetVertical(true); int Idx = 0; if (SearchView) d->SubSplit->SetViewAt(Idx++, SearchView); d->SubSplit->SetViewAt(Idx++, Content); d->SubSplit->SetViewAt(Idx++, PreviewPanel); break; } case FoldersAndList: { if (Content) Content->Sunken(SUNKEN_CTRL); if (SearchView) { #if LGI_VIEW_HANDLE if (!d->SubSplit->Handle()) Splitter->SetViewAt(1, d->SubSplit); #endif d->SubSplit->SetVertical(true); Splitter->SetViewAt(1, d->SubSplit); int Idx = 0; if (SearchView) d->SubSplit->SetViewAt(Idx++, SearchView); d->SubSplit->SetViewAt(Idx++, Content); } else { d->SubSplit->Detach(); Splitter->SetViewAt(1, Content); } break; } } } else { if (Tree) Tree->Sunken(SUNKEN_CTRL); if (Content) Content->Sunken(SUNKEN_CTRL); if (PreviewPanel) PreviewPanel->Sunken(SUNKEN_CTRL); switch (Mode) { default: case FoldersListAndPreview: { Splitter->SetVertical(false); d->SubSplit->SetVertical(true); Splitter->SetViewAt(0, Tree); Splitter->SetViewAt(1, d->SubSplit); int Idx = 0; if (SearchView) d->SubSplit->SetViewAt(Idx++, SearchView); d->SubSplit->SetViewAt(Idx++, Content); d->SubSplit->SetViewAt(Idx++, PreviewPanel); DeleteObj(d->SearchSplit); break; } case PreviewOnBottom: { Splitter->SetVertical(true); d->SubSplit->SetVertical(false); Splitter->SetViewAt(0, d->SubSplit); Splitter->SetViewAt(1, PreviewPanel); d->SubSplit->SetViewAt(0, Tree); if (SearchView) { if (!d->SearchSplit) d->SearchSplit = new LBox; d->SubSplit->SetViewAt(1, d->SearchSplit); d->SearchSplit->SetVertical(true); d->SearchSplit->SetViewAt(0, SearchView); d->SearchSplit->SetViewAt(1, Content); } else { d->SubSplit->SetViewAt(1, Content); DeleteObj(d->SearchSplit); } break; } case FoldersAndList: { Splitter->SetVertical(false); Splitter->SetViewAt(0, Tree); if (SearchView) { d->SubSplit->SetVertical(true); Splitter->SetViewAt(1, d->SubSplit); d->SubSplit->SetViewAt(0, SearchView); d->SubSplit->SetViewAt(1, Content); } else { d->SubSplit->Detach(); Splitter->SetViewAt(1, Content); } DeleteObj(d->SearchSplit); break; } case ThreeColumn: { Splitter->SetVertical(false); Splitter->SetViewAt(0, Tree); if (SearchView) { d->SubSplit->SetVertical(true); Splitter->SetViewAt(1, d->SubSplit); d->SubSplit->SetViewAt(0, SearchView); d->SubSplit->SetViewAt(1, Content); } else { d->SubSplit->Detach(); Splitter->SetViewAt(1, Content); } Splitter->SetViewAt(2, PreviewPanel); DeleteObj(d->SearchSplit); break; } } } if (!SearchView) { LVariant Pos = 200; GetOptions()->GetValue(OPT_SplitterPos, Pos); if (Pos.CastInt32() < 10) Pos = 200; Splitter->Value(Pos.CastInt32()); LRect r = Splitter->GetPos(); r.x2++; Splitter->SetPos(r); if (d->SubSplit->IsAttached()) { Pos = 200; GetOptions()->GetValue(OPT_SubSplitPos, Pos); if (Pos.CastInt32() < 10) Pos = 200; d->SubSplit->Value(Pos.CastInt32()); } } PourAll(); #ifdef LINUX LYield(); #endif d->LastLayout = Mode; } void ScribeWnd::SetupUi() { // Show the window if (!SerializeState(GetOptions(), OPT_ScribeWndPos, true)) { LRect r(0, 0, 1023, 767); SetPos(r); MoveToCenter(); } Visible(true); // Main toolbar Commands = LoadToolbar(this, GetResourceFile(ResToolbarFile), ToolbarImgs); if (Commands) { Commands->Attach(this); #ifdef MAC Commands->Raised(false); #else Commands->Raised(RAISED_LOOK); #endif Commands->AppendButton(RemoveAmp(LLoadString(IDS_NEW_EMAIL)), IDM_NEW_EMAIL, TBT_PUSH, true, IMG_NEW_MAIL); Commands->AppendButton(RemoveAmp(LLoadString(IDS_NEW_CONTACT)), IDM_NEW_CONTACT, TBT_PUSH, true, IMG_NEW_CONTACT); Commands->AppendSeparator(); CmdSend.ToolButton = Commands->AppendButton(RemoveAmp(LLoadString(IDS_SEND)), IDM_SEND_MAIL, TBT_PUSH, true, IMG_SEND); Commands->AppendButton("+", IDM_RECEIVE_AND_SEND, TBT_PUSH, true, -2); CmdReceive.ToolButton = Commands->AppendButton(RemoveAmp(LLoadString(IDS_RECEIVE)), IDM_RECEIVE_MAIL, TBT_PUSH, true, IMG_RECEIVE); CmdPreview.ToolButton = Commands->AppendButton(RemoveAmp(LLoadString(IDS_PREVIEW)), IDM_PREVIEW_POP3, TBT_PUSH, true, IMG_PREVIEW); Commands->AppendSeparator(); Commands->AppendButton(RemoveAmp(LLoadString(IDS_DELETE)), IDM_DELETE, TBT_PUSH, true, IMG_TRASH); Commands->AppendButton(RemoveAmp(LLoadString(IDS_SPAM)), IDM_DELETE_AS_SPAM, TBT_PUSH, true, IMG_DELETE_SPAM); Commands->AppendSeparator(); Commands->AppendButton(RemoveAmp(LLoadString(IDS_REPLY)), IDM_REPLY, TBT_PUSH, true, IMG_REPLY); Commands->AppendButton(RemoveAmp(LLoadString(IDS_REPLYALL)), IDM_REPLY_ALL, TBT_PUSH, true, IMG_REPLY_ALL); Commands->AppendButton(RemoveAmp(LLoadString(IDS_FORWARD)), IDM_FORWARD, TBT_PUSH, true, IMG_FORWARD); Commands->AppendButton(RemoveAmp(LLoadString(IDS_BOUNCE)), IDM_BOUNCE, TBT_PUSH, true, IMG_BOUNCE); Commands->AppendSeparator(); Commands->AppendButton(RemoveAmp(LLoadString(IDS_PRINT)), IDM_PRINT, TBT_PUSH, true, IMG_PRINT); Commands->AppendButton(RemoveAmp(LLoadString(IDS_CALENDAR)), IDM_CALENDAR, TBT_PUSH, true, IMG_CALENDAR); Commands->AppendButton(RemoveAmp(LLoadString(IDS_ITEM_FILTER)), IDM_ITEM_FILTER, TBT_TOGGLE, true, IMG_SEARCH); Commands->AppendButton(RemoveAmp(LLoadString(IDS_THREAD)), IDM_THREAD, TBT_TOGGLE, true, IMG_THREADS); d->ShowConsoleBtn = Commands->AppendButton(RemoveAmp(LLoadString(IDS_SHOW_CONSOLE)), IDM_SHOW_CONSOLE, TBT_PUSH, true, IMG_CONSOLE_NOMSG); Commands->AppendSeparator(); Commands->AppendButton(RemoveAmp(LLoadString(IDS_HELP)), IDM_HELP, TBT_PUSH, true, IMG_HELP); Commands->Customizable(GetOptions(), OPT_ScribeWndToolbar); if (d->ScriptToolbar.Reset(new LScriptUi(Commands))) d->ScriptToolbar->SetupCallbacks(this, 0, 0, LApplicationToolbar); PourAll(); } CmdSend.Enabled(false); CmdReceive.Enabled(false); // Preview and status windows PreviewPanel = new LPreviewPanel(this); StatusPanel = new AccountStatusPanel(this, ImageList); if (PreviewPanel && StatusPanel) { #ifdef MAC StatusPanel->Raised(false); #else StatusPanel->Raised(RAISED_LOOK); #endif StatusPanel->Attach(this); PourAll(); } // Splitter window, for folders and item list Tree = new MailTree(this); if (ImageList) { Tree->AskImage(true); Tree->SetImageList(ImageList, false); } d->SubSplit = new LBox; Splitter = new LBox; if (Splitter) { Splitter->Attach(this); #ifdef MAC Splitter->Raised(false); #else Splitter->Raised(RAISED_LOOK); #endif SetLayout(); } #if WINNATIVE TrayIcon.Load(MAKEINTRESOURCE(IDI_SMALL)); TrayIcon.Load(MAKEINTRESOURCE(IDI_ERR)); TrayIcon.Load(MAKEINTRESOURCE(IDI_MAIL)); TrayIcon.Load(MAKEINTRESOURCE(IDI_BLANK)); #else TrayIcon.Load(_T("tray_small.png")); TrayIcon.Load(_T("tray_error.png")); TrayIcon.Load(_T("tray_mail.png")); #endif LStringPipe s(256); LVariant UserName; s.Print("%s", AppName); if (GetOptions()->GetValue(OPT_UserName, UserName)) { s.Print(" [%s]", UserName.Str()); } auto AppTitle = s.NewGStr(); TrayIcon.Name(AppTitle); Name(AppTitle); TrayIcon.Value(TRAY_ICON_NORMAL); TrayIcon.Visible(true); auto Item = Menu->FindItem(IDM_SCRIPTING_CONSOLE); if (Item) { LVariant v; if (GetOptions()->GetValue(OPT_ShowScriptConsole, v)) { Item->Checked(v.CastInt32() != 0); LScribeScript::Inst->ShowScriptingWindow(Item->Checked()); } } if (Tree) { Tree->Focus(true); } } Thing *ScribeWnd::CreateItem(int Type, ScribeFolder *Folder, bool Ui) { auto FolderStore = Folder && Folder->GetObject() ? Folder->GetObject()->GetStore() : NULL; auto DefaultStore = GetDefaultMailStore(); auto Store = FolderStore ? FolderStore : (DefaultStore ? DefaultStore->Store : NULL); if (!Store) { LAssert(!"no store"); LgiTrace("%s:%i - No store for creating calendar object.\n", _FL); return NULL; } auto Obj = Store->Create(Type); if (!Obj) { LAssert(!"create failed"); LgiTrace("%s:%i - store failed to create object.\n", _FL); return NULL; } #define HANDLE_CREATE_ITEM(Magic, Type) \ case Magic: \ { \ auto o = new Type(this, Obj); \ if (!o) \ { \ LgiTrace("%s:%i - Alloc failed.\n", _FL); \ break; \ } \ if (Folder) o->SetParentFolder(Folder); \ if (Ui) o->DoUI(); \ return o; \ } switch ((uint32_t)Type) { case MAGIC_MAIL: { // create a new mail message Mail *m = new Mail(this, Obj); if (!m) { LgiTrace("%s:%i - Alloc failed.\n", _FL); break; } if (!m->GetObject()) { m->DecRef(); return 0; } m->OnCreate(); if (Folder) m->SetParentFolder(Folder); if (Ui) m->DoUI(); return m; } HANDLE_CREATE_ITEM(MAGIC_CONTACT, Contact) HANDLE_CREATE_ITEM(MAGIC_CALENDAR, Calendar) HANDLE_CREATE_ITEM(MAGIC_FILTER, Filter) HANDLE_CREATE_ITEM(MAGIC_GROUP, ContactGroup) default: LAssert(!"Unhandled object type."); break; } return NULL; } void ScribeWnd::OnPaint(LSurface *pDC) { #if 0 pDC->Colour(LColour::Red); pDC->Rectangle(); #else LCssTools Tools(this); auto c = GetClient(); // c.Offset(0, -26); Tools.PaintContent(pDC, c); #endif } int CompareContacts(Contact **a, Contact **b) { if (a && b) { auto A = (*a)->GetFirst(); auto B = (*b)->GetFirst(); if (A && B) return _stricmp(A, B); } return 0; } bool ScribeWnd::OpenAMail(ScribeFolder *Folder) { if (Folder && Tree) { Folder->LoadThings(); for (auto i: Folder->Items) { Mail *m = i->IsMail(); if (m && !(m->GetFlags() & MAIL_READ)) { Tree->Select(Folder); m->DoUI(); return true; } } for (auto *t=Folder->GetChild(); t; t=t->GetNext()) { ScribeFolder *f = dynamic_cast(t); if (OpenAMail(f)) { return true; } } } return false; } void AddContactToMenu(LSubMenu *Menu, Contact *c, ssize_t Index) { if (!c || Index < 0) return; auto Email = c->GetEmail(); if (!Email) return; // has an email, list it auto First = c->GetFirst(); auto Last = c->GetLast(); if (First || Last) { char Buf[256]; sprintf_s(Buf, sizeof(Buf), "%s %s", (First)?First:"", (Last)?Last:""); auto Item = Menu->AppendItem(Buf, TRAY_CONTACT_BASE + (int)Index, true); if (Item) Item->Icon(ICON_CONTACT); } } void ScribeWnd::AddContactsToMenu(LSubMenu *Menu) { if (!Menu) return; d->TrayMenuContacts.Sort(CompareContacts); if (((ssize_t)d->TrayMenuContacts.Length() << 4) > GdcD->Y() - 200) { // Group contacts by starting letter LArray Alpha[26]; LArray Other; for (auto c: d->TrayMenuContacts) { auto First = c->GetFirst(); auto Last = c->GetLast(); auto Email = c->GetEmail(); if (Email) { // has an email, list it if (First || Last) { auto Name = First?First:Last; if ( (*Name >= 'a' && *Name <= 'z') || (*Name >= 'A' && *Name <= 'Z') ) { int Ind = tolower(*Name) - 'a'; if (Ind >= 0 && Ind < CountOf(Alpha)) Alpha[Ind].Add(c); else Other.Add(c); } else Other.Add(c); } } } for (int i=0; i 0) { char Group[64]; sprintf_s(Group, sizeof(Group), "%c...", 'a' + i); auto Sub = Menu->AppendSub(Group); if (Sub) { for (auto c: Alpha[i]) AddContactToMenu(Sub, c, d->TrayMenuContacts.IndexOf(c)); } } } if (Other.Length()) { auto Sub = Menu->AppendSub("Other..."); if (Sub) { for (auto c: Other) AddContactToMenu(Sub, c, d->TrayMenuContacts.IndexOf(c)); } } } else { // Display all... for (size_t i=0; iTrayMenuContacts.Length(); i++) { AddContactToMenu(Menu, d->TrayMenuContacts[i], i); } } } void ScribeWnd::OnUrl(const char *Url) { LUri u(Url); if (u.sProtocol && !_stricmp(u.sProtocol, "mailto")) { CreateMail(0, Url, 0); } } void ScribeWnd::OnReceiveFiles(LArray &Files) { int UnknownFormats = 0; int64 Period = LCurrentTime() - LastDrop; if (Period > 500) // Lock out drops within 500ms of an LGI drop { LString sSend, sPages; bool HasSend = LAppInst->GetOption("send", sSend); bool HasPrint = LAppInst->GetOption("p", sPages); LArray NewMail; for (unsigned i=0; i str(new LFile); if (str->Open(f, O_READ)) { if (t->Import(t->AutoCast(str), MimeType)) { if (HasSend) { Mail *m = t->IsMail(); if (m) { NewMail.Add(m); m->SetFlags(m->GetFlags() | MAIL_CREATED | MAIL_READ); } } else { t->DoUI(); } } } } } else UnknownFormats++; } bool SendNow = sSend ? atoi(sSend) != 0 : false; for (unsigned i=0; iSetFlags(m->GetFlags() | MAIL_READY_TO_SEND); m->Save(); } else if (HasPrint) { - ThingPrint(m, GetPrinter(), 0, sPages ? atoi(sPages) : 0); + ThingPrint(NULL, m, GetPrinter(), 0, sPages ? atoi(sPages) : 0); } else { m->DoUI(); } } if (SendNow) Send(); } } void ScribeWnd::OnTrayClick(LMouse &m) { if (m.Down()) { #ifndef MAC // No support for different mouse button info so the default // action is to show the sub-menu of contacts and actions. if (m.IsContextMenu()) #endif { LWindow::OnTrayClick(m); } #ifndef MAC else if (m.Left()) { if (m.Double()) { if (Mail::NewMailLst.Length() > 0) { ScribeFolder *InBox = GetFolder(FOLDER_INBOX); OpenAMail(InBox); } } else { if (GetZoom() == LZoomMin) { SetZoom(LZoomNormal); } else { if (Obscured()) SetZoom(LZoomNormal); // Bounce in front, first. else SetZoom(LZoomMin); } Visible(true); MoveOnScreen(); Raise(); if (MailList) { MailList->Focus(true); } } } else if (m.Middle()) { Mail::NewMailLst.Empty(); } #endif } } void ScribeWnd::OnTrayMenu(LSubMenu &m) { m.SetImageList(ImageList, false); d->TrayMenuContacts.Length(0); #ifdef MAC m.AppendItem(LLoadString(IDS_OPEN), IDM_OPEN); m.AppendSeparator(); #endif LHashTbl,Contact*> Added; LArray Srcs = GetThingSources(MAGIC_CONTACT); for (auto c: Srcs) { c->LoadThings(); for (auto i: c->Items) { Contact *c = i->IsContact(); if (!c) continue; bool IsAdded = false; auto Emails = c->GetEmails(); for (auto e: Emails) { if (Added.Find(e)) IsAdded = true; } if (Emails.Length() && !IsAdded) { for (auto e: Emails) Added.Add(e, c); d->TrayMenuContacts.Add(c); } } } AddContactsToMenu(&m); m.AppendSeparator(); if (Mail::NewMailLst.Length() > 0) { int i=0; for (auto ml: Mail::NewMailLst) { LStringPipe p; // This code figures out how many UTF characters to print. We // can't split a UTF character because downstream conversions // will fail. const char *Subj = ml->GetSubject(); if (!Subj) Subj = "(No Subject)"; LUtf8Ptr u((uint8_t*)Subj); while ((int32)u && u.GetPtr() - (uchar*)Subj < 64) u++; ssize_t Bytes = u.GetPtr() - (uchar*)Subj; p.Print("%.*s, %s <%s>", Bytes, Subj?Subj:(char*)"(No Subject)", ml->GetFromStr(FIELD_NAME), ml->GetFromStr(FIELD_EMAIL)); LAutoString a(p.NewStr()); LAssert(LIsUtf8(a)); auto Item = m.AppendItem(a, TRAY_MAIL_BASE+i++, true); if (Item) { Item->Icon(ICON_UNREAD_MAIL); } } m.AppendSeparator(); } if (GetZoom() == LZoomMin) { m.AppendItem(LLoadString(IDS_OPEN), IDM_OPEN, true); } auto NewMail = m.AppendItem(LLoadString(IDS_NEW_EMAIL), IDM_NEW_EMAIL); if (NewMail) NewMail->Icon(ICON_UNSENT_MAIL); m.AppendItem(LLoadString(IDS_EXIT), IDM_EXIT); } void ScribeWnd::OnTrayMenuResult(int MenuId) { switch (MenuId) { case IDM_OPEN: { if (GetZoom() == LZoomMin) { SetZoom(LZoomNormal); } Visible(true); Raise(); if (MailList) { MailList->Focus(true); } break; } case IDM_NEW_EMAIL: { CreateMail(); break; } case IDM_EXIT: { d->IngoreOnClose = true; PostEvent(M_CLOSE); break; } default: { auto i = MenuId - TRAY_CONTACT_BASE; Contact *c = d->TrayMenuContacts.IdxCheck(i) ? d->TrayMenuContacts[i] : NULL; if (c) { CreateMail(c); } Mail *m = Mail::NewMailLst[MenuId - TRAY_MAIL_BASE]; if (m) { Mail::NewMailLst.Delete(m); m->DoUI(); } break; } } } void ScribeWnd::OnZoom(LWindowZoom Action) { if (Action == LZoomMin) { LVariant i; if (GetOptions()->GetValue(OPT_MinimizeToTray, i) && i.CastInt32()) { Visible(false); } } } struct UserInput { + std::function Callback; LView *Parent; - LString Result; LString Msg; - bool Password, Done; + bool Password; + UserInput() { - Password = Done = false; + Password = false; } }; -LString ScribeWnd::GetUserInput(LView *Parent, LString Msg, bool Password) +void ScribeWnd::GetUserInput(LView *Parent, LString Msg, bool Password, std::function Callback) { if (InThread()) { - LInput Inp(Parent ? Parent : this, "", Msg, AppName, Password); - return Inp.DoModal() ? Inp.GetStr() : NULL; - } - - UserInput i; - i.Parent = Parent; - i.Msg = Msg; - i.Password = Password; - if (!PostEvent(M_GET_USER_INPUT, (LMessage::Param)&i)) + auto Inp = new LInput(Parent ? Parent : this, "", Msg, AppName, Password); + Inp->DoModal([this, Inp, Callback](auto dlg, auto id) + { + if (Callback) + Callback(id ? Inp->GetStr() : NULL); + delete dlg; + }); + } + + auto i = new UserInput; + i->Parent = Parent; + i->Msg = Msg; + i->Password = Password; + i->Callback = Callback; + if (!PostEvent(M_GET_USER_INPUT, (LMessage::Param)i)) { LAssert(!"PostEvent failed."); - return NULL; - } - - while (!i.Done) - LSleep(50); - - return i.Result; + if (Callback) + Callback(NULL); + } } LMessage::Result ScribeWnd::OnEvent(LMessage *Msg) { TrayIcon.OnEvent(Msg); BayesianFilter::OnEvent(Msg); switch (Msg->Msg()) { case M_UNIT_TEST: { LAutoPtr j((LJson*)Msg->A()); if (!j) break; auto cmd = j->Get("cmd"); if (!cmd) break; if (cmd.Equals("somecmd")) { } break; } case M_CALENDAR_SOURCE_EVENT: { CalendarSource *cs = (CalendarSource*)Msg->A(); LAutoPtr m((LMessage*)Msg->B()); if (cs && m) cs->OnEvent(m); break; } case M_GET_USER_INPUT: { - UserInput *i = (UserInput*)Msg->A(); + LAutoPtr i((UserInput*)Msg->A()); LAssert(i); LAssert(InThread()); // Least we get stuck in an infinite loop - i->Result = GetUserInput(i->Parent, i->Msg, i->Password); - i->Done = true; + + GetUserInput(i->Parent, i->Msg, i->Password, i->Callback); break; } case M_SET_HTML: { LAutoPtr Html((LString*)Msg->A()); if (PreviewPanel && Html) { LVariant Ret; LArray Arg; Arg.Add(new LVariant(Html->Get())); PreviewPanel->CallMethod(DomToStr(SdSetHtml), &Ret, Arg); Arg.DeleteObjects(); } break; } case M_NEW_CONSOLE_MSG: { if (d->ShowConsoleBtn) d->ShowConsoleBtn->Image(IDM_CONSOLE_MSGS); break; } case M_NEEDS_CAP: { LAutoString c((char*)Msg->A()); LAutoString param((char*)Msg->B()); NeedsCapability(c, param); return 0; break; } case M_STORAGE_EVENT: { LDataStoreI *Store = LDataStoreI::Map.Find((int)Msg->A()); if (Store) Store->OnEvent((void*)Msg->B()); break; } case M_SCRIBE_SET_MSG_FLAG: { LAutoString p((char*)Msg->A()); if (p) { char *d = strrchr(p, '/'); if (d) { *d++ = 0; ScribeFolder *f = GetFolder(p); if (f) { LUri u; LString a = u.DecodeStr(d); - Mail *r = f->GetMessageById(a); - if (r) + f->GetMessageById(a, [&](auto r) { - int ExistingFlags = r->GetFlags(); - int NewFlag = (int)Msg->B(); - r->SetFlags(ExistingFlags | NewFlag); - } + if (r) + { + int ExistingFlags = r->GetFlags(); + int NewFlag = (int)Msg->B(); + r->SetFlags(ExistingFlags | NewFlag); + } + }); } } } break; } case M_SCRIBE_DEL_THING: { Thing *t = (Thing*)Msg->A(); DeleteObj(t); break; } case M_SCRIBE_LOADED: { if (d->FoldersLoaded) { // Ok let the user in CmdSend.Enabled(true); CmdReceive.Enabled(true); CmdPreview.Enabled(true); } break; } case M_SCRIBE_THREAD_DONE: { // Finialize connection AccountThread *Thread = (AccountThread*) Msg->A(); Accountlet *Acc = (Accountlet*) Msg->B(); if (Thread && Acc) { OnAfterConnect(Acc->GetAccount(), Acc->IsReceive()); d->NewMailTimeout = 2; Acc->OnThreadDone(); } else { LAssert(0); } break; } case M_SCRIBE_MSG: { char *m = (char*)Msg->A(); if (m) { if (Msg->B()) { if (LgiMsg(this, m, AppName, MB_YESNO) == IDYES) { PostEvent(M_COMMAND, IDM_OPTIONS, 0); } } else { LgiMsg(this, "%s", AppName, MB_OK, m); } DeleteArray(m); } break; } /* case M_SCRIBE_LOG_MSG: { List *Log = (List*)Msg->A(); LAutoPtr Entry((LogEntry*)Msg->B()); if (ScribeState != ScribeExiting && Log && Entry) { Log->Insert(Entry.Release()); // Trim long list... while (Log->Length() > 1024) { LogEntry *e = Log->First(); Log->Delete(e); DeleteObj(e); } } break; } */ case M_SCRIBE_NEW_MAIL: { if (Lock(_FL)) { if (NewMailDlg) { NewMailDlg->AddThings(&Mail::NewMailLst); } Unlock(); } break; } case M_SCRIBE_OPEN_THING: { Thing *t = (Thing*) Msg->A(); if (t) { t->DoUI(); } break; } case M_SCRIBE_ITEM_SELECT: { if (!MailList || !IsAttached()) break; List Things; MailList->GetSelection(Things); OnSelect(&Things); break; } case M_URL: { LAutoPtr Url((LString*)Msg->A()); if (Url) { LUri u(*Url); if (u.sProtocol && !_stricmp(u.sProtocol, "mailto")) { Mailto mt(this, *Url); if (mt.To.Length() > 0) { Thing *t = CreateItem(MAGIC_MAIL, NULL, false); if (t) { Mail *m = t->IsMail(); if (m) { mt.Apply(m); m->DoUI(); } else DeleteObj(t); } } } } break; } } return LWindow::OnEvent(Msg); } bool ScribeWnd::IsMyEmail(const char *Email) { if (Email) { LVariant e; for (auto a : *GetAccounts()) { LVariant e = a->Identity.Email(); if (e.Str() && _stricmp(Email, e.Str()) == 0) { return true; } } } return false; } int ScribeWnd::GetMaxPages() { return d->PrintMaxPages; } -bool ScribeWnd::ThingPrint(ThingType *m, LPrinter *Printer, LView *Parent, int MaxPages) -{ - int Status = 0; +void ScribeWnd::ThingPrint(std::function Callback, ThingType *m, LPrinter *Printer, LView *Parent, int MaxPages) +{ d->PrintMaxPages = MaxPages; if (!Printer) Printer = GetPrinter(); if (!Printer) - return false; + { + if (Callback) Callback(false); + return; + } Thing *t = dynamic_cast(m); if (!t) - return false; + { + if (Callback) Callback(false); + return; + } ScribePrintContext Events(this, t); - Status = Printer->Print(&Events, AppName, -1, Parent ? Parent : this); - if (Status == Events.OnBeginPrintError) - LgiMsg(Parent, "Printing failed: %s", AppName, MB_OK, Printer->GetErrorMsg().Get()); - else - Status = true; - - return Status > 0; // Number of pages printed.. + Printer->Print( &Events, + [&](auto pages) + { + if (pages == Events.OnBeginPrintError) + { + LgiMsg(Parent, "Printing failed: %s", AppName, MB_OK, Printer->GetErrorMsg().Get()); + if (Callback) + Callback(false); + } + else if (Callback) + { + Callback(true); + } + }, + AppName, + -1, + Parent ? Parent : this); } bool ScribeWnd::MailReplyTo(Mail *m, bool All) { bool Status = false; if (m) { LDataStoreI *Store = m->GetObject() ? m->GetObject()->GetStore() : NULL; LDataI *NewMailObj = Store && Store->GetInt(FIELD_STORE_TYPE) == Store3Sqlite ? Store->Create(MAGIC_MAIL) : NULL; Mail *NewMail = new Mail(this, NewMailObj); if (NewMail) { if (NewMail->GetObject()) { NewMail->OnReply(m, All, true); LView *w = NewMail->DoUI(); if (w) { LViewI *t = w->FindControl(IDC_TEXT); if (t) { t->Focus(true); } } Status = true; } else DeleteObj(NewMail); } } return Status; } bool ScribeWnd::MailForward(Mail *m) { bool Status = false; if (m) { Mail *NewMail = new Mail(this); if (NewMail) { if (NewMail->OnForward(m, true)) { NewMail->DoUI(); Status = true; } else { NewMail->DecRef(); } } } return Status; } bool ScribeWnd::MailBounce(Mail *m) { bool Status = false; if (m) { Mail *NewMail = new Mail(this); if (NewMail) { if (NewMail->OnBounce(m, true)) { NewMail->DoUI(); Status = true; } else { DeleteObj(NewMail); } } } return Status; } Mail *ScribeWnd::CreateMail(Contact *c, const char *Email, const char *Name) { Mail *m = dynamic_cast(CreateItem(MAGIC_MAIL, NULL, false)); if (m) { bool IsMailTo = false; if (Email) { IsMailTo = _strnicmp(Email, "mailto:", 7) == 0; if (IsMailTo) { Mailto mt(this, Email); mt.Apply(m); } } MailUi *UI = dynamic_cast(m->DoUI()); if (UI) { if (c) { UI->AddRecipient(c); } if (Email && !IsMailTo) { UI->AddRecipient(Email, Name); } } } return m; } Mail *ScribeWnd::LookupMailRef(const char *MsgRef, bool TraceAllUids) { if (!MsgRef) return 0; LAutoString p(NewStr(MsgRef)); char *RawUid = strrchr(p, '/'); if (RawUid) { *RawUid++ = 0; LUri u; LString Uid = u.DecodeStr(RawUid); // Try the mail message map first... Mail *m = Mail::GetMailFromId(Uid); if (m) return m; // Ok, not found, so look in last known folder... ScribeFolder *f = GetFolder(p); if (f) { for (auto t : f->Items) { Mail *m = t->IsMail(); if (m) { auto s = m->GetMessageId(); if (s && !strcmp(s, Uid)) { return m; } if (TraceAllUids) LgiTrace("\t%s\n", s); } } } } return 0; } void ScribeWnd::OnBayesAnalyse(const char *Msg, const char *WhiteListEmail) { LString s, q; s.Printf("
%s
", Msg); if (WhiteListEmail) { q.Printf(LLoadString(IDS_REMOVE_WHITELIST), WhiteListEmail); s += LString("
") + q; } s += ""; - auto result = LHtmlMsg(this, s, AppName, WhiteListEmail ? MB_YESNO : MB_OK); - if (result == IDYES) - { - RemoveFromWhitelist(WhiteListEmail); - } + LHtmlMsg([&](auto result) + { + if (result == IDYES) + RemoveFromWhitelist(WhiteListEmail); + }, + this, + s, + AppName, + WhiteListEmail ? MB_YESNO : MB_OK); } bool ScribeWnd::OnBayesResult(const char *MailRef, double Rating) { Mail *m = LookupMailRef(MailRef); if (m) return OnBayesResult(m, Rating); #ifdef _DEBUG else { LgiTrace("%s:%i - error finding mail ref: %s\n", _FL, MailRef); LookupMailRef(MailRef, true); LAssert(!"We should always be able to resolve the reference, unless m is completely deleted"); } #endif return false; } bool ScribeWnd::OnBayesResult(Mail *m, double Rating) { if (!m) return false; LVariant v; GetOptions()->GetValue(OPT_BayesThreshold, v); double BayesThresh = v.CastDouble(); if (BayesThresh < 0.1) BayesThresh = 0.1; if (BayesThresh > 1.0) BayesThresh = 1.0; if (Rating < BayesThresh) { // Not spam, so we continue new mail processing if (m->NewEmail == Mail::NewEmailBayes) { List Nm; Nm.Insert(m); m->NewEmail = Mail::NewEmailGrowl; OnNewMail(&Nm, true); } return false; } // Spam is pink! m->SetMarkColour(Rgb32(255, 0, 0)); m->SetFlags(m->GetFlags() | MAIL_BAYES_SPAM); ScribeBayesianFilterMode FilterMode = BayesOff; if (GetOptions()->GetValue(OPT_BayesFilterMode, v)) FilterMode = (ScribeBayesianFilterMode)v.CastInt32(); if (FilterMode == BayesTrain) { // Move to folder LVariant MoveToPath; if (!GetOptions()->GetValue(OPT_BayesMoveTo, MoveToPath)) { MoveToPath = "/Spam/Probably"; } ScribeFolder *f = GetFolder(MoveToPath.Str()); if (f) { LArray Items; Items.Add(m); f->MoveTo(Items); List obj; obj.Insert(m); OnNewMail(&obj, false); } } else { m->DeleteAsSpam(this); } return true; } static int AccountCmp(ScribeAccount *a, ScribeAccount *b, int Data) { return a->Identity.Sort() - b->Identity.Sort(); } class ScribePasteState : public LProgressDlg { ScribeWnd *App = NULL; ScribeFolder *Folder = NULL; LAutoPtr Data; ssize_t Size = 0; LDataStoreI::StoreTrans Trans; LProgressPane *LoadPane = NULL, *SavePane = NULL; ScribeClipboardFmt *tl = NULL; uint32_t Errors = 0; ssize_t Idx = 0; enum PasteState { LoadingThings, SavingThings, } State = LoadingThings; public: ScribePasteState(ScribeWnd *app, ScribeFolder *folder, LAutoPtr data, ssize_t size) : LProgressDlg(app), App(app), Folder(folder), Data(data), Size(size) { // Paste 'ScribeThingList' tl = (ScribeClipboardFmt*)Data.Get(); Trans = Folder->GetObject()->GetStore()->StartTransaction(); LoadPane = ItemAt(0); LoadPane->SetDescription("Loading objects..."); LoadPane->SetRange(tl->Length()); SavePane = Push(); SavePane->SetRange(tl->Length()); SavePane->SetDescription("Saving: No errors..."); // LProgressDlg will do a SetPulse in it's OnCreate } void OnPulse() { auto Start = LCurrentTime(); static int TimeSlice = 300; //ms if (State == LoadingThings) { while ( Idx < tl->Length() && !IsCancelled() && LCurrentTime() - Start < TimeSlice) { Thing *t = tl->ThingAt(Idx++); if (!t) continue; auto Obj = t->GetObject(); if (Obj->GetInt(FIELD_LOADED) < Store3Loaded) Obj->SetInt(FIELD_LOADED, Store3Loaded); } Value(Idx); if (Idx >= tl->Length()) { State = SavingThings; Idx = 0; } } else if (State == SavingThings) { while ( Idx < tl->Length() && !IsCancelled() && LCurrentTime() - Start < TimeSlice) { Thing *t = tl->ThingAt(Idx++); if (!t) continue; auto Obj = t->GetObject(); LAssert(Obj->GetInt(FIELD_LOADED) == Store3Loaded); // Load loop should have done this already Thing *Dst = App->CreateItem(Obj->Type(), Folder, false); if (Dst) { *Dst = *t; Dst->Update(); if (!Dst->Save(Folder)) { LString s; s.Printf("Saving: " LPrintfSSizeT " error(s)", ++Errors); SetDescription(s); } } else Errors++; } SavePane->Value(Idx); if (Idx >= tl->Length()) { if (Errors > 0) LgiMsg(this, "Failed to save %i of %i objects.", AppName, MB_OK, Errors, tl->Length()); Quit(); return; } } LProgressDlg::OnPulse(); } }; int ScribeWnd::OnCommand(int Cmd, int Event, OsView WndHandle) { // Send mail multi-menu if (Cmd >= IDM_SEND_FROM && Cmd <= IDM_SEND_FROM + (ssize_t)Accounts.Length()) { Send(Cmd - IDM_SEND_FROM); return 0; } // Receive mail multi-menu if (Cmd >= IDM_RECEIVE_FROM && Cmd < IDM_RECEIVE_FROM + (ssize_t)Accounts.Length()) { Receive(Cmd - IDM_RECEIVE_FROM); return 0; } // Preview mail multi-menu if (Cmd >= IDM_PREVIEW_FROM && Cmd < IDM_PREVIEW_FROM + (ssize_t)Accounts.Length()) { Preview(Cmd - IDM_PREVIEW_FROM); return 0; } // Identity multi-menu if (Cmd >= IDM_IDENTITY_BASE && Cmd <= IDM_IDENTITY_BASE + (ssize_t)Accounts.Length()) { SetCurrentIdentity(Cmd - IDM_IDENTITY_BASE - 1); return 0; } // Is this a script tool? if (LScribeScript::Inst && Cmd >= IDM_TOOL_SCRIPT_BASE && Cmd < IDM_TOOL_SCRIPT_BASE + (int)d->Scripts.Length()) { // Do tools menu callback... find the right callback.... LArray c; if (GetScriptCallbacks(LToolsMenu, c)) { for (unsigned i=0; iFunc && c[i]->Param == Cmd) { // Call the callback char Msg[MAX_PATH_LEN]; LScribeScript::Inst->GetLog()->Write ( Msg, sprintf_s(Msg, sizeof(Msg), "\n\nRunning tool script '%s'...\n", c[i]->Script->Code->GetFileName()) ); // Setup the arguments... LScriptArguments Args(NULL); Args.New() = new LVariant((LDom*)this); Args.New() = new LVariant(Cmd); // Call the method ExecuteScriptCallback(*c[i], Args); // Cleanup Args.DeleteObjects(); break; } } } return 0; } // New from template multi-menu if (Cmd >= IDM_NEW_FROM_TEMPLATE && Cmd < IDM_NEW_FROM_TEMPLATE + 100) { int Index = Cmd - IDM_NEW_FROM_TEMPLATE; ScribeFolder *Templates = GetFolder(FOLDER_TEMPLATES); if (Templates) { Templates->LoadThings(); for (auto i: Templates->Items) { Mail *m = i->IsMail(); if (m) { if (Index == 0) { Thing *t = CreateItem(MAGIC_MAIL, 0, false); // GetFolder(FOLDER_OUTBOX) Mail *NewMail = IsMail(t); if (NewMail) { *NewMail = (Thing&)*m; NewMail->DoUI(); break; } } Index--; } } } return 0; } switch (Cmd) { // File menu case IDM_MANAGE_MAIL_STORES: { - ManageMailStores Dlg(this); - if (Dlg.DoModal()) - { - SaveOptions(); - - if (!UnLoadFolders()) - break; - - LXmlTag *Ms = GetOptions()->LockTag(OPT_MailStores, _FL); - if (Ms) - { - while (Ms->Children.Length()) - delete Ms->Children[0]; + auto Dlg = new ManageMailStores(this); + Dlg->DoModal([this, Dlg](auto dlg, auto id) + { + LAutoPtr mem(dlg); + if (id) + { + SaveOptions(); + + if (!UnLoadFolders()) + return; + + LXmlTag *Ms = GetOptions()->LockTag(OPT_MailStores, _FL); + if (Ms) + { + while (Ms->Children.Length()) + delete Ms->Children[0]; - LXmlTag *t = Dlg.Options.GetChildTag(OPT_MailStores); - if (t) - { - for (auto c: t->Children) + LXmlTag *t = Dlg->Options.GetChildTag(OPT_MailStores); + if (t) { - LXmlTag *n = new LXmlTag; - n->Copy(*c, true); - Ms->InsertTag(n); + for (auto c: t->Children) + { + LXmlTag *n = new LXmlTag; + n->Copy(*c, true); + Ms->InsertTag(n); + } } - } - - GetOptions()->Unlock(); - } - - LVariant v; - GetOptions()->SetValue(OPT_CreateFoldersIfMissing, v = true); - - if (!Dlg.Options.GetValue(OPT_StartInFolder, v)) - v.Empty(); - GetOptions()->SetValue(OPT_StartInFolder, v); - - LoadFolders(); - } + + GetOptions()->Unlock(); + } + + LVariant v; + GetOptions()->SetValue(OPT_CreateFoldersIfMissing, v = true); + + if (!Dlg->Options.GetValue(OPT_StartInFolder, v)) + v.Empty(); + GetOptions()->SetValue(OPT_StartInFolder, v); + + LoadFolders(NULL); + } + }); break; } case IDM_REPLICATE: { - ReplicateDlg Dlg(this); - if (Dlg.DoModal()) - { - UnLoadFolders(); - Dlg.StartProcess(); - } + auto Dlg = new ReplicateDlg(this); + Dlg->DoModal([this, Dlg](auto dlg, auto id) + { + if (id) + { + UnLoadFolders(); + Dlg->StartProcess(); + // Don't delete dialog... let it run + } + else delete dlg; + }); break; } case IDM_SECURITY: { // Check for user perm password... // No point allow any old one to edit the security settings. - bool Allow = true; + auto ShowDialog = [&]() + { + auto Dlg = new SecurityDlg(this); + Dlg->DoModal(NULL); + }; + GPassword p; if (p.Serialize(GetOptions(), OPT_UserPermPassword, false)) { - Allow = GetAccessLevel(this, PermRequireUser, "Security Settings"); - } - if (Allow) - { - SecurityDlg Dlg(this); - Dlg.DoModal(); + GetAccessLevel(this, PermRequireUser, "Security Settings", [&](bool Allow) + { + if (Allow) + ShowDialog(); + }); + } + else + { + ShowDialog(); } break; } case IDM_OPTIONS: { LVariant ShowTotals; GetOptions()->GetValue(OPT_ShowFolderTotals, ShowTotals); // do the dialog - OptionsDlg Dlg(this); - int Status = Dlg.DoModal(); - if (Status) - { - // set up the POP3 accounts - SetupAccounts(); - SaveOptions(); - - // close any IMAP accounts that are now disabled. - for (auto a : Accounts) - { - if (a->Receive.IsConfigured() && - a->Receive.IsPersistant()) - { - if (a->Receive.Disabled()) - a->Receive.Disconnect(); - else - Receive(a->GetIndex()); - } - } - - // List/Tree view options update - LVariant i; - if (GetOptions()->GetValue(OPT_ShowFolderTotals, i) && - i.CastInt32() != ShowTotals.CastInt32()) - { - Tree->UpdateAllItems(); - } - if (GetOptions()->GetValue(OPT_PreviewLines, i)) - { - Mail::PreviewLines = i.CastInt32() != 0; - } - if (MailList) - { - if (GetOptions()->GetValue(OPT_GridLines, i)) - { - MailList->DrawGridLines(i.CastInt32() != 0); - } - MailList->Invalidate(); - } - - // date formats - if (GetOptions()->GetValue(OPT_DateFormat, i)) - { - int Idx = i.CastInt32(); - if (Idx >= 0 && Idx < CountOf(DateTimeFormats)) - { - LDateTime::SetDefaultFormat(DateTimeFormats[Idx]); - } - } - if (GetOptions()->GetValue(OPT_AdjustDateTz, i)) - Mail::AdjustDateTz = i.CastInt32() == 0; - - // SSL debug logging - if (GetOptions()->GetValue(OPT_DebugSSL, i)) - SslSocket::DebugLogging = i.CastInt32() != 0; - - // Html edit menu - if (GetOptions()->GetValue(OPT_EditControl, i)) - { - auto mi = Menu->FindItem(IDM_HTML_EDITOR); - if (mi) mi->Checked(i.CastInt32() != 0); - } - } + auto Dlg = new OptionsDlg(this); + Dlg->DoModal([this, Dlg, ShowTotals](auto dlg, auto id) + { + if (id) + { + // set up the POP3 accounts + SetupAccounts(); + SaveOptions(); + + // close any IMAP accounts that are now disabled. + for (auto a : Accounts) + { + if (a->Receive.IsConfigured() && + a->Receive.IsPersistant()) + { + if (a->Receive.Disabled()) + a->Receive.Disconnect(); + else + Receive(a->GetIndex()); + } + } + + // List/Tree view options update + LVariant i; + if (GetOptions()->GetValue(OPT_ShowFolderTotals, i) && + i.CastInt32() != ShowTotals.CastInt32()) + { + Tree->UpdateAllItems(); + } + if (GetOptions()->GetValue(OPT_PreviewLines, i)) + { + Mail::PreviewLines = i.CastInt32() != 0; + } + if (MailList) + { + if (GetOptions()->GetValue(OPT_GridLines, i)) + { + MailList->DrawGridLines(i.CastInt32() != 0); + } + MailList->Invalidate(); + } + + // date formats + if (GetOptions()->GetValue(OPT_DateFormat, i)) + { + int Idx = i.CastInt32(); + if (Idx >= 0 && Idx < CountOf(DateTimeFormats)) + { + LDateTime::SetDefaultFormat(DateTimeFormats[Idx]); + } + } + if (GetOptions()->GetValue(OPT_AdjustDateTz, i)) + Mail::AdjustDateTz = i.CastInt32() == 0; + + // SSL debug logging + if (GetOptions()->GetValue(OPT_DebugSSL, i)) + SslSocket::DebugLogging = i.CastInt32() != 0; + + // Html edit menu + if (GetOptions()->GetValue(OPT_EditControl, i)) + { + auto mi = Menu->FindItem(IDM_HTML_EDITOR); + if (mi) mi->Checked(i.CastInt32() != 0); + } + } + delete dlg; + }); break; } case IDM_WORK_OFFLINE: { if (WorkOffline) { WorkOffline->Checked(!WorkOffline->Checked()); LVariant v; GetOptions()->SetValue(OPT_WorkOffline, v = WorkOffline->Checked()); if (!WorkOffline->Checked()) { // Offline -> Online transition. // Check if any pending messages are in the Outbox ScribeFolder *Outbox = GetFolder(FOLDER_OUTBOX); if (Outbox) { bool HasMailToSend = false; for (auto t: Outbox->Items) { Mail *m = t->IsMail(); if (m) { if (TestFlag(m->GetFlags(), MAIL_READY_TO_SEND)) { HasMailToSend = true; break; } } } if (HasMailToSend) { PostEvent(M_COMMAND, IDM_SEND_MAIL, #ifndef __GTK_H__ (LMessage::Param)Handle() #else 0 #endif ); } } } } break; } case IDM_ITEM_FILTER: { if (GetCtrlValue(IDM_ITEM_FILTER)) { if ((SearchView = new LSearchView(this))) { SearchView->Focus(true); SetLayout(); } } else { DeleteObj(SearchView); } ScribeFolder *Folder = GetCurrentFolder(); if (Folder) { Folder->Populate(MailList); } break; } case IDM_PRINT: { if (MailList) { List Sel; if (MailList->LList::GetSelection(Sel)) { for (auto i: Sel) { ThingType *t = dynamic_cast(i); - ThingPrint(t); + ThingPrint(NULL, t); } } } break; } case IDM_PRINTSETUP: { auto *p = GetPrinter(); if (p && p->Browse(this)) { LString Str; if (p->Serialize(Str, true)) { LVariant v; GetOptions()->SetValue(OPT_PrintSettings, v = Str); } } break; } case IDM_PAGE_SETUP: { - ScribePageSetup Dlg(this, GetOptions()); - Dlg.DoModal(); + auto Dlg = new ScribePageSetup(this, GetOptions()); + Dlg->DoModal(NULL); break; } case IDM_EXIT: { LMouse m; GetMouse(m); d->IngoreOnClose = m.Ctrl(); LCloseApp(); break; } // Edit menu case IDM_FIND: { auto v = GetFocus(); LDocView *doc = dynamic_cast(v); if (doc) { - doc->DoFind(); + doc->DoFind(NULL); } else { ScribeFolder *Folder = GetCurrentFolder(); OpenFinder(this, Folder); } break; } case IDM_COPY: { if (MailList && MailList->Focus()) { List Lst; if (!MailList->GetSelection(Lst)) break; // Copy 'ScribeThingList' ScribeClipboardFmt *tl = ScribeClipboardFmt::Alloc(Lst); if (!tl) break; LClipBoard Clip(this); if (Clip.IsOpen()) { if (!Clip.Binary(d->ClipboardFormat, (uchar*)tl, tl->Sizeof(), true)) { LgiMsg(this, "Couldn't set the clipboard data.", AppName, MB_OK); } } else { LgiMsg(this, "Couldn't open the clipboard.", AppName, MB_OK); } free(tl); } else { LViewI *v = LAppInst->GetFocus(); if (v) v->PostEvent(M_COPY); } break; } case IDM_PASTE: { LViewI *v = LAppInst->GetFocus(); if (v && v->GetWindow() != (LWindow*)this) { v->PostEvent(M_PASTE); break; } if (!MailList->Focus() && !Tree->Focus()) { LgiTrace("%s:%i - List/Tree doesn't have focus.\n"); break; } ScribeFolder *Folder = dynamic_cast(Tree->Selection()); if (!Folder || !Folder->GetObject()) { LgiMsg(this, "No current folder.", AppName, MB_OK); break; } LClipBoard Clip(this); if (!Clip.IsOpen()) { LgiMsg(this, "Couldn't open the clipboard.", AppName, MB_OK); break; } LAutoPtr Data; ssize_t Size = 0; if (!Clip.Binary(d->ClipboardFormat, Data, &Size)) { LgiMsg(this, "Couldn't get the clipboard data.", AppName, MB_OK); break; } if (ScribeClipboardFmt::IsThing(Data.Get(), Size)) { new ScribePasteState(this, Folder, Data, Size); } break; } case IDM_DELETE: { LViewI *f = LAppInst->GetFocus(); LEdit *e = dynamic_cast(f); if (e) { // This handles the case where on a mac the menu eats the delete key, even // when the edit control needs it LKey k(LK_DELETE, 0); k.Down(true); f->OnKey(k); k.Down(false); f->OnKey(k); } else { OnDelete(); } break; } case IDM_DELETE_AS_SPAM: { if (MailList) { List Sel; MailList->GetSelection(Sel); int Index = -1; for (auto i: Sel) { Mail *m = IsMail(i); if (m) { if (Index < 0) { Index = MailList->IndexOf(i); } m->DeleteAsSpam(this); } } if (Index >= 0) { LListItem *i = MailList->ItemAt(Index); if (!i) i = MailList->ItemAt(MailList->Length()-1); if (i) i->Select(true); } } break; } case IDM_REFRESH: { ScribeFolder *f = GetCurrentFolder(); if (!f) break; const char *s = DomToStr(SdRefresh); f->GetFldObj()->OnCommand(s); break; } // Mail menu case IDM_NEW_EMAIL: { CreateMail(); break; } case IDM_SET_READ: case IDM_SET_UNREAD: { ScribeFolder *f = GetCurrentFolder(); if (!f) break; bool SetRead = Cmd == IDM_SET_READ; f->LoadThings(); LArray Change; for (auto t: f->Items) { Mail *m = t->IsMail(); if (m && m->Select()) Change.Add(m->GetObject()); } LVariant v = MAIL_READ; LDataStoreI *Store = f->GetObject()->GetStore(); if (Store->Change(Change, FIELD_FLAGS, v, SetRead ? OpPlusEquals : OpMinusEquals) == Store3Error) { for (auto t : f->Items) { Mail *m = t->IsMail(); if (!m) continue; if (!m->Select()) continue; if (SetRead) m->SetFlags(m->GetFlags() | MAIL_READ); else m->SetFlags(m->GetFlags() & ~MAIL_READ); } } break; } case IDM_REPLY: case IDM_REPLY_ALL: { if (MailList) MailReplyTo(IsMail(MailList->GetSelected()), (Cmd == IDM_REPLY_ALL)); break; } case IDM_FORWARD: { if (MailList) MailForward(IsMail(MailList->GetSelected())); break; } case IDM_BOUNCE: { if (MailList) MailBounce(IsMail(MailList->GetSelected())); break; } case IDM_SEND_MAIL: { Send(); break; } case IDM_RECEIVE_AND_SEND: { d->SendAfterReceive = true; PostEvent(M_COMMAND, IDM_RECEIVE_MAIL, (LMessage::Param)FindControl(IDM_RECEIVE_MAIL)); break; } case IDM_THREAD: { if (MailList) { ScribeFolder *f = GetCurrentFolder(); if (f) { f->SetThreaded(!f->GetThreaded()); f->Populate(MailList); } } break; } case IDM_RECEIVE_ALL: { #define LOG_RECEIVE_ALL 0 int i = 0; Accounts.Sort(AccountCmp); for (auto a : Accounts) { #if LOG_RECEIVE_ALL auto name = a->Identity.Name(); auto email = a->Identity.Email(); LString desc; desc.Printf("%s/%s", name.Str(), email.Str()); #endif if (!a->Receive.IsConfigured()) { #if LOG_RECEIVE_ALL LgiTrace("%s:%i - %i/%s not configured.\n", _FL, a->GetIndex(), desc.Get()); #endif } else if (a->Receive.Disabled() > 0) { #if LOG_RECEIVE_ALL LgiTrace("%s:%i - %i/%s is disabled.\n", _FL, a->GetIndex(), desc.Get()); #endif } else { #if LOG_RECEIVE_ALL LgiTrace("%s:%i - %i/%s will connect.\n", _FL, a->GetIndex(), desc.Get()); #endif Receive(a->GetIndex()); } i++; } break; } case IDM_RECEIVE_MAIL: { LVariant Def; if (GetOptions()->GetValue(OPT_Pop3DefAction, Def) && Def.CastInt32() == 0) return OnCommand(IDM_RECEIVE_ALL, 0, NULL); Receive(0); break; } case IDM_PREVIEW_POP3: { LArray Account; Accounts.Sort(AccountCmp); for (auto a: Accounts) { if (!a->Receive.IsConfigured()) continue; auto Protocol = ProtocolStrToEnum(a->Receive.Protocol().Str()); if (Protocol == ProtocolPop3) { Account.Add(a); break; } } if (Account.Length() == 1) OpenPopView(this, Account); break; } case IDM_CALENDAR: { extern void OpenCalender(ScribeFolder *folder); ScribeFolder *Folder = GetFolder(FOLDER_CALENDAR); if (Folder) { OpenCalender(Folder); } break; } // Contact menu case IDM_NEW_CONTACT: { CreateItem(MAGIC_CONTACT, NULL); break; } case IDM_NEW_GROUP: { CreateItem(MAGIC_GROUP, NULL); break; } // Filter menu case IDM_NEW_FILTER: { Thing *t = CreateItem(MAGIC_FILTER, NULL, false); if (t) { t->IsFilter()->SetIncoming(true); t->DoUI(); } break; } case IDM_FILTER_CURRENT_FOLDER: { ScribeFolder *Folder = GetCurrentFolder(); if (Folder) { List Filters; GetFilters(Filters, false, false, true); List Src; for (auto i: Folder->Items) { if (i->IsMail()) { Src.Insert(i->IsMail()); } } if (!Src[0]) { LgiMsg(this, LLoadString(IDS_NO_MAIL_TO_FILTER), AppName); } else { Filter::ApplyFilters(this, Filters, Src); } } break; } case IDM_FILTER_SELECTION: { ScribeFolder *Folder = GetCurrentFolder(); if (Folder) { List Filters; GetFilters(Filters, false, false, true); List Src; for (auto i: Folder->Items) { if (i->IsMail() && i->Select()) { Src.Insert(i->IsMail()); } } if (Src.Length()) { Filter::ApplyFilters(this, Filters, Src); } } break; } case IDM_DEBUG_FILTERS: { auto i = Menu->FindItem(IDM_DEBUG_FILTERS); if (i) { i->Checked(!i->Checked()); } break; } case IDM_HTML_EDITOR: { auto i = Menu->FindItem(IDM_HTML_EDITOR); if (i) { i->Checked(!i->Checked()); LVariant v; GetOptions()->SetValue(OPT_EditControl, v = i->Checked() ? 1 : 0); } break; } case IDM_FILTERS_DISABLE: { if (d->DisableUserFilters) { d->DisableUserFilters->Checked(!d->DisableUserFilters->Checked()); LVariant v; GetOptions()->SetValue(OPT_DisableUserFilters, v = d->DisableUserFilters->Checked()); } break; } case IDM_BUILD_BAYES_DB: { BuildSpamDb(); break; } case IDM_BAYES_STATS: { BuildStats(); break; } case IDM_BAYES_SETTINGS: { - BayesDlg Dlg(this); - if (Dlg.DoModal()) - { - LVariant i; - if (GetOptions()->GetValue(OPT_BayesFilterMode, i)) - { - ScribeBayesianFilterMode m = ((ScribeBayesianFilterMode)i.CastInt32()); - if (m != BayesOff) - { - LVariant SpamPath, ProbablyPath; - GetOptions()->GetValue(OPT_SpamFolder, SpamPath); - GetOptions()->GetValue(OPT_BayesMoveTo, ProbablyPath); - - if (m == BayesFilter) + auto Dlg = new BayesDlg(this); + Dlg->DoModal([this, Dlg](auto dlg, auto id) + { + if (id) + { + LVariant i; + if (GetOptions()->GetValue(OPT_BayesFilterMode, i)) + { + ScribeBayesianFilterMode m = ((ScribeBayesianFilterMode)i.CastInt32()); + if (m != BayesOff) { - ScribeFolder *Spam = GetFolder(SpamPath.Str()); - if (!Spam) + LVariant SpamPath, ProbablyPath; + GetOptions()->GetValue(OPT_SpamFolder, SpamPath); + GetOptions()->GetValue(OPT_BayesMoveTo, ProbablyPath); + + if (m == BayesFilter) { - - LMailStore *RelevantStore = GetMailStoreForPath(SpamPath.Str()); - if (RelevantStore) + ScribeFolder *Spam = GetFolder(SpamPath.Str()); + if (!Spam) { - LString p = SpamPath.Str(); - LString::Array a = p.SplitDelimit("/"); + + LMailStore *RelevantStore = GetMailStoreForPath(SpamPath.Str()); + if (RelevantStore) + { + LString p = SpamPath.Str(); + LString::Array a = p.SplitDelimit("/"); - Spam = RelevantStore->Root; - for (unsigned i=1; iGetSubFolder(a[i]); - if (!c) - c = Spam->CreateSubDirectory(a[i], MAGIC_MAIL); - Spam = c; + Spam = RelevantStore->Root; + for (unsigned i=1; iGetSubFolder(a[i]); + if (!c) + c = Spam->CreateSubDirectory(a[i], MAGIC_MAIL); + Spam = c; + } } } + + if (Spam) + { + LVariant v; + GetOptions()->SetValue(OPT_HasSpam, v = 1); + } } - - if (Spam) + else if (m == BayesTrain) { - LVariant v; - GetOptions()->SetValue(OPT_HasSpam, v = 1); + ScribeFolder *Probably = GetFolder(ProbablyPath.Str()); + if (!Probably) + { + LgiMsg(this, "Couldn't find the folder '%s'", AppName, MB_OK, ProbablyPath.Str()); + } } } - else if (m == BayesTrain) - { - ScribeFolder *Probably = GetFolder(ProbablyPath.Str()); - if (!Probably) - { - LgiMsg(this, "Couldn't find the folder '%s'", AppName, MB_OK, ProbablyPath.Str()); - } - } - } - } - } + } + } + delete dlg; + }); break; } case IDM_BAYES_CHECK: { List Sel; if (MailList) MailList->GetSelection(Sel); for (auto i: Sel) { Thing *t = dynamic_cast(i); if (t) { Mail *m = t->IsMail(); if (m) { d->BayesLog.Empty(); double SpamRating = 0.0; IsSpam(SpamRating, m, true); break; } } } break; } // Tools menu case IDM_SCRIPTING_CONSOLE: case IDM_SHOW_CONSOLE: { ShowScriptingConsole(); if (d->ShowConsoleBtn) d->ShowConsoleBtn->Image(IMG_CONSOLE_NOMSG); break; } case IDM_EXPORT_TEXT_MBOX: { Export_UnixMBox(this); break; } case IDM_IMPORT_CSV: { ImportCsv(this); break; } case IDM_EXPORT_CSV: { ExportCsv(this); break; } case IDM_IMPORT_EML: { ImportEml(this); break; } case IDM_EXPORT_SCRIBE: { extern void ExportScribe(ScribeWnd *App); ExportScribe(this); break; } case IDM_IMPORT_TEXT_MBOX: { Import_UnixMBox(this); break; } case IDM_IMP_EUDORA_ADDR: { Import_EudoraAddressBook(this); break; } case IDM_IMP_MOZILLA_ADDR: { Import_MozillaAddressBook(this); break; } case IDM_IMP_MOZILLA_MAIL: { Import_MozillaMail(this); break; } #if WINNATIVE case IDM_IMPORT_OUTLOOK_PAB: { Import_OutlookContacts(this); break; } case IDM_IMPORT_OUTLOOK_ITEMS: { Import_Outlook(this, IMP_OUTLOOK); break; } case IDM_EXPORT_OUTLOOK_ITEMS: { Export_Outlook(this); break; } #endif case IDM_IMP_MBX_EMAIL: { Import_OutlookExpress(this, false); // v4 break; } case IDM_IMP_DBX_EMAIL: { Import_OutlookExpress(this); // v5 break; } case IDM_IMPORT_NS_CONTACTS: { Import_NetscapeContacts(this); break; } case IDM_CHECK_UPDATE: { LVariant v; GetOptions()->GetValue(OPT_SoftwareUpdateIncBeta, v); SoftwareUpdate(this, true, v.CastInt32() != 0, [](auto goingToUpdate) { if (goingToUpdate) LCloseApp(); }); break; } case IDM_LOGOUT: { CurrentAuthLevel = PermRequireNone; auto i = Menu->FindItem(IDM_LOGOUT); if (i) - { i->Enabled(false); - } break; } case IDM_LAYOUT1: { LVariant v; int TwoThirds = GetClient().Y() >> 1; GetOptions()->SetValue(OPT_SplitterPos, v = 200); GetOptions()->SetValue(OPT_SubSplitPos, v = TwoThirds); SetLayout(FoldersListAndPreview); break; } case IDM_LAYOUT2: { LVariant v; int TwoThirds = GetClient().Y() >> 1; GetOptions()->SetValue(OPT_SplitterPos, v = TwoThirds); GetOptions()->SetValue(OPT_SubSplitPos, v = 200); SetLayout(PreviewOnBottom); break; } case IDM_LAYOUT3: { LVariant v; GetOptions()->SetValue(OPT_SplitterPos, v = 200); SetLayout(FoldersAndList); break; } case IDM_LAYOUT4: { LVariant v; GetOptions()->SetValue(OPT_SplitterPos, v = 200); GetOptions()->SetValue(OPT_SubSplitPos, v); SetLayout(ThreeColumn); break; } case IDM_CRASH: { int *Crash = 0; *Crash = true; break; } case IDM_DUMP_MEM: { LDumpMemoryStats(0); break; } case IDM_SCRIPT_DEBUG: { LVariant v; if (GetOptions()) GetOptions()->SetValue(OPT_ScriptDebugger, v = true); LVirtualMachine *vm = new LVirtualMachine(d); if (!vm) break; LVmDebugger *dbg = vm->OpenDebugger(); if (!dbg) break; dbg->OwnVm(true); break; } case IDM_SCRIPT_BREAK_ON_WARN: { auto mi = GetMenu()->FindItem(IDM_SCRIPT_BREAK_ON_WARN); if (!mi) break; LVirtualMachine::BreakOnWarning = !mi->Checked(); mi->Checked(LVirtualMachine::BreakOnWarning); break; } // Help menu case IDM_HELP: { LaunchHelp("index.html"); // LgiMsg(this, LLoadString(IDS_ERROR_NO_HELP), AppName, MB_OK); break; } case IDM_FEEDBACK: { LVariant e; if (GetOptions()->GetValue("author", e)) CreateMail(0, e.Str()); else CreateMail(0, AuthorEmailAddr); break; } case IDM_MEMECODE: { LExecute(AuthorHomepage); break; } case IDM_HOMEPAGE: { LVariant hp; if (GetOptions()->GetValue("homepage", hp)) LExecute(hp.Str()); else LExecute(ApplicationHomepage); break; } case IDM_VERSION_HISTORY: { LExecute("http://www.memecode.com/site/ver.php?id=445"); break; } case IDM_DEBUG_INFO: { char s[256]; sprintf_s(s, sizeof(s), "%s#debug", ApplicationHomepage); LExecute(s); break; } case IDM_TUTORIALS: { LExecute("http://www.memecode.com/scribe/tutorials"); break; } case IDM_INSCRIBE_LINK: { LExecute(CommercialHomepage); break; } case IDM_SCRIBE_FAQ: { LExecute(FaqHomepage); break; } case IDM_ABOUT: { extern void ScribeAbout(ScribeWnd *Parent); ScribeAbout(this); break; } default: { if (d->ScriptToolbar) d->ScriptToolbar->ExecuteCallbacks(this, 0, 0, Cmd); break; } } return 0; } void ScribeWnd::OnDelete() { LVariant ConfirmDelete; GetOptions()->GetValue(OPT_ConfirmDelete, ConfirmDelete); if (!ConfirmDelete.CastInt32() || LgiMsg(this, LLoadString(IDS_DELETE_ASK), AppName, MB_YESNO) == IDYES) { LArray Del; if (Tree && Tree->Focus()) { ScribeFolder *Item = dynamic_cast(Tree->Selection()); if (Item) { Tree->OnDelete(Item, false); } } else if (MailList #ifdef MAC && MailList->Focus() #endif ) { List Sel; MailList->GetSelection(Sel); for (auto i: Sel) { Thing *t = dynamic_cast(i); if (t) Del.Add(t->GetObject()); } if (Del.Length()) { auto Store = Del[0]->GetStore(); Store->Delete(Del, true); } else LgiTrace("%s:%i - Nothing to delete\n", _FL); #ifndef MAC MailList->Focus(true); #endif } } } int ScribeWnd::OnNotify(LViewI *Ctrl, LNotification n) { switch (Ctrl->GetId()) { case IDC_THING_LIST: { if (n.Type == LNotifyReturnKey) { LListItem *i = MailList ? MailList->GetSelected() : 0; Thing *t = dynamic_cast(i); if (t) { t->DoUI(); } } else if (n.Type == LNotifyDeleteKey) { /* This is now handled by the menu OnDelete(); return true; */ } if (SearchView && MailList) { SearchView->OnNotify(Ctrl, n); } break; } case IDC_TEXT: { if (PreviewPanel) { PreviewPanel->OnNotify(Ctrl, n); } break; } } return 0; } void ScribeWnd::AddThingSrc(ScribeFolder *src) { if (!d->ThingSources.HasItem(src)) d->ThingSources.Add(src); } void ScribeWnd::RemoveThingSrc(ScribeFolder *src) { d->ThingSources.Delete(src); } LArray ScribeWnd::GetThingSources(Store3ItemTypes Type) { LArray a; for (auto f: d->ThingSources) { if (f->GetItemType() == Type && !f->IsInTrash()) { a.Add(f); } } return a; } bool ScribeWnd::LogFilterActivity() { auto i = Menu->FindItem(IDM_DEBUG_FILTERS); return i ? i->Checked() : false; } bool ScribeWnd::CreateFolders(LAutoString &FileName) { bool Status = false; if (FileName) { char *Ext = LGetExtension(FileName); if (!Ext) { char File[300]; strcpy_s(File, sizeof(File), FileName); strcat(File, ".mail3"); FileName.Reset(NewStr(File)); } // Create objects, and then close the file.. it'll be reloaded later LAutoPtr m(CreateDataStore(FileName, true)); if (m) { m->GetRoot(true); Status = true; } else LgiTrace("%s:%i - CreateDataStore failed.\n", _FL); } else LgiTrace("%s:%i - No file name for CreateFolder.\n", _FL); return Status; } bool ScribeWnd::CompactFolders(LMailStore &Store, bool Interactive) { if (!Store.Store) return false; Store3Progress Dlg(this, Interactive); Dlg.SetDescription(LLoadString(IDS_CHECKING_OBJECTS)); bool Offline = false; if (WorkOffline) { Offline = WorkOffline->Checked(); WorkOffline->Checked(true); } Store.Store->Compact(this, &Dlg, [this, Offline](auto status) { if (WorkOffline) WorkOffline->Checked(Offline); }); return true; } CalendarSource *CalendarSource::Create(ScribeWnd *App, const char *ObjName, const char *Id) { if (!Stricmp(ObjName, "RemoteCalendarSource")) return new RemoteCalendarSource(App, Id); return new FolderCalendarSource(App, Id); } int ScribeWnd::GetCalendarSources(LArray &Out) { static bool Loaded = false; if (!Loaded) { Loaded = true; CalendarSource::SetCreateIn(NULL); LVariant Create; GetOptions()->GetValue(OPT_CalendarCreateIn, Create); // This should be a list of all calendar folders in ANY mail store... auto CalFlds = GetThingSources(MAGIC_CALENDAR); LXmlTag *t = GetOptions()->LockTag(OPT_CalendarSources, _FL); if (t) { bool AutoPopulate = t->Children.Length() == 0; for (auto c: t->Children) { auto s = CalendarSource::Create(this, c->GetAttr(CalendarSource::OptObject), c->GetTag()); if (s && s->Read()) { // Add known source... if (!Stricmp(Create.Str(), c->GetTag())) { CalendarSource::SetCreateIn(s); } // Remove from CalFlds FolderCalendarSource *Fcs = dynamic_cast(c); if (Fcs) { auto Path = Fcs->GetPath(); for (auto c: CalFlds) { if (c->GetPath().Equals(Path)) { CalFlds.Delete(c); break; } } } } } if (AutoPopulate) { // Now CalFlds should be a list of all calendar folders NOT in the source XML tag for (auto c: CalFlds) { FolderCalendarSource *s = new FolderCalendarSource(this); if (s) { // So add an entry to track it... auto Path = c->GetPath(); s->SetPath(Path); s->SetDisplay(true); s->SetColour(CalendarSource::FindUnusedColour()); s->Write(); } } } GetOptions()->Unlock(); } if (!CalendarSource::GetCreateIn() && CalendarSource::GetSources().Length()) { CalendarSource::SetCreateIn(CalendarSource::GetSources().ItemAt(0)); } } for (unsigned i=0; iPathOption; fi++) { bool Check = true; if (fi->HasOption) { LVariant v; if (GetOptions()->GetValue(fi->HasOption, v)) Check = v.CastInt32() != 0; } if (Check) { ScribeFolder *c = GetFolder(fi->Id); if (c == f) return fi->Id; } } return -1; } ScribeFolder *ScribeWnd::GetCurrentFolder() { if (Tree) { auto *Item = Tree->Selection(); if (Item) { return dynamic_cast(Item); } } return 0; } bool ScribeWnd::GetSystemPath(int Folder, LVariant &Path) { char KeyName[64]; sprintf_s(KeyName, sizeof(KeyName), "Folder-%i", Folder); return GetOptions()->GetValue(KeyName, Path); } LMailStore *ScribeWnd::GetMailStoreForIdentity(const char *IdEmail) { LVariant Tmp; if (!IdEmail) { // Get current identity ScribeAccount *Cur = GetCurrentAccount(); if (Cur) { Tmp = Cur->Identity.Email(); IdEmail = Tmp.Str(); } } if (!IdEmail) return NULL; ScribeAccount *a = NULL; for (auto Acc : Accounts) { LVariant e = Acc->Identity.Email(); if (e.Str() && !_stricmp(e.Str(), IdEmail)) { a = Acc; break; } } if (!a) return NULL; LVariant DestPath = a->Receive.DestinationFolder(); if (!DestPath.Str()) return NULL; return GetMailStoreForPath(DestPath.Str()); } ScribeFolder *ScribeWnd::GetFolder(int Id, LDataI *s) { if (s) { for (auto &f: Folders) if (s->GetStore() == f.Store) return GetFolder(Id, &f); } return GetFolder(Id); } ScribeFolder *ScribeWnd::GetFolder(int Id, LMailStore *Store, bool Quiet) { char KeyName[64]; sprintf_s(KeyName, sizeof(KeyName), "Folder-%i", Id); LVariant FolderName; bool NoOption = false; if (GetOptions()->GetValue(KeyName, FolderName)) { if (ValidStr(FolderName.Str()) && strlen(FolderName.Str()) > 0) { ScribeFolder *c = GetFolder(FolderName.Str(), Store); if (c) { return c; } else if (!Quiet) { LgiTrace("%s:%i - '%s' doesn't exist.\n", _FL, FolderName.Str()); } } } else if (!Quiet) { // LgiTrace("%s:%i - No option '%s'\n", _FL, KeyName); NoOption = true; } switch (Id) { case FOLDER_INBOX: case FOLDER_OUTBOX: case FOLDER_SENT: case FOLDER_TRASH: case FOLDER_CONTACTS: case FOLDER_TEMPLATES: case FOLDER_FILTERS: case FOLDER_CALENDAR: case FOLDER_GROUPS: case FOLDER_SPAM: { ScribeFolder *c = GetFolder(DefaultFolderNames[Id], Store); if (!c) { // if (!Quiet) // LgiTrace("%s:%i - Default folder '%s' doesn't exist.\n", _FL, DefaultFolderNames[Id]); } else if (NoOption) { auto p = c->GetPath(); GetOptions()->SetValue(KeyName, FolderName = p.Get()); } return c; } } return NULL; } bool ScribeWnd::OnMailStore(LMailStore **MailStore, bool Add) { if (!MailStore) { LAssert(!"No mail store pointer?"); return false; } if (Add) { *MailStore = &Folders.New(); if (*MailStore) return true; } else { ssize_t Idx = *MailStore - &Folders[0]; if (Idx >= 0 && Idx < (ssize_t)Folders.Length()) { Folders.DeleteAt(Idx, true); *MailStore = NULL; return true; } else { LAssert(!"Index out of range."); } } return false; } LMailStore *ScribeWnd::GetMailStoreForPath(const char *Path) { if (!Path) return NULL; LToken t(Path, "/"); if (t.Length() > 0) { const char *First = t[0]; // Find the mail store that that t[0] refers to for (unsigned i=0; iGetText(); if (RootStr && !_stricmp(RootStr, First)) { return &Folders[i]; } } } } return NULL; } ScribeFolder *ScribeWnd::GetFolder(const char *Name, LMailStore *s) { ScribeFolder *Folder = 0; if (ValidStr(Name)) { LString Sep("/"); auto t = LString(Name).Split(Sep); LMailStore tmp; LString TmpName; if (t.Length() > 0) { if (!s) { s = GetMailStoreForPath(Name); if (!s) { // IMAP folders? for (auto a: Accounts) { ScribeProtocol Proto = a->Receive.ProtocolType(); if (Proto == ProtocolImapFull) { ScribeFolder *Root = a->Receive.GetRootFolder(); if (Root) { const char *RootStr = Root->GetText(); if (RootStr && a->Receive.GetDataStore() && !_stricmp(RootStr, t[0])) { tmp.Root = Root; tmp.Store = a->Receive.GetDataStore(); s = &tmp; break; } } } } } if (s) { if (*Name == '/') Name++; Name = strchr(Name, '/'); if (!Name) Name = "/"; } } else if (s->Root) { // Check if the store name is on the start of the folder auto RootName = s->Root->GetName(true); if (RootName.Equals(t[0])) { LString::Array a; for (unsigned i=1; iRoot; Folder = s->Root ? s->Root->GetSubFolder(Name) : 0; } } return Folder; } void ScribeWnd::Update(int What) { if (What & UPDATE_TREE) { Tree->Invalidate(); return; } if (What & UPDATE_LIST) { if (MailList) MailList->Invalidate(); return; } } void ScribeWnd::DoDebug(char *s) { } Thing *ScribeWnd::CreateThingOfType(Store3ItemTypes Type, LDataI *obj) { Thing *t = NULL; switch (Type) { case MAGIC_CONTACT: { t = new Contact(this, obj); break; } case MAGIC_MAIL: { t = new Mail(this, obj); break; } case MAGIC_ATTACHMENT: { t = new Attachment(this, obj); break; } case MAGIC_FILTER: { t = new Filter(this, obj); break; } case MAGIC_CALENDAR: { t = new Calendar(this, obj); break; } case MAGIC_GROUP: { t = new ContactGroup(this, obj); break; } default: break; } if (t) { t->App = this; } return t; } void ScribeWnd::GetFilters(List &Filters, bool JustIn, bool JustOut, bool JustInternal) { auto Srcs = GetThingSources(MAGIC_FILTER); for (auto f: Srcs) { for (auto t: f->Items) { Filter *Ftr = t->IsFilter(); if (Ftr) { if (JustIn && !Ftr->GetIncoming()) continue; if (JustOut && !Ftr->GetOutgoing()) continue; if (JustInternal && !Ftr->GetInternal()) continue; Filters.Insert(Ftr); } } } extern int FilterCompare(Filter *a, Filter *b, NativeInt Data); Filters.Sort(FilterCompare); } bool ScribeWnd::ShowToolbarText() { LVariant i; if (GetOptions()->GetValue(OPT_ToolbarText, i)) { return i.CastInt32() != 0; } GetOptions()->SetValue(OPT_ToolbarText, i = true); return true; } void ScribeWnd::HashContacts(LHashTbl,Contact*> &Contacts, ScribeFolder *Folder, bool Deep) { if (!Folder) { // Default item is the contacts folder Folder = GetFolder(FOLDER_CONTACTS); } // recurse through each folder and make a list // of every contact object we find. if (Folder) { Folder->LoadThings(); for (auto t: Folder->Items) { Contact *c = t->IsContact(); if (c) { auto Emails = c->GetEmails(); for (auto e: Emails) if (e && !Contacts.Find(e)) Contacts.Add(e, c); } } for (ScribeFolder *f = Folder->GetChildFolder(); Deep && f; f = f->GetNextFolder()) { HashContacts(Contacts, f, Deep); } } } List *ScribeWnd::GetEveryone() { return &Contact::Everyone; } bool ScribeWnd::GetContacts(List &Contacts, ScribeFolder *Folder, bool Deep) { LArray Folders; if (!Folder) { Folders = GetThingSources(MAGIC_CONTACT); auto f = GetFolder(FOLDER_CONTACTS); if (f && !Folders.HasItem(f)) Folders.Add(f); } else Folders.Add(Folder); if (!Folders.Length()) return false; for (auto f: Folders) { // recurse through each folder and make a list // of every contact object we find. ScribePerm Perm = f->GetFolderPerms(ScribeReadAccess); bool Safe = CurrentAuthLevel >= Perm; if (Safe) { f->LoadThings(); for (auto t: f->Items) { Contact *c = t->IsContact(); if (c) Contacts.Insert(c); } for (ScribeFolder *c = f->GetChildFolder(); Deep && c; c = c->GetNextFolder()) GetContacts(Contacts, c, Deep); } } return true; } /* This function goes through the database and checks for some basic requirements and fixes things up if they aren't ok. */ bool ScribeWnd::ValidateFolder(LMailStore *s, int Id) { char OptName[32]; sprintf_s(OptName, sizeof(OptName), "Folder-%i", Id); LVariant Path; if (!GetOptions()->GetValue(OptName, Path)) { char Opt[256]; sprintf_s(Opt, sizeof(Opt), "/%s", DefaultFolderNames[Id]); GetOptions()->SetValue(OptName, Path = Opt); } // If the path name has the store name at the start, strip that off... LString Sep("/"); LString::Array Parts = LString(Path.Str()).Split(Sep); if (Parts.Length() > 1) { if (Parts[0].Equals(s->Name)) { Parts.DeleteAt(0, true); Path = Sep.Join(Parts); } else { LMailStore *ms = GetMailStoreForPath(Path.Str()); if (ms) { s = ms; } else { // Most likely the user has renamed something and broken the // path. Lets just error out instead of creating the wrong folder return false; } } } // Now resolve the path... ScribeFolder *Folder = GetFolder(Path.Str(), s); if (!Folder) { char *p = Path.Str(); if (_strnicmp(p, "/IMAP ", 6) != 0) { LAssert(DefaultFolderTypes[Id] != MAGIC_NONE); Folder = s->Root->CreateSubDirectory(*p=='/'?p+1:p, DefaultFolderTypes[Id]); } } if (!Folder) return false; Folder->SetDefaultFields(); return true; } void ScribeWnd::Validate(LMailStore *s) { // Check for all the basic folders int Errors = 0; for (SystemFolderInfo *fi = SystemFolders; fi->PathOption; fi++) { bool Check = true; if (fi->HasOption) { LVariant v; if (GetOptions()->GetValue(fi->HasOption, v)) Check = v.CastInt32() != 0; } if (Check) { if (!ValidateFolder(s, fi->Id)) Errors++; } } if (Errors && LgiMsg(this, "There were errors validating the system folders." "Would you like to review the mail store's system folder paths?", AppName, MB_YESNO) == IDYES) { PostEvent(M_COMMAND, IDM_MANAGE_MAIL_STORES); } } ThingFilter *ScribeWnd::GetThingFilter() { return SearchView; } ScribeAccount *ScribeWnd::GetSendAccount() { LVariant DefSendAcc = 0; if (!GetOptions()->GetValue(OPT_DefaultSendAccount, DefSendAcc)) { for (auto a : Accounts) if (a->Send.Server().Str()) return a; } ScribeAccount *i = Accounts.ItemAt(DefSendAcc.CastInt32()); if (i && i->Send.Server().Str()) return i; return NULL; } LPrinter *ScribeWnd::GetPrinter() { if (!d->PrintOptions) d->PrintOptions.Reset(new LPrinter); return d->PrintOptions; } int ScribeWnd::GetActiveThreads() { int Status = 0; for (auto i: Accounts) { if (i->IsOnline()) { Status++; } } return Status; } class DefaultClientDlg : public LDialog { public: bool DontWarn; DefaultClientDlg(LView *parent) { DontWarn = false; SetParent(parent); LoadFromResource(IDD_WARN_DEFAULT); MoveToCenter(); } int OnNotify(LViewI *Ctrl, LNotification n) { switch (Ctrl->GetId()) { case ID_YES: case ID_NO: { LCheckBox *DW; if (GetViewById(IDC_DONT_WARN, DW)) { DontWarn = DW->Value() != 0; } EndModal(Ctrl->GetId() == ID_YES); break; } } return 0; } }; #if WINNATIVE struct DefaultClient { char DefIcon[MAX_PATH_LEN]; char CmdLine[MAX_PATH_LEN]; char DllPath[MAX_PATH_LEN]; DefaultClient() { auto Exe = LGetExeFile(); sprintf_s(DefIcon, sizeof(DefIcon), "%s,1", Exe.Get()); sprintf_s(CmdLine, sizeof(CmdLine), "\"%s\" /m \"%%1\"", Exe.Get()); LMakePath(DllPath, sizeof(DllPath), Exe, "../ScribeMapi.dll"); } bool IsWindowsXp() { LArray Ver; int Os = LGetOs(&Ver); if ( ( Os == LGI_OS_WIN32 || Os == LGI_OS_WIN64 ) && Ver.Length() > 1 && Ver[0] == 5 && Ver[1] == 1 ) return true; return false; } bool InstallMailto(bool Write) { LAutoPtr mailto = CheckKey(Write, "HKCR\\mailto"); if (!mailto) return false; if (!CheckString(Write, mailto, NULL, "URL:MailTo Protocol")) return false; LAutoPtr deficon = CheckKey(Write, "HKCR\\mailto\\DefaultIcon"); if (!deficon) return false; if (!CheckString(Write, deficon, NULL, DefIcon)) return false; LAutoPtr shell = CheckKey(Write, "HKCR\\mailto\\shell"); if (!shell) return false; if (!CheckString(Write, shell, NULL, "open")) return false; LAutoPtr cmd = CheckKey(Write, "HKCR\\mailto\\shell\\open\\command"); if (!cmd) return false; if (!CheckString(Write, cmd, NULL, CmdLine)) return false; return true; } - LAutoPtr CheckKey(bool Write, const char *Key, ...) + LAutoPtr CheckKey(bool Write, const char *Key, ...) const { char Buffer[512]; va_list Arg; va_start(Arg, Key); vsprintf_s(Buffer, sizeof(Buffer), Key, Arg); va_end(Arg); LAutoPtr k(new LRegKey(Write, Buffer)); if (k && Write && !k->IsOk()) { if (!k->Create()) { k.Reset(); LgiTrace("%s:%i - Failed to create '%s'\n", _FL, Buffer); } } return k; } bool CheckInt(bool Write, LRegKey *k, const char *Name, uint32_t Value) { if (!k) { LgiTrace("%s:%i - No key: '%s'\n", _FL, Name); return false; } uint32_t Cur; if (!k->GetInt(Name, Cur)) Cur = Value + 1; if (Cur == Value) return true; if (Write) { bool Status = k->SetInt(Name, Value); if (!Status) LgiTrace("%s:%i - Failed to set key '%s': '%s' to %i\n", _FL, k->Name(), Name, Value); return Status; } return false; } bool CheckString(bool Write, LRegKey *k, const char *StrName, const char *StrValue) { if (!k) { LgiTrace("%s:%i - No key: '%s' to '%s'\n", _FL, StrName, StrValue); return false; } LString v; if (k->GetStr(StrName, v)) { bool Same = Stricmp(v.Get(), StrValue) == 0; if (Write && !Same) { bool Status = k->SetStr(StrName, StrValue); if (!Status) LgiTrace("%s:%i - Failed to set key '%s': '%s' to '%s'\n", _FL, k->Name(), StrName, StrValue); return Status; } return Same; } else if (Write) { bool Status = k->SetStr(StrName, StrValue); if (!Status) LgiTrace("%s:%i - Failed to set key '%s': '%s' to '%s'\n", _FL, k->Name(), StrName, StrValue); return Status; } return false; } bool IsDefault() { LAutoPtr mail = CheckKey(false, "HKCU\\Software\\Clients\\Mail"); if (!mail) return false; LString v; if (!mail->GetStr(NULL, v)) return false; return !_stricmp(v, "Scribe"); } - bool SetDefault() + bool SetDefault() const { LAutoPtr mail = CheckKey(true, "HKCU\\Software\\Clients\\Mail"); if (!mail) return false; // Set the default client in the current user tree. mail->SetStr(NULL, "Scribe"); // Configure the mailto handler const char *Base = "HKEY_ROOT"; bool Error = false; LRegKey Mt(true, "%s\\mailto", Base); if (Mt.IsOk() || Mt.Create()) { if (!Mt.SetStr(0, "URL:MailTo Protocol") || !Mt.SetStr("URL Protocol", "")) Error = true; } else { LgiTrace("%s:%i - Couldn't open/create registry key (err=%i).\n", _FL, GetLastError()); Error = true; } LRegKey Di(true, "%s\\mailto\\DefaultIcon", Base); if (Di.IsOk() || Di.Create()) { if (!Di.SetStr(0, DefIcon)) Error = true; } else { LgiTrace("%s:%i - Couldn't open/create registry key (err=%i).\n", _FL, GetLastError()); Error = true; } LRegKey c(true, "%s\\mailto\\shell\\open\\command", Base); if (c.IsOk() || c.Create()) { if (!c.SetStr(NULL, CmdLine)) Error = true; } else { LgiTrace("%s:%i - Couldn't open/create registry key (err=%i).\n", _FL, GetLastError()); Error = true; } return Error; } bool InstallAsClient(char *Base, bool Write) { // Create software client entry, to put Scribe in the Internet Options for mail clients. LAutoPtr mail = CheckKey(Write, "%s\\Software\\Clients\\Mail", Base); if (!mail) return false; LAutoPtr app = CheckKey(Write, "%s\\Software\\Clients\\Mail\\Scribe", Base); if (!app) return false; if (!CheckString(Write, app, NULL, AppName)) return false; if (!CheckString(Write, app, "DllPath", DllPath)) return false; LAutoPtr shell = CheckKey(Write, "%s\\Software\\Clients\\Mail\\Scribe\\shell\\open\\command", Base); if (!shell) return false; if (!CheckString(Write, shell, NULL, CmdLine)) return false; LAutoPtr icon = CheckKey(Write, "%s\\Software\\Clients\\Mail\\Scribe\\DefaultIcon", Base); if (!icon) return false; if (!CheckString(Write, icon, NULL, DefIcon)) return false; LAutoPtr proto = CheckKey(Write, "%s\\Software\\Classes\\Protocol\\mailto", Base); if (!proto) return false; if (!CheckString(Write, proto, NULL, "URL:MailTo Protocol")) return false; if (!CheckString(Write, proto, "URL Protocol", "")) return false; if (!CheckInt(Write, proto, "EditFlags", 0x2)) return false; LAutoPtr proto_cmd = CheckKey(Write, "%s\\Software\\Classes\\Protocol\\mailto\\shell\\open\\command", Base); if (!proto_cmd) return false; if (!CheckString(Write, proto_cmd, NULL, CmdLine)) return false; return true; } struct FileType { char *Name; char *Desc; int Icon; }; static FileType FileTypes[]; bool Win7Install(bool Write) { // http://msdn.microsoft.com/en-us/library/windows/desktop/cc144154%28v=vs.85%29.aspx LArray Ver; int Os = LGetOs(&Ver); if ( ( Os == LGI_OS_WIN32 || Os == LGI_OS_WIN64 ) && Ver[0] >= 6) { char Path[MAX_PATH_LEN]; auto Exe = LGetExeFile(); for (int i=0; FileTypes[i].Name; i++) { LAutoPtr base = CheckKey(Write, "HKEY_CLASSES_ROOT\\%s", FileTypes[i].Name); if (!base) return false; if (!CheckString(Write, base, NULL, FileTypes[i].Desc)) return false; LAutoPtr r = CheckKey(Write, "HKEY_CLASSES_ROOT\\%s\\shell\\Open\\command", FileTypes[i].Name); if (!r) return false; sprintf_s(Path, sizeof(Path), "\"%s\" -u \"%%1\"", Exe.Get()); if (!CheckString(Write, r, NULL, Path)) return false; LAutoPtr ico = CheckKey(Write, "HKEY_CLASSES_ROOT\\%s\\DefaultIcon", FileTypes[i].Name); if (!ico) return false; sprintf_s(Path, sizeof(Path), "%s,%i", Exe.Get(), FileTypes[i].Icon); if (!CheckString(Write, ico, NULL, Path)) return false; } LAutoPtr r = CheckKey(Write, "HKEY_LOCAL_MACHINE\\SOFTWARE\\Clients\\Mail\\Scribe\\Capabilities"); if (!r) return false; if (!CheckString(Write, r, "ApplicationDescription", "Scribe is a small lightweight email client.") && !CheckString(Write, r, "ApplicationName", "Scribe") && !CheckString(Write, r, "ApplicationIcon", DefIcon)) return false; LAutoPtr as = CheckKey(Write, "HKEY_LOCAL_MACHINE\\SOFTWARE\\Clients\\Mail\\Scribe\\Capabilities\\FileAssociations"); if (!as) return false; if (!CheckString(Write, as, ".eml", "Scribe.Email") && !CheckString(Write, as, ".msg", "Scribe.Email") && !CheckString(Write, as, ".mbox", "Scribe.Folder") && !CheckString(Write, as, ".mbx", "Scribe.Folder") && !CheckString(Write, as, ".ics", "Scribe.Calendar") && !CheckString(Write, as, ".vcs", "Scribe.Calendar") && !CheckString(Write, as, ".vcf", "Scribe.Contact") && !CheckString(Write, as, ".mail3", "Scribe.MailStore")) return false; LAutoPtr ua = CheckKey(Write, "HKEY_LOCAL_MACHINE\\SOFTWARE\\Clients\\Mail\\Scribe\\Capabilities\\UrlAssociations"); if (!ua) return false; if (!CheckString(Write, ua, "mailto", "Scribe.Mailto")) return false; LAutoPtr a = CheckKey(Write, "HKEY_LOCAL_MACHINE\\SOFTWARE\\RegisteredApplications"); if (!a) return false; if (!CheckString(Write, a, "Scribe", "SOFTWARE\\Clients\\Mail\\Scribe\\Capabilities")) return false; } return true; } void Win7Uninstall() { for (int i=0; FileTypes[i].Name; i++) { LRegKey base(true, "HKEY_CLASSES_ROOT\\%s", FileTypes[i].Name); base.DeleteKey(); } } }; DefaultClient::FileType DefaultClient::FileTypes[] = { { "Scribe.Email", "Email", 2 }, { "Scribe.Folder", "Mailbox", 0 }, { "Scribe.Calendar", "Calendar Event", 6 }, { "Scribe.Contact", "Contact", 4 }, { "Scribe.MailStore", "Mail Store", 0 }, { "Scribe.Mailto", "Mailto Protocol", 0 }, { 0, 0 } }; #endif void ScribeWnd::SetDefaultHandler() { #if WINNATIVE if (LAppInst->GetOption("noreg")) return; LVariant RegisterClient; if (!GetOptions()->GetValue(OPT_RegisterWindowsClient, RegisterClient)) RegisterClient = true; if (!RegisterClient.CastInt32()) return; // Create IE mail client entries for local machine and current user DefaultClient Def; bool OldAssert = LRegKey::AssertOnError; LRegKey::AssertOnError = false; bool RegistryOk = ( !Def.IsWindowsXp() || Def.InstallMailto(true) ) && Def.InstallAsClient("HKLM", true) && Def.Win7Install(true); LRegKey::AssertOnError = OldAssert; if (!RegistryOk) { // Need write permissions to fix up the registry? NeedsCapability("RegistryWritePermissions"); return; } // Check if the user wants us to be the default client LVariant n = true; GetOptions()->GetValue(OPT_CheckDefaultEmail, n); if (n.CastInt32()) { // HKEY_CURRENT_USER\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\mailto\UserChoice LRegKey::AssertOnError = false; - bool Error = false; bool IsDef = Def.IsDefault(); if (!IsDef) { // Ask the user... - DefaultClientDlg Dlg(this); - if (Dlg.DoModal()) - { - Error = !Def.SetDefault(); - GetOptions()->SetValue(OPT_CheckDefaultEmail, n = (int) (!Dlg.DontWarn)); - } - } - - LRegKey::AssertOnError = OldAssert; - if (Error) - { - NeedsCapability("RegistryWritePermissions"); - } - } + auto Dlg = new DefaultClientDlg(this); + Dlg->DoModal([this, Dlg, Def, OldAssert](auto dlg, auto id) + { + if (id) + { + auto Error = !Def.SetDefault(); + LVariant v; + GetOptions()->SetValue(OPT_CheckDefaultEmail, v = (int) (!Dlg->DontWarn)); + OnSetDefaultHandler(Error, OldAssert); + } + delete dlg; + }); + } + else OnSetDefaultHandler(false, OldAssert); + } + #endif } +void ScribeWnd::OnSetDefaultHandler(bool Error, bool OldAssert) +{ + #if WINDOWS + LRegKey::AssertOnError = OldAssert; + #endif + if (Error) + NeedsCapability("RegistryWritePermissions"); +} + void ScribeWnd::OnSelect(List *l, bool ChangeEvent) { Mail *m = (l && l->Length() == 1) ? (*l)[0]->IsMail() : 0; if (Commands) { bool NotCreated = m && !TestFlag(m->GetFlags(), MAIL_CREATED); Commands->SetCtrlEnabled(IDM_DELETE, l && l->Length() > 0); Commands->SetCtrlEnabled(IDM_DELETE_AS_SPAM, l && l->Length() > 0); Commands->SetCtrlEnabled(IDM_PRINT, l && l->Length() == 1); Commands->SetCtrlEnabled(IDM_REPLY, NotCreated); Commands->SetCtrlEnabled(IDM_REPLY_ALL, NotCreated); Commands->SetCtrlEnabled(IDM_FORWARD, m != 0); Commands->SetCtrlEnabled(IDM_BOUNCE, m != 0); } if (PreviewPanel && GetEffectiveLayoutMode() != 3) { if (!PreviewPanel->IsAttached()) { SetItemPreview(PreviewPanel); } Thing *t = (l && l->Length() == 1) ? (*l)[0] : 0; PreviewPanel->OnThing(t, ChangeEvent); /* if (d->Debug) d->Debug->OnThing(t); */ } } class SpellErrorInst { public: int Id; LString Word; LString::Array Suggestions; SpellErrorInst(int id) { Id = id; // Decor = LCss::TextDecorSquiggle; // DecorColour.Rgb(255, 0, 0); } ~SpellErrorInst() { } bool OnMenu(LSubMenu *m) { if (Suggestions.Length()) { for (unsigned i=0; iAppendItem(Suggestions[i], 100 + i, true); } m->AppendSeparator(); } char Buf[256]; sprintf_s(Buf, sizeof(Buf), LLoadString(IDS_ADD_TO_DICTIONARY, "Add '%s' to dictionary"), Word.Get()); m->AppendItem(Buf, 1, true); return true; } void OnMenuClick(int i) { if (i == 1) { // Add to dictionary... /* if (PostThreadEvent(SpellHnd, M_ADD_WORD, (LMessage::Param) new LString(Word))) { // FIXME LAssert(!"Impl me."); // View->PostEvent(M_DELETE_STYLE, (LMessage::Param) dynamic_cast(this)); } */ } else if (i >= 100 && i < 100 + (int)Suggestions.Length()) { // Change spelling.. char *Replace = Suggestions[i - 100]; if (Replace) { char16 *w = Utf8ToWide(Replace); if (w) { /* int NewLen = StrlenW(w); if (NewLen > Len) { // Bigger... memcpy(View->NameW() + Start, w, Len * sizeof(char16)); View->Insert(Start + Len, w + Len, NewLen - Len); } else if (NewLen < Len) { // Smaller... memcpy(View->NameW() + Start, w, NewLen * sizeof(char16)); View->Delete(Start + NewLen, Len - NewLen); } else { // Just copy... memcpy(View->NameW() + Start, w, Len * sizeof(char16)); RefreshLayout(Start, Len); } */ DeleteArray(w); } } } } }; class MailTextView : public LTextView3 { ScribeWnd *App; LSpellCheck *Thread; LColour c[8]; LHashTbl, SpellErrorInst*> ErrMap; SpellErrorInst *NewErrorInst() { int Id; while (ErrMap.Find(Id = LRand(10000))) ; SpellErrorInst *Inst = new SpellErrorInst(Id); if (!Inst) return NULL; ErrMap.Add(Id, Inst); return Inst; } public: MailTextView(ScribeWnd *app, int Id, int x, int y, int cx, int cy, LFontType *FontType) : LTextView3(Id, x, y, cx, cy, FontType) { App = app; Thread = 0; int i=0; c[i++].Rgb(0x80, 0, 0); c[i++].Rgb(0, 0x80, 0); c[i++].Rgb(0, 0, 0x80); c[i++].Rgb(0x80, 0x80, 0); c[i++].Rgb(0x80, 0, 0x80); c[i++].Rgb(0, 0x80, 0x80); c[i++].Rgb(0x80, 0x80, 0x80); c[i++].Rgb(0xc0, 0xc0, 0xc0); for (i=0; i 0 && !StrchrW(SpellDelim, Text[Start-1])) Start--; if (Len > 0) { // Text being added Len += Origin - Start; while ((ssize_t)Start + Len < Size && !StrchrW(SpellDelim, Text[Start + Len])) Len++; } else if (Len < 0) { // Text being deleted Len = Origin - Start; while ((ssize_t)Start + Len < Size && !StrchrW(SpellDelim, Text[Start + Len])) Len++; } if (!Thread) Thread = App->GetSpellThread(); if (Thread && Len > 0) { LString Str(Text+Start, Len); LArray Params; Thread->Check(AddDispatch(), Str, Start, Len, &Params); } // Adjust all the positions of the styles after this. for (auto s = Style.begin(); s != Style.end(); ) { if (s->Start >= Origin && s->Owner == 1) { if (Length < 0 && s->Start < Origin - Length) { // In the deleted text... Style.Delete(s); continue; } // After the deleted text s->Start += Length; LAssert(s->Start >= 0); } s++; } } } void PourText(size_t Start, ssize_t Len) { LTextView3::PourText(Start, Len); for (auto l: Line) { int n=0; char16 *t = Text + l->Start; char16 *e = t + l->Len; while ((*t == ' ' || *t == '>') && t < e) if (*t++ == '>') n++; if (n > 0) l->c = c[(n-1)%CountOf(c)]; } } LMessage::Result OnEvent(LMessage *m) { switch (m->Msg()) { case M_CHECK_TEXT: { LAutoPtr Ct((LSpellCheck::CheckText*)m->A()); if (!Ct || !Thread) break; // Clear existing spelling error styles ssize_t Start = Ct->Start; ssize_t End = Start + Ct->Len; for (auto i = Style.begin(); i != Style.end(); ) { if (i->End() < (size_t)Start || i->Start >= End) { // Outside the area we are re-styling. i++; } else { if (i->Owner == STYLE_SPELLING) { // Existing error style inside the area Style.Delete(i); } else { // Existing non-error style... i++; } } } // Insert the new styles for (auto Ct: Ct->Errors) { SpellErrorInst *ErrInst = NewErrorInst(); LAutoPtr Style(new LTextView3::LStyle(STYLE_SPELLING)); if (Style && ErrInst) { Style->View = this; Style->Start = Ct.Start; Style->Len = Ct.Len; Style->Font = GetFont(); Style->Data = ErrInst->Id; Style->DecorColour = LColour::Red; Style->Decor = LCss::TextDecorSquiggle; ErrInst->Word = LString(Text + Style->Start, Style->End()); ErrInst->Suggestions = Ct.Suggestions; InsertStyle(Style); } } // Update the screen... Invalidate(); break; } case M_DELETE_STYLE: { /* LTextView3::LStyle *s = (LTextView3::LStyle*)m->A(); if (s && Style.HasItem(s)) { Style.Delete(s); Invalidate(); } else LAssert(0); */ break; } } return LTextView3::OnEvent(m); } bool OnStyleClick(LStyle *style, LMouse *m) { switch (style->Owner) { case STYLE_URL: { if (m->Left() && m->Down() && m->Double()) { LString s(Text + style->Start, style->Len); LUri u(s); if ( (u.sProtocol && !_stricmp(u.sProtocol, "mailto")) || LIsValidEmail(s) ) { Mailto m(App, s); Mail *email = App->CreateMail(); if (email) { m.Apply(email); email->DoUI(); return true; } } else { // Web link? LExecute(s); } } break; } default: return false; } return true; } }; LDocView *ScribeWnd::CreateTextControl(int Id, const char *MimeType, bool Editor, Mail *m) { LDocView *Ctrl = 0; // Get the default font LFontType FontType; bool UseFont = FontType.Serialize(GetOptions(), OPT_EditorFont, false); if (Editor) { if (MimeType && !_stricmp(MimeType, sTextHtml)) { // Use the built in html editor LRichTextEdit *Rte; if ((Ctrl = Rte = new LRichTextEdit(Id))) { if (UseFont) Ctrl->SetFont(FontType.Create(), true); // Give the control the speller settings: LVariant Check, Lang, Dict; if (GetOptions()->GetValue(OPT_SpellCheck, Check) && Check.CastInt32() != 0) { if (GetOptions()->GetValue(OPT_SpellCheckLanguage, Lang)) Rte->SetValue(LDomPropToString(SpellCheckLanguage), Lang); if (GetOptions()->GetValue(OPT_SpellCheckDictionary, Dict)) Rte->SetValue(LDomPropToString(SpellCheckDictionary), Dict); // Set the spell thread: LSpellCheck *t = GetSpellThread(); if (t) Rte->SetSpellCheck(t); } } } else { // Use the built in plain text editor Ctrl = new MailTextView(this, Id, 0, 0, 200, 200, (UseFont) ? &FontType : 0); } } else { // Create a view only control for the mime type: LDocView *HtmlCtrl = NULL; if (!MimeType || _stricmp(MimeType, sTextPlain) == 0) Ctrl = new MailTextView(this, Id, 0, 0, 200, 200, (UseFont) ? &FontType : 0); #if 0 // defined(WINDOWS) && !defined(__GTK_H__) else if (_stricmp(MimeType, sApplicationInternetExplorer) == 0) HtmlCtrl = Ctrl = CreateIeControl(Id); #endif else HtmlCtrl = Ctrl = new Html1::LHtml(Id, 0, 0, 200, 200); if (HtmlCtrl && UseFont) { LVariant LoadImg; if (GetOptions()->GetValue(OPT_HtmlLoadImages, LoadImg)) HtmlCtrl->SetLoadImages(LoadImg.CastInt32() != 0); HtmlCtrl->SetFont(FontType.Create(), true); } } if (Ctrl) { Ctrl->SetUrlDetect(true); Ctrl->SetAutoIndent(true); LVariant WrapOption; if (GetOptions()->GetValue(OPT_WordWrap, WrapOption)) { if (WrapOption.CastInt32()) { LVariant WrapCols = 80; GetOptions()->GetValue(OPT_WrapAtColumn, WrapCols); Ctrl->SetWrapAtCol(WrapCols.CastInt32()); } else { Ctrl->SetWrapAtCol(0); } } } return Ctrl; } void ScribeWnd::GrowlInfo(LString title, LString text) { LGrowl *g = d->GetGrowl(); if (!g) return; LAutoPtr n(new LGrowl::LNotify); n->Name = "info"; n->Title = title; n->Text = text; g->Notify(n); } void ScribeWnd::GrowlOnMail(Mail *m) { LVariant v; LAutoPtr n(new LGrowl::LNotify); n->Name = "new-mail"; n->Title = m->GetSubject(); int Len = 64; char sLen[16]; sprintf_s(sLen, sizeof(sLen), "%i", Len); if (m->GetVariant("BodyAsText", v, sLen)) { char *s = v.Str(); if (s) { int Words = 0; bool Lut[256]; memset(Lut, 0, sizeof(Lut)); Lut[(int)' '] = Lut[(int)'\t'] = Lut[(int)'\r'] = Lut[(int)'\n'] = true; char *c; for (c = s; *c && Words < 30; ) { while (*c && Lut[(int)*c]) c++; while (*c && !Lut[(int)*c]) c++; Words++; } n->Text.Set(s, c - s); } } LGrowl *g = d->GetGrowl(); if (g) { g->Notify(n); m->NewEmail = Mail::NewEmailTray; } } void ScribeWnd::OnNewMailSound() { static uint64 PrevTs = 0; auto Now = LCurrentTime(); if (Now - PrevTs > 30000) { PrevTs = Now; LVariant v; if (GetOptions()->GetValue(OPT_NewMailSoundFile, v) && LFileExists(v.Str())) { LPlaySound(v.Str(), SND_ASYNC); } } } void ScribeWnd::OnFolderSelect(ScribeFolder *f) { if (SearchView) SearchView->OnFolder(); } void ScribeWnd::OnNewMail(List *MailObjs, bool Add) { if (!MailObjs) return; LVariant v; bool ShowDetail = MailObjs->Length() < 5; List NeedsFiltering; LArray NeedsBayes; LArray NeedsGrowl; LArray Resort; for (auto m: *MailObjs) { if (Add) { #if DEBUG_NEW_MAIL LgiTrace("%s:%i - NewMail.OnNewMail t=%p, uid=%s, mode=%s\n", _FL, (Thing*)m, m->GetServerUid().ToString().Get(), toString(m->NewEmail)); #endif switch (m->NewEmail) { case Mail::NewEmailNone: { auto Loaded = m->GetLoaded(); #if DEBUG_NEW_MAIL LgiTrace("%s:%i - NewMail.OnNewMail.GetLoaded=%i uid=%s\n", _FL, (int)Loaded, m->GetServerUid().ToString().Get()); #endif if (Loaded != Store3Loaded) { LOG_STORE("\tOnNewMail calling SetLoaded.\n"); m->SetLoaded(); m->NewEmail = Mail::NewEmailLoading; } else { m->NewEmail = Mail::NewEmailFilter; LOG_STORE("\tOnNewMail none->NeedsFiltering.\n"); NeedsFiltering.Insert(m); } break; } case Mail::NewEmailLoading: { auto Loaded = m->GetLoaded(); if (Loaded == Store3Loaded) { m->NewEmail = Mail::NewEmailFilter; NeedsFiltering.Insert(m); if (m->GetFolder() && !Resort.HasItem(m->GetFolder())) { Resort.Add(m->GetFolder()); } } break; } case Mail::NewEmailFilter: { NeedsFiltering.Insert(m); break; } case Mail::NewEmailBayes: { NeedsBayes.Add(m); break; } case Mail::NewEmailGrowl: { if (d->Growl) { NeedsGrowl.Add(m); break; } else { m->NewEmail = Mail::NewEmailTray; // no Growl loaded so fall through to new tray mail } } case Mail::NewEmailTray: { LAssert(m->GetObject()); Mail::NewMailLst.Insert(m); OnNewMailSound(); break; } default: { LAssert(!"Hmmm what happen?"); break; } } } else { #if DEBUG_NEW_MAIL LgiTrace("%s:%i - NewMail.OnNewMail.RemoveNewMail t=%p, uid=%s\n", _FL, (Thing*)m, m->GetServerUid().ToString().Get()); #endif Mail::NewMailLst.Delete(m); if (m->NewEmail == Mail::NewEmailFilter) m->NewEmail = Mail::NewEmailNone; } } if (Add) { // Do filtering if (NeedsFiltering.Length()) { List Filters; if (!GetOptions()->GetValue(OPT_DisableUserFilters, v) || !v.CastInt32()) { GetFilters(Filters, true, false, false); } if (Filters.Length() > 0) { // Run the filters #if DEBUG_NEW_MAIL LgiTrace("%s:%i - NewMail.OnNewMail.Filtering %i mail through %i filters\n", _FL, (int)NeedsFiltering.Length(), (int)Filters.Length()); #endif Filter::ApplyFilters(NULL, Filters, NeedsFiltering); // All the email not filtered now needs to be sent to the bayes filter. for (auto m: NeedsFiltering) { if (m->NewEmail == Mail::NewEmailBayes) { #if DEBUG_NEW_MAIL LgiTrace("%s:%i - NewMail.OnNewMail.NeedsBayes t=%p, msgid=%s\n", _FL, (Thing*)m, m->GetMessageId()); #endif NeedsBayes.Add(m); } else if (m->NewEmail == Mail::NewEmailGrowl) { #if DEBUG_NEW_MAIL LgiTrace("%s:%i - NewMail.OnNewMail.NeedsGrowl t=%p, msgid=%s\n", _FL, (Thing*)m, m->GetMessageId()); #endif NeedsGrowl.Add(m); } } } } // Do bayes if (NeedsBayes.Length()) { ScribeBayesianFilterMode FilterMode = BayesOff; if (GetOptions()->GetValue(OPT_BayesFilterMode, v)) FilterMode = (ScribeBayesianFilterMode)v.CastInt32(); for (unsigned i=0; iMailMessageIdMap(); // Start the Bayesian rating process off Store3Status Status = IsSpam(Rating, m); if (Status == Store3Success) { // Bayes done... this stops OnBayesResult from passing it back to OnNewMail m->NewEmail = Mail::NewEmailGrowl; // Process bayes result if (!OnBayesResult(m, Rating)) { // Not spam... so on to growl #if DEBUG_NEW_MAIL LgiTrace("%s:%i - NewMail.Bayes.NeedsGrowl t=%p, msgid=%s\n", _FL, (Thing*)m, m->GetMessageId()); #endif NeedsGrowl.Add(m); } else { // Is spam... do nothing... #if DEBUG_NEW_MAIL LgiTrace("%s:%i - NEW_MAIL: Bayes->IsSpam t=%p, msgid=%s\n", _FL, (Thing*)m, m->GetMessageId()); #endif m->NewEmail = Mail::NewEmailNone; m = 0; } } else { // Didn't get classified immediately, so it'll be further // processed when OnBayesResult gets called later. } } else { // Bayes filter not active... move it to growl m->NewEmail = Mail::NewEmailGrowl; NeedsGrowl.Add(m); } } } if (NeedsGrowl.Length()) { if (d->Growl) { if (!ShowDetail) { LAutoPtr n(new LGrowl::LNotify); n->Name = "new-mail"; n->Title = "New Mail"; n->Text.Printf("%i new messages", (int)MailObjs->Length()); d->Growl->Notify(n); } else { for (unsigned i=0; iGetLoaded(); LAssert(state == Store3Loaded); // If loaded then notify GrowlOnMail(m); } } } for (unsigned i=0; iNewEmail = Mail::NewEmailTray; #if DEBUG_NEW_MAIL LgiTrace("%s:%i - NewMail.OnNewMail.Growl->Tray t=%p, msgid=%s\n", _FL, (Thing*)m, m->GetMessageId()); #endif LAssert(m->GetObject()); Mail::NewMailLst.Insert(m); OnNewMailSound(); } } if (GetOptions()->GetValue(OPT_NewMailNotify, v) && v.CastInt32()) { PostEvent(M_SCRIBE_NEW_MAIL); } for (unsigned i=0; iGetPath(); LgiTrace("%s:%i - NewMail.OnNewMail.Resort=%s\n", _FL, Path.Get()); #endif Resort[i]->ReSort(); } } } LColour ScribeWnd::GetColour(int i) { static LColour MailPreview; static LColour UnreadCount; #define ReadColDef(Var, Tag, Default) \ case Tag: \ { \ if (!Var.IsValid()) \ { \ Var = Default; \ LColour::GetConfigColour("Colour."#Tag, Var); \ } \ return Var; \ break; \ } switch (i) { ReadColDef(MailPreview, L_MAIL_PREVIEW, LColour(0, 0, 255)); ReadColDef(UnreadCount, L_UNREAD_COUNT, LColour(0, 0, 255)); default: { return LColour((LSystemColour)i); break; } } return LColour(); } bool WriteXmlTag(LStream &p, LXmlTag *t) { const char *Tag = t->GetTag(); bool ValidTag = ValidStr(Tag) && !IsDigit(Tag[0]); if (ValidTag) p.Print("<%s", Tag); else { LAssert(0); return false; } LXmlTree Tree; static const char *EncodeEntitiesAttr = "\'<>\"\n"; for (unsigned i=0; iAttr.Length(); i++) { auto &a = t->Attr[i]; // Write the attribute name p.Print(" %s=\"", a.GetName()); // Encode the value if (!Tree.EncodeEntities(&p, a.GetValue(), -1, EncodeEntitiesAttr)) { LAssert(0); return false; } // Write the delimiter p.Write((void*)"\"", 1); if (iAttr.Length()-1 /*&& TestFlag(d->Flags, GXT_PRETTY_WHITESPACE)*/) { p.Write((void*)"\n", 1); } } p.Write(">", 1); return true; } LString ScribeWnd::ProcessReplyForwardTemplate(Mail *m, Mail *r, char *Xml, int &Cursor, const char *MimeType) { LStringPipe p(256); if (m && r && Xml) { bool IsHtml = MimeType && !_stricmp(MimeType, sTextHtml); LMemStream mem(Xml, strlen(Xml)); LXmlTag x; LXmlTree t(GXT_KEEP_WHITESPACE | GXT_NO_DOM); if (t.Read(&x, &mem, 0)) { ScribeDom Dom(this); Dom.Email = m; if (IsHtml) { const char *EncodeEntitiesContent = "\'<>\""; for (auto Tag: x.Children) { if (!WriteXmlTag(p, Tag)) { break; } for (const char *c = Tag->GetContent(); c; ) { const char *s = strstr(c, "") : NULL; if (s && e) { if (s > c) { t.EncodeEntities(&p, (char*)c, s - c, EncodeEntitiesContent); } s += 2; LString Var = LString(s, e - s).Strip(); LVariant v; if (Var) { LString::Array parts = Var.SplitDelimit(" "); if (parts.Length() > 0) { if (Dom.GetValue(parts[0], v)) { for (unsigned mod = 1; mod < parts.Length(); mod++) { LString::Array m = parts[mod].SplitDelimit("=", 1); if (m.Length() == 2) { if (m[0].Equals("quote")) { LVariant Quote; if (Dom.GetValue(m[1], Quote)) { LVariant WrapColumn; if (!GetOptions()->GetValue(OPT_WrapAtColumn, WrapColumn) || WrapColumn.CastInt32() <= 0) WrapColumn = 76; WrapAndQuote(p, Quote.Str(), WrapColumn.CastInt32(), v.Str(), NULL, MimeType); v.Empty(); } } } } switch (v.Type) { case GV_STRING: { p.Push(v.Str()); break; } case GV_DATETIME: { p.Push(v.Value.Date->Get()); break; } case GV_NULL: break; default: { LAssert(!"Unsupported type."); break; } } } } } c = e + 2; } else { p.Print("%s", c); break; } } } } else { LArray Tags; Tags.Add(&x); for (auto Tag: x.Children) { Tags.Add(Tag); } for (unsigned i=0; iGetTag() && Dom.GetValue(Tag->GetTag(), v)) { char *s = v.Str(); if (s) { const char *Quote; if ((Quote = Tag->GetAttr("quote"))) { LVariant q, IsQuote; GetOptions()->GetValue(OPT_QuoteReply, IsQuote); if (r->GetValue(Quote, q)) { Quote = q.Str(); } else { Quote = "> "; } if (Quote && IsQuote.CastInt32()) { LVariant WrapColumn; if (!GetOptions()->GetValue(OPT_WrapAtColumn, WrapColumn) || WrapColumn.CastInt32() <= 0) WrapColumn = 76; WrapAndQuote(p, Quote, WrapColumn.CastInt32(), s); } else { p.Push(s); } } else { p.Push(s); } } else if (v.Type == GV_DATETIME && v.Value.Date) { char b[64]; v.Value.Date->Get(b, sizeof(b)); p.Push(b); } } else if (Tag->IsTag("cursor")) { int Size = (int)p.GetSize(); char *Buf = new char[Size+1]; if (Buf) { p.Peek((uchar*)Buf, Size); Buf[Size] = 0; RemoveReturns(Buf); Cursor = LCharLen(Buf, "utf-8"); DeleteArray(Buf); } } if (Tag->GetContent()) { p.Push(Tag->GetContent()); } } } } } return p.NewGStr(); } LAutoString ScribeWnd::ProcessSig(Mail *m, char *Xml, const char *MimeType) { LStringPipe p; if (!m || !Xml) return LAutoString(); if (MimeType && !_stricmp(MimeType, sTextHtml)) p.Write(Xml, strlen(Xml)); else { LMemStream mem(Xml, strlen(Xml)); LXmlTag x; LXmlTree t(GXT_KEEP_WHITESPACE|GXT_NO_DOM); if (t.Read(&x, &mem, 0)) { for (auto Tag: x.Children) { if (Tag->IsTag("random-line")) { char *FileName = 0; if ((FileName = Tag->GetAttr("Filename"))) { char *File = LReadTextFile(FileName); if (File) { LToken Lines(File, "\r\n"); DeleteArray(File); char *RandomLine = Lines[LRand((unsigned)Lines.Length())]; if (RandomLine) { p.Push(RandomLine); } } } } else if (Tag->IsTag("random-paragraph")) { char *FileName = 0; if ((FileName = Tag->GetAttr("Filename"))) { char *File = LReadTextFile(FileName); if (File) { List Para; for (char *f=File; f && *f; ) { // skip whitespace while (strchr(" \t\r\n", *f)) f++; if (*f) { char *Start = f; char *n; while ((n = strchr(f, '\n'))) { f = n + 1; if (f[1] == '\n' || (f[1] == '\r' && f[2] == '\n')) { break; } } if (f == Start) f += strlen(f); Para.Insert(NewStr(Start, f-Start)); } } DeleteArray(File); char *RandomPara = Para.ItemAt(LRand((int)Para.Length())); if (RandomPara) { p.Push(RandomPara); } Para.DeleteArrays(); } } } else if (Tag->IsTag("include-file")) { char *FileName = 0; if ((FileName = Tag->GetAttr("filename"))) { char *File = LReadTextFile(FileName); if (File) { p.Push(File); DeleteArray(File); } } } else if (Tag->IsTag("quote-file")) { char *FileName = 0; char *QuoteStr = 0; if ((FileName = Tag->GetAttr("filename")) && (QuoteStr = Tag->GetAttr("Quote"))) { } } else { p.Push(Tag->GetContent()); } } } } return LAutoString(p.NewStr()); } -bool ScribeWnd::GetAccessLevel(LViewI *Parent, ScribePerm Required, const char *ResourceName) -{ - if (Required <= CurrentAuthLevel) - { - return true; +// Get the effective permissions for a resource. +// +// This method can be used by both sync and async code: +// In sync mode, don't supply a callback (ie = NULL) and the return value will be: +// Store3Error - no access +// Store3Delayed - no access, asking the user for password +// Store3Success - allow immediate access +// +// In async mode, supply a callback and wait for the response. +// callback(false) - no access +// callback(true) - allow immediate access +// in this mode the same return values as sync mode are used. +Store3Status ScribeWnd::GetAccessLevel(LViewI *Parent, ScribePerm Required, const char *ResourceName, std::function Callback) +{ + if (Required >= CurrentAuthLevel) + { + if (Callback) Callback(true); + return Store3Success; } if (!Parent) - { Parent = this; - } switch (Required) { - case PermRequireNone: - { - return true; - break; - } + default: + break; case PermRequireUser: { bool Status = false; GPassword p; - if (p.Serialize(GetOptions(), OPT_UserPermPassword, false)) - { - char Msg[256]; - sprintf_s(Msg, sizeof(Msg), LLoadString(IDS_ASK_USER_PASS), ResourceName); + if (!p.Serialize(GetOptions(), OPT_UserPermPassword, false)) + { + if (Callback) Callback(true); + return Store3Success; + } + + char Msg[256]; + sprintf_s(Msg, sizeof(Msg), LLoadString(IDS_ASK_USER_PASS), ResourceName); - LInput d(Parent, "", Msg, AppName, true); - if (d.DoModal() && d.GetStr()) + auto d = new LInput(Parent, "", Msg, AppName, true); + d->DoModal([this, d, p, Callback](auto dlg, auto id) + { + if (id && d->GetStr()) { char Pass[256]; p.Get(Pass); - Status = strcmp(Pass, d.GetStr()) == 0; + bool Status = strcmp(Pass, d->GetStr()) == 0; if (Status) { CurrentAuthLevel = PermRequireUser; auto i = Menu->FindItem(IDM_LOGOUT); if (i) i->Enabled(true); - } - } - } - else - { - Status = true; - } - - return Status; + if (Callback) Callback(true); + } + else + { + if (Callback) Callback(false); + } + } + delete dlg; + }); + + return Store3Delayed; } case PermRequireAdmin: { LString Key; Key.Printf("Scribe.%s", OPT_AdminPassword); auto Hash = LAppInst->GetConfig(Key); if (ValidStr(Hash)) { - uchar Bin[256]; - ssize_t BinLen = 0; - if ((BinLen = ConvertBase64ToBinary(Bin, sizeof(Bin), Hash, strlen(Hash))) == 16) - { - LInput d(Parent, "", LLoadString(IDS_ASK_ADMIN_PASS), AppName, true); - if (d.DoModal() && d.GetStr()) - { - unsigned char Digest[16]; - char Str[256]; - sprintf_s(Str, sizeof(Str), "%s admin", d.GetStr().Get()); - MDStringToDigest(Digest, Str); + if (Callback) Callback(false); + return Store3Error; + } + + uchar Bin[256]; + ssize_t BinLen = 0; + if ((BinLen = ConvertBase64ToBinary(Bin, sizeof(Bin), Hash, strlen(Hash))) != 16) + { + LgiMsg(Parent, "Admin password not correctly encoded.", AppName); + if (Callback) Callback(false); + return Store3Error; + } + + auto d = new LInput(Parent, "", LLoadString(IDS_ASK_ADMIN_PASS), AppName, true); + d->DoModal([this, d, Bin, Callback](auto dlg, auto id) + { + if (id && d->GetStr()) + { + unsigned char Digest[16]; + char Str[256]; + sprintf_s(Str, sizeof(Str), "%s admin", d->GetStr().Get()); + MDStringToDigest(Digest, Str); - if (memcmp(Bin, Digest, 16) == 0) - { - CurrentAuthLevel = PermRequireAdmin; - auto i = Menu->FindItem(IDM_LOGOUT); - if (i) i->Enabled(true); - return true; - } - } - } - else - { - LgiMsg(Parent, "Admin password not correctly encoded.", AppName); - } - - return false; - } - break; - } - } - - return false; -} - -bool ScribeWnd::GetAccountSettingsAccess(LViewI *Parent, ScribeAccessType AccessType) + if (memcmp(Bin, Digest, 16) == 0) + { + CurrentAuthLevel = PermRequireAdmin; + auto i = Menu->FindItem(IDM_LOGOUT); + if (i) i->Enabled(true); + if (Callback) Callback(true); + } + else + { + if (Callback) Callback(false); + } + } + delete dlg; + }); + + return Store3Delayed; + } + } + + if (Callback) Callback(true); + return Store3Success; +} + +void ScribeWnd::GetAccountSettingsAccess(LViewI *Parent, ScribeAccessType AccessType, std::function Callback) { LVariant Level = (int)PermRequireNone; // Check if user level access is required char *Opt = (char*)(AccessType == ScribeReadAccess ? OPT_AccPermRead : OPT_AccPermWrite); GetOptions()->GetValue(Opt, Level); /* // Check if admin access is required char *Admin = GetScribeAccountPerm((char*) (AccessType == ScribeReadAccess ? "Read" : "Write")); if (Admin && _stricmp(Admin, "Admin") == 0) { Level = PermRequireAdmin; } */ - return GetAccessLevel(Parent ? Parent : this, (ScribePerm)Level.CastInt32(), "Account Settings"); + GetAccessLevel(Parent ? Parent : this, (ScribePerm)Level.CastInt32(), "Account Settings", Callback); } LMutex *ScribeWnd::GetLock() { return _Lock; } void ScribeWnd::OnBeforeConnect(ScribeAccount *Account, bool Receive) { if (Receive) { Account->Receive.Enabled(false); } else { Account->Send.Enabled(true); } if (StatusPanel) { StatusPanel->Invalidate(); } } void ScribeWnd::OnAfterConnect(ScribeAccount *Account, bool Receive) { if (Account) { SaveOptions(); if (ScribeState == ScribeExiting) { LCloseApp(); } } if (Receive) { Account->Receive.Enabled(true); } else { Account->Send.Enabled(true); } if (StatusPanel) { StatusPanel->Invalidate(); } if (d->SendAfterReceive) { bool Online = false; for (auto a: Accounts) { bool p = a->Receive.IsPersistant(); bool o = a->Receive.IsOnline(); if (!p && o) { Online = true; break; } } if (!Online) { Send(-1, true); d->SendAfterReceive = false; } } } void ScribeWnd::Send(int Which, bool Quiet) { if (ScribeState == ScribeExiting) return; if (Which < 0) { LVariant v; if (GetOptions()->GetValue(OPT_DefaultSendAccount, v)) Which = v.CastInt32(); } LArray Outboxes; unsigned i; for (i=0; iLoadThings(); Outboxes.Add(OutBox); } } if (Outboxes.Length() < 1) { LgiMsg(this, LLoadString(IDS_NO_OUTGOING_FOLDER), AppName, MB_OK); return; } int MailToSend = 0; List Acc; SendAccountlet *Default = 0; { // Create list of accounts for (auto a: Accounts) { Acc.Insert(&a->Send); a->Send.Outbox.DeleteObjects(); if (Which < 0 || a->GetIndex() == Which) Default = &a->Send; } // If the default it not in the list try the first one.. if (!Default) Default = Acc[0]; } for (i=0; iItems) { Mail *m = t->IsMail(); if (!m) continue; uint32_t Flags = m->GetFlags(); if (!TestFlag(Flags, MAIL_SENT) && TestFlag(Flags, MAIL_READY_TO_SEND)) { GDataIt To = m->GetObject()->GetList(FIELD_TO); if (To && To->Length()) { LAutoPtr Out(new ScribeEnvelope); if (Out) { if (m->OnBeforeSend(Out)) { SendAccountlet *Send = 0; for (auto a: Acc) { LVariant Ie = a->GetAccount()->Identity.Email(); if (ValidStr(Ie.Str()) && a->OnlySendThroughThisAccount()) { if (Ie.Str() && m->GetFromStr(FIELD_EMAIL) && _stricmp(Ie.Str(), m->GetFromStr(FIELD_EMAIL)) == 0) { Send = a; break; } } } if (!Send) { Send = Default; } if (Send) { LAssert(Out->To.Length() > 0); Out->SourceFolder = OutBox->GetPath(); Send->Outbox.Add(Out.Release()); MailToSend++; } } } } else { LgiMsg( this, LLoadString(IDS_ERROR_NO_RECIPIENTS), AppName, MB_OK, m->GetSubject() ? m->GetSubject() : (char*)"(none)"); } } } } if (MailToSend) { for (auto a: Acc) { if (a->Outbox.Length() > 0 && !a->IsOnline()) { if (a->IsConfigured()) { a->Connect(0, Quiet); } else { - LAlert d(this, + auto d = new LAlert(this, AppName, LLoadString(IDS_ERROR_NO_CONFIG_SEND), LLoadString(IDS_CONFIGURE), LLoadString(IDS_CANCEL)); - if (d.DoModal() == 1) - { - a->GetAccount()->InitUI(this, 1); - } + d->DoModal([this, d, a](auto dlg, auto id) + { + if (id == 1) + a->GetAccount()->InitUI(this, 1, NULL); + delete dlg; + }); } } } } else { LgiMsg(this, LLoadString(IDS_NO_MAIL_TO_SEND), AppName, MB_OK, Outboxes[0]->GetText()); } } void ScribeWnd::Receive(int Which) { #define LOG_RECEIVE 0 if (ScribeState == ScribeExiting) { LgiTrace("%s:%i - Won't receive, is trying to exit.\n", _FL); return; } for (ScribeAccount *i: Accounts) { if (i->GetIndex() != Which) continue; if (i->Receive.IsOnline()) { #if LOG_RECEIVE LgiTrace("%s:%i - %i already online.\n", _FL, Which); #endif } else if (i->Receive.Disabled() > 0) { #if LOG_RECEIVE LgiTrace("%s:%i - %i is disabled.\n", _FL, Which); #endif } else if (!i->Receive.IsConfigured()) { #if LOG_RECEIVE LgiTrace("%s:%i - %i is not configured.\n", _FL, Which); #endif - LAlert a(this, + auto a = new LAlert(this, AppName, LLoadString(IDS_ERROR_NO_CONFIG_RECEIVE), LLoadString(IDS_CONFIGURE), LLoadString(IDS_CANCEL)); - if (a.DoModal() == 1) - { - i->InitUI(this, 2); - } + a->DoModal([this, a, i](auto dlg, auto id) + { + if (id == 1) + i->InitUI(this, 2, NULL); + delete dlg; + }); } else { i->Receive.Connect(0, false); } break; } } bool ScribeWnd::GetHelpFilesPath(char *Path, int PathSize) { const char *Index = "index.html"; char Install[MAX_PATH_LEN]; strcpy_s(Install, sizeof(Install), ScribeResourcePath()); for (int i=0; i<5; i++) { char p[MAX_PATH_LEN]; LMakePath(p, sizeof(p), Install, "Help"); LMakePath(p, sizeof(p), p, Index); LgiTrace("Trying '%s'\n", p); if (LFileExists(p)) { LTrimDir(p); strcpy_s(Path, PathSize, p); return true; } #ifdef MAC LMakePath(p, sizeof(p), Install, "Resources/Help"); LMakePath(p, sizeof(p), p, Index); // LgiTrace("Trying '%s'\n", p); if (LFileExists(p)) { LTrimDir(p); strcpy_s(Path, PathSize, p); return true; } #endif LTrimDir(Install); // Try all the parent folders... } LArray Ext; LArray Help; Ext.Add("index.html"); LRecursiveFileSearch(Install, &Ext, &Help); for (unsigned i=0; iSetEvents(d); return Browse->SetUri(Path); } #else #ifdef MAC if (LExecute(Path)) return true; #else if (Hash) *Hash = '#'; // Get browser... char Browser[256]; if (!LGetAppForMimeType("application/browser", Browser, sizeof(Browser))) { LgiTrace("%s:%i - LGetAppForMimeType('text/html') failed.\n", _FL); goto HelpError; } // Execute browser to view help... char Uri[256]; sprintf_s(Uri, sizeof(Uri), "\"file://%s\"", Path); #ifdef WIN32 char *c; while (c = strchr(Uri, '\\')) *c = '/'; #endif LgiTrace("LaunchHelp('%s','%s').\n", Browser, Uri); if (!LExecute(Browser, Uri)) { LgiTrace("%s:%i - LExecute('%s','%s') failed.\n", _FL, Browser, Uri); goto HelpError; } return true; #endif #endif HelpError: LgiMsg(this, LLoadString(IDS_ERROR_NO_HELP), AppName, MB_OK); } return false; } void ScribeWnd::Preview(int Which) { LArray a; a.Add(Accounts[Which]); OpenPopView(this, a); } bool MergeSegments(LDataPropI *DstProp, LDataPropI *SrcProp, LDom *Dom) { LDataI *Src = dynamic_cast(SrcProp); LDataI *Dst = dynamic_cast(DstProp); if (!Dst || !Src) return false; Store3MimeType Mt(Src->GetStr(FIELD_MIME_TYPE)); if (Mt.IsText()) { // Set the headers... Dst->SetStr(FIELD_INTERNET_HEADER, Src->GetStr(FIELD_INTERNET_HEADER)); // Do mail merge of data part... LAutoStreamI Data = Src->GetStream(_FL); if (Data) { // Read data out into a string string... LAutoString Str(new char[(int)Data->GetSize()+1]); Data->Read(Str, (int)Data->GetSize()); Str[Data->GetSize()] = 0; // Do field insert and save result to segment char *Merged = ScribeInsertFields(Str, Dom); LAutoStreamI Mem(new LMemStream(Merged, strlen(Merged))); Dst->SetStream(Mem); } } else { // Straight copy... Dst->CopyProps(*Src); } // Merge children segments as well GDataIt Sc = Src->GetList(FIELD_MIME_SEG); GDataIt Dc = Dst->GetList(FIELD_MIME_SEG); if (Dc && Sc) { for (unsigned i=0; iLength(); i++) { // Create new dest child, and merge the source child across LDataPropI *NewDestChild = Dc->Create(Dst->GetStore()); if (!MergeSegments(NewDestChild, (*Sc)[i], Dom)) return false; LDataI *NewDest = dynamic_cast(NewDestChild); if (NewDest) NewDest->Save(Dst); } } return true; } // Either 'FileName' or 'Source' will be valid void ScribeWnd::MailMerge(LArray &Contacts, const char *FileName, Mail *Source) { ScribeFolder *Outbox = GetFolder(FOLDER_OUTBOX); if (Outbox && Contacts.Length() && (FileName || Source)) { LAutoPtr ImportEmail; if (FileName) { LAutoPtr File(new LFile); if (File->Open(FileName, O_READ)) { Thing *t = CreateItem(MAGIC_MAIL, Outbox, false); ImportEmail.Reset(Source = t->IsMail()); if (!Source->Import(Source->AutoCast(File), sMimeMessage)) { Source = 0; } } } if (Source) { List Msgs; ScribeDom Dom(this); // Do the merging of the document with the database for (unsigned i=0; iGetContact() : 0; Contact *temp = new Contact(this); if (!c) { temp->SetFirst(la->sName); temp->SetEmail(la->sAddr); c = temp; } Dom.Con = c; Thing *t = CreateItem(MAGIC_MAIL, Outbox, false); if (t) { Dom.Email = t->IsMail(); LAutoString s; if (s.Reset(ScribeInsertFields(Source->GetSubject(), &Dom))) Dom.Email->SetSubject(s); LDataPropI *Recip = Dom.Email->GetTo()->Create(Dom.Email->GetObject()->GetStore()); if (Recip) { Recip->CopyProps(*Dom.Con->GetObject()); Recip->SetInt(FIELD_CC, 0); Dom.Email->GetTo()->Insert(Recip); } MergeSegments( Dom.Email->GetObject()->GetObj(FIELD_MIME_SEG), Source->GetObject()->GetObj(FIELD_MIME_SEG), &Dom); Msgs.Insert(Dom.Email); } temp->DecRef(); } // Ask user what to do if (Msgs[0]) { char Msg[256]; sprintf_s(Msg, sizeof(Msg), LLoadString(IDS_MAIL_MERGE_Q), Msgs.Length()); - LAlert Ask(this, AppName, Msg, + auto Ask = new LAlert(this, AppName, Msg, LLoadString(IDS_SAVE_TO_OUTBOX), LLoadString(IDS_CANCEL)); - switch (Ask.DoModal()) - { - case 1: // Save To Outbox - { - for (size_t i=0; iDoModal([this, Msgs, Outbox](auto dlg, auto id) + { + switch (id) + { + case 1: // Save To Outbox { - Msgs[i]->Save(Outbox); + for (size_t i=0; iSave(Outbox); + } + break; } - break; - } - case 2: // Cancel - { - for (size_t i=0; iOnDelete(); + for (size_t i=0; iOnDelete(); + } + break; } - break; - } - } + } + delete dlg; + }); } else { LgiMsg(this, LLoadString(IDS_MAIL_MERGE_EMPTY), AppName); } } } } ScribeFolder *CastFolder(LDataI *f) { if (!f) { LAssert(!"Null pointer"); return 0; } if (f->Type() == MAGIC_FOLDER) { return (ScribeFolder*)f->UserData; } return 0; } Thing *CastThing(LDataI *t) { if (!t) { LAssert(!"Null pointer"); return 0; } if (t->Type() == MAGIC_FOLDER) { LAssert(!"Shouldn't be a folder"); return 0; } return (Thing*)t->UserData; } LString _GetUids(LArray &items) { LString::Array a; for (auto i: items) a.New().Printf(LPrintfInt64, i->GetInt(FIELD_SERVER_UID)); return LString(",").Join(a); } /// Received new items from a storage backend. void ScribeWnd::OnNew( /// The parent folder of the new item LDataFolderI *Parent, /// All the new items LArray &NewItems, /// The position in the parent folder or -1 int Pos, /// Non-zero if the object is a new email. bool IsNew) { ScribeFolder *Fld = CastFolder(Parent); int UnreadDiff = 0; if (Stricmp(Parent->GetStr(FIELD_FOLDER_NAME), "Contacts") && Stricmp(Parent->GetStr(FIELD_FOLDER_NAME), "Calendar")) { LOG_STORE("OnNew(%s, %s, %i, %i)\n", Parent->GetStr(FIELD_FOLDER_NAME), _GetUids(NewItems).Get(), Pos, IsNew); } if (!Fld) { // When this happens the parent hasn't been loaded by the UI thread // yet, so if say the IMAP back end notices a new sub-folder then we // can safely ignore it until such time as the UI loads the parent // folder. At which point the child we got told about here will be // loaded anyway. #if DEBUG_NEW_MAIL LDataFolderI *p = dynamic_cast(Parent); LgiTrace("%s:%i - NewMail.OnNew no UI object for '%s'\n", _FL, p ? p->GetStr(FIELD_FOLDER_NAME) : NULL); #endif return; } if (!Parent || !NewItems.Length()) { LAssert(!"Param error"); return; } for (auto cb: d->Store3EventCallbacks) cb->OnNew(Parent, NewItems, Pos, IsNew); List NewMail; for (auto Item: NewItems) { if (Item->Type() == MAGIC_FOLDER) { // Insert new folder into the right point on the tree... LDataFolderI *SubObject = dynamic_cast(Item); if (!SubObject) { LAssert(!"Not a valid folder."); continue; } ScribeFolder *Sub = CastFolder(SubObject); // LgiTrace("OnNew '%s', Sub=%p Children=%i\n", SubObject->GetStr(FIELD_FOLDER_NAME), Sub, SubObject->SubFolders().Length()); if (!Sub) { // New folder... if ((Sub = new ScribeFolder)) { Sub->App = this; Sub->SetObject(SubObject, false, _FL); Fld->Insert(Sub); } } else { // Existing folder... Fld->Insert(Sub, Pos); } } else { Thing *t = CastThing(Item); if (!t) { // Completely new thing... t = CreateThingOfType((Store3ItemTypes) Item->Type(), Item); } if (t) { if (t->DeleteOnAdd.Obj) { // Complete a delayed move auto OldFolder = t->App->GetFolder(t->DeleteOnAdd.Path); if (!OldFolder) { LgiTrace("%s:%i - Couldn't resolve old folder '%s'\n", _FL, t->DeleteOnAdd.Path.Get()); } else { auto OldItem = t->DeleteOnAdd.Obj; if (!OldFolder->Items.HasItem(OldItem)) LgiTrace("%s:%i - Couldn't find old obj.\n", _FL); else OldItem->OnDelete(); } } t->SetParentFolder(Fld); Fld->Update(); if (Fld->Select()) { // Existing thing... t->SetFieldArray(Fld->GetFieldArray()); ThingFilter *Filter = GetThingFilter(); if (!Filter || Filter->TestThing(t)) { MailList->Insert(t, -1, false); } } Mail *m = t->IsMail(); if (m) { // LgiTrace("OnNew %p\n", m->GetObject()); UnreadDiff += TestFlag(m->GetFlags(), MAIL_READ) ? 0 : 1; #if DEBUG_NEW_MAIL LgiTrace("%s:%i - NewMail.OnNew t=%p uid=%s IsNew=%i\n", _FL, t, m->GetServerUid().ToString().Get(), IsNew); #endif if (IsNew) { LAssert(!NewMail.HasItem(m)); NewMail.Insert(m); } } t->Update(); } } } if (UnreadDiff) Fld->OnUpdateUnRead(UnreadDiff, false); if (MailList && Fld->Select()) MailList->Sort(ListItemCompare, (NativeInt)Fld); if (NewMail.Length()) OnNewMail(&NewMail); } void ScribeWnd::OnPropChange(LDataStoreI *store, int Prop, LVariantType Type) { switch (Prop) { case FIELD_IS_ONLINE: { // This is in case we receive a message after the app has shutdown and // deleted 'this'. if (ScribeState > ScribeRunning) break; if (StatusPanel) StatusPanel->Invalidate(); for (auto a : Accounts) { if (a->Receive.GetDataStore() == store) { int64 Online = store->GetInt(FIELD_IS_ONLINE); auto Old = ScribeState; // This prevents the folders unloading during this call. // Which causes crashes. ScribeState = ScribeLoadingFolders; a->Receive.OnOnlineChange(Online != 0); ScribeState = Old; break; } } break; } } } void ScribeWnd::SetContext(const char *file, int line) { d->CtxFile = file; d->CtxLine = line; } bool ScribeWnd::OnChange(LArray &items, int FieldHint) { bool UpdateSelection = false; List NewMail; ScribeFolder *Parent = 0; // LOG_STORE("OnChange(%i, %i)\n", (int)items.Length(), FieldHint); LAssert(d->CtxFile != NULL); for (unsigned c=0; cStore3EventCallbacks.Length(); c++) { d->Store3EventCallbacks[c]->OnChange(items, FieldHint); } for (unsigned i=0; iType() == MAGIC_FOLDER) { auto ItemFolder = dynamic_cast(Item); ScribeFolder *fld = CastFolder(ItemFolder); if (fld) { if (FieldHint == FIELD_STATUS) { // This is the delayed folder load case: fld->IsLoaded(true); if (fld->Select()) fld->Populate(GetMailList()); } else { fld->Update(); } } else { // LAssert(!"Can't cast to folder?"); } } else if ((t = CastThing(Item))) { ThingUi *Ui = t->GetUI(); if (Ui) Ui->OnChange(); Mail *m = t->IsMail(); if (m) { auto StoreFlags = Item->GetInt(FIELD_FLAGS); #if 0 LgiTrace("App.OnChange(%i) handler %p: %s -> %s\n", FieldHint, m, EmailFlagsToStr(m->FlagsCache).Get(), EmailFlagsToStr(StoreFlags).Get()); #endif if (TestFlag(m->FlagsCache, MAIL_NEW) && !TestFlag(StoreFlags, MAIL_NEW)) { Mail::NewMailLst.Delete(m); Parent = m->GetFolder(); } if (m->FlagsCache != StoreFlags) { // LgiTrace("%s:%i - OnChange mail flags changed.\n", _FL); m->SetFlagsCache(StoreFlags, false, false); Parent = m->GetFolder(); } if (m->NewEmail == Mail::NewEmailLoading) { auto Loaded = m->GetLoaded(); if (Loaded < Store3Loaded) { #if DEBUG_NEW_MAIL LgiTrace("%s:%i - NewMail.OnChange.GetBody t=%p, uid=%s, mode=%s, loaded=%s (%s:%i)\n", _FL, (Thing*)m, m->GetServerUid().ToString().Get(), toString(m->NewEmail), toString(Loaded), d->CtxFile, d->CtxLine); #endif m->GetBody(); } else { #if DEBUG_NEW_MAIL LgiTrace("%s:%i - NewMail.OnChange.NewMail t=%p, uid=%s, mode=%s, loaded=%s (%s:%i)\n", _FL, (Thing*)m, m->GetServerUid().ToString().Get(), toString(m->NewEmail), toString(Loaded), d->CtxFile, d->CtxLine); #endif NewMail.Insert(m); } } } else if (t->IsCalendar()) { for (auto cv: CalendarView::CalendarViews) cv->OnContentsChanged(); } if (FieldHint != FIELD_FLAGS) { // Call the on load handler... t->IsLoaded(true); } if (t->GetList()) { t->Update(); if (FieldHint != FIELD_FLAGS) UpdateSelection |= t->Select(); } } } if (MailList && UpdateSelection) { List Sel; if (MailList->GetSelection(Sel)) { OnSelect(&Sel, true); } } if (Parent) Parent->OnUpdateUnRead(0, true); if (NewMail.Length()) OnNewMail(&NewMail); d->CtxFile = NULL; d->CtxLine = 0; return true; } ContactGroup *ScribeWnd::FindGroup(char *SearchName) { ScribeFolder *g = GetFolder(FOLDER_GROUPS); if (!g || !SearchName) return 0; g->LoadThings(); for (Thing *t: g->Items) { ContactGroup *Grp = t->IsGroup(); if (!Grp) continue; auto Name = Grp->GetObject()->GetStr(FIELD_GROUP_NAME); if (Name) { if (!_stricmp(Name, SearchName)) { return Grp; } } } return 0; } bool ScribeWnd::Match(LDataStoreI *Store, LDataPropI *Address, int ObjType, LArray &Matches) { if (!Store || !Address) return 0; // char *Addr = Address->GetStr(ObjType == MAGIC_CONTACT ? FIELD_EMAIL : FIELD_NAME); auto Addr = Address->GetStr(FIELD_EMAIL); if (!Addr) return 0; List *l = GetEveryone(); if (!l) return 0; Matches.Length(0); if (ObjType == MAGIC_CONTACT) { for (auto c: *l) { if (strchr(Addr, '@')) { auto Emails = c->GetEmails(); for (auto e: Emails) { if (e.Equals(Addr)) { Matches.Add(c); break; } } } } } else if (ObjType == MAGIC_GROUP) { ScribeFolder *g = GetFolder(FOLDER_GROUPS); if (!g) return false; g->LoadThings(); for (auto t: g->Items) { ContactGroup *Grp = t->IsGroup(); if (!Grp) continue; List GrpAddr; if (!Grp->GetAddresses(GrpAddr)) continue; for (auto a: GrpAddr) { if (_stricmp(a, Addr) == 0) { Matches.Add(t); break; } } GrpAddr.DeleteArrays(); } } return Matches.Length() > 0; } bool ScribeWnd::OnMove(LDataFolderI *new_parent, LDataFolderI *old_parent, LArray &items) { if (!new_parent || items.Length() == 0) return false; bool Status = false; for (auto cb: d->Store3EventCallbacks) cb->OnMove(new_parent, old_parent, items); ScribeFolder *New = CastFolder(new_parent); ScribeFolder *Old = old_parent ? CastFolder(old_parent) : NULL; if (New) { ssize_t SelIdx = -1; for (unsigned n=0; nType()) { case MAGIC_FOLDER: { ScribeFolder *i = CastFolder(items[n]); if (i) { i->Detach(); New->Insert(i); Status = true; } break; } default: { Thing *t = CastThing(items[n]); if (t) { int UnreadMail = t->IsMail() ? !TestFlag(t->IsMail()->GetFlags(), MAIL_READ) : false; if (Old && UnreadMail) Old->OnUpdateUnRead(-1, false); if (t->GetList()) { if (t->Select() && SelIdx < 0) SelIdx = t->GetList()->IndexOf(t); t->GetList()->Remove(t); } // This closes the user interface if the object is being moved to the trash... if (New->GetSystemFolderType() == Store3SystemTrash) t->SetUI(); t->SetParentFolder(New); if (New->Select()) { MailList->Insert(t); MailList->ReSort(); } if (UnreadMail) New->OnUpdateUnRead(1, false); if (New->GetItemType() == MAGIC_ANY && t->IsMail()) { Mail::NewMailLst.Delete(t->IsMail()); } Status = true; } break; } } } if (MailList && SelIdx >= 0 && MailList->Length() > 0) { if (SelIdx >= (ssize_t)MailList->Length()) SelIdx = MailList->Length()-1; MailList->Value(SelIdx); } } return Status; } bool ScribeWnd::OnDelete(LDataFolderI *Parent, LArray &Items) { int UnreadAdjust = 0; int SelectIdx = -1; LOG_STORE("OnDelete(%s, %i)\n", Parent->GetStr(FIELD_FOLDER_NAME), (int)Items.Length()); if (!Items.Length()) return false; for (unsigned c=0; cStore3EventCallbacks.Length(); c++) { d->Store3EventCallbacks[c]->OnDelete(Parent, Items); } ScribeFolder *Fld = 0; for (unsigned i=0; iType() == MAGIC_FOLDER) { // Insert new folder into the right point on the tree... ScribeFolder *Sub = CastFolder(Item); if (!Sub) return true; LgiTrace("OnDelete folder %p (%i)\n", (ThingType*)Sub, ThingType::DirtyThings.HasItem(Sub)); // Remove the deleted object from the dirty queue... ThingType::DirtyThings.Delete(Sub); // And make sure it can't get dirty again... Sub->SetWillDirty(false); // Remove all the children LDataIterator &SubItems = Sub->GetFldObj()->Children(); LArray Children; for (LDataI *c = SubItems.First(); c; c = SubItems.Next()) { Children.Add(c); } OnDelete(Sub->GetFldObj(), Children); // Remove the UI element Sub->Remove(); // Free the memory for the object DeleteObj(Sub); } else { Fld = Parent ? CastFolder(Parent) : 0; if (!Fld) return false; Thing *t = CastThing(Item); if (!t) { // This happens when the item moves from one store to another // of a different type and a copy of the old object is made. The // 'Thing' is detached from 'Item' and attached to the new LDataI // object. // However if the object is an unread email... we should still decrement the // folder's unread count. if (Item->Type() == MAGIC_MAIL && !(Item->GetInt(FIELD_FLAGS) & MAIL_READ)) { Fld->OnUpdateUnRead(-1, false); } } else { LAssert(!Fld || Fld == t->GetFolder()); // LgiTrace("OnDelete thing %p (%i)\n", (ThingType*)t, ThingType::DirtyThings.HasItem(t)); // Remove the deleted object from the dirty queue... ThingType::DirtyThings.Delete(t); // And make sure it can't get dirty again... t->SetWillDirty(false); // Was the thing currently being previewed? if (PreviewPanel && PreviewPanel->GetCurrent() == t) PreviewPanel->OnThing(0, false); // Was the object selected in the thing list? if (Fld && Fld->Select()) { // If so, select an adjacent item. if (SelectIdx < 0 && t->Select()) SelectIdx = MailList->IndexOf(t); MailList->Remove(t); } // Was the object an unread email? Mail *m = t->IsMail(); if (m) { if (!TestFlag(m->GetFlags(), MAIL_READ) && Fld) UnreadAdjust--; Mail::NewMailLst.Delete(m); } if (Fld) { LAssert(Fld->Items.HasItem(t)); Fld->Items.Delete(t); Fld->Update(); } t->SetUI(); t->SetParentFolder(NULL); t->SetObject(NULL, false, _FL); if (!t->DecRef()) { t->SetDirty(false); t->SetWillDirty(false); } } } } if (Fld) Fld->OnUpdateUnRead(UnreadAdjust, false); if (SelectIdx >= 0) { LListItem *i = MailList->ItemAt(SelectIdx); if (!i && MailList->Length() > 0) i = MailList->ItemAt(MailList->Length()-1); if (i) i->Select(true); } return true; } bool ScribeWnd::AddStore3EventHandler(LDataEventsI *callback) { if (!d->Store3EventCallbacks.HasItem(callback)) { d->Store3EventCallbacks.Add(callback); } return true; } bool ScribeWnd::RemoveStore3EventHandler(LDataEventsI *callback) { d->Store3EventCallbacks.Delete(callback); return true; } bool ScribeWnd::OnMailTransferEvent(MailTransferEvent *t) { if (!Lock(_FL)) return false; LAssert(t); d->Transfers.Add(t); Unlock(); return true; } bool ScribeWnd::OnTransfer() { LVariant v; LArray Local; // Lock the transfer list if (Lock(_FL)) { // Take out a bunch of emails... for (int i=0; i<5 && d->Transfers.Length() > 0; i++) { auto t = d->Transfers[0]; LAssert(t); d->Transfers.DeleteAt(0, true); // Save them to a local array Local.Add(t); } Unlock(); if (Local.Length() == 0) // Didn't get any return false; } LArray Trans; for (unsigned s=0; sStartTransaction(); } for (auto Transfer: Local) { ReceiveStatus NewStatus = MailReceivedError; if (!Transfer) { LAssert(0); continue; } // We have to set Transfer->Status to something other than "waiting" // in all branches of this loop. Othewise the account thread will hang. Accountlet *Acc = Transfer->Account; #if DEBUG_NEW_MAIL LgiTrace( "%s:%i - NewMail.OnTransfer t=%p receive=%i act=%i\n", _FL, Transfer, Acc->IsReceive(), Transfer->Action); #endif if (Acc->IsReceive()) { switch (Transfer->Action) { default: break; case MailDownload: case MailDownloadAndDelete: { // Import newly received mail from file ReceiveAccountlet *Receive = dynamic_cast(Acc); if (Receive) { LVariant Path = Receive->DestinationFolder(); ScribeFolder *Inbox = Path.Str() ? GetFolder(Path.Str()) : NULL; if (!Inbox) Inbox = GetFolder(FOLDER_INBOX); if (Inbox) { Mail *m = new Mail(this, Inbox->GetObject()->GetStore()->Create(MAGIC_MAIL)); if (m) { // Just in case m->SetParentFolder(Inbox); // Set the new flag... m->SetFlags(m->GetFlags() | MAIL_NEW, true, false); // Set the account to, which is used to map the email back // to the incoming account in the case that the headers // don't have the correct "To" information. m->SetAccountId(Receive->Id()); // Decode the email m->OnAfterReceive(Transfer->Rfc822Msg); LVariant v; m->SetServerUid(v = Transfer->Uid); // Save to permanent storage if (m->Save(Inbox)) { m->SetDirty(false); NewStatus = MailReceivedOk; // Only after we've safely stored the email can we // actually mark it as downloaded. if (Transfer->Uid) { Receive->AddMsg(Transfer->Uid); } } else LgiTrace("%s:%i - Error: Couldn't save mail to folders.\n", _FL); } else LgiTrace("%s:%i - Error: Memory alloc failed.\n", _FL); } else LgiTrace("%s:%i - Error: No Inbox.\n", _FL); } else LgiTrace("%s:%i - Error: Bad ptr.\n", _FL); break; } case MailHeaders: { LList *Lst; if (Transfer->Msg && (Lst = Transfer->GetList())) { //LgiTrace("Using Lst=%p\n", Lst); Lst->Insert(Transfer->Msg); NewStatus = MailReceivedOk; } break; } } } else if (Transfer->Send) { ScribeFolder *Outbox = GetFolder(Transfer->Send->SourceFolder); if (!Outbox) Outbox = GetFolder(FOLDER_OUTBOX); - if (Outbox) - { - Mail *m = Outbox->GetMessageById(Transfer->Send->MsgId); + Outbox->GetMessageById(Transfer->Send->MsgId, [&](auto m) + { if (!m) { LAssert(!"Where is the email?"); LgiTrace("%s:%i - Can't find outbox for msg id '%s'\n", _FL, Transfer->Send->MsgId.Get()); } else { if (Transfer->OutgoingHeaders) { m->SetInternetHeader(Transfer->OutgoingHeaders); DeleteArray(Transfer->OutgoingHeaders); } m->OnAfterSend(); m->Save(); // Do filtering LVariant DisableFilters; GetOptions()->GetValue(OPT_DisableUserFilters, DisableFilters); if (!DisableFilters.CastInt32()) { // Run the filters List Filters; GetFilters(Filters, false, true, false); if (Filters[0]) { List In; In.Insert(m); Filter::ApplyFilters(0, Filters, In); } } // Add to bayesian spam whitelist... LVariant v; ScribeBayesianFilterMode FilterMode = BayesOff; GetOptions()->GetValue(OPT_BayesFilterMode, v); FilterMode = (ScribeBayesianFilterMode) v.CastInt32(); if (FilterMode != BayesOff && m->GetObject()) { GDataIt To = m->GetObject()->GetList(FIELD_TO); if (To) { for (LDataPropI *a = To->First(); a; a = To->Next()) { if (a->GetStr(FIELD_EMAIL)) WhiteListIncrement(a->GetStr(FIELD_EMAIL)); } } } NewStatus = MailReceivedOk; } + }); } } #if DEBUG_NEW_MAIL LgiTrace( "%s:%i - NewMail.OnTransfer t=%p NewStatus=%i\n", _FL, Transfer, NewStatus); #endif if (NewStatus != MailReceivedOk) { // So tell the thread not to delete it from the server LgiTrace("%s:%i - Mail[%i] error: %s\n", _FL, Transfer->Index, ReceiveStatusName(Transfer->Status)); Transfer->Action = MailNoop; } Transfer->Status = NewStatus; } return Local.Length() > 0; } bool ScribeWnd::OnIdle() { bool Status = false; for (auto a : Accounts) Status |= a->Receive.OnIdle(); Status |= OnTransfer(); LMessage m(M_SCRIBE_IDLE); BayesianFilter::OnEvent(&m); SaveDirtyObjects(); #ifdef _DEBUG static uint64_t LastTs = 0; auto Now = LCurrentTime(); if (Now - LastTs >= 1000) { LastTs = Now; if (Thing::DirtyThings.Length() > 0) LgiTrace("%s:%i - Thing::DirtyThings=" LPrintfInt64 "\n", _FL, Thing::DirtyThings.Length()); } #endif return Status; } void ScribeWnd::OnScriptCompileError(const char *Source, Filter *f) { const char *CompileMsg = LLoadString(IDS_ERROR_SCRIPT_COMPILE); LArray Actions; Actions.Add(LLoadString(IDS_SHOW_CONSOLE)); Actions.Add(LLoadString(IDS_OPEN_SOURCE)); Actions.Add(LLoadString(IDS_OK)); if (!d->Bar) { // FYI Capabilities are handled in ScribeWnd::StartAction. d->ErrSource = Source; d->ErrFilter = f; d->Bar = new MissingCapsBar(this, &d->MissingCaps, CompileMsg, this, Actions); AddView(d->Bar, 2); AttachChildren(); OnPosChange(); } } diff --git a/Code/ScribeAttachment.cpp b/Code/ScribeAttachment.cpp --- a/Code/ScribeAttachment.cpp +++ b/Code/ScribeAttachment.cpp @@ -1,1377 +1,1384 @@ /* ** FILE: ScribeAttachment.cpp ** AUTHOR: Matthew Allen ** DATE: 7/12/98 ** DESCRIPTION: Scribe Attachments ** ** Copyright (C) 1998, Matthew Allen ** fret@memecode.com */ #include #include #include #include #include "Scribe.h" #include "resdefs.h" #include "lgi/common/NetTools.h" #include "lgi/common/ProgressDlg.h" #include "lgi/common/Tnef.h" #include "lgi/common/LgiRes.h" #include "lgi/common/TextConvert.h" #include "lgi/common/FileSelect.h" extern char *ExtractCodePage(char *ContentType); #ifdef WIN32 char NotAllowed[] = "\\/*:?\"|<>"; #else char NotAllowed[] = "\\/"; #endif ////////////////////////////////////////////////////////////////////////////// char *StripPath(const char *Full) { if (Full) { auto Dos = strrchr(Full, '\\'); auto Unix = strrchr(Full, '/'); if (Dos) { return NewStr(Dos+1); } else if (Unix) { return NewStr(Unix+1); } } return NewStr(Full); } void CleanFileName(char *i) { if (i) { char *o = i; while (*i) { if ((uint8_t)*i >= ' ' && !strchr(NotAllowed, *i)) { *o++ = *i; } i++; } *o++ = 0; } } ////////////////////////////////////////////////////////////////////////////// void Attachment::_New(LDataI *object) { DefaultObject(object); LAssert(GetObject() != NULL); Owner = 0; Msg = 0; IsResizing = false; } Attachment::Attachment(ScribeWnd *App, Attachment *From) : Thing(App) { _New(From && From->GetObject() ? From->GetObject()->GetStore()->Create(MAGIC_ATTACHMENT) : 0); if (From) { char *Ptr = 0; ssize_t Len = 0; if (From->Get(&Ptr, &Len)) { Set(Ptr, Len); SetName(From->GetName()); SetMimeType(From->GetMimeType()); SetContentId(From->GetContentId()); SetCharset(From->GetCharset()); } } } Attachment::Attachment(ScribeWnd *App, LDataI *object, const char *Import) : Thing(App) { _New(object); if (Import) ImportFile(Import); } Attachment::~Attachment() { DeleteObj(Msg); if (Owner) Owner->Attachments.Delete(this); } bool Attachment::ImportFile(const char *FileName) { LAutoPtr f(new LFile); if (!f) { LAssert(!"Out of memory"); return false; } LString Mime = ScribeGetFileMimeType(FileName); if (!f->Open(FileName, O_READ)) { LAssert(!"Can't open file."); return false; } char *c = strrchr((char*)FileName, DIR_CHAR); if (c) SetName(c + 1); else SetName(FileName); if (Mime) SetMimeType(Mime); LAutoStreamI s(f.Release()); GetObject()->SetStream(s); return true; } bool Attachment::ImportStream(const char *FileName, const char *MimeType, LAutoStreamI Stream) { if (!FileName || !MimeType || !Stream) { LAssert(!"Parameter error"); return false; } char *c = strrchr((char*)FileName, DIR_CHAR); if (c) SetName(c + 1); else SetName(FileName); SetMimeType(MimeType); GetObject()->SetStream(Stream); return true; } void Attachment::SetOwner(Mail *msg) { Owner = msg; } bool Attachment::CallMethod(const char *MethodName, LVariant *Ret, LArray &Args) { ScribeDomType Fld = StrToDom(MethodName); *Ret = false; switch (Fld) { case SdSave: // Type: (String FileName) { auto Fn = Args.Length() > 0 ? Args[0]->Str() : NULL; if (Fn) *Ret = SaveTo(Fn, true); return true; } default: break; } return Thing::CallMethod(MethodName, Ret, Args); } bool Attachment::GetVariant(const char *Name, LVariant &Value, const char *Array) { ScribeDomType Fld = StrToDom(Name); switch (Fld) { case SdLength: // Type: Int64 { Value = GetSize(); break; } case SdName: // Type: String { Value = GetName(); break; } case SdMimeType: // Type: String { Value = GetMimeType(); break; } case SdContentId: // Type: String { Value = GetContentId(); break; } case SdData: // Type: Binary { LAutoPtr s(GotoObject(_FL)); if (!s) return false; Value.Empty(); Value.Type = GV_BINARY; if ((Value.Value.Binary.Data = new char[Value.Value.Binary.Length = (int)s->GetSize()])) s->Read(Value.Value.Binary.Data, Value.Value.Binary.Length); break; } case SdType: // Type: Int32 { Value = GetObject()->Type(); break; } default: { return false; } } return true; } Thing &Attachment::operator =(Thing &t) { LAssert(0); return *this; } void IncFileIndex(char *FilePath, size_t FilePathLen) { char File[MAX_PATH_LEN]; char Ext[256] = ""; // Get the extension part char *Dot = strrchr(FilePath, '.'); if (Dot) { strcpy_s(Ext, sizeof(Ext), Dot); } // Get the filename part ssize_t FileLen = (ssize_t)strlen(FilePath) - strlen(Ext); memcpy(File, FilePath, FileLen); File[FileLen] = 0; // Seek to start of digits char *Digits = File + strlen(File) - 1; while (IsDigit(*Digits) && Digits > File) { Digits--; } if (!IsDigit(*Digits)) Digits++; // Increment the index int Index = atoi(Digits); sprintf_s(Digits, sizeof(File)-(Digits-File), "%i", Index + 1); // Write the resulting filename sprintf_s(FilePath, FilePathLen, "%s%s", File, Ext); } void Attachment::SetMsg(Mail *m) { Msg = m; } Mail *Attachment::GetMsg() { if (!Msg && IsMailMessage()) { // is an email LAutoStreamI f = GetObject()->GetStream(_FL); if (f) { if ((Msg = new Mail(App))) { Msg->SetWillDirty(false); Msg->App = App; Msg->ParentFile = this; Msg->OnAfterReceive(f); } } } return Msg; } bool Attachment::GetIsResizing() { return IsResizing; } void Attachment::SetIsResizing(bool b) { IsResizing = b; Update(); } bool Attachment::IsMailMessage() { return GetMimeType() && !_stricmp(GetMimeType(), sMimeMessage); } bool Attachment::IsVCalendar() { return GetMimeType() && ( !_stricmp(GetMimeType(), sMimeVCalendar) || !_stricmp(GetMimeType(), sMimeICalendar) ); } bool Attachment::IsVCard() { return GetMimeType() && !_stricmp(GetMimeType(), sMimeVCard); } char *Attachment::GetDropFileName() { if (!DropFileName) DropFileName = MakeFileName(); return DropFileName; } bool Attachment::GetDropFiles(LString::Array &Files) { bool Status = false; if (GetDropFileName()) { char p[MAX_PATH_LEN]; LMakePath(p, sizeof(p), ScribeTempPath(), DropFileName); if (SaveTo(p, true)) { Files.Add(p); Status = true; } } return Status; } LAutoString Attachment::MakeFileName() { auto Name = GetName(); LAutoString CleanName; if (Name) { CleanName.Reset(StripPath(Name)); CleanFileName(CleanName); } else { LArray Ext; char s[256] = "Attachment"; auto MimeType = GetMimeType(); LGetMimeTypeExtensions(MimeType, Ext); if (Ext.Length()) { size_t len = strlen(s); sprintf_s(s+len, sizeof(s)-len, ".%s", Ext[0].Get()); } CleanName.Reset(NewStr(s)); } return CleanName; } void Attachment::OnOpen(LView *Parent, char *Dest) { bool VCal; if (GetMsg()) { // Open the mail message... Msg->DoUI(Owner); } else if ((VCal = IsVCalendar()) || IsVCard()) { // Open the event or contact... Thing *c = App->CreateItem(VCal ? MAGIC_CALENDAR : MAGIC_CONTACT, 0, false); if (c) { LAutoPtr f(GotoObject(_FL)); if (f) { if (c->Import(f, GetMimeType())) { c->DoUI(); } else LgiMsg(Parent, "Failed to parse calendar.", "Error"); } else LgiTrace("%s:%i - Failed to get attachment stream.\n", _FL); } } else // is some generic file { bool IsExe = false; int TnefSizeLimit = 8 << 20; LStream *TnefStream = 0; LArray TnefIndex; int64 AttachPos = -1; LAutoStreamI f = GetObject()->GetStream(_FL); if (f) { IsExe = LIsFileExecutable(GetName(), f, AttachPos = f->GetPos(), f->GetSize()); if (!IsExe && f->GetSize() < TnefSizeLimit) { f->SetPos(AttachPos); if (!TnefReadIndex(f, TnefIndex)) { DeleteObj(TnefStream); } } f.Reset(); } if (IsExe) { LgiMsg(Parent, LLoadString(IDS_ERROR_EXE_FILE), AppName); return; } // Check for TNEF if (TnefStream) { LStringPipe p; for (unsigned n=0; nSize); p.Print("\t%s (%s)\n", TnefIndex[n]->Name, Size); } char *FileList = p.NewStr(); if (Owner && LgiMsg(Parent, LLoadString(IDS_ASK_TNEF_DECODE), AppName, MB_YESNO, FileList) == IDYES) { char *Tmp = ScribeTempPath(); if (Tmp) { for (unsigned i=0; iName); LFile Out; if (Out.Open(s, O_WRITE)) { Out.SetSize(0); if (TnefExtract(TnefStream, &Out, TnefIndex[i])) { Out.Close(); Attachment *NewFile = 0; Owner->AttachFile(NewFile = new Attachment(App, GetObject()->GetStore()->Create(MAGIC_ATTACHMENT), s)); if (NewFile && Owner->GetUI()) { AttachmentList *Lst = Owner->GetUI()->GetAttachments(); if (Lst) { Lst->Insert(NewFile); Lst->ResizeColumnsToContent(); } } } Out.Close(); } } } Owner->Save(); OnDeleteAttachment(Parent, false); } DeleteObj(TnefStream); DeleteArray(FileList); } else { // Open file... LAutoString FileToExecute; char *Tmp = ScribeTempPath(); if (!Tmp) return; // get the file name char FileName[MAX_PATH_LEN]; LAutoString CleanName = MakeFileName(); if (CleanName) { LMakePath(FileName, sizeof(FileName), Tmp, CleanName); while (LFileExists(FileName)) { IncFileIndex(FileName, sizeof(FileName)); } // write the file out if (SaveTo(FileName)) { FileToExecute.Reset(NewStr(FileName)); } } // open the file auto Mime = GetMimeType(); if (FileToExecute) { LAutoString AssociatedApp; LXmlTag *FileTypes = App->GetOptions()->LockTag(OPT_FileTypes, _FL); if (FileTypes) { auto Mime = GetMimeType(); auto FileName = GetName(); for (auto t: FileTypes->Children) { char *mt = t->GetAttr("mime"); char *ext = t->GetAttr("extension"); bool MimeMatch = mt && Mime && !_stricmp(mt, Mime); bool ExtMatch = ext && FileName && MatchStr(ext, FileName); if (MimeMatch || ExtMatch) { AssociatedApp.Reset(TrimStr(t->GetContent())); break; } } App->GetOptions()->Unlock(); } if (AssociatedApp) { const char *s = AssociatedApp; LAutoString Exe(LTokStr(s)); if (Exe) { char Args[MAX_PATH_LEN+100]; if (!strchr(s, '%') || sprintf_s(Args, sizeof(Args), s, FileToExecute.Get()) < 0) { if (sprintf_s(Args, sizeof(Args), "\"%s\"", FileToExecute.Get()) < 0) Args[0] = 0; } if (Args[0] && LExecute(Exe, Args)) { // Successful.. return; } } } if (!LExecute(FileToExecute, 0, Tmp)) { // if the default open fails.. open as text LString AppPath = LGetAppForMimeType(Mime ? Mime : sTextPlain); bool Status = false; if (AppPath) { char *s = strchr(AppPath, '%'); if (s) s[0] = 0; Status = LExecute(AppPath, FileToExecute, Tmp); } if (!Status) { LgiMsg(Parent, "Couldn't open file.", AppName, MB_OK); } } } } } } void Attachment::OnDeleteAttachment(LView *Parent, bool Ask) { if (Owner) { LVariant ConfirmDelete; App->GetOptions()->GetValue(OPT_ConfirmDelete, ConfirmDelete); if (!Ask || !ConfirmDelete.CastInt32() || LgiMsg(GetList(), LLoadString(IDS_DELETE_ASK), AppName, MB_YESNO) == IDYES) { List Sel; LList *p = GetList(); if (p) { p->GetSelection(Sel); } else { Sel.Insert(this); } for (LListItem *i: Sel) { Attachment *a = dynamic_cast(i); if (a) { if (p) { p->Remove(i); } a->Owner->DeleteAttachment(a); } } if (p) { p->Invalidate(); } } } } bool Attachment::SaveTo(char *FileName, bool Quite, LView *Parent) { bool Status = false; if (FileName) { LFile Out; Status = true; if (LFileExists(FileName)) { if (Quite) { return true; } else { Out.Close(); LString Msg = AskOverwriteMsg(FileName); Status = LgiMsg(Parent ? Parent : App, Msg, AppName, MB_YESNO) == IDYES; } } if (!Out.Open(FileName, O_WRITE)) { if (!Quite) LgiMsg(App, LLoadString(IDS_ERROR_CANT_WRITE), AppName, MB_OK, FileName); else LgiTrace("%s:%i - Can't open '%s' for writing (err=0x%x)\n", _FL, FileName, Out.GetError()); Status = false; } if (Status) { Out.SetSize(0); if (GetObject()) { int BufSize = 64 << 10; char *Buf = new char[BufSize]; LStreamI *f = GotoObject(_FL); if (f && Buf) { f->SetPos(0); Out.SetSize(0); int64 MySize = f->GetSize(); int64 s = MySize; while (s > 0) { ssize_t r = (int)MIN(BufSize, s); r = f->Read(Buf, r); if (r > 0) { Out.Write(Buf, r); s -= r; } else break; } int64 OutPos = Out.GetPos(); if (OutPos < MySize) { // Error writing to disk... Out.Close(); FileDev->Delete(FileName, false); LAssert(!"Failed to write whole attachment to disk."); Status = false; } } else { LgiTrace("%s:%i - GotoObject failed.\n", _FL); Status = false; } DeleteObj(f); DeleteArray(Buf); } /* else if (Data) { int Written = Out.Write(Data, Size); Status = Written == Size; } */ } } return Status; } void Attachment::OnSaveAs(LView *Parent) { auto Name = GetName(); char *n = StripPath(Name); if (!n) { n = NewStr("untitled"); } if (n) { CleanFileName(n); - LFileSelect Select; + auto Select = new LFileSelect(Parent); - Select.Parent(Parent); - Select.Type("All files", LGI_ALL_FILES); - Select.Name(n); + Select->Type("All files", LGI_ALL_FILES); + Select->Name(n); List Files; if (LListItem::Parent) { LListItem::Parent->GetSelection(Files); } else { Files.Insert(this); } if (Files.Length() > 0) { - bool Status = false; - - if (Files.Length() > 1) - { - // multiple files, ask which directory to write to - Status = Select.OpenFolder(); - } - else - { - // single file, ask for filename and path - Status = Select.Save(); - } - - if (Status) + auto DoSave = [&](LFileSelect *Select) { char Dir[MAX_PATH_LEN]; - strcpy_s(Dir, sizeof(Dir), Select.Name()); + strcpy_s(Dir, sizeof(Dir), Select->Name()); if (Files.Length() > 1) { // Loop through all the files and write them to that directory for (LListItem *i: Files) { Attachment *a = dynamic_cast(i); if (a) { char Path[MAX_PATH_LEN]; auto d = StripPath(a->GetName()); if (d) { sprintf_s(Path, sizeof(Path), "%s%s%s", Dir, DIR_STR, d); a->SaveTo(Path); DeleteArray(d); } } } } else { // Write the file Attachment *a = dynamic_cast(Files[0]); if (a) { a->SaveTo(Dir, false, Parent); } } + }; + + if (Files.Length() > 1) + { + // multiple files, ask which directory to write to + Select->OpenFolder([&](auto dlg, auto status) + { + if (status) + DoSave(dlg); + delete dlg; + }); + } + else + { + // single file, ask for filename and path + Select->Save([&](auto dlg, auto status) + { + if (status) + DoSave(dlg); + delete dlg; + }); } } DeleteArray(n); } } bool Attachment::OnKey(LKey &k) { if (k.vkey == LK_RETURN && k.IsChar) { if (k.Down()) { OnOpen(GetList()); } return true; } return false; } void Attachment::OnMouseClick(LMouse &m) { auto mt = GetMimeType(); bool OpenAttachment = false; if (m.IsContextMenu()) { // open the right click menu LSubMenu RClick; LString MimeType = GetMimeType(); RClick.AppendItem(LLoadString(IDS_ADD_TO_CAL), IDM_ADD_TO_CAL, IsVCalendar()); RClick.AppendSeparator(); RClick.AppendItem(LLoadString(IDS_OPEN), IDM_OPEN, true); RClick.AppendItem(LLoadString(IDS_SAVEAS), IDM_SAVEAS, true); RClick.AppendItem(LLoadString(IDS_DELETE), IDM_DELETE, true); RClick.AppendSeparator(); RClick.AppendItem(LLoadString(IDS_RESIZE), IDM_RESIZE, MimeType.Lower().Find("image/") >= 0); if (Parent->GetMouse(m, true)) { switch (RClick.Float(Parent, m.x, m.y)) { case IDM_OPEN: { OpenAttachment = true; break; } case IDM_DELETE: { OnDeleteAttachment(Parent, true); break; } case IDM_SAVEAS: { OnSaveAs(Parent); break; } case IDM_ADD_TO_CAL: { ScribeFolder *Cal = App->GetFolder(FOLDER_CALENDAR); if (!Cal) LgiMsg(Parent, "Can't find the calendar folder.", AppName); else { Thing *c = App->CreateItem(MAGIC_CALENDAR, 0, false); if (c) { LAutoPtr f(GotoObject(_FL)); if (f) { if (c->Import(c->AutoCast(f), mt)) { c->Save(Cal); c->DoUI(); } else LgiTrace("%s:%i - Failed to import cal stream.\n", _FL); } else LgiTrace("%s:%i - Failed to get attachment stream.\n", _FL); } else LgiTrace("%s:%i - Failed to create calendar obj.\n", _FL); } break; } case IDM_RESIZE: { List Sel; if (GetList() && GetList()->GetSelection(Sel)) { for (auto a: Sel) { if (!a->Owner) { LgiTrace("%s:%i - No owner?", _FL); break; } a->Owner->ResizeImage(a); } GetList()->ResizeColumnsToContent(); } break; } } } } else if (m.Double()) { OpenAttachment = true; } if (OpenAttachment) { // open the attachment OnOpen(Parent); } } bool Attachment::GetFormats(LDragFormats &Formats) { Formats.SupportsFileDrops(); return Formats.Length() > 0; } bool Attachment::GetData(LArray &Data) { int SetCount = 0; for (unsigned idx=0; idx Att; if (Parent->GetSelection(Att)) { LString::Array Files; for (auto a: Att) { // char *Nm = a->GetName(); if (!a->DropSourceFile || !LFileExists(a->DropSourceFile)) { a->DropSourceFile.Reset(); char p[MAX_PATH_LEN]; LAutoString Clean = a->MakeFileName(); LMakePath(p, sizeof(p), ScribeTempPath(), Clean); char Ext[256]; char *d = strrchr(p, '.'); if (!d) d = p + strlen(p); strcpy_s(Ext, sizeof(Ext), d); for (int i=1; LFileExists(p); i++) { sprintf_s(d, sizeof(p)-(d-p), "_%i%s", i, Ext); } if (a->SaveTo(p, true)) { a->DropSourceFile.Reset(NewStr(p)); } } if (a->DropSourceFile) { Files.Add(a->DropSourceFile.Get()); } else LAssert(0); } if (Files.First()) { LMouse m; App->GetMouse(m, true); if (CreateFileDrop(&dd, m, Files)) { SetCount++; } } } } } return SetCount > 0; } LStreamI *Attachment::GotoObject(const char *file, int line) { if (!GetObject()) return 0; LAutoStreamI s = GetObject()->GetStream(file, line); return s.Release(); } int Attachment::Sizeof() { return 0; } bool Attachment::Serialize(LFile &f, bool Write) { /* ulong Magic = MAGIC_ATTACHMENT; LView *Parent = Window; if (Owner && Owner->GetUi()) { Parent = Owner->GetUi(); } if (Write) { f << Magic; f << Content; // Check we have the data to write out, as it will effect the // size we write out before the data bool DataOk = false; LFile In; if (Data) { DataOk = true; } else if (ImportName) { // Check we can open the file... while (!In.Open(ImportName, O_READ)) { char Msg[256]; sprintf_s(Msg, sizeof(Msg), LLoadString(IDS_ERROR_CANT_READ), ImportName); LAlert Dlg( Parent, AppName, Msg, LLoadString(IDS_RETRY), LLoadString(IDS_CANCEL)); int Result = Dlg.DoModal(); if (Result == 2) { break; } } DataOk = In.IsOpen(); } else { // We're skipping the data already on disk DataOk = true; } if (!DataOk) { // No point continuing return false; } f << Size; WriteStr(f, Name); if (Data) { // write the file itself f.Write(Data, Size); } else if (ImportName) { // import from the file uint64 Last = LCurrentTime(); LProgressDlg *Prog = 0; int BufSize = 64 << 10; uchar *Buf = new uchar[BufSize]; if (Buf) { int s = Size; while (s > 0 && f.GetStatus()) { int r = min(s, BufSize); r = In.Read(Buf, r); f.Write(Buf, r); s -= r; uint64 Now = LCurrentTime(); if (Prog) { if (Now - Last > 300) { Prog->Value((Size-s) >> 10); LYield(); Last = Now; if (Prog->Cancel()) { DeleteArray(Buf); DeleteObj(Prog); return false; } } } else if (Now - Last > 1000) { Prog = new LProgressDlg(Parent); if (Prog) { Prog->SetDescription("Importing file..."); Prog->SetLimits(0, Size >> 10); Prog->SetType("K"); } Last = Now; } } DeleteArray(ImportName); DeleteObj(Prog); } DeleteArray(Buf); } else { // skip over data on hard disk f.Seek(Size, SEEK_CUR); } // new style fields if (MimeType) { WriteStrField(FIELD_MIME_TYPE, MimeType); } if (ContentId) { WriteStrField(FIELD_CONTENT_ID, ContentId); } } else { f >> Magic; if (Magic == MAGIC_ATTACHMENT) // The versions before v1.25 didn't // set this correctly, but that is so old // now, I've removed the hack to allow // the attachment in. { f >> Content; f >> Size; DeleteArray(Name); Name = ReadStr(f PassDebugArgs); // Skip over the data DeleteArray(Data); int StartPos = f.GetPos(); f.Seek(Size, SEEK_CUR); int ExtraPos = f.GetPos(); // read list of new-style fields bool Done = false; bool Eob = false; while ( !(Eob = Store->EndOfObj(f)) && !Done) { short FieldId = 0; ulong FieldSize = 0; f >> FieldId; switch (FieldId) { ReadStrField(FIELD_MIME_TYPE, MimeType); ReadStrField(FIELD_CONTENT_ID, ContentId); default: { // Error: unknown chunk SetDirty(); Done = true; break; } } } LFormatSize(SizeStr, Size); } else return false; } return f.GetStatus(); */ return false; } char *GetSubField(char *s, char *Field, bool AllowConversion = true) { char *Status = 0; if (s && Field) { s = strchr(s, ';'); if (s) { s++; size_t FieldLen = strlen(Field); char White[] = " \t\r\n"; while (*s) { // Skip leading whitespace while (*s && (strchr(White, *s) || *s == ';')) s++; // Parse field name if (IsAlpha(*s)) { char *f = s; while (*s && (IsAlpha(*s) || *s == '-')) s++; bool HasField = ((s-f) == FieldLen) && (_strnicmp(Field, f, FieldLen) == 0); while (*s && strchr(White, *s)) s++; if (*s == '=') { s++; while (*s && strchr(White, *s)) s++; if (*s && strchr("\'\"", *s)) { // Quote Delimited Field char d = *s++; char *e = strchr(s, d); if (e) { if (HasField) { if (AllowConversion) { Status = DecodeRfc2047(NewStr(s, e-s)); } else { Status = NewStr(s, e-s); } break; } s = e + 1; } else break; } else { // Delimited Field char *e = s; while (*e && *e != ';') e++; if (HasField) { Status = DecodeRfc2047(NewStr(s, e-s)); break; } s = e; } } else break; } else break; } } } return Status; } const char *Attachment::GetText(int i) { if (FieldArray.Length()) { return "This is an attachment!!!!!"; } else { switch (i) { case 0: { auto Nm = GetName(); if (IsResizing) { Buf.Printf("%s (%s)", Nm, LLoadString(IDS_RESIZING)); return Buf; } return Nm; } case 1: { static char s[64]; LFormatSize(s, sizeof(s), GetSize()); return s; } case 2: { return GetMimeType(); } case 3: { return GetContentId(); } } } return 0; } bool Attachment::Get(char **ptr, ssize_t *size) { if (!ptr || !size || *size <= 0) return false; LStreamI *f = GotoObject(_FL); if (!f) return false; *size = f->GetSize(); *ptr = new char[*size+1]; if (*ptr) { auto r = f->Read(*ptr, *size); (*ptr)[r] = 0; } DeleteObj(f); return true; } bool Attachment::Set(LAutoStreamI Stream) { if (!GetObject()) { LAssert(0); return false; } if (!GetObject()->SetStream(Stream)) { LAssert(0); return false; } return true; } bool Attachment::Set(char *ptr, ssize_t size) { LAutoStreamI s(new LMemStream(ptr, size)); return Set(s); } diff --git a/Code/ScribeContact.cpp b/Code/ScribeContact.cpp --- a/Code/ScribeContact.cpp +++ b/Code/ScribeContact.cpp @@ -1,2392 +1,2395 @@ /* ** FILE: ScribeContact.cpp ** AUTHOR: Matthew Allen ** DATE: 11/11/98 ** DESCRIPTION: Scribe contact object and UI ** ** Copyright (C) 1998, Matthew Allen ** fret@memecode.com */ #include #include #include #include #include "Scribe.h" #include "ScribePageSetup.h" #include "lgi/common/Map.h" #include "lgi/common/vCard-vCal.h" #include "lgi/common/Combo.h" #include "lgi/common/Edit.h" #include "lgi/common/TabView.h" #include "lgi/common/TableLayout.h" #include "lgi/common/DisplayString.h" #include "lgi/common/Button.h" #include "lgi/common/GdcTools.h" #include "lgi/common/DropFiles.h" #include "lgi/common/Http.h" #include "lgi/common/LgiRes.h" #include "lgi/common/ClipBoard.h" #include "lgi/common/List.h" #include "lgi/common/Json.h" #include "lgi/common/CssTools.h" #include "lgi/common/FileSelect.h" #include "PrintContext.h" #include "resdefs.h" #include "resource.h" bool ConvertImageToContactSize(LAutoPtr &Img, int Px, LSurface *Raw) { if (!Raw) { LAssert(!"No raw image?"); return false; } if (!Img.Reset(new LMemDC(Px, Px, Raw->GetColourSpace()))) { LAssert(!"Failed to create image?"); return false; } LRect Src; if (Raw->X() > Raw->Y()) { // Wider than high Src.ZOff(Raw->Y()-1, Raw->Y()-1); Src.Offset((Raw->X()-Raw->Y())>>1, 0); } else if (Raw->X() < Raw->Y()) { Src.ZOff(Raw->X()-1, Raw->X()-1); Src.Offset(0, (Raw->Y()-Raw->X())>>1); } else { Src = Raw->Bounds(); } ResampleDC(Img, Raw, &Src); return true; } class ContactPriv { public: Contact *Item; char *Scratch; List Plugins; LAutoPtr Image; LString ImagePath; LString DateCache; LAutoPtr CustomCache; ContactPriv(Contact *c) : Item(c) { Scratch = 0; } ~ContactPriv() { DeleteArray(Scratch); Plugins.DeleteArrays(); } LJson *GetJson() { auto Obj = Item->GetObject(); if (!Obj) return NULL; if (!CustomCache) { auto json = Obj->GetStr(FIELD_CONTACT_JSON); CustomCache.Reset(new LJson(json)); } return CustomCache; } }; ItemFieldDef ContactFieldDefs[] = { // Standard fields {"Title", SdTitle, GV_STRING, FIELD_TITLE, IDC_TITLE, OPT_Title}, {"First", SdFirst, GV_STRING, FIELD_FIRST_NAME, IDC_FIRST, OPT_First}, {"Last", SdSurname, GV_STRING, FIELD_LAST_NAME, IDC_LAST, OPT_Last}, {"Email", SdEmail, GV_STRING, FIELD_EMAIL, IDC_EMAIL, OPT_Email}, {"Nickname", SdNickname, GV_STRING, FIELD_NICK, IDC_NICK, OPT_Nick}, {"Spouse", SdSpouse, GV_STRING, FIELD_SPOUSE, IDC_SPOUSE, OPT_Spouse}, {"Notes", SdNotes, GV_STRING, FIELD_NOTE, IDC_NOTE, OPT_Note}, {"Uid", SdUid, GV_INT32, FIELD_UID, -1, OPT_Uid}, {"TimeZone", SdTimeZone, GV_STRING, FIELD_TIMEZONE, IDC_TIMEZONE, OPT_TimeZone}, // Home fields {"Home Street", SdHomeStreet, GV_STRING, FIELD_HOME_STREET, IDC_HOME_STREET, OPT_HomeStreet}, {"Home Suburb", SdHomeSuburb, GV_STRING, FIELD_HOME_SUBURB, IDC_HOME_SUBURB, OPT_HomeSuburb}, {"Home Postcode", SdHomePostcode, GV_STRING, FIELD_HOME_POSTCODE, IDC_HOME_POSTCODE, OPT_HomePostcode}, {"Home State", SdHomeState, GV_STRING, FIELD_HOME_STATE, IDC_HOME_STATE, OPT_HomeState}, {"Home Country", SdHomeCountry, GV_STRING, FIELD_HOME_COUNTRY, IDC_HOME_COUNTRY, OPT_HomeCountry}, {"Home Phone", SdHomePhone, GV_STRING, FIELD_HOME_PHONE, IDC_HOME_PHONE, OPT_HomePhone}, {"Home Mobile", SdHomeMobile, GV_STRING, FIELD_HOME_MOBILE, IDC_HOME_MOBILE, OPT_HomeMobile}, {"Home IM#", SdHomeIM, GV_STRING, FIELD_HOME_IM, IDC_HOME_IM, OPT_HomeIM}, {"Home Fax", SdHomeFax, GV_STRING, FIELD_HOME_FAX, IDC_HOME_FAX, OPT_HomeFax}, {"Home Webpage", SdHomeWebpage, GV_STRING, FIELD_HOME_WEBPAGE, IDC_HOME_WEBPAGE, OPT_HomeWebPage}, // Work fields {"Work Street", SdWorkStreet, GV_STRING, FIELD_WORK_STREET, IDC_WORK_STREET, OPT_WorkStreet}, {"Work Suburb", SdWorkSuburb, GV_STRING, FIELD_WORK_SUBURB, IDC_WORK_SUBURB, OPT_WorkSuburb}, {"Work Postcode", SdWorkPostcode, GV_STRING, FIELD_WORK_POSTCODE, IDC_WORK_POSTCODE, OPT_WorkPostcode}, {"Work State", SdWorkState, GV_STRING, FIELD_WORK_STATE, IDC_WORK_STATE, OPT_WorkState}, {"Work Country", SdWorkCountry, GV_STRING, FIELD_WORK_COUNTRY, IDC_WORK_COUNTRY, OPT_WorkCountry}, {"Work Phone", SdWorkPhone, GV_STRING, FIELD_WORK_PHONE, IDC_WORK_PHONE, OPT_WorkPhone}, {"Work Mobile", SdWorkMobile, GV_STRING, FIELD_WORK_MOBILE, IDC_WORK_MOBILE, OPT_WorkMobile}, {"Work IM#", SdWorkIM, GV_STRING, FIELD_WORK_IM, IDC_WORK_IM, OPT_WorkIM}, {"Work Fax", SdWorkFax, GV_STRING, FIELD_WORK_FAX, IDC_WORK_FAX, OPT_WorkFax}, {"Company", SdCompany, GV_STRING, FIELD_COMPANY, IDC_COMPANY, OPT_Company}, {"Work Webpage", SdWorkWebpage, GV_STRING, FIELD_WORK_WEBPAGE, IDC_WORK_WEBPAGE, OPT_WorkWebPage}, {"CustomFields", SdCustomFields, GV_STRING, FIELD_CONTACT_JSON, IDC_CUSTOM_FIELDS, OPT_CustomFields}, {"DateModified", SdDateModified, GV_DATETIME, FIELD_DATE_MODIFIED, -1, NULL}, {0} }; #define ForAllContactFields(var) ItemFieldDef *var = 0; for (var = ContactFieldDefs; var->Option && var->FieldId; var++) #define MacroAllFields(Macro) \ Macro(FIELD_TITLE, OPT_Title); \ Macro(FIELD_FIRST_NAME, OPT_First); \ Macro(FIELD_LAST_NAME, OPT_Last); \ Macro(FIELD_EMAIL, OPT_Email); \ Macro(FIELD_NICK, OPT_Nick); \ Macro(FIELD_SPOUSE, OPT_Spouse); \ Macro(FIELD_NOTE, OPT_Note); \ Macro(FIELD_TIMEZONE, OPT_TimeZone); \ Macro(FIELD_HOME_STREET, OPT_HomeStreet); \ Macro(FIELD_HOME_SUBURB, OPT_HomeSuburb); \ Macro(FIELD_HOME_POSTCODE, OPT_HomePostcode); \ Macro(FIELD_HOME_STATE, OPT_HomeState); \ Macro(FIELD_HOME_COUNTRY, OPT_HomeCountry); \ Macro(FIELD_HOME_PHONE, OPT_HomePhone); \ Macro(FIELD_HOME_MOBILE, OPT_HomeMobile); \ Macro(FIELD_HOME_IM, OPT_HomeIM); \ Macro(FIELD_HOME_FAX, OPT_HomeFax); \ Macro(FIELD_HOME_WEBPAGE, OPT_HomeWebPage); \ Macro(FIELD_WORK_STREET, OPT_WorkStreet); \ Macro(FIELD_WORK_SUBURB, OPT_WorkSuburb); \ Macro(FIELD_WORK_POSTCODE, OPT_WorkPostcode); \ Macro(FIELD_WORK_STATE, OPT_WorkState); \ Macro(FIELD_WORK_COUNTRY, OPT_WorkCountry); \ Macro(FIELD_WORK_PHONE, OPT_WorkPhone); \ Macro(FIELD_WORK_MOBILE, OPT_WorkMobile); \ Macro(FIELD_WORK_IM, OPT_WorkIM); \ Macro(FIELD_WORK_FAX, OPT_WorkFax); \ Macro(FIELD_WORK_WEBPAGE, OPT_WorkWebPage); \ Macro(FIELD_COMPANY, OPT_Company); \ Macro(FIELD_CONTACT_JSON, OPT_CustomFields); ////////////////////////////////////////////////////////////////////////////// const char *WinFmtUrl = "UniformResourceLocatorW"; const char *FmtUrlList = "text/uri-list"; #include "lgi/common/SkinEngine.h" class LContactBtn : public LView { bool Down; bool Over; public: LContactBtn(int id) { SetId(id); Over = false; Down = false; LRect r(0, 0, 23, 23); SetPos(r); } ~LContactBtn() { } void OnMouseClick(LMouse &m) { Capture(Down = m.Down()); Invalidate(); if (m.Down()) Over = true; else if (Over) SendNotify(); } void OnMouseMove(LMouse &m) { if (IsCapturing()) { bool o = GetClient().Overlap(m.x, m.y); if (o ^ Over) { Down = Over = o; Invalidate(); } } } void OnPaint(LSurface *pDC) { LRect c = GetClient(); if (LApp::SkinEngine && c.X() && c.Y()) { LCssTools Tools(this); LColour Base = Tools.GetBack(); LMemDC Mem(c.X(), c.Y(), System32BitColourSpace); Mem.Colour(0, 32); Mem.Rectangle(); LRect r(0, 0, X()-1, Y()-1); LApp::SkinEngine->DrawBtn(&Mem, r, Base, Down, true); int Dot = 2; int Gap = 3; int Sz = (Dot * 3) + (Gap * 2); int x = ((X()-Sz) >> 1) + Down; int y = (Y() / 2) + Down; Mem.Colour(L_TEXT); for (int i=0; i<3; i++) { Mem.Rectangle(x, y, x + 1, y + 1); x += Dot + Gap; } pDC->Op(GDC_ALPHA); pDC->Blt(0, 0, &Mem); } } }; class LContactImage : public LView, public ResObject, public LDragDropTarget { friend class ContactUi; constexpr static int M_IMAGE_LOADED = M_USER + 100; ScribeWnd *App = NULL; Contact *c = NULL; LContactBtn *Btn = NULL; bool IsNoFace = false; // This is the uncompressed bitmap which is not saved. LAutoPtr Img; // This is a compressed version that is saved. LVariant CompressedImg; class UriLoader : public LThread, public LCancel { LContactImage *Ci; LString Uri; public: bool Status; LMemStream Data; LString Error; UriLoader(LContactImage *ci, LString uri) : LThread("LContactImage"), Data(1024) { Ci = ci; Uri = uri; Status = false; Run(); } ~UriLoader() { while (!IsExited()) LSleep(1); } int Main() { Status = LgiGetUri(this, &Data, &Error, Uri, NULL, NULL); Ci->PostEvent(M_IMAGE_LOADED); return 0; } }; LAutoPtr Worker; public: LContactImage() : ResObject(Res_Custom) { SetTabStop(true); } LVariant &GetImage() { if (!IsNoFace && CompressedImg.Type != GV_BINARY) { LMemStream m(4 << 10); if (GdcD->Save(&m, Img, "icon.jpg")) { CompressedImg.Type = GV_BINARY; auto &Bin = CompressedImg.Value.Binary; Bin.Length = (ssize_t) m.GetSize(); Bin.Data = new char[Bin.Length]; m.SetPos(0); m.Read(Bin.Data, Bin.Length); } } return CompressedImg; } bool OnKey(LKey &k) { switch (k.vkey) { case 'v': case 'V': { if (k.Ctrl()) { if (k.Down()) { CompressedImg.Empty(); LClipBoard c(this); IsNoFace = false; c.Bitmap([this](auto bmp, auto str) { SetImage(bmp); }); } return true; } break; } case LK_DELETE: { // Delete the image... CompressedImg.Empty(); SetDefaultNoFace(); break; } } return false; } void OnMouseClick(LMouse &m) { if (m.Down()) Focus(true); } void SetImage(LAutoPtr Raw) { if (Raw && (Raw->X() > 160 || Raw->Y() > 160)) ConvertImageToContactSize(Img, 160, Raw); else Img = Raw; Invalidate(); } void OnCreate() { SetWindow(this); } bool OnLayout(LViewLayoutInfo &Inf) { if (!Inf.Width.Min) Inf.Width.Min = Inf.Width.Max = 160; else Inf.Height.Min = Inf.Height.Max = 160; return true; } void OnPaint(LSurface *pDC) { LRegion rgn(GetClient()); if (Img) { pDC->Blt(0, 0, Img); LRect Bnds = Img->Bounds(); rgn.Subtract(&Bnds); } for (int i=0; iColour(L_LOW); pDC->Rectangle(rgn[i]); } if (Btn) { LRect c = GetClient(); c.Inset(6, 6); LRect p = Btn->GetPos(); p.Offset(c.x2 - p.X() - p.x1 + 1, c.y1 - p.y1); Btn->SetPos(p); } if (Focus()) { auto c = GetClient(); PatternBox(pDC, LRect(c.x1, c.y1, c.x1+1, c.y2)); PatternBox(pDC, LRect(c.x2-1, c.y1, c.x2, c.y2)); PatternBox(pDC, LRect(c.x1+2, c.y1, c.x2-2, c.y1+1)); PatternBox(pDC, LRect(c.x1+2, c.y2-1, c.x2-2, c.y2)); } } void SetDefaultNoFace() { if (App) { LVariant NoFace; if (App->GetValue("NoContact.Image[160]", NoFace)) { if (NoFace.Str()) { IsNoFace = Img.Reset(GdcD->Load(NoFace.Str())); Invalidate(); } } } } void SetContact(Contact *contact) { c = contact; if (c) { App = c->App; SetDefaultNoFace(); if (!Btn) { Btn = new LContactBtn(100); } if (Btn) { #if 0 Btn->GetCss(true)->NoPaintColor(LCss::ColorDef(LCss::ColorRgb, Rgb32(0xcb,0xcb,0xcb))); #else Btn->GetCss(true)->NoPaintColor(LCss::ColorDef(LCss::ColorTransparent)); #endif AddView(Btn); } } } bool SetImage(const LVariant &v, const char *FileHint = NULL) { if (v.Type != GV_BINARY) return false; CompressedImg = v; LMemStream mem( CompressedImg.Value.Binary.Data, CompressedImg.Value.Binary.Length, false); LAutoPtr i(GdcD->Load(&mem, FileHint)); if (!i) { CompressedImg.Empty(); return false; } SetImage(i); IsNoFace = false; return true; } bool Load(const char *File) { LFile f; if (!f.Open(File, O_READ)) { LgiMsg(this, "Couldn't open image file.", AppName, MB_OK); return false; } int Sz = (int)f.GetSize(); LAutoPtr p(new uint8_t[Sz]); if (!p) { LgiMsg(this, "Couldn't alloc mem for image.", AppName, MB_OK); return false; } ssize_t rd = f.Read(p, Sz); if (rd <= 0) { LgiMsg(this, "Couldn't read from image file.", AppName, MB_OK); return false; } LVariant v; v.SetBinary(rd, p.Release(), true); if (!SetImage(v, File)) { LgiMsg(this, "Couldn't decompress image.", AppName, MB_OK); return false; } IsNoFace = false; return true; } LMessage::Param OnEvent(LMessage *Msg) { if (Msg->Msg() == M_IMAGE_LOADED) { if (Worker && Worker->Status) { LAutoPtr pDC(GdcD->Load(&Worker->Data, NULL)); if (pDC) { IsNoFace = false; SetImage(pDC); } } Worker.Reset(); return 0; } return LView::OnEvent(Msg); } int OnNotify(LViewI *Ctrl, LNotification n) { if (Ctrl->GetId() == 100) { - LFileSelect s; - s.Parent(this); - if (s.Open()) - Load(s.Name()); + auto s = new LFileSelect(this); + s->Open([this](auto dlg, auto status) + { + if (status) + Load(dlg->Name()); + delete dlg; + }); } return 0; } int WillAccept(LDragFormats &Formats, LPoint Pt, int KeyState) { if (Formats.HasFormat(LGI_FileDropFormat)) Formats.SupportsFileDrops(); else { #if WINDOWS Formats.Supports(WinFmtUrl); #endif Formats.Supports(FmtUrlList); } return Formats.GetSupported().Length() ? DROPEFFECT_COPY : DROPEFFECT_NONE; } int OnDrop(LArray &Data, LPoint Pt, int KeyState) { for (unsigned i=0; i 0) { Load(files[0]); return DROPEFFECT_COPY; } } else if (dd.IsFormat(FmtUrlList) || dd.IsFormat(WinFmtUrl)) { auto &v = dd.Data[0]; if (v.Type == GV_BINARY) { LString uri = (char16*)v.Value.Binary.Data; if (uri) Worker.Reset(new UriLoader(this, uri)); } else if (v.Type == GV_STRING) { Worker.Reset(new UriLoader(this, v.Str())); } else LAssert(!"Unexpected type."); } } return DROPEFFECT_NONE; } }; class LContactImageFactory : public LViewFactory { LView *NewView(const char *Class, LRect *Pos, const char *Text) { if (Class && !_stricmp(Class, "LContactImage")) return new LContactImage; return NULL; } } ContactImageFactory; ////////////////////////////////////////////////////////////////////////////// class LFieldItem : public LListItem { public: LString Field, Value; const char *GetText(int i) { switch (i) { case 0: return Field; case 1: return Value; } return NULL; } bool SetText(const char *s, int i=0) { switch (i) { case 0: Field = s; break; case 1: Value = s; break; default: return false; } LListItem::SetText(s, i); auto Lst = GetList(); Lst->ResizeColumnsToContent(); Lst->OnNotify(Lst, LNotifyItemChange); return true; } void OnMouseClick(LMouse &m) { if (m.Down() && m.Double()) { int Col = GetList()->ColumnAtX(m.x); if (Col >= 0 && Col <= 1) EditLabel(Col); } } }; class LFieldEditor : public LList { LString u; LAutoWString w; public: LFieldEditor() : LList(IDC_STATIC) { SetObjectName(Res_Custom); DrawGridLines(true); AllowEditLabels(true); SetNotify(this); AddColumn("Field", 110); AddColumn("Value", 110); OnChange(); } void OnChange() { LArray a; GetAll(a); for (auto i: a) if (!i->Field && !i->Value) return; Insert(new LFieldItem); } int OnNotify(LViewI *Ctrl, LNotification n) { if (Ctrl->GetId() == GetId()) { switch (Flags) { case LNotifyItemChange: { OnChange(); break; } case LNotifyDeleteKey: { LArray a; GetSelection(a); a.DeleteObjects(); OnChange(); break; } } } return LList::OnNotify(Ctrl, n); } bool NameW(const char16 *n) { LAutoString a(WideToUtf8(n)); return Name(a); } const char16 *NameW() { w.Reset(Utf8ToWide(Name())); return w; } bool Name(const char *n) { Empty(); LJson j(n); LArray keys = j.GetKeys(); for (auto k: keys) { LFieldItem *i = new LFieldItem; i->Field = k; i->Value = j.Get(k); Insert(i); } OnChange(); // LgiTrace("SetText: %s\n", n); return true; } const char *Name() { LJson j; LArray items; GetAll(items); for (auto i: items) { if (i->Field) j.Set(i->Field, i->Value); } u = j.GetJson(); // LgiTrace("GetText: %s\n", u.Get()); return u; } }; class LFieldEditorFactory : public LViewFactory { LView *NewView(const char *Class, LRect *Pos, const char *Text) { if (Class && !_stricmp(Class, "LFieldEditor")) return new LFieldEditor; return NULL; } } FieldEditorFactory; ////////////////////////////////////////////////////////////////////////////// List Contact::Everyone; LHashTbl, int> Contact::PropMap; Contact::Contact(ScribeWnd *app, LDataI *object) : Thing(app, object) { if (!PropMap.Length()) { PropMap.Add("Type", FIELD_TYPE); ForAllContactFields(Fld) { LAssert(Fld->FieldId > 0 && Fld->FieldId < FIELD_MAX); // LgiTrace("AddProp %i, %s\n", Fld->FieldId, Fld->Option); PropMap.Add(Fld->Option, Fld->FieldId); } } DefaultObject(object); d = new ContactPriv(this); Everyone.Insert(this); } Contact::~Contact() { Everyone.Delete(this); DeleteObj(Ui); DeleteObj(d); } Contact *Contact::LookupEmail(const char *Email) { if (!Email) return NULL; for (auto c : Everyone) if (c->HasEmail(Email)) return c; return NULL; } bool Contact::Get(const char *Opt, int &Value) { if (GetObject()) { int Id = PropMap.Find(Opt); if (Id) { int i = (int)GetObject()->GetInt(Id); if (i >= 0) { Value = i; return true; } } } return false; } bool Contact::Get(const char *Opt, const char *&Value) { if (GetObject()) { int Id = PropMap.Find(Opt); if (Id) { Value = GetObject()->GetStr(Id); return Value != 0; } } return false; } bool Contact::Set(const char *Opt, int Value) { if (GetObject()) { int Id = PropMap.Find(Opt); if (Id) { return GetObject()->SetInt(Id, Value) > Store3Error; } } return false; } bool Contact::Set(const char *Opt, const char *Value) { auto o = GetObject(); if (!o) return false; auto Id = PropMap.Find(Opt); if (Id <= 0 || Id >= FIELD_MAX) { LAssert(!"Invalid ID."); return false; } return o->SetStr(Id, Value) > Store3Error; } int Contact::GetAddrCount() { if (!GetObject()) return 0; auto DefEmail = GetObject()->GetStr(FIELD_EMAIL); auto AltEmail = GetObject()->GetStr(FIELD_ALT_EMAIL); int c = ValidStr(DefEmail) ? 1 : 0; if (AltEmail) { c++; for (auto s = AltEmail; s && *s; s++) { if (*s == ',') c++; } } return c; } LString::Array Contact::GetEmails() { LString::Array e; const char *s; if (Get(OPT_Email, s) && s) e.New() = s; if (GetObject()) { auto alt = LString(GetObject()->GetStr(FIELD_ALT_EMAIL)).Split(","); if (alt.Length()) e += alt; } return e; } bool Contact::HasEmail(LString email) { auto Emails = GetEmails(); for (auto e: Emails) if (e.Equals(email)) return true; return false; } LString Contact::GetAddrAt(int i) { if (i == 0) { const char *e; if (Get(OPT_Email, e)) return e; } else if (i > 0) { auto t = LString(GetObject()->GetStr(FIELD_ALT_EMAIL)).SplitDelimit(","); if (i <= (int)t.Length()) return t[i-1]; } return NULL; } bool Contact::SetVariant(const char *Name, LVariant &Value, const char *Array) { auto Obj = GetObject(); if (!Name || !Obj) return false; // Check normal fields.. int Id = PropMap.Find(Name); if (Id) { // Pre-defined field auto r = Obj->SetStr(Id, Value.CastString()); if (r > Store3Error) { SetDirty(); return true; } } else { // Custom field... auto j = d->GetJson(); if (!j) return false; if (!j->Set(Name, Value.CastString())) return false; if (!Obj->SetStr(FIELD_CONTACT_JSON, j->GetJson())) return false; SetDirty(); return true; } return false; } bool Contact::GetVariant(const char *Name, LVariant &Value, const char *Array) { // Check scribe level fields... ScribeDomType Fld = StrToDom(Name); switch (Fld) { case SdScribe: // Type: ScribeWnd { Value = (LDom*)App; return true; } case SdFolder: // Type: ScribeFolder { ScribeFolder *f = GetFolder(); if (!f) return false; Value = (LDom*)f; return true; } default: break; } auto Obj = GetObject(); if (!Obj) return false; switch (Fld) { case SdEmail: // Type: String[] { if (!Value.SetList()) return false; int c = GetAddrCount(); for (int i=0; iInsert(new LVariant(e)); } return true; } case SdType: // Type: Int32 { Value = Obj->Type(); return true; } case SdGroups: // Type: String[] { LHashTbl,bool> Emails; for (int i=0; i Grps; auto Srcs = App->GetThingSources(MAGIC_GROUP); for (auto g: Srcs) { for (auto t: g->Items) { ContactGroup *Grp = t->IsGroup(); if (Grp) { List Addr; if (Grp->GetAddresses(Addr)) { for (auto a: Addr) { if (Emails.Find(a)) { auto Name = Grp->GetFieldText(FIELD_GROUP_NAME); if (Name) { Grps.Insert(new LVariant(Name)); } } } Addr.DeleteArrays(); } } } } Value.SetList(&Grps); return true; } case SdImage: // Type: Image { LAssert(!"Impl."); return false; } case SdImageHtml: // Type: String { if (!d->ImagePath) { int Px = 80; if (Array) { int i = atoi(Array); if (i > 0) Px = i; } // Check to see if the contact has an image... const LVariant *Bin = Obj->GetVar(FIELD_CONTACT_IMAGE); if (Bin && Bin->Type != GV_NULL) { if (Bin->Type == GV_BINARY) { auto Png = LFilterFactory::New("name.png", O_WRITE, NULL); if (Png) { if (!d->Image) { LMemStream Mem(Bin->Value.Binary.Data, Bin->Value.Binary.Length, false); LAutoPtr Raw(GdcD->Load(&Mem)); if (Raw) { ConvertImageToContactSize(d->Image, Px, Raw); } } if (d->Image) { LFile::Path p(ScribeTempPath()); const char *First = Obj->GetStr(FIELD_FIRST_NAME); const char *Last = Obj->GetStr(FIELD_LAST_NAME); LString leaf; leaf.Printf("%s%sContact.png", First, Last); p += leaf; LFile f; if (f.Open(p, O_WRITE)) { if (Png->WriteImage(&f, d->Image)) { #if 0 // We could save the resized version here? int64 NewSize = f.GetSize(); if (Bin->Length() > (50 << 10)) { int asd=0; } #endif d->ImagePath = p.GetFull(); } } } } } else LAssert(0); } } if (d->ImagePath) { LString html; html.Printf("\n", d->ImagePath.Get()); Value = html; return true; } return App->GetValue("NoContact.ImageHtml", Value); } case SdTitle: // Type: String Value = Obj->GetStr(FIELD_TITLE); return true; case SdFirst: // Type: String Value = Obj->GetStr(FIELD_FIRST_NAME); return true; case SdSurname: // Type: String Value = Obj->GetStr(FIELD_LAST_NAME); return true; case SdNickname: // Type: String Value = Obj->GetStr(FIELD_NICK); return true; case SdSpouse: // Type: String Value = Obj->GetStr(FIELD_SPOUSE); return true; case SdNotes: // Type: String Value = Obj->GetStr(FIELD_NOTE); return true; case SdUid: // Type: Int64 Value = Obj->GetInt(FIELD_UID); return true; case SdTimeZone: // Type: String Value = Obj->GetStr(FIELD_TIMEZONE); return true; case SdHomeStreet: // Type: String Value = Obj->GetStr(FIELD_HOME_STREET); return true; case SdHomeSuburb: // Type: String Value = Obj->GetStr(FIELD_HOME_SUBURB); return true; case SdHomePostcode: // Type: String Value = Obj->GetStr(FIELD_HOME_POSTCODE); return true; case SdHomeState: // Type: String Value = Obj->GetStr(FIELD_HOME_STATE); return true; case SdHomeCountry: // Type: String Value = Obj->GetStr(FIELD_HOME_COUNTRY); return true; case SdHomePhone: // Type: String Value = Obj->GetStr(FIELD_HOME_PHONE); return true; case SdHomeMobile: // Type: String Value = Obj->GetStr(FIELD_HOME_MOBILE); return true; case SdHomeIM: // Type: String Value = Obj->GetStr(FIELD_HOME_IM); return true; case SdHomeFax: // Type: String Value = Obj->GetStr(FIELD_HOME_FAX); return true; case SdHomeWebpage: // Type: String Value = Obj->GetStr(FIELD_HOME_WEBPAGE); return true; case SdWorkStreet: // Type: String Value = Obj->GetStr(FIELD_WORK_STREET); return true; case SdWorkSuburb: // Type: String Value = Obj->GetStr(FIELD_WORK_SUBURB); return true; case SdWorkPostcode: // Type: String Value = Obj->GetStr(FIELD_WORK_POSTCODE); return true; case SdWorkState: // Type: String Value = Obj->GetStr(FIELD_WORK_STATE); return true; case SdWorkCountry: // Type: String Value = Obj->GetStr(FIELD_WORK_COUNTRY); return true; case SdWorkPhone: // Type: String Value = Obj->GetStr(FIELD_WORK_POSTCODE); return true; case SdWorkMobile: // Type: String Value = Obj->GetStr(FIELD_WORK_MOBILE); return true; case SdWorkIM: // Type: String Value = Obj->GetStr(FIELD_WORK_IM); return true; case SdWorkFax: // Type: String Value = Obj->GetStr(FIELD_WORK_FAX); return true; case SdCompany: // Type: String Value = Obj->GetStr(FIELD_COMPANY); return true; case SdWorkWebpage: // Type: String Value = Obj->GetStr(FIELD_WORK_WEBPAGE); return true; default: { // Check custom fields.. if (!d->CustomCache) { auto json = Obj->GetStr(FIELD_CONTACT_JSON); d->CustomCache.Reset(new LJson(json)); } if (!d->CustomCache) return false; Value = d->CustomCache->Get(Name); return true; } } return false; } bool Contact::CallMethod(const char *MethodName, LVariant *ReturnValue, LArray &Args) { // ScribeDomType Fld = StrToDom(MethodName); return Thing::CallMethod(MethodName, ReturnValue, Args); } Thing &Contact::operator =(Thing &c) { Contact *Arg = c.IsContact(); if (GetObject() && Arg && Arg->GetObject()) { GetObject()->CopyProps(*Arg->GetObject()); } return *this; } bool Contact::IsAssociatedWith(char *PluginName) { if (PluginName) { for (auto p: d->Plugins) { if (_stricmp(p, PluginName) == 0) { return true; } } } return false; } int Contact::Compare(LListItem *Arg, ssize_t FieldId) { Contact *c1 = this; Contact *c2 = dynamic_cast(Arg); if (c1 && c2) { static int Fields[] = {FIELD_FIRST_NAME, FIELD_LAST_NAME, FIELD_EMAIL}; if (FieldId < 0) { int i = -(int)FieldId - 1; if (i >= 0 && i < CountOf(Fields)) { FieldId = Fields[i]; } } auto s1 = c1->GetObject()->GetStr((int)FieldId); auto s2 = c2->GetObject()->GetStr((int)FieldId); const char *Empty = ""; return _stricmp(s1?s1:Empty, s2?s2:Empty); } return 0; } ThingUi *Contact::DoUI(MailContainer *c) { if (!Ui) { Ui = new ContactUi(this); } return Ui; } size_t Contact::SizeofField(const char *Name) { LAssert(0); return 0; } size_t Contact::Sizeof() { LAssert(0); return 0; } bool Contact::Serialize(LFile &f, bool Write) { LAssert(0); return false; } int Contact::DefaultContactFields[] = { FIELD_FIRST_NAME, FIELD_LAST_NAME, FIELD_EMAIL, 0 }; int *Contact::GetDefaultFields() { return DefaultContactFields; } const char *Contact::GetFieldText(int Field) { const char *Status = NULL; #define GetFldText(Id, Opt) \ case Id: Get(Opt, Status); break; switch (Field) { MacroAllFields(GetFldText); case FIELD_DATE_MODIFIED: { auto Dt = GetObject()->GetDate(Field); if (Dt) d->DateCache = Dt->Local().Get(); else d->DateCache = LLoadString(IDS_NONE); return d->DateCache; break; } case FIELD_PLUGIN_ASSOC: { static char Str[256]; Str[0] = 0; for (auto p: d->Plugins) { if (Str[0]) strcat(Str, ", "); strcat(Str, p); } return Str; break; } } return Status; } const char *Contact::GetText(int i) { if (FieldArray.Length()) return GetFieldText(FieldArray[i]); return GetFieldText(DefaultContactFields[i]); } void Contact::OnMouseClick(LMouse &m) { if (m.IsContextMenu()) { LScriptUi s(new LSubMenu); if (s.Sub) { bool Sep = false; const char *Email; if (Get(OPT_Email, Email) && ValidStr(Email)) { char Str[256]; const char *First = 0, *Last = 0; Get(OPT_First, First); Get(OPT_Last, Last); char *LiteralEmail = NewStr(AddAmp(LLoadString(IDS_EMAIL), 'e')); if (First || Last) { sprintf_s(Str, sizeof(Str), "%s %s %s", LiteralEmail, (First) ? First : "", (Last) ? Last : ""); } else { sprintf_s(Str, sizeof(Str), "%s %s", LiteralEmail, Email); } DeleteArray(LiteralEmail); s.Sub->AppendItem(Str, IDM_NEW_EMAIL, true); Sep = true; } const char *Web = 0; if (Get(OPT_HomeWebPage, Web) && ValidStr(Web)) { s.Sub->AppendItem(Web, IDM_LOAD, true); Sep = true; } if (Sep) { s.Sub->AppendSeparator(); } s.Sub->AppendItem(LLoadString(IDS_OPEN), IDM_OPEN); s.Sub->AppendItem(LLoadString(IDS_DELETE), IDM_DELETE); s.Sub->AppendItem(LLoadString(IDS_EXPORT), IDM_EXPORT); LArray Callbacks; if (App->GetScriptCallbacks(LThingContextMenu, Callbacks)) { LScriptArguments Args(NULL); Args[0] = new LVariant(App); Args[1] = new LVariant(this); Args[2] = new LVariant(&s); for (auto c: Callbacks) { App->ExecuteScriptCallback(*c, Args); } Args.DeleteObjects(); } if (GetList()->GetMouse(m, true)) { int Result; switch (Result = s.Sub->Float(GetList(), m.x, m.y)) { case IDM_NEW_EMAIL: { App->CreateMail(this); break; } case IDM_LOAD: { if (Web) LExecute(Web, 0, 0); break; } case IDM_OPEN: { DoUI(); break; } case IDM_DELETE: { LVariant ConfirmDelete; App->GetOptions()->GetValue(OPT_ConfirmDelete, ConfirmDelete); if (!ConfirmDelete.CastInt32() || LgiMsg(GetList(), LLoadString(IDS_DELETE_ASK), AppName, MB_YESNO) == IDYES) { List Del; LList *ParentList = LListItem::Parent; if (ParentList && ParentList->GetSelection(Del)) { for (auto It = Del.rbegin(); It != Del.end(); It--) { auto c = dynamic_cast(*It); if (c) c->OnDelete(); } ParentList->Invalidate(); } } break; } case IDM_EXPORT: { - ExportAll(GetList(), sMimeVCard); + ExportAll(GetList(), sMimeVCard, NULL); break; } default: { // Handle any installed callbacks for menu items for (unsigned i=0; iExecuteScriptCallback(Cb, Args); } } break; } } } DeleteObj(s.Sub); } } else if (m.Left()) { if (m.Double()) { DoUI(); } } } bool Contact::Save(ScribeFolder *Folder) { bool Status = false; if (!Folder) { Folder = GetFolder(); } if (!Folder && App) { Folder = App->GetFolder(FOLDER_CONTACTS); } if (Folder) { Status = Folder->WriteThing(this) != Store3Error; if (Status) SetDirty(false); } return Status; } bool Contact::GetFormats(bool Export, LString::Array &MimeTypes) { if (!Export) MimeTypes.Add(sMimeVCard); return MimeTypes.Length() > 0; } Thing::IoProgress Contact::Import(IoProgressImplArgs) { if (Stricmp(mimeType, sMimeVCard)) IoProgressNotImpl(); VCard vCard; if (!vCard.Import(GetObject(), stream)) IoProgressError("vCard import failed."); IoProgressSuccess(); } Thing::IoProgress Contact::Export(IoProgressImplArgs) { if (Stricmp(mimeType, sMimeVCard)) IoProgressNotImpl(); VCard vCard; if (!vCard.Export(GetObject(), stream)) IoProgressError("vCard export failed."); IoProgressSuccess(); } char *Contact::GetDropFileName() { if (!DropFileName) { char File[64] = "Contact"; const char *First = 0, *Last = 0; Get(OPT_First, First); Get(OPT_Last, Last); if (First && Last) sprintf_s(File, sizeof(File), "%s-%s", First, Last); else if (First) strcpy_s(File, sizeof(File), First); else if (Last) strcpy_s(File, sizeof(File), Last); DropFileName.Reset(MakeFileName(File, "vcf")); } return DropFileName; } bool Contact::GetDropFiles(LString::Array &Files) { bool Status = false; if (GetDropFileName()) { if (!LFileExists(DropFileName)) { LAutoPtr F(new LFile); if (F->Open(DropFileName, O_WRITE)) { F->SetSize(0); Export(AutoCast(F), sMimeVCard); } } if (LFileExists(DropFileName)) { Files.Add(DropFileName.Get()); Status = true; } } return Status; } void Contact::OnPrintHeaders(struct ScribePrintContext &Context) { LDisplayString *ds = Context.Text(LLoadString(IDS_CONTACT)); LRect &r = Context.MarginPx; int Line = ds->Y(); LDrawListSurface *Page = Context.Pages.Last(); Page->Rectangle(r.x1, Context.CurrentY + (Line * 5 / 10), r.x2, Context.CurrentY + (Line * 6 / 10)); Context.CurrentY += Line; } void Contact::OnPrintText(ScribePrintContext &Context, LPrintPageRanges &Pages) { // Print document ForAllContactFields(Fld) { const char *Value = 0; if (Get(Fld->Option, Value)) { LString v = Value; LString::Array Lines = v.SplitDelimit("\n"); int x = 0; for (unsigned i=0; iFieldId); f.Printf("%s: ", Name ? Name : Fld->DisplayText); LDisplayString *ds = Context.Text(f); if (ds) { x = ds->X(); Context.CurrentY -= ds->Y(); Context.Text(Lines[i], x); } } } } } } const char *ToField(int f) { ForAllContactFields(Fld) { if (Fld->FieldId == f) { return Fld->Option; } } if (f == FIELD_PERMISSIONS) { return "Perms"; } LAssert(0); return 0; } //////////////////////////////////////////////////////////////////////////////////////////// #define M_REMOVE_EMAIL_ADDR (M_USER+0x400) class EmailAddr : public LListItem { Contact *c; LAutoString Email; bool EditOneShot; bool Default; const char *ClickToAdd; public: EmailAddr(Contact *contact, const char *email = 0, bool def = false) { c = contact; EditOneShot = false; Default = def; Email.Reset(NewStr(email)); ClickToAdd = LLoadString(IDS_CLICK_TO_ADD); } bool GetDefault() { return Default; } char *GetEmail() { return Email; } LFont *GetFont() { return Default ? c->App->GetBoldFont() : 0; } const char *GetText(int Col=0) { if (EditOneShot) { EditOneShot = false; return (char*)""; } return Email ? Email : (char*)ClickToAdd; } void Delete() { if (Parent->Length() > 1) { Parent->GetWindow()->PostEvent(M_REMOVE_EMAIL_ADDR, (LMessage::Param)this); if (Default) { // Set one of the other's to the default List All; Parent->GetAll(All); for (auto a: All) { if (a != this && a->Email) { a->Default = true; a->Update(); break; } } } } else { Email.Reset(); } } bool SetText(const char *s, int Col=0) { if (s && Col == 0) { if (ValidStr(s)) { if (!Email) { Parent->Insert(new EmailAddr(c)); } Email.Reset(NewStr(s)); if (GetCss()) GetCss()->DeleteProp(LCss::PropColor); Update(); } else { Delete(); } } return false; } /* void OnPaint(LItem::ItemPaintCtx &Ctx) { if (!Email) { LColour Back; if (Select()) Back.Set(LC_FOCUS_SEL_BACK, 24); else Back.Set(LC_WORKSPACE, 24); COLOUR Fore24 = LC_LOW; COLOUR BackGrey = GdcGreyScale(Back.c24(), 24); COLOUR ForeGrey = GdcGreyScale(Fore24, 24); int Diff = abs((int)(BackGrey - ForeGrey)); if (Diff < 94) Fore24 = LC_FOCUS_SEL_FORE; // LgiTrace("Fore=%i Back=%i Diff=%i Fore=%x\n", BackGrey, ForeGrey, Diff, Fore24); SetForegroundFill(new GViewFill(Fore24, 24)); } else SetForegroundFill(0); LListItem::OnPaint(Ctx); } */ bool OnKey(LKey &k) { if (k.Down()) { switch (k.c16) { default: { if ( k.Down() && ( IsAlpha(k.c16) || k.c16 == ' ' || k.c16 == LK_F2 ) ) { EditOneShot = !Email; LViewI *v = EditLabel(0); if (v && k.IsChar && IsAlpha(k.c16)) { v->IterateViews().DeleteObjects(); if (v) { LEdit *e = dynamic_cast(v); if (e && !ValidStr(v->Name())) { LAutoString u(WideToUtf8(&k.c16, 1)); if (u) { e->Name(u); e->SetCaret(1); } } v->Focus(true); } else { LgiTrace("%s:%i - no edit.\n", __FILE__, __LINE__); } } return true; } break; } case LK_DELETE: { Parent->Delete(this); return true; break; } } } return false; } void OnMouseClick(LMouse &m) { if (m.Down()) { if (m.IsContextMenu()) { auto RClick = new LSubMenu; if (RClick) { #define IDM_SET_DEFAULT 200 RClick->AppendItem(LLoadString(IDS_SET_DEFAULT), IDM_SET_DEFAULT, !Default); RClick->AppendItem(LLoadString(IDS_DELETE), IDM_DELETE, true); // RClick->AppendSeparator(); if (LListItem::Parent->GetMouse(m, true)) { switch (RClick->Float(LListItem::Parent, m.x, m.y)) { case IDM_SET_DEFAULT: { // Clear the old default List All; Parent->GetAll(All); for (auto a: All) { if (a->Default) { a->Default = false; a->Update(); break; } } // Set the new default Default = true; Update(); break; } case IDM_DELETE: { Delete(); break; } } } DeleteObj(RClick); } } else if (m.Left()) { int c = Parent->ColumnAtX(m.x); if (c >= 0) { EditOneShot = !Email; EditLabel(c); } } } } }; ContactUi::ContactUi(Contact *item) : ThingUi(item, "Contact") { Lst = 0; ImgView = NULL; Item = item; if (!(Item && Item->App)) { return; } #if WINNATIVE SetStyle(GetStyle() & ~WS_VISIBLE); CreateClassW32("Scribe::ContactUi", LoadIcon(LProcessInst(), MAKEINTRESOURCE(IDI_CONTACT))); #endif if (Attach(0)) { LRect p; LAutoString s(NewStr("Contact")); if (LoadFromResource(IDD_CONTACT, this, &p, &s)) { // size/position Name(s); SetPos(p); MoveSameScreen(App); // MoveToCenter(); // list setup if (GetViewById(IDC_EMAIL, Lst)) { Lst->ShowColumnHeader(false); Lst->AddColumn("Email", Lst->X()); Lst->Insert(new EmailAddr(Item)); } // Tz setup LCombo *c; if (GetViewById(IDC_PICK_TZ, c)) { c->Sort(true); c->Sub(GV_DOUBLE); GTimeZone *Tz = GTimeZones; while (Tz->Text) { char s[256]; sprintf_s(s, sizeof(s), "%.1f %s", Tz->Offset, Tz->Text); c->Insert(s); Tz++; } } // Show buttons to toggle mode... LButton *Show; if (GetViewById(IDC_SHOW_ADDR, Show)) { Show->SetIsToggle(true); Show->Value(1); } if (GetViewById(IDC_SHOW_EXTRA, Show)) Show->SetIsToggle(true); // controls AttachChildren(); // Image setup if (GetViewById(IDC_IMAGE, ImgView)) { ImgView->SetContact(Item); } OnLoad(); // show the window Visible(true); // set default button _Default = FindControl(IDOK); LViewI *f = FindControl(IDC_FIRST); if (f) f->Focus(true); } } SetPulse(1000); } ContactUi::~ContactUi() { Item->Ui = 0; } void ContactUi::OnDestroy() { if (Item) { Item->Ui = 0; } } bool ContactUi::InitField(int Id, const char *Name) { if (Item) { const char *s; if (Item->Get(Name, s)) { SetCtrlName(Id, s); return true; } int i; if (Item->Get(Name, i)) { SetCtrlValue(Id, i); return true; } } return false; } bool ContactUi::SaveField(int Id, const char *Name) { if (Item) { if (Id == FIELD_UID) { Item->Set(Name, (int)GetCtrlValue(Id)); return true; } else { Item->Set(Name, GetCtrlName(Id)); return true; } } return false; } void ContactUi::OnLoad() { int Insert = 0; ForAllContactFields(f) { if (f->FieldId == FIELD_EMAIL) { const char *e; if (Item->Get(f->Option, e)) { Lst->Insert(new EmailAddr(Item, e, true), Insert++); } } else if (f->CtrlId > 0) { InitField(f->CtrlId, f->Option); } } auto AltEmail = LString(Item->GetObject()->GetStr(FIELD_ALT_EMAIL)).SplitDelimit(","); for (auto e: AltEmail) Lst->Insert(new EmailAddr(Item, e), Insert++); if (ImgView) { const LVariant *v = Item->GetObject()->GetVar(FIELD_CONTACT_IMAGE); if (v && v->Type == GV_BINARY) { ImgView->IsNoFace = false; ImgView->SetImage(*v); } } auto FirstName = Item->GetObject()->GetStr(FIELD_FIRST_NAME); auto LastName = Item->GetObject()->GetStr(FIELD_LAST_NAME); if (ValidStr(FirstName)||ValidStr(LastName)) { LString s; s.Printf("%s - %s%s%s", LLoadString(IDS_CONTACT), FirstName?FirstName:"", FirstName?" ":"", LastName?LastName:""); Name(s); } LViewI *c; if (GetViewById(IDC_TIMEZONE, c)) { LNotification note(LNotifyValueChanged); OnNotify(c, note); } } void ContactUi::OnSave() { auto Obj = Item->GetObject(); EmailAddr *Def = NULL; List All; Lst->GetAll(All); ForAllContactFields(f) { if (f->FieldId == FIELD_EMAIL) { for (auto a: All) { if (a->GetDefault()) { Def = a; break; } } if (!Def) Def = All[0]; if (Def) Item->Set(f->Option, Def->GetEmail()); } else if (f->CtrlId > 0) { SaveField(f->CtrlId, f->Option); } } LString::Array AltEmails; for (auto a: All) { if (a != Def && a->GetEmail()) AltEmails.New() = a->GetEmail(); } LString AltEmail = LString(",").Join(AltEmails); Obj->SetStr(FIELD_ALT_EMAIL, AltEmail); if (ImgView) { // Reset the cache... Item->d->Image.Reset(); Item->d->ImagePath.Empty(); // Set the image in the back end store... LVariant *Img = &ImgView->GetImage(); Obj->SetVar(FIELD_CONTACT_IMAGE, Img); } if (Item && Item->App) { Item->Save(); LArray c; c.Add(Obj); Item->App->SetContext(_FL); Item->App->OnChange(c, 0); } } void ContactUi::OnPosChange() { } LMessage::Result ContactUi::OnEvent(LMessage *Msg) { switch (Msg->Msg()) { case M_REMOVE_EMAIL_ADDR: { EmailAddr *Addr = (EmailAddr*)Msg->A(); if (Addr) { Lst->Delete(Addr); } break; } } return ThingUi::OnEvent(Msg); } int ContactUi::OnNotify(LViewI *Ctrl, LNotification n) { switch (Ctrl->GetId()) { case IDOK: { OnSave(); // fall thru } case IDCANCEL: { PostEvent(M_CLOSE); break; } case IDC_TIMEZONE: { LString v = Ctrl->Name(); if (!v) break; auto dv = v.Float(); LCombo *c; if (GetViewById(IDC_PICK_TZ, c)) { for (size_t i=0; iLength(); i++) { auto s = (*c)[i]; if (s) { auto sval = atof(s); if (ABS(sval-dv) < 0.0001) { c->Value(i); return true; } } } } break; } case IDC_PICK_TZ: { if (Ctrl->Value() >= 0 && Ctrl->Name()) { double Tz = atof(Ctrl->Name()); char s[32]; sprintf_s(s, sizeof(s), "%.1f", Tz); SetCtrlName(IDC_TIMEZONE, s); } break; } case IDC_SHOW_ADDR: { LTableLayout *Tbl; if (GetViewById(IDC_TABLE, Tbl)) { int64 Shown = Ctrl->Value(); Ctrl->Name(Shown ? "-" : "+"); int y = 2; auto *c = Tbl->GetCell(0, y); if (c) c->Display(Shown ? LCss::DispBlock : LCss::DispNone); c = Tbl->GetCell(1, y); if (c) c->Display(Shown ? LCss::DispBlock : LCss::DispNone); Tbl->InvalidateLayout(); } break; } case IDC_SHOW_EXTRA: { LTableLayout *Tbl; if (GetViewById(IDC_TABLE, Tbl)) { int64 Shown = Ctrl->Value(); Ctrl->Name(Shown ? "-" : "+"); int y = 4; auto *c = Tbl->GetCell(0, y); if (c) c->Display(Shown ? LCss::DispBlock : LCss::DispNone); c = Tbl->GetCell(1, y); if (c) c->Display(Shown ? LCss::DispBlock : LCss::DispNone); Tbl->InvalidateLayout(); } break; } } return 0; } char *Contact::GetLocalTime(const char *TimeZone) { char *Status = 0; if (!ValidStr(TimeZone)) { Get(OPT_TimeZone, TimeZone); } if (ValidStr(TimeZone)) { double TheirTz = atof(TimeZone); LDateTime d; d.SetNow(); d.SetTimeZone((int)(TheirTz * 60), true); char s[256]; d.Get(s, sizeof(s)); Status = NewStr(s); } return Status; } void ContactUi::OnPulse() { char *Local = Item->GetLocalTime(GetCtrlName(IDC_TIMEZONE)); if (Local) { SetCtrlName(IDC_LOCALTIME, Local); DeleteArray(Local); } } diff --git a/Code/ScribeFilter.cpp b/Code/ScribeFilter.cpp --- a/Code/ScribeFilter.cpp +++ b/Code/ScribeFilter.cpp @@ -1,4183 +1,4201 @@ /* ** FILE: ScribeFilter.cpp ** AUTHOR: Matthew Allen ** DATE: 29/10/1999 ** DESCRIPTION: Scribe filters ** ** Copyright (C) 1999-2022, Matthew Allen ** fret@memecode.com ** */ #include #include #include #include #include #include "Scribe.h" #include "lgi/common/NetTools.h" #include "lgi/common/Combo.h" #include "lgi/common/Edit.h" #include "lgi/common/Button.h" #include "lgi/common/ProgressDlg.h" #include "lgi/common/FilterUi.h" #include "lgi/common/XmlTree.h" #include "lgi/common/ClipBoard.h" #include "lgi/common/TabView.h" #include "lgi/common/Printer.h" #include "lgi/common/LgiRes.h" #include "lgi/common/FileSelect.h" #include "lgi/common/Charset.h" #include "resdefs.h" #include "resource.h" #define COMBINE_OP_AND 0 #define COMBINE_OP_OR 1 #define IDM_TRUE 500 #define IDM_FALSE 501 #define IDM_NOTNEW 502 #define IDM_LOCAL 503 #define IDM_SERVER 504 #define IDM_LOCAL_AND_SERVER 505 const char *ATTR_NOT = "Not"; const char *ATTR_FIELD = "Field"; const char *ATTR_OP = "Op"; const char *ATTR_VALUE = "Value"; const char *ELEMENT_AND = "And"; const char *ELEMENT_OR = "Or"; const char *ELEMENT_CONDITION = "Condition"; const char *ELEMENT_CONDITIONS = "Conditions"; const char *ELEMENT_ACTION = "Action"; #define SkipWs(s) while ((*s) && strchr(WhiteSpace, *s)) s++; ////////////////////////////////////////////////////////////// class FilterPrivate { public: bool *Stop; LStream *Log; FilterPrivate() { Stop = 0; Log = 0; } }; ////////////////////////////////////////////////////////////// // Filter field definitions ItemFieldDef FilterFieldDefs[] = { {"Name", SdName, GV_STRING, FIELD_FILTER_NAME}, {"Index", SdIndex, GV_INT32, FIELD_FILTER_INDEX}, {"Incoming", SdIncoming, GV_INT32, FIELD_FILTER_INCOMING}, {"Outgoing", SdOutgoing, GV_INT32, FIELD_FILTER_OUTGOING}, {"Internal", SdInternal, GV_INT32, FIELD_FILTER_INTERNAL}, {0} }; int DefaultFilterFields[] = { FIELD_FILTER_NAME, FIELD_FILTER_INDEX, FIELD_FILTER_INCOMING, FIELD_FILTER_OUTGOING, FIELD_FILTER_INTERNAL, 0 }; #define ForCondField(Macro, Value) \ switch (Value) \ { \ case 0: Macro("To"); break; \ case 1: Macro("From"); break; \ case 2: Macro("Subject"); break; \ case 3: Macro("Size"); break; \ case 4: Macro("DateReceived"); break; \ case 5: Macro("DateSent"); break; \ case 6: Macro("Body"); break; \ case 7: Macro("InternetHeaders"); break; \ case 8: Macro("MessageID"); break; \ case 9: Macro("Priority"); break; \ case 10: /* flags */ break; \ case 11: Macro("Html"); break; \ case 12: Macro("Label"); break; \ case 13: Macro("From.Contact"); break; \ case 14: Macro("ImapCacheFile"); break; \ case 15: Macro("ImapFlags"); break; \ case 16: Macro("Attachments"); break; \ case 17: Macro("AttachmentNames"); break; \ case 18: Macro("From.Groups"); break; \ case 19: Macro("*"); break; \ } // Macro("", FIELD_FLAGS) struct ActionName { int Id; const char *Default; }; ActionName ActionNames[] = { {IDS_ACTION_MOVE_FOLDER, "Move to Folder"}, {IDC_DELETE, "Delete"}, {IDS_PRINT, "Print"}, {IDS_ACTION_SOUND, "Play Sound"}, {IDS_ACTION_OPEN, "Open Email"}, {IDS_ACTION_EXECUTE, "Execute Process"}, {IDS_ACTION_SET_COLOUR, "Set Colour"}, {IDS_SET_READ, "Set Read"}, {IDS_ACTION_SET_LABEL, "Set Label"}, {IDS_ACTION_EMPTY_FOLDER, "Empty Folder"}, {IDS_ACTION_MARK_SPAM, "Mark As Spam"}, {IDS_REPLY, "Reply"}, {IDS_FORWARD, "Forward"}, {IDS_BOUNCE, "Bounce"}, {IDS_ACTION_SAVE_ATTACHMENTS, "Save Attachment(s)"}, {IDS_ACTION_DELETE_ATTACHMENTS, "Delete Attachments(s)"}, {L_CHANGE_CHARSET, "Change Charset"}, {IDS_ACTION_COPY, "Copy to Folder"}, {IDS_EXPORT, "Export"}, {0, 0}, {IDS_ACTION_CREATE_FOLDER, "Create Folder"}, {0, 0} }; // These are the english names used for storing XML const char *OpNames[] = { "=", "!=", "<", "<=", ">=", ">", "Like", // LLoadString(IDS_LIKE), "Contains", // LLoadString(IDS_CONTAINS), "Starts With", // LLoadString(IDS_STARTS_WITH), "Ends With", // LLoadString(IDS_ENDS_WITH), 0 }; const char *TranslatedOpNames[] = { "=", "!=", "<", "<=", ">=", ">", 0, // like 0, // contains 0, // starts with 0, // ends with 0 }; const char **GetOpNames(bool Translated) { if (Translated) { if (TranslatedOpNames[6] == NULL) { TranslatedOpNames[6] = LLoadString(IDS_LIKE); TranslatedOpNames[7] = LLoadString(IDS_CONTAINS); TranslatedOpNames[8] = LLoadString(IDS_STARTS_WITH); TranslatedOpNames[9] = LLoadString(IDS_ENDS_WITH); } if (TranslatedOpNames[6]) { return TranslatedOpNames; } } return OpNames; } ////////////////////////////////////////////////////////////// void SkipSep(const char *&s) { while (s && *s && strchr(" \t,", *s)) s++; } LCombo *LoadTemplates(ScribeWnd *App, LView *Wnd, List &MsgIds, int Ctrl, char *Template) { LCombo *Temp; if (Wnd->GetViewById(IDC_TEMPLATE, Temp)) { ScribeFolder *Templates = App->GetFolder(FOLDER_TEMPLATES); if (Templates) { int n=0; for (auto t: Templates->Items) { Mail *m = t->IsMail(); if (m) { auto Id = m->GetMessageId(true); if (Id) { MsgIds.Insert(NewStr(Id)); Temp->Insert(m->GetSubject() ? m->GetSubject() : (char*)"(no subject)"); if (Template && strcmp(Template, Id) == 0) { Temp->Value(n); } } } n++; } } } return Temp; } class BrowseReply : public LDialog { List MsgIds; LCombo *Temp; public: char *Arg; BrowseReply(ScribeWnd *App, LView *Parent, const char *arg) { SetParent(Parent); LoadFromResource(IDD_FILTER_REPLY); MoveToCenter(); char *Template = LTokStr(arg); SkipSep(arg); char *All = LTokStr(arg); SkipSep(arg); char *MarkReplied = LTokStr(arg); if (All) { SetCtrlValue(IDC_ALL, atoi(All)); } if (MarkReplied) { SetCtrlValue(IDC_MARK_REPLIED, atoi(MarkReplied)); } Temp = LoadTemplates(App, this, MsgIds, IDC_TEMPLATE, Template); DeleteArray(Template); DeleteArray(MarkReplied); DeleteArray(All); } ~BrowseReply() { MsgIds.DeleteArrays(); } int OnNotify(LViewI *c, LNotification n) { switch (c->GetId()) { case IDOK: { if (Temp) { char *Template = MsgIds[(int)Temp->Value()]; int All = (int)GetCtrlValue(IDC_ALL); int MarkReplied = (int)GetCtrlValue(IDC_MARK_REPLIED); char s[256]; sprintf_s(s, sizeof(s), "\"%s\" %i %i", Template?Template:(char*)"", All, MarkReplied); Arg = NewStr(s); } // Fall thru } case IDCANCEL: { EndModal(c->GetId() == IDOK); break; } } return 0; } }; class BrowseForward : public LDialog { List MsgIds; ScribeWnd *App; LCombo *Temp; bool UseTemplate; public: char *Arg; BrowseForward(ScribeWnd *app, LView *parent, const char *arg, bool temp) { UseTemplate = temp; App = app; Arg = 0; Temp = 0; SetParent(parent); LoadFromResource(IDD_FILTER_FORWARD); MoveToCenter(); char *Template = 0; if (UseTemplate) { Template = LTokStr(arg); SkipSep(arg); } char *Email = LTokStr(arg); SkipSep(arg); char *Forward = LTokStr(arg); SkipSep(arg); char *MarkForwarded = LTokStr(arg); if (UseTemplate) { bool HasTemplate = ValidStr(Template) ? strlen(Template) > 1 : 0; SetCtrlValue(IDC_USE_TEMPLATE, HasTemplate); Temp = LoadTemplates(App, this, MsgIds, IDC_TEMPLATE, HasTemplate ? Template : 0); } else { LViewI *v = FindControl(IDC_USE_TEMPLATE); if (v) { int y1 = v->GetPos().y1; Children.Delete(v); DeleteObj(v); v = FindControl(IDC_TEMPLATE); if (v) { int y2 = v->GetPos().y2; Children.Delete(v); DeleteObj(v); int Sub = y1 - y2 - 10; for (auto v: Children) { LRect r = v->GetPos(); r.Offset(0, Sub); v->SetPos(r); } LRect r = GetPos(); r.y2 += Sub; SetPos(r); } } } SetCtrlName(IDC_EMAIL, Email); if (Forward) SetCtrlValue(IDC_ATTACHMENTS, atoi(Forward)); if (MarkForwarded) SetCtrlValue(IDC_MARK_FORWARDED, atoi(MarkForwarded)); DeleteArray(Template); DeleteArray(Email); DeleteArray(Forward); DeleteArray(MarkForwarded); } ~BrowseForward() { DeleteArray(Arg); } int OnNotify(LViewI *c, LNotification n) { switch (c->GetId()) { case IDOK: { char s[256]; bool HasTemplate = GetCtrlValue(IDC_USE_TEMPLATE) != 0; char *MsgId = HasTemplate ? MsgIds[(int)GetCtrlValue(IDC_TEMPLATE)] : 0; const char *Email = GetCtrlName(IDC_EMAIL); int Attachments = (int)GetCtrlValue(IDC_ATTACHMENTS); int MarkForwarded = (int)GetCtrlValue(IDC_MARK_FORWARDED); if (UseTemplate) { sprintf_s(s, sizeof(s), "\"%s\" \"%s\" %i %i", MsgId, Email?Email:(char*)"", Attachments, MarkForwarded); } else { sprintf_s(s, sizeof(s), "\"%s\" %i %i", Email?Email:(char*)"", Attachments, MarkForwarded); } Arg = NewStr(s); // Fall thru } case IDCANCEL: { EndModal(c->GetId() == IDOK); } } return 0; } }; class BrowseSaveAttach : public LDialog { ScribeWnd *App; public: char *Arg; BrowseSaveAttach(ScribeWnd *app, LView *parent, const char *arg) { Arg = 0; App = app; SetParent(parent); if (LoadFromResource(IDD_FILTER_SAVE_ATTACH)) { MoveToCenter(); char *Dir = LTokStr(arg); SkipSep(arg); char *Types = LTokStr(arg); SetCtrlName(IDC_DIR, Dir); SetCtrlName(IDC_TYPES, Types); DeleteArray(Dir); DeleteArray(Types); } } ~BrowseSaveAttach() { DeleteArray(Arg); } int OnNotify(LViewI *c, LNotification n) { switch (c->GetId()) { case IDC_BROWSE_DIR: { - LFileSelect s; - s.Parent(this); - s.Name(GetCtrlName(IDC_DIR)); - if (s.OpenFolder()) + auto s = new LFileSelect(this); + s->Name(GetCtrlName(IDC_DIR)); + s->OpenFolder([&](auto dlg, auto status) { - SetCtrlName(IDC_DIR, s.Name()); - } + if (status) + SetCtrlName(IDC_DIR, s->Name()); + delete dlg; + }); break; } case IDOK: { char s[512]; const char *Dir = GetCtrlName(IDC_DIR); const char *Types = GetCtrlName(IDC_TYPES); sprintf_s(s, sizeof(s), "\"%s\",\"%s\"", Dir?Dir:(char*)"", Types?Types:(char*)""); Arg = NewStr(s); // Fall thru } case IDCANCEL: { EndModal(c->GetId() == IDOK); } } return 0; } }; bool LgiCreateTempFileName(char *Path, int PathLen) { if (Path) { #if defined WIN32 int Len = GetTempPathA(PathLen, Path); #else strcpy(Path, "/tmp"); int Len = (int)strlen(Path); #endif if (Path[Len-1] != DIR_CHAR) strcat(Path, DIR_STR); int len = (int) strlen(Path); sprintf_s(Path+len, PathLen-len, "~%i.txt", LRand(10000)); return true; } return false; } ////////////////////////////////////////////////////////////// FilterCondition::FilterCondition() { Op = 0; Not = false; } FilterCondition &FilterCondition::operator=(FilterCondition &c) { Source.Reset(NewStr(c.Source)); Op = c.Op; Not = c.Not; Value.Reset(NewStr(c.Value)); return *this; } bool FilterCondition::Test(Filter *F, Mail *m, LStream *Log) { if (Log) Log->Print("\tCondition.Test Fld='%s'\n", (char*)Source); if (ValidStr(Source)) { int Flds = 0; for (; MailFieldDefs[Flds].FieldId; Flds++) { } LVariant v; // Get data if (_stricmp(Source, "mail.attachments") == 0) { // attachment(s) data List Attachments; if (m->GetAttachments(&Attachments)) { for (auto a: Attachments) { char *Data; ssize_t Length; LDateTime Temp; if (a->Get(&Data, &Length)) { // Zero terminate the string LVariant v; v.SetBinary(Length, Data); // Test the file if (TestData(F, v, Log)) { return true; } } } } return false; } else if (_stricmp(Source, "mail.attachmentnames") == 0) { // attachment(s) name List Attachments; // ItemFieldDef AttachType = {"Attachment(s) Name", SdAttachmentNames, GV_STRING}; LDateTime Temp; if (m->GetAttachments(&Attachments)) { for (auto a: Attachments) { LVariant v = a->GetName(); if (TestData(F, v, Log)) { return true; } } } return false; } else { bool Status = false; ItemFieldDef *f = 0; if (_stricmp(Source, "mail.*") == 0) { ItemFieldDef *Start = MailFieldDefs; ItemFieldDef *End = MailFieldDefs + Flds - 1; for (f = Start; f <= End && !Status; f++) { switch (f->FieldId) { case FIELD_TO: { for (LDataPropI *a = m->GetTo()->First(); a; a = m->GetTo()->Next()) { char Data[256]; sprintf_s(Data, sizeof(Data), "%s <%s>", a->GetStr(FIELD_NAME), a->GetStr(FIELD_EMAIL)); LVariant v(Data); if (TestData(F, v, Log)) { Status |= true; } } continue; break; } case FIELD_FROM: { char Data[256]; sprintf_s(Data, sizeof(Data), "%s <%s>", m->GetFrom()->GetStr(FIELD_NAME), m->GetFrom()->GetStr(FIELD_EMAIL)); v = Data; break; } case FIELD_REPLY: { char Data[256]; sprintf_s(Data, sizeof(Data), "%s <%s>", m->GetReply()->GetStr(FIELD_NAME), m->GetReply()->GetStr(FIELD_EMAIL)); v = Data; break; } case FIELD_SUBJECT: v = m->GetSubject(); break; case FIELD_SIZE: v = m->TotalSizeof(); break; case FIELD_DATE_RECEIVED: v = m->GetDateReceived(); break; case FIELD_DATE_SENT: v = m->GetDateSent(); break; case FIELD_TEXT: v = m->GetBody(); break; case FIELD_INTERNET_HEADER: v = m->GetInternetHeader(); break; case FIELD_MESSAGE_ID: v = m->GetMessageId(); break; case FIELD_PRIORITY: v = m->GetPriority(); break; case FIELD_ALTERNATE_HTML: v = m->GetHtml(); break; case FIELD_LABEL: v = m->GetLabel(); break; } } } else { if (F) { F->GetValue(Source, v); } else if (Log) { Log->Print("%s:%i - Error: No filter to query value.\n", __FILE__, __LINE__); } } if (v.Type) { // Test data Status |= TestData(F, v, Log); } else if (Log) { Log->Print("%s:%i - Error: Variant doesn't have type!\n", __FILE__, __LINE__); } return Status; } } return false; } char *LogPreview(char *s) { LStringPipe p(1 << 10); if (s) { char *c; for (c = s; *c && c - s < 200; c++) { switch (*c) { case '\n': p.Push("\\n"); break; case '\r': p.Push("\\r"); break; case '\t': p.Push("\\t"); break; default: { p.Push(c, 1); } } } if (*c) { p.Push("..."); } } return p.NewStr(); } bool FilterCondition::TestData(Filter *F, LVariant &Var, LStream *Log) { // Do DOM lookup on the Value LVariant Val; if (F && F->Evaluate(Value, Val)) { // Compare using type switch (Var.Type) { case GV_LIST: { for (auto v: *Var.Value.Lst) { if (TestData(F, *v, Log)) { return true; } } break; } case GV_DOM: { // Probably an address field LVariant n; if (Var.Value.Dom->GetValue("Name", n)) { if (TestData(F, n, Log)) { return true; } } if (Var.Value.Dom->GetValue("Email", n)) { if (TestData(F, n, Log)) { return true; } } break; } case GV_STRING: { char *sVar = Var.Str(); char *sVal = Val.Str(); bool IsStr = ValidStr(sVar); bool IsVal = ValidStr(sVal); char *VarLog = Log ? LogPreview(sVar) : 0; bool m = false; switch (Op) { case OP_EQUAL: { if (!IsStr && !IsVal) { m = true; } else if (IsStr && IsVal) { m = _stricmp(sVal, sVar) == 0; } if (Log) Log->Print("\t\t\t'%s' == '%s' = %i\n", VarLog, sVal, m); break; } case OP_LIKE: { m = MatchStr(sVal, sVar); if (Log) Log->Print("\t\t\t'%s' like '%s' = %i\n", VarLog, sVal, m); break; } case OP_CONTAINS: { if (IsVal && IsStr) { m = stristr(sVar, sVal) != 0; if (Log) Log->Print("\t\t\t'%s' contains '%s' = %i\n", VarLog, sVal, m); } break; } case OP_STARTS_WITH: { if (IsVal && IsStr) { size_t Len = strlen(sVal); m = _strnicmp(sVar, sVal, Len) == 0; if (Log) Log->Print("\t\t\t'%s' starts with '%s' = %i\n", VarLog, sVal, m); } break; } case OP_ENDS_WITH: { if (IsVal && IsStr) { size_t SLen = strlen(sVar); size_t VLen = strlen(sVal); if (SLen >= VLen) { m = _strnicmp(sVar + SLen - VLen, sVal, VLen) == 0; if (Log) Log->Print("\t\t\t'%s' ends with '%s' = %i\n", VarLog, sVal, m); } else { if (Log) Log->Print("\t\t\tEnds With Error: '%s' is shorter than '%s'\n", sVar, sVal); } } else { if (Log) Log->Print("\t\t\tEnds With Error: invalid arguments\n"); } break; } } DeleteArray(VarLog); return m; break; } case GV_INT32: { // Convert Val to int int Int = 0; if (Val.Str()) { Int = atoi(Val.Str()); } else if (Val.Type == GV_INT32) { Int = Val.Value.Int; } int IntVal = Var.Value.Int; switch (Op) { case OP_LIKE: // for lack anything better case OP_EQUAL: { bool m = Int == IntVal; if (Log) Log->Print("\t\t\t%i == %i = %i\n", Int, IntVal, m); return m; } case OP_NOT_EQUAL: { bool m = Int != IntVal; if (Log) Log->Print("\t\t\t%i != %i = %i\n", Int, IntVal, m); return m; } case OP_LESS_THAN: { bool m = Int < IntVal; if (Log) Log->Print("\t\t\t%i < %i = %i\n", Int, IntVal, m); return m; } case OP_LESS_THAN_OR_EQUAL: { bool m = Int <= IntVal; if (Log) Log->Print("\t\t\t%i <= %i = %i\n", Int, IntVal, m); return m; } case OP_GREATER_THAN: { bool m = Int > IntVal; if (Log) Log->Print("\t\t\t%i > %i = %i\n", Int, IntVal, m); return m; } case OP_GREATER_THAN_OR_EQUAL: { bool m = Int >= IntVal; if (Log) Log->Print("\t\t\t%i >= %i = %i\n", Int, IntVal, m); return m; } } break; } case GV_DATETIME: { LDateTime Temp; LDateTime *DVal; if (Val.Type == GV_DATETIME) { DVal = Val.Value.Date; } else if (Val.Type == GV_STRING) { Temp.Set(Val.Str()); DVal = &Temp; } else break; LDateTime *DVar = Var.Value.Date; if (DVal && DVar) { bool Less = *DVar < *DVal; bool Greater = *DVar > *DVal; bool Equal = !Less && !Greater; char LogVal[64]; char LogVar[64]; if (Log) { DVal->Get(LogVal, sizeof(LogVal)); DVar->Get(LogVar, sizeof(LogVar)); } switch (Op) { case OP_LIKE: case OP_EQUAL: { if (Log) Log->Print("\t\t\t%s = %s == %i\n", LogVar, LogVal, Equal); return Equal; } case OP_NOT_EQUAL: { if (Log) Log->Print("\t\t\t%s != %s == %i\n", LogVar, LogVal, !Equal); return !Equal; } case OP_LESS_THAN: { if (Log) Log->Print("\t\t\t%s <= %s == %i\n", LogVar, LogVal, Less); return Less; } case OP_LESS_THAN_OR_EQUAL: { if (Log) Log->Print("\t\t\t%s < %s == %i\n", LogVar, LogVal, Less || Equal); return Less || Equal; } case OP_GREATER_THAN: { if (Log) Log->Print("\t\t\t%s > %s == %i\n", LogVar, LogVal, Greater); return Greater; } case OP_GREATER_THAN_OR_EQUAL: { if (Log) Log->Print("\t\t\t%s >= %s == %i\n", LogVar, LogVal, Greater || Equal); return Greater || Equal; } } } break; } default: { if (Log) Log->Print("\t\t\tUnknown data type %i.\n", Var.Type); break; } } } return false; } ThingUi *FilterCondition::DoUI(MailContainer *c) { return NULL; } ////////////////////////////////////////////////////////////// // #define OPT_Action "Action" #define OPT_Type "Type" #define OPT_Arg1 "Arg1" #define IDC_TYPE_CBO 2000 #define IDC_ARG_EDIT 2001 #define IDC_BROWSE_ARG 2002 FilterAction::FilterAction(LDataStoreI *Store) { Type = ACTION_MOVE_TO_FOLDER; TypeCbo = 0; ArgEdit = 0; Btn = 0; } FilterAction::~FilterAction() { DeleteObj(TypeCbo); DeleteObj(ArgEdit); DeleteObj(Btn); } int FilterAction::OnNotify(LViewI *c, LNotification n) { switch (c->GetId()) { case IDC_TYPE_CBO: { Type = (FilterActionTypes) c->Value(); break; } case IDC_ARG_EDIT: { Arg1.Reset(NewStr(c->Name())); break; } case IDC_BROWSE_ARG: { if (ArgEdit) ArgEdit->Name(Arg1); break; } } return 0; } void FilterAction::OnMeasure(LPoint *Info) { LListItem::OnMeasure(Info); if (Select()) Info->y += 2; } bool FilterAction::Select() { return LListItem::Select(); } void FilterAction::OnPaintColumn(LItem::ItemPaintCtx &Ctx, int i, LItemColumn *c) { LListItem::OnPaintColumn(Ctx, i, c); if (LListItem::Select() && !TypeCbo && !ArgEdit) Select(true); else if (i == 0 && TypeCbo) TypeCbo->SetPos(*GetPos(i)); else if (i == 1 && ArgEdit) ArgEdit->SetPos(*GetPos(i)); else if (i == 2 && Btn) Btn->SetPos(*GetPos(i)); } void FilterAction::Select(bool b) { LListItem::Select(b); if (b) { LList *Lst = LListItem::GetList(); if (Lst && Lst->IsAttached()) { LRect *r = GetPos(0); if (!TypeCbo) { TypeCbo = new LCombo(IDC_TYPE_CBO, r->x1, r->y1, r->X(), r->Y(), 0); for (int i=0; ActionNames[i].Id; i++) TypeCbo->Insert(LLoadString(ActionNames[i].Id)); TypeCbo->Attach(Lst); } TypeCbo->Value(Type); TypeCbo->SetPos(*r); r = GetPos(1); if (!ArgEdit) { ArgEdit = new LEdit(IDC_ARG_EDIT, r->x1, r->y1, r->X(), r->Y(), 0); ArgEdit->Attach(Lst); } ArgEdit->Name(Arg1); ArgEdit->SetPos(*r); r = GetPos(2); if (!Btn) { Btn = new LButton(IDC_BROWSE_ARG, r->x1, r->y1, r->X(), r->Y(), "..."); Btn->Attach(Lst); } Btn->SetPos(*r); } } else { DeleteObj(TypeCbo); DeleteObj(ArgEdit); DeleteObj(Btn); } } const char *FilterAction::GetText(int Col) { switch (Col) { case 0: return (char*)LLoadString(ActionNames[Type].Id); break; case 1: return Arg1; break; case 2: break; } return 0; } bool FilterAction::Get(LXmlTag *t) { if (!t) return false; // Obj -> XML t->SetAttr(OPT_Type, Type); t->SetAttr(OPT_Arg1, Arg1); return true; } bool FilterAction::Set(LXmlTag *t) { if (!t) return false; // XML -> Obj Type = (FilterActionTypes) t->GetAsInt(OPT_Type); Arg1.Reset(NewStr(t->GetAttr(OPT_Arg1))); return true; } LDataPropI &FilterAction::operator =(LDataPropI &p) { FilterAction *c = dynamic_cast(&p); if (c) { Type = c->Type; Arg1.Reset(NewStr(c->Arg1)); } return *this; } ThingUi *FilterAction::DoUI(MailContainer *c) { return 0; } const char *GetFileName(const char *Path) { if (Path) { auto d = strrchr(Path, DIR_CHAR); if (d) return d + 1; else return Path; } return 0; } bool CollectAttachmentsByPattern(Mail *m, char *Pattern, List &Files) { Files.Empty(); if (m) { List Attachments; if (m->GetAttachments(&Attachments)) { LToken p(Pattern, " ,;"); for (auto a: Attachments) { bool Match = true; for (unsigned i=0; Match && iGetName()); if (d) Match = MatchStr(p[i], d); } if (Match) Files.Insert(a); } } } return Files[0] != 0; } Mail *GetTemplateMail(ScribeWnd *App, char *TemplateMsgId) { ScribeFolder *Templates = App->GetFolder(FOLDER_TEMPLATES); if (Templates) { // Mail *Template = 0; for (auto t: Templates->Items) { Mail *m = t->IsMail(); if (m) { auto MsgId = m->GetMessageId(); if (MsgId && strcmp(MsgId, TemplateMsgId) == 0) { return m; break; } } } } return 0; } class FilterScribeDom : public ScribeDom { public: FilterScribeDom(ScribeWnd *a) : ScribeDom(a) { } bool GetVariant(const char *Name, LVariant &Value, const char *Array = 0) { if (!Name) return false; if (!_stricmp(Name, "file")) { LVariant v; if (!GetValue(Array, v)) return false; char p[MAX_PATH_LEN], fn[32]; do { sprintf_s(fn, sizeof(fn), "file_%d.tmp", LRand()); LMakePath(p, sizeof(p), ScribeTempPath(), fn); } while (LFileExists(p)); LFile f; if (f.Open(p, O_WRITE)) { switch (v.Type) { case GV_INT32: f.Print("%i", v.Value.Int); break; case GV_INT64: f.Print(LPrintfInt64, v.Value.Int64); break; case GV_BOOL: f.Print("%s", v.Value.Bool ? "true" : "false"); break; case GV_DOUBLE: f.Print("%f", v.Value.Dbl); break; case GV_STRING: case GV_WSTRING: f.Print("%s", v.Str()); break; case GV_BINARY: f.Write(v.Value.Binary.Data, v.Value.Binary.Length); break; case GV_DATETIME: v.Value.Date->Get(fn, sizeof(fn)); f.Write(fn, strlen(fn)); break; default: f.Print("Unsupported type."); break; } f.Close(); Value = p; return true; } } return ScribeDom::GetVariant(Name, Value, Array); } }; bool FilterAction::Do(Filter *F, ScribeWnd *App, Mail *&m, LStream *Log) { bool Status = false; if (!F || !App || !m) { LAssert(!"Param error."); return false; } switch (Type) { case ACTION_MOVE_TO_FOLDER: { ScribeFolder *Folder = App->GetFolder(Arg1); if (Folder) { LArray Items; Items.Add(m); Status = Folder->MoveTo(Items); m = Items[0]->IsMail(); if (Log) Log->Print("\tACTION_MOVE_TO_FOLDER(%s) = %i.\n", Arg1.Get(), Status); } else if (Log) { Log->Print("\tACTION_MOVE_TO_FOLDER(%s) failed, folder missing.\n", Arg1.Get()); } break; } case ACTION_COPY: { ScribeFolder *Folder = App->GetFolder(Arg1); if (Folder) { LArray Items; Items.Add(m); Status = Folder->MoveTo(Items, true); m = Items[0]->IsMail(); if (Log) Log->Print("\tACTION_COPY(%s) = %i.\n", Arg1.Get(), Status); } else if (Log) { Log->Print("\tACTION_COPY(%s) failed, folder missing.\n", Arg1.Get()); } break; } case ACTION_EXPORT: { LFile::Path p(Arg1); if (!p.IsFolder()) { if (Log) Log->Print("\tACTION_EXPORT(%s) failed, folder missing.\n", Arg1.Get()); break; } auto Fn = m->GetDropFileName(); p += LGetLeaf(Fn); LAutoPtr out(new LFile); if (!out->Open(p, O_WRITE)) { if (Log) Log->Print("\tACTION_EXPORT(%s) failed: couldn't open file: %s.\n", Arg1.Get(), p.GetFull().Get()); break; } if (!m->Export(m->AutoCast(out), sMimeMessage)) { if (Log) Log->Print("\tACTION_EXPORT(%s) failed: couldn't export file.\n", Arg1.Get()); } break; } case ACTION_DELETE: { bool Local = ValidStr(Arg1) ? stristr(Arg1, "local") != 0 : true; bool Server = stristr(Arg1, "server") != 0; if (Server) { ScribeAccount *a = m->GetAccountSentTo(); if (!a) break; auto Uid = m->GetServerUid(); if (Uid.Str()) { if (Log) Log->Print("\tACTION_DELETE - Setting '%s' to be deleted on the server (Uid=%s)\n", m->GetSubject(), Uid.Str()); a->Receive.DeleteAsSpam(Uid.Str()); m->SetServerUid(Uid = NULL); } else { LVariant Uid; if (m->GetValue("InternetHeader[X-UIDL]", Uid)) { if (Log) Log->Print("\tACTION_DELETE - Setting '%s' to be deleted on the server (Uid=%s)\n", m->GetSubject(), Uid.Str()); a->Receive.DeleteAsSpam(Uid.Str()); } } } if (Local) { bool DeleteStatus = m->OnDelete(); if (Log) Log->Print("\tACTION_DELETE(%s) status %i.\n", Arg1.Get(), DeleteStatus); /* ScribeFolder *Folder = App->GetFolder(FOLDER_TRASH); if (Folder) { Thing *t = m; Status = Folder->MoveTo(t); m = t->IsMail(); if (Log) Log->Print("\tACTION_DELETE(%s) = %i.\n", Arg1.Get(), Status); } else if (Log) { Log->Print("\tACTION_DELETE(%s) failed, trash missing.\n", Arg1.Get()); } */ } break; } case ACTION_SET_READ: { bool Read = true; bool NotNew = false; if (ValidStr(Arg1)) { if (IsDigit(*Arg1)) { // boolean number Read = atoi(Arg1) != 0; } else if (stristr(Arg1, "true")) { Read = true; } else if (stristr(Arg1, "false")) { Read = false; } if (stristr(Arg1, "notnew")) { NotNew = true; } } int f = m->GetFlags(); if (Read) SetFlag(f, MAIL_READ); else ClearFlag(f, MAIL_READ); m->SetFlags(f); if (Log) Log->Print("\tACTION_SET_READ(%s)\n", Arg1.Get()); if (NotNew) { List Objs; Objs.Insert(m); App->OnNewMail(&Objs, false); } break; } case ACTION_LABEL: { LVariant v; if (!F || !F->GetValue(Arg1, v)) v = Arg1; m->SetVariant("Label", v); break; } case ACTION_EMPTY_FOLDER: { if (F->App && Arg1) { ScribeFolder *Folder = F->App->GetFolder(Arg1); if (Folder) { Folder->LoadThings(); List m; Thing *t; while ((t = Folder->Items[0])) { if (t->IsMail()) { m.Insert(t->IsMail()); } if (Folder->DeleteThing(t)) { DeleteObj(t); } } F->App->OnNewMail(&m, false); Folder->OnUpdateUnRead(0, true); if (Log) Log->Print("\tACTION_EMPTY_FOLDER(%s)\n", Arg1.Get()); } } break; } case ACTION_MARK_AS_SPAM: { m->DeleteAsSpam(App); break; } case ACTION_PRINT: { LPrinter Info; #ifdef _MSC_VER #pragma message ("Warning: ACTION_PRINT not implemented.") #endif /* FIXME if (Info.Serialize(Arg1, false)) { App->ThingPrint(m, &Info); } */ break; } case ACTION_PLAY_SOUND: { LPlaySound(Arg1, true); break; } case ACTION_EXECUTE: { FilterScribeDom dom(App); dom.Email = m; dom.Fil = F; LAutoString cmd(ScribeInsertFields(Arg1, &dom)); if (cmd) { const char *s = cmd; LAutoString exe(LTokStr(s)); LExecute(exe, s); } break; } case ACTION_OPEN: { m->DoUI(); break; } case ACTION_MARK: { if (_stricmp(Arg1, "false") == 0) { // unmark the item... m->SetMarkColour(0); } else { // parse out RGB LToken T(Arg1, ","); if (T.Length() == 3) { // we have an RGB, so set it baby uint32_t c = Rgb32(atoi(T[0]), atoi(T[1]), atoi(T[2])); m->SetMarkColour(c); } else { uint32_t c = Rgb32(0, 0, 255); m->SetMarkColour(c); } } break; } case ACTION_REPLY: { if (Arg1) { const char *s = Arg1; char *TemplateMsgId = LTokStr(s); SkipSep(s); char *ReplyAll = LTokStr(s); SkipSep(s); char *MarkReplied = LTokStr(s); Mail *Template = GetTemplateMail(App, TemplateMsgId); if (Template) { Thing *t = App->CreateThingOfType(MAGIC_MAIL); if (t) { Mail *n = t->IsMail(); if (n) { bool MarkOriginal = MarkReplied && atoi(MarkReplied); // Prepare mail... n->OnReply(m, ValidStr(ReplyAll)?atoi(ReplyAll)!=0:false, MarkOriginal); n->SetFlags(m->GetFlags() | MAIL_READY_TO_SEND); if (ValidStr(Template->GetSubject())) { n->SetSubject(Template->GetSubject()); } n->SetBody(ScribeInsertFields(Template->GetBody(), F)); // Save it... n->Save(0); // Send it... if not offline. LVariant Offline; App->GetOptions()->GetValue(OPT_WorkOffline, Offline); if (!Offline.CastInt32()) { App->PostEvent(M_COMMAND, IDM_SEND_MAIL, 0); } } else DeleteObj(t); } } DeleteArray(TemplateMsgId); DeleteArray(ReplyAll); DeleteArray(MarkReplied); } break; } case ACTION_FORWARD: { const char *s = Arg1; char *TemplateMsgId = LTokStr(s); SkipSep(s); char *Email = LTokStr(s); SkipSep(s); char *Attach = LTokStr(s); SkipSep(s); char *MarkForwarded = LTokStr(s); bool Attachments = Attach ? atoi(Attach)!=0 : true; if (ValidStr(Email)) { Thing *t = App->CreateThingOfType(MAGIC_MAIL); if (t) { Mail *n = t->IsMail(); if (n) { bool MarkOriginal = MarkForwarded && atoi(MarkForwarded); // Setup email... Mail *Template = TemplateMsgId ? GetTemplateMail(App, TemplateMsgId) : 0; if (Template) { n->SetSubject(Template->GetSubject()); if (ValidStr(Template->GetBody())) { n->SetBody(ScribeInsertFields(Template->GetBody(), F)); } if (Attachments) { List Att; m->GetAttachments(&Att); for (auto a: Att) { n->AttachFile(new Attachment(App, a)); } } } else { n->OnForward(m, MarkOriginal, Attachments); } n->SetFlags(m->GetFlags() | MAIL_READY_TO_SEND); LDataPropI *Addr = n->GetTo()->Create(n->GetObject()->GetStore()); if (Addr) { LVariant v; if (F->GetValue(Email, v)) { Addr->SetStr(FIELD_EMAIL, v.Str()); } else { Addr->SetStr(FIELD_EMAIL, Email); } n->GetTo()->Insert(Addr); } // Save it... n->Save(0); // Send it... if not offline. LVariant Offline; App->GetOptions()->GetValue(OPT_WorkOffline, Offline); if (!Offline.CastInt32()) { App->PostEvent(M_COMMAND, IDM_SEND_MAIL, 0); } } else DeleteObj(t); } } DeleteArray(Email); DeleteArray(Attach); DeleteArray(MarkForwarded); break; } case ACTION_BOUNCE: { const char *s = Arg1; char *Email = LTokStr(s); SkipSep(s); char *Attach = LTokStr(s); SkipSep(s); char *Mark = LTokStr(s); if (ValidStr(Email)) { Thing *t = App->CreateThingOfType(MAGIC_MAIL); if (t) { Mail *n = t->IsMail(); if (n) { bool Attachments = Attach && atoi(Attach); bool MarkOriginal = Mark && atoi(Mark); // Setup email... n->OnBounce(m, MarkOriginal, Attachments); n->SetFlags(m->GetFlags() | MAIL_READY_TO_SEND); LDataPropI *Addr = n->GetTo()->Create(n->GetObject()->GetStore()); if (Addr) { LVariant v; if (F->GetValue(Email, v)) { Addr->SetStr(FIELD_EMAIL, v.Str()); } else { Addr->SetStr(FIELD_EMAIL, Email); } n->GetTo()->Insert(Addr); } // Save it... n->Save(0); // Send it... if not offline. LVariant Offline; App->GetOptions()->GetValue(OPT_WorkOffline, Offline); if (!Offline.CastInt32()) { App->PostEvent(M_COMMAND, IDM_SEND_MAIL, 0); } } else DeleteObj(t); } } DeleteArray(Email); DeleteArray(Attach); DeleteArray(Mark); break; } case ACTION_SAVE_ATTACHMENTS: { const char *arg = Arg1; char *Dir = LTokStr(arg); SkipSep(arg); char *Types = LTokStr(arg); List Files; if (CollectAttachmentsByPattern(m, Types, Files)) { for (auto a: Files) { auto d = GetFileName(a->GetName()); if (d) { char Path[256]; LMakePath(Path, sizeof(Path), Dir, d); a->SaveTo(Path); } } } DeleteArray(Dir); DeleteArray(Types); break; } case ACTION_DELETE_ATTACHMENTS: { List Files; if (CollectAttachmentsByPattern(m, Arg1, Files)) { for (auto a: Files) { m->DeleteAttachment(a); } } break; } case ACTION_CHANGE_CHARSET: { m->SetBodyCharset(Arg1); break; } } return Status; } int CsCmp(LCharset **a, LCharset **b) { return _stricmp((*a)->Charset, (*b)->Charset); } void FilterAction::DescribeHtml(Filter *Flt, LStream &s) { s.Print("%s ", GetText(0)); switch (Type) { case ACTION_MOVE_TO_FOLDER: { ScribeFolder *Folder = Flt->App->GetFolder(Arg1); if (Folder) s.Print("\"%s\"\n", Arg1.Get()); else s.Print("\"%s\"\n", Arg1.Get()); break; } case ACTION_DELETE: break; case ACTION_PRINT: break; case ACTION_PLAY_SOUND: break; case ACTION_OPEN: break; case ACTION_EXECUTE: break; case ACTION_MARK: break; case ACTION_SET_READ: break; case ACTION_LABEL: break; case ACTION_EMPTY_FOLDER: break; case ACTION_MARK_AS_SPAM: break; case ACTION_REPLY: break; case ACTION_FORWARD: break; case ACTION_BOUNCE: break; case ACTION_SAVE_ATTACHMENTS: break; case ACTION_DELETE_ATTACHMENTS: break; case ACTION_CHANGE_CHARSET: break; case ACTION_COPY: break; case ACTION_EXPORT: break; } } void FilterAction::Browse(ScribeWnd *App, LView *Parent) { if (!Parent) return; switch (Type) { default: LAssert(0); break; case ACTION_MOVE_TO_FOLDER: case ACTION_COPY: case ACTION_EMPTY_FOLDER: { - FolderDlg Dlg(Parent, App, MAGIC_MAIL); - if (Dlg.DoModal()) - Arg1.Reset(NewStr(Dlg.Get())); + auto Dlg = new FolderDlg(Parent, App, MAGIC_MAIL); + Dlg->DoModal([this, Dlg](auto dlg, auto id) + { + if (id) + Arg1.Reset(NewStr(Dlg->Get())); + delete dlg; + }); break; } case ACTION_EXPORT: { - LFileSelect s; - s.Parent(Parent); - if (s.OpenFolder()) - Arg1.Reset(NewStr(s.Name())); + auto s = new LFileSelect(Parent); + s->OpenFolder([&](auto dlg, auto status) + { + if (status) + Arg1.Reset(NewStr(s->Name())); + delete dlg; + }); break; } case ACTION_DELETE: { auto RClick = new LSubMenu; if (RClick) { RClick->AppendItem("Local (default)", IDM_LOCAL, true); RClick->AppendItem("From Server", IDM_SERVER, true); RClick->AppendItem("Local and from Server", IDM_LOCAL_AND_SERVER, true); LMouse m; if (Parent->GetMouse(m, true)) { switch (RClick->Float(Parent, m.x, m.y)) { case IDM_LOCAL: { Arg1.Reset(NewStr("local")); break; } case IDM_SERVER: { Arg1.Reset(NewStr("server")); break; } case IDM_LOCAL_AND_SERVER: { Arg1.Reset(NewStr("local,server")); break; } } } DeleteObj(RClick); } break; } case ACTION_OPEN: { // no configuration break; } case ACTION_SET_READ: { auto RClick = new LSubMenu; if (RClick) { RClick->AppendItem("Read", IDM_TRUE, true); RClick->AppendItem("Unread", IDM_FALSE, true); RClick->AppendItem("Unread But Not New", IDM_NOTNEW, true); LMouse m; if (Parent->GetMouse(m, true)) { switch (RClick->Float(Parent, m.x, m.y)) { case IDM_TRUE: { Arg1.Reset(NewStr("true")); break; } case IDM_FALSE: { Arg1.Reset(NewStr("false")); break; } case IDM_NOTNEW: { Arg1.Reset(NewStr("false,notnew")); break; } } } DeleteObj(RClick); } break; } case ACTION_MARK: { auto RClick = new LSubMenu; if (RClick) { BuildMarkMenu(RClick, MS_One, 0); LMouse m; if (Parent->GetMouse(m, true)) { int Result = RClick->Float(Parent, m.x, m.y); if (Result == IDM_UNMARK) { Arg1.Reset(NewStr("False")); } else if (Result >= IDM_MARK_BASE) { char s[32]; sprintf_s(s, sizeof(s), "%i,%i,%i", R32(MarkColours32[Result-IDM_MARK_BASE]), G32(MarkColours32[Result-IDM_MARK_BASE]), B32(MarkColours32[Result-IDM_MARK_BASE])); Arg1.Reset(NewStr(s)); } } } break; } case ACTION_PRINT: { LPrinter Info; #ifdef _MSC_VER #pragma message ("Warning: ACTION_PRINT not implemented.") #endif /* Info.Serialize(Arg1, false); if (Info.Browse(Parent)) { Info.Serialize(Arg1, true); } */ break; } case ACTION_PLAY_SOUND: case ACTION_EXECUTE: { - LFileSelect Select; - - Select.Parent(Parent); + auto Select = new LFileSelect(Parent); + + Select->Parent(Parent); if (Type == ACTION_PLAY_SOUND) { - Select.Type("Wave files", "*.wav"); + Select->Type("Wave files", "*.wav"); } else { - Select.Type("Executables", "*.exe"); - Select.Type("All Files", LGI_ALL_FILES); + Select->Type("Executables", "*.exe"); + Select->Type("All Files", LGI_ALL_FILES); } - Select.Name(Arg1); - - if (Select.Open()) + Select->Name(Arg1); + + Select->Open([this](auto dlg, auto id) { - Arg1.Reset(NewStr(Select.Name())); - } + if (id) + Arg1.Reset(NewStr(dlg->Name())); + delete dlg; + }); break; } case ACTION_REPLY: { - BrowseReply Dlg(App, Parent, Arg1); - if (Dlg.DoModal()) + auto Dlg = new BrowseReply(App, Parent, Arg1); + Dlg->DoModal([this, Dlg](auto dlg, auto id) { - Arg1.Reset(NewStr(Dlg.Arg)); - } + if (id) + Arg1.Reset(NewStr(Dlg->Arg)); + delete dlg; + }); break; } case ACTION_FORWARD: { - BrowseForward Dlg(App, Parent, Arg1, true); - if (Dlg.DoModal()) + auto Dlg = new BrowseForward(App, Parent, Arg1, true); + Dlg->DoModal([this, Dlg](auto dlg, auto id) { - Arg1.Reset(NewStr(Dlg.Arg)); - } + if (id) + Arg1.Reset(NewStr(Dlg->Arg)); + delete dlg; + }); break; } case ACTION_BOUNCE: { - BrowseForward Dlg(App, Parent, Arg1, false); - if (Dlg.DoModal()) + auto Dlg = new BrowseForward(App, Parent, Arg1, false); + Dlg->DoModal([this, Dlg](auto dlg, auto id) { - Arg1.Reset(NewStr(Dlg.Arg)); - } + if (id) + Arg1.Reset(NewStr(Dlg->Arg)); + delete dlg; + }); break; } case ACTION_SAVE_ATTACHMENTS: { - BrowseSaveAttach Dlg(App, Parent, Arg1); - if (Dlg.DoModal()) + auto Dlg = new BrowseSaveAttach(App, Parent, Arg1); + Dlg->DoModal([this, Dlg](auto dlg, auto id) { - Arg1.Reset(NewStr(Dlg.Arg)); - } + if (id) + Arg1.Reset(NewStr(Dlg->Arg)); + delete dlg; + }); break; } case ACTION_CHANGE_CHARSET: { auto s = new LSubMenu; if (s) { LArray Cs; for (LCharset *c = LGetCsList(); c->Charset; c++) { Cs.Add(c); } Cs.Sort(CsCmp); for (unsigned i=0; iCharset[0] != Cs[i]->Charset[0]) break; n++; } if (n > 1) { char a[64]; char *One = LSeekUtf8(Cs[i]->Charset, 1); ssize_t Len = One - Cs[i]->Charset; memcpy(a, Cs[i]->Charset, Len); strcpy_s(a + Len, sizeof(a) - Len, "..."); auto Sub = s->AppendSub(a); if (Sub) { for (unsigned k=0; kAppendItem(Cs[i+k]->Charset, i+k+1, Cs[i+k]->IsAvailable()); } i += n - 1; } } else { s->AppendItem(Cs[i]->Charset, i+1, Cs[i]->IsAvailable()); } } LMouse m; Parent->GetMouse(m, true); int Result = s->Float(Parent, m.x, m.y, true); if (Result) { Result--; if (Result >= 0 && Result < (int)Cs.Length()) { Arg1.Reset(NewStr(Cs[Result]->Charset)); } } DeleteObj(s); } break; } } } /////////////////////////////////////////////////////////////// int Filter::MaxIndex = -1; Filter::Filter(ScribeWnd *window, LDataI *object) : Thing(window, object) { DefaultObject(object); d = new FilterPrivate; Ui = 0; Current = 0; IgnoreCheckEvents = true; ChkIncoming = new LListItemCheckBox(this, 2, GetIncoming()!=0); ChkOutgoing = new LListItemCheckBox(this, 3, GetOutgoing()!=0); ChkInternal = new LListItemCheckBox(this, 4, GetInternal()!=0); IgnoreCheckEvents = false; } Filter::~Filter() { Empty(); DeleteObj(d); } bool Filter::GetFormats(bool Export, LString::Array &MimeTypes) { MimeTypes.Add(sTextXml); return MimeTypes.Length() > 0; } char *Filter::GetDropFileName() { if (!DropFileName) { auto Nm = GetName(); char f[256]; if (Nm) { LUtf8Ptr o(f); for (LUtf8Ptr i(Nm); (uint32_t)i; i++) { if (!strchr(LGI_IllegalFileNameChars, (uint32_t)i)) o.Add(i); } o.Add(0); } else strcpy_s(f, sizeof(f), "Filter"); strcat_s(f, sizeof(f), ".xml"); DropFileName.Reset(NewStr(f)); } return DropFileName; } bool Filter::GetDropFiles(LString::Array &Files) { char Tmp[MAX_PATH_LEN]; LMakePath(Tmp, sizeof(Tmp), ScribeTempPath(), GetDropFileName()); LAutoPtr Out(new LFile); if (!Out->Open(Tmp, O_WRITE)) return false; if (!Export(AutoCast(Out), sTextXml)) return false; Files.Add(Tmp); return true; } Thing::IoProgress Filter::Import(IoProgressImplArgs) { if (Stricmp(mimeType, sTextXml) && Stricmp(mimeType, sMimeXml)) IoProgressNotImpl(); LXmlTree Tree; LXmlTag r; if (!Tree.Read(&r, stream)) IoProgressError("Xml parse error."); if (!r.IsTag("Filter")) IoProgressError("No filter tag."); Empty(); LXmlTag *t = r.GetChildTag("Name"); if (t && t->GetContent()) SetName(t->GetContent()); SetIndex(r.GetAsInt("index")); if ((t = r.GetChildTag(ELEMENT_CONDITIONS))) { LStringPipe p; if (Tree.Write(t, &p)) { LAutoString s(p.NewStr()); ConditionsCache.Reset(); SetConditionsXml(s); } if ((t = r.GetChildTag("Actions"))) { LStringPipe p; if (Tree.Write(t, &p)) { LAutoString s(p.NewStr()); SetActionsXml(s); } else IoProgressError("Xml write failed."); } } IoProgressSuccess(); } Thing::IoProgress Filter::Export(IoProgressImplArgs) { if (Stricmp(mimeType, sMimeXml)) return Store3NotImpl; LXmlTag r("Filter"); LXmlTag *t; if ((t = r.CreateTag("Name"))) t->SetContent(GetName()); r.SetAttr("index", GetIndex()); LAutoPtr Cond = Parse(false); r.InsertTag(Cond.Release()); LAutoPtr Act = Parse(true); r.InsertTag(Act.Release()); LXmlTree tree; return tree.Write(&r, stream) ? Store3Success : Store3Error; } /// This filters a list of email. The email will have it's NewEmail state set to /// one of three things: /// If the email is not filtered then: /// Mail::NewEmailBayes /// If the email is filtered but not set to !NEW then: /// Mail::NewEmailGrowl /// If the email is filtered AND set to !NEW then: /// Mail::NewMailNone int Filter::ApplyFilters(LView *Parent, List &Filters, List &Email) { int Status = 0; ScribeWnd *App = Filters.Length() > 0 ? Filters[0]->App : NULL; if (!App) return 0; bool Logging = App->LogFilterActivity(); LStream *LogStream = NULL; if (Logging) LogStream = App->ShowScriptingConsole(); LAutoPtr Prog; if (Parent && Prog.Reset(new LProgressDlg(Parent))) { Prog->SetRange(Email.Length()); Prog->SetDescription("Filtering..."); Prog->SetType("email"); } for (auto m: Email) { bool Act = false; bool Stop = false; m->IncRef(); for (auto f: Filters) { if (Stop) break; if (f->Test(m, Stop, LogStream)) { f->DoActions(m, Stop, LogStream); Act = true; } } if (Act) { Status++; if (m && m->NewEmail == Mail::NewEmailFilter) { m->NewEmail = Mail::NewEmailGrowl; } } else if (m && m->NewEmail == Mail::NewEmailFilter) { m->NewEmail = Mail::NewEmailBayes; } m->DecRef(); m = NULL; if (Prog) { Prog->Value(Prog->Value() + 1); if (Prog->IsCancelled()) break; } } return Status; } Filter *Filter::GetFilterAt(int Index) { ScribeFolder *f = GetFolder(); if (f) { for (auto t: f->Items) { Filter *f = t->IsFilter(); if (f && f->GetIndex() == Index) { return f; } } } return 0; } enum TermType { TermString, TermVariant }; class ExpTerm { public: TermType Type; LVariant Value; ExpTerm(TermType t) { Type = t; } }; bool Filter::Evaluate(char *str, LVariant &v) { char *BufStr = NewStr(str); char *s = BufStr; if (s) { List Terms; const char *Ws = " \t\r\n"; while (s && *s) { while (*s && strchr(Ws, *s)) s++; if (*s && *s == '\"') { char *Start = ++s; char *In = s; char *Out = s; while (*In) { if (In[0] == '\"') { if (In[1] == '\"') { // Quote *Out++ = '\"'; In += 2; } else { // End of string In++; break; } } else { *Out++ = *In++; } } *Out++ = 0; s = In; ExpTerm *t; Terms.Insert(t = new ExpTerm(TermString)); if (t) { t->Value = Start; } } else { char *Start = s; while (*s && !strchr(Ws, *s)) s++; while (*s && strchr(Ws, *s)) s++; char *Src = NewStr(Start, s-Start); if (Src) { ExpTerm *t; Terms.Insert(t = new ExpTerm(TermVariant)); if (t) { if (GetValue(Start, t->Value)) { switch (t->Value.Type) { default: break; case GV_BINARY: case GV_LIST: case GV_DOM: case GV_VOID_PTR: { v = t->Value; DeleteArray(BufStr); DeleteArray(Src); Terms.DeleteObjects(); return true; } } } else { t->Type = TermString; t->Value = Src; } } DeleteArray(Src); } } } LStringPipe Out; auto It = Terms.begin(); ExpTerm *t = *It; if (t) { if (Terms.Length() > 1) { // Collapse terms for (; t; t=*(++It)) { char Buf[128]; switch (t->Value.Type) { default: break; case GV_INT32: { sprintf_s(Buf, sizeof(Buf), "%i", t->Value.Value.Int); Out.Push(Buf); break; } case GV_INT64: { sprintf_s(Buf, sizeof(Buf), LPrintfInt64, t->Value.Value.Int64); Out.Push(Buf); break; } case GV_BOOL: { sprintf_s(Buf, sizeof(Buf), "%i", (int)t->Value.Value.Bool); Out.Push(Buf); break; } case GV_DOUBLE: { sprintf_s(Buf, sizeof(Buf), "%g", t->Value.Value.Dbl); Out.Push(Buf); break; } case GV_STRING: { if (t->Value.Str()) Out.Push(t->Value.Str()); break; } case GV_DATETIME: { t->Value.Value.Date->Get(Buf, sizeof(Buf)); Out.Push(Buf); break; } case GV_BINARY: case GV_LIST: case GV_DOM: case GV_NULL: case GV_VOID_PTR: { break; } } } v.OwnStr(Out.NewStr()); } else { v = t->Value; } } Terms.DeleteObjects(); } DeleteArray(BufStr); return true; } bool Filter::SetVariant(const char *Name, LVariant &Value, const char *Array) { ScribeDomType Field = StrToDom(Name); switch (Field) { case SdName: SetName(Value.Str()); break; case SdConditionsXml: ConditionsCache.Reset(); SetConditionsXml(Value.Str()); break; case SdActionsXml: SetActionsXml(Value.Str()); break; default: return false; } return true; } bool Filter::CallMethod(const char *MethodName, LVariant *ReturnValue, LArray &Args) { ScribeDomType Method = StrToDom(MethodName); switch (Method) { case SdAddCondition: // Type: (String Feild, String Op, String Value) { if (Args.Length() != 3) { LgiTrace("%s:%i - SdAddCondition: wrong number of parameters %i (expecting 3)\n", _FL, Args.Length()); break; } const char *Field = Args[0]->Str(); const char *Op = Args[1]->Str(); const char *Value = Args[2]->Str(); if (!Field || !Op || !Value) { LgiTrace("%s:%i - SdAddCondition: Missing values.\n", _FL); break; } LAutoPtr t = Parse(false); LXmlTag *Cond; if (!t || !t->IsTag(ELEMENT_CONDITIONS) || (Cond = t->Children[0]) == NULL) { LgiTrace("%s:%i - SdAddCondition: Failed to parse conditions.\n", _FL); break; } if (!Cond->IsTag(ELEMENT_AND) && !Cond->IsTag(ELEMENT_OR)) { LgiTrace("%s:%i - SdAddCondition: Unexpected root operator.\n", _FL); break; } // Check that the condition doesn't already exist... for (auto c : Cond->Children) { if (c->IsTag(ELEMENT_CONDITION)) { char *CFeild = c->GetAttr(ATTR_FIELD); char *COp = c->GetAttr(ATTR_OP); char *CVal = c->GetAttr(ATTR_VALUE); if (!Stricmp(Field, CFeild) && !Stricmp(Op, COp) && !Stricmp(Value, CVal)) { return true; } } } LXmlTag *n = new LXmlTag(ELEMENT_CONDITION); if (!n) { LgiTrace("%s:%i - SdAddCondition: Alloc failed.\n", _FL); break; } n->SetAttr(ATTR_FIELD, Field); n->SetAttr(ATTR_OP, Op); n->SetAttr(ATTR_VALUE, Value); Cond->InsertTag(n); LXmlTree tree; LStringPipe p; if (!tree.Write(t, &p)) { LgiTrace("%s:%i - SdAddCondition: Failed to write XML.\n", _FL); break; } LAutoString a(p.NewStr()); ConditionsCache.Reset(); SetConditionsXml(a); SetDirty(true); return true; } case SdAddAction: // Type: (String ActionName, String Value) { if (Args.Length() != 2) { LgiTrace("%s:%i - SdAddAction: wrong number of parameters %i (expecting 3)\n", _FL, Args.Length()); break; } LString Action = Args[0]->CastString(); if (!Action) { LgiTrace("%s:%i - SdAddAction: Missing action name.\n", _FL); break; } FilterAction a(GetObject()->GetStore()); for (ActionName *an = ActionNames; an->Id; an++) { if (Action.Equals(an->Default)) { a.Type = (FilterActionTypes) (an - ActionNames); a.Arg1.Reset(NewStr(Args[1]->CastString())); AddAction(&a); return true; } } LgiTrace("%s:%i - SdAddAction: Action '%s' not found.\n", _FL, Action.Get()); return false; } case SdStopFiltering: // Type: () { if (d->Stop) { *d->Stop = true; return true; } else LgiTrace("%s:%i - No stop parameter to set.\n", _FL); return true; } case SdDoActions: // Type: (Mail Object) { if (Args.Length() == 1) { LDom *d = Args[0]->CastDom(); Mail *m = dynamic_cast(d); if (m) { bool Stop = false; return DoActions(m, Stop); } else LgiTrace("%s:%i - DoActions: failed to cast arg1 to Mail object.\n", _FL); } else LgiTrace("%s:%i - DoActions is expecting 1 argument, not %i.\n", _FL, Args.Length()); return true; } default: break; } return Thing::CallMethod(MethodName, ReturnValue, Args); } bool Filter::GetVariant(const char *Var, LVariant &Value, const char *Array) { ScribeDomType Fld = StrToDom(Var); switch (Fld) { case SdMail: // Type: Mail { if (!Current) return false; Value = *Current; break; } case SdScribe: // Type: ScribeWnd { Value = (LDom*)App; break; } case SdName: // Type: String { Value = GetName(); break; } case SdTestConditions: // Type: Bool { if (Current && *Current) { bool s; bool &Stop = d->Stop ? *d->Stop : s; Value = EvaluateXml(*Current, Stop, d->Log); } else return false; break; } case SdType: // Type: Int32 { Value = GetObject()->Type(); break; } case SdConditionsXml: // Type: String { Value = GetConditionsXml(); break; } case SdActionsXml: // Type: String { Value = GetActionsXml(); break; } case SdIndex: // Type: Int32 { Value = GetIndex(); break; } default: { return false; } } return true; } Thing &Filter::operator =(Thing &t) { Filter *f = t.IsFilter(); if (f) { if (GetObject() && f->GetObject()) { GetObject()->CopyProps(*f->GetObject()); } } return *this; } int Filter::Compare(LListItem *Arg, ssize_t Field) { Filter *a = dynamic_cast(Arg); if (a) { switch (Field) { case FIELD_FILTER_NAME: return a && GetName() ? _stricmp(GetName(), a->GetName()) : -1; case FIELD_FILTER_INDEX: return GetIndex() - a->GetIndex(); case FIELD_FILTER_INCOMING: return GetIncoming() - a->GetIncoming(); case FIELD_FILTER_OUTGOING: return GetOutgoing() - a->GetOutgoing(); } } return 0; } int Filter::GetImage(int Flags) { return ICON_FILTER; } void DisplayXml(LStream &s, char *xml) { char *start = xml; char *e; for (e = xml; *e; e++) { if (*e == '<') { s.Write(start, e - start); s.Print("<"); start = e + 1; } } s.Write(start, e - start); } void DescribeCondition(LStream &s, LXmlTag *t) { int i = 0; if (t->IsTag(ELEMENT_AND) || t->IsTag(ELEMENT_OR)) { for (auto c: t->Children) { LStringPipe p; DescribeCondition(p, c); LAutoString a(p.NewStr()); if (i) s.Print(" %s ", t->GetTag()); s.Print("(%s)", a.Get()); i++; } } else { // Condition char *f = t->GetAttr(ATTR_FIELD); char *v = t->GetAttr(ATTR_VALUE); int Not = t->GetAsInt(ATTR_NOT) > 0; const char *o = t->GetAttr(ATTR_OP); if (o && IsDigit(*o)) o = OpNames[atoi(o)]; s.Print("%s%s %s \"%s\"", Not ? "!" : "", f, o, v); } } LAutoString Filter::DescribeHtml() { LStringPipe p(256); p.Print("<style>\n" ".error { color: red; font-weight: bold; }\n" ".op { color: blue; }\n" ".var { color: #800; }\n" "pre { color: green; }\n" "</style>\n" "\n" "Name: %s
\n", GetName()); p.Print("Incoming: %i
\n", GetIncoming()); p.Print("Outgoing: %i
\n", GetOutgoing()); if (GetConditionsXml()) { LAutoPtr r = Parse(false); if (r && r->Children.Length()) { p.Print("Conditions:
    \n"); for (auto c: r->Children) { p.Print("
  • "); DescribeCondition(p, c); } p.Print("
\n"); } } if (GetActionsXml()) { LAutoPtr r = Parse(true); if (r && r->Children.Length()) { p.Print("Actions:
    \n"); for (auto c: r->Children) { if (!c->IsTag(ELEMENT_ACTION)) continue; LAutoPtr a(new FilterAction(GetObject()->GetStore())); if (a->Set(c)) { p.Print("
  • "); a->DescribeHtml(this, p); } } p.Print("
\n"); } } if (GetScript()) { LXmlTree t; LAutoString e(t.EncodeEntities(GetScript(), -1, "<>")); p.Print("Script:
%s
\n", e.Get()); } p.Print("\n"); return LAutoString(p.NewStr()); } void Filter::Empty() { if (GetObject()) { ConditionsCache.Reset(); SetConditionsXml(0); SetName(0); } } bool Filter::EvaluateTree(LXmlTag *n, Mail *m, bool &Stop, LStream *Log) { bool Status = false; if (n && n->GetTag() && m && m->GetObject()) { if (n->IsTag(ELEMENT_AND)) { if (Log) Log->Print("\tAnd {\n"); for (auto c: n->Children) { if (!EvaluateTree(c, m, Stop, Log)) { if (Log) Log->Print("\t} (false)\n"); return false; } } if (Log) Log->Print("\t} (true)\n"); Status = true; } else if (n->IsTag(ELEMENT_OR)) { if (Log) Log->Print("\tOr {\n"); for (auto c: n->Children) { if (EvaluateTree(c, m, Stop, Log)) { if (Log) Log->Print("\t} (true)\n"); return true; } } if (Log) Log->Print("\t} (false)\n"); } else if (n->IsTag(ELEMENT_CONDITION)) { FilterCondition *c = new FilterCondition; if (c) { if (!c->Set(n)) { LAssert(0); } else { Status = c->Test(this, m, Log); if (c->Not) Status = !Status; if (Log) { Log->Print("\tResult=%i (not=%i)\n", Status, c->Not); } } DeleteObj(c); } } else LAssert(0); } else LAssert(0); return Status; } bool FilterCondition::Set(LXmlTag *t) { if (!t) return false; Source.Reset(NewStr(t->GetAttr(ATTR_FIELD))); Value.Reset(NewStr(t->GetAttr(ATTR_VALUE))); Not = t->GetAsInt(ATTR_NOT) > 0; char *o = t->GetAttr(ATTR_OP); if (o) { if (IsDigit(*o)) Op = atoi(o); else { for (int i=0; OpNames[i]; i++) { if (!_stricmp(OpNames[i], o)) { Op = i; break; } } } } return true; } bool Filter::EvaluateXml(Mail *m, bool &Stop, LStream *Log) { bool Status = false; if (ValidStr(GetConditionsXml())) { if (!ConditionsCache) ConditionsCache = Parse(false); if (ConditionsCache && ConditionsCache->Children.Length()) Status = EvaluateTree(ConditionsCache->Children[0], m, Stop, Log); } return Status; } bool Filter::Test(Mail *m, bool &Stop, LStream *Log) { bool Status = false; if (Log) Log->Print("Filter.Test '%s':\n", GetName()); Current = &m; if (m && m->GetObject()) { d->Stop = &Stop; d->Log = Log; if (ValidStr(GetScript())) { OnFilterScript(this, m, GetScript()); } else if (ValidStr(GetConditionsXml())) { Status = EvaluateXml(m, Stop, Log); } else LAssert(0); d->Stop = 0; d->Log = 0; } Current = 0; return Status; } bool Filter::DoActions(Mail *&m, bool &Stop, LStream *Log) { if (!App || !m) return false; Current = &m; LAutoPtr r = Parse(true); if (r) { LArray Act; for (auto c: r->Children) { if (c->IsTag(ELEMENT_ACTION)) { FilterAction *a = new FilterAction(GetObject()->GetStore()); if (a) { if (a->Set(c)) Act.Add(a); else LAssert(!"Can't convert xml to action."); } } } for (unsigned i=0; iGetObject(); i++) { FilterAction *a = Act[i]; a->Do(this, App, m, Log); } Act.DeleteObjects(); } Current = 0; if (GetStopFiltering()) { Stop = true; } return true; } ThingUi *Filter::DoUI(MailContainer *c) { if (!Ui) { MaxIndex = MAX(MaxIndex, GetIndex()); if (GetIndex() < 0) { SetIndex(++MaxIndex); } Ui = new FilterUi(this); } #if WINNATIVE if (Ui) SetForegroundWindow(Ui->Handle()); #endif return Ui; } LAutoPtr Filter::Parse(bool Actions) { LAutoPtr Ret; auto RawXml = Actions ? GetActionsXml() : GetConditionsXml(); if (!RawXml) return Ret; LMemStream Xml(RawXml, strlen(RawXml), false); LXmlTree t; Ret.Reset(new LXmlTag); if (!t.Read(Ret, &Xml)) Ret.Reset(); return Ret; } void Filter::AddAction(FilterAction *Action) { if (!Action) return; LAutoPtr a = Parse(true); if (!a) a.Reset(new LXmlTag("Actions")); LAutoPtr n(new LXmlTag(ELEMENT_ACTION)); if (!Action->Get(n)) return; a->InsertTag(n.Release()); LXmlTree t; LStringPipe p; if (!t.Write(a, &p)) return; LAutoString Xml(p.NewStr()); SetActionsXml(Xml); SetDirty(true); } bool Filter::Save(ScribeFolder *Into) { bool Status = false; if (!Into) { Into = GetFolder(); if (!Into) { Into = App->GetFolder(FOLDER_FILTERS); } } if (Into) { SetParentFolder(Into); if (ChkIncoming) SetIncoming(ChkIncoming->Value()!=0); if (ChkOutgoing) SetOutgoing(ChkOutgoing->Value()!=0); if (ChkInternal) SetInternal(ChkInternal->Value()!=0); Store3Status s = Into->WriteThing(this); Status = s != Store3Error; if (Status) SetDirty(false); } return Status; } void Filter::OnPaint(ItemPaintCtx &Ctx) { LListItem::OnPaint(Ctx); } void Filter::OnMouseClick(LMouse &m) { LListItem::OnMouseClick(m); if (m.Double()) { // open the UI for the Item DoUI(); } else if (m.Right()) { // open the right click menu auto RClick = new LSubMenu; if (RClick) { RClick->AppendItem(LLoadString(IDS_OPEN), IDM_OPEN); RClick->AppendItem(LLoadString(IDS_DELETE), IDM_DELETE); RClick->AppendItem(LLoadString(IDS_EXPORT), IDM_EXPORT); if (Parent->GetMouse(m, true)) { switch (RClick->Float(Parent, m.x, m.y)) { case IDM_OPEN: { DoUI(); break; } case IDM_DELETE: { LVariant ConfirmDelete; App->GetOptions()->GetValue(OPT_ConfirmDelete, ConfirmDelete); if (!ConfirmDelete.CastInt32() || LgiMsg(GetList(), LLoadString(IDS_DELETE_ASK), AppName, MB_YESNO) == IDYES) { List Del; LList *ParentList = LListItem::Parent; if (ParentList && ParentList->GetSelection(Del)) { for (auto m: Del) { auto f = dynamic_cast(m); if (f) f->OnDelete(); } } } break; } case IDM_EXPORT: { - ExportAll(GetList(), sTextXml); + ExportAll(GetList(), sTextXml, NULL); break; } } } DeleteObj(RClick); } } } int *Filter::GetDefaultFields() { static int Def[] = { FIELD_FILTER_NAME, 0, 0, 0 }; return Def; } const char *Filter::GetFieldText(int Field) { switch (Field) { case FIELD_FILTER_NAME: return GetName(); case FIELD_FILTER_CONDITIONS_XML: return GetConditionsXml(); case FIELD_FILTER_ACTIONS_XML: return GetActionsXml(); case FIELD_FILTER_SCRIPT: return GetScript(); } return NULL; } void Filter::OnColumnNotify(int Col, int64 Data) { if (!IgnoreCheckEvents) { switch (Col) { case 2: case 3: case 4: { SetDirty(true); break; } } } } const char *Filter::GetText(int i) { int Field = 0; if (FieldArray.Length()) { if (i >= 0 && i < (int)FieldArray.Length()) Field = FieldArray[i]; } else if (i >= 0 && i < CountOf(DefaultFilterFields)) { Field = DefaultFilterFields[i]; } switch (Field) { case FIELD_FILTER_NAME: { return GetName(); break; } case FIELD_FILTER_INDEX: { static char i[16]; sprintf_s(i, sizeof(i), "%i", GetIndex()); return i; break; } } return 0; } int FilterIndexCmp(Filter **a, Filter **b) { int ai = (*a)->GetIndex(); int bi = (*b)->GetIndex(); return ai - bi; } void Filter::Reindex(ScribeFolder *Folder) { if (!Folder) return; LArray Zero; LArray Sort; for (auto t : Folder->Items) { Filter *f = t->IsFilter(); if (f) { int Idx = f->GetIndex(); if (Idx > 0) { Sort.Add(f); } else { Zero.Add(f); } } } Sort.Sort(FilterIndexCmp); LArray Map; for (unsigned n=0; nGetIndex(); if (Idx != i + 1) { Sort[i]->SetIndex(i + 1); Sort[i]->SetDirty(); Sort[i]->Update(); Changed = true; } } if (Changed) Folder->ReSort(); } ////////////////////////////////////////////////////////// enum ConditionOptions { FIELD_ANYWHERE = FIELD_MAX }; int FilterCallback( LFilterView *View, LFilterItem *Item, GFilterMenu Menu, LRect &r, LArray *GetList, void *Data) { int Status = -1; if (!View) return Status; switch (Menu) { case FMENU_FIELD: { LSubMenu s; LString::Array Names; ItemFieldDef *FieldDefs = MailFieldDefs; for (ItemFieldDef *i = FieldDefs; i->DisplayText; i++) { const char *Trans = LLoadString(i->FieldId); auto idx = (i - FieldDefs) + 1; Names[idx] = DomToStr(i->Dom); s.AppendItem(Trans ? Trans : i->DisplayText, (int)idx, true); } s.AppendItem(LLoadString(IDS_ATTACHMENTS_DATA), FIELD_ATTACHMENTS_DATA, true); s.AppendItem(LLoadString(IDS_ATTACHMENTS_NAME), FIELD_ATTACHMENTS_NAME, true); s.AppendItem(LLoadString(IDS_MEMBER_OF_GROUP), FIELD_MEMBER_OF_GROUP, true); s.AppendItem(LLoadString(IDS_ANYWHERE), FIELD_ANYWHERE, true); LPoint p(r.x1, r.y2 + 1); View->PointToScreen(p); int Cmd = s.Float(View, p.x, p.y, true); switch (Cmd) { case FIELD_ATTACHMENTS_DATA: Item->SetField("mail.Attachments"); break; case FIELD_ATTACHMENTS_NAME: Item->SetField("mail.AttachmentNames"); break; case FIELD_MEMBER_OF_GROUP: Item->SetField("mail.From.Groups"); break; case FIELD_ANYWHERE: Item->SetField("mail.*"); break; default: if (Cmd > 0 && Cmd < Names.Length()) Item->SetField(LString("mail.") + Names[Cmd]); break; } break; } case FMENU_OP: { if (GetList) { for (const char **o = GetOpNames(true); *o; o++) { GetList->Add(NewStr(*o)); } Status = true; } /* else { auto s = new LSubMenu; if (s) { int n = 1; for (char **o = GetOpNames(true); *o; o++) { s->AppendItem(*o, n++, true); } LPoint p(r.x1, r.y2 + 1); View->PointToScreen(p); int Cmd = s->Float(View, p.x, p.y, true); if (Cmd > 0) { Item->SetOp(OpNames[Cmd - 1]); } DeleteObj(s); } } */ break; } case FMENU_VALUE: { break; } } return Status; } ////////////////////////////////////////////////////////// struct FilterUiPriv { }; FilterUi::FilterUi(Filter *item) : ThingUi(item, "Filter") { d = new FilterUiPriv; Item = item; if (!(Item && Item->App)) { return; } Script = 0; Tab = 0; Actions = 0; Conditions = 0; LRect r(100, 100, 800, 600); SetPos(r); MoveSameScreen(item->App); // Create window #if WINNATIVE CreateClassW32("FilterUi", LoadIcon(LProcessInst(), MAKEINTRESOURCE(IDI_FILTER))); #endif if (Attach(0)) { // Setup UI Commands.Toolbar = Item->App->LoadToolbar(this, Item->App->GetResourceFile(ResToolbarFile), Item->App->GetToolbarImgList()); if (Commands.Toolbar) { Commands.Toolbar->Attach(this); Commands.Toolbar->AppendButton(RemoveAmp(LLoadString(IDS_SAVE)), IDM_SAVE, TBT_PUSH, true, IMG_SAVE); Commands.Toolbar->AppendButton(RemoveAmp(LLoadString(IDS_SAVE_CLOSE)), IDM_SAVE_CLOSE, TBT_PUSH, true, IMG_SAVE_AND_CLOSE); Commands.Toolbar->AppendButton(RemoveAmp(LLoadString(IDS_DELETE)), IDM_DELETE, TBT_PUSH, true, IMG_TRASH); Commands.Toolbar->AppendButton(RemoveAmp(LLoadString(IDS_HELP)), IDM_HELP, TBT_PUSH, true, IMG_HELP); Commands.SetupCallbacks(GetItem()->App, this, GetItem(), LThingUiToolbar); } LTabPage *Cond = 0; LTabPage *Act = 0; Tab = new LTabView(91, 0, 0, 1000, 1000, 0); if (Tab) { Tab->Attach(this); Tab->SetPourChildren(true); LTabPage *Filter = Tab->Append(LLoadString(IDS_FILTER)); if (Filter) { #ifdef _DEBUG auto Status = #endif Filter->LoadFromResource(IDD_FILTER); LAssert(Status); Name(Filter->Name()); } Cond = Tab->Append(LLoadString(IDS_CONDITIONS)); if (Cond) { Conditions = new LFilterView(FilterCallback, Item); if (Conditions) { Cond->Append(Conditions); Conditions->SetPourLargest(true); } } Act = Tab->Append(LLoadString(IDS_ACTIONS)); if (Act) { #ifdef _DEBUG auto Status = #endif Act->LoadFromResource(IDD_FILTER_ACTION); LAssert(Status); if (GetViewById(IDC_FILTER_ACTIONS, Actions)) Actions->MultiSelect(false); } LTabPage *ScriptTab = Tab->Append(""); if (ScriptTab && ScriptTab->LoadFromResource(IDD_FILTER_SCRIPT)) { if (GetViewById(IDC_SCRIPT, Script)) { Script->SetWrapType(TEXTED_WRAP_NONE); Script->Sunken(true); Script->SetPourLargest(true); } else LAssert(0); } } // Show window Visible(true); if (Cond && Item) { LCombo *Cbo; if (GetViewById(IDC_ACTION, Cbo)) { for (ActionName *o = ActionNames; o->Id; o++) { const char *s = LLoadString(o->Id, o->Default); Cbo->Insert(s); } } } OnLoad(); } RegisterHook(this, LKeyEvents); } FilterUi::~FilterUi() { if (Item) Item->Ui = 0; DeleteObj(d); } bool FilterUi::OnViewKey(LView *v, LKey &k) { if (k.CtrlCmd()) { switch (k.c16) { case 's': case 'S': { if (k.Down()) OnSave(); return true; } case 'w': case 'W': { if (k.Down()) { OnSave(); Quit(); } return true; } } } return false; } int FilterUi::OnNotify(LViewI *Col, LNotification n) { int Reindex = 0; int InsertOffset = 0; switch (Col->GetId()) { case IDC_TYPE_CBO: case IDC_ARG_EDIT: { if (Actions) { List Sel; if (Actions->GetSelection(Sel)) { for (auto a: Sel) a->OnNotify(Col, n); } } break; } case IDC_BROWSE_ARG: { if (Actions) { List Sel; if (Actions->GetSelection(Sel)) { FilterAction *a = Sel[0]; if (a) { a->Browse(Item->App, this); a->OnNotify(Col, n); } } } break; } case IDC_UP: InsertOffset = -1; // fall thru case IDC_DOWN: { if (!InsertOffset) InsertOffset = 1; if (!Actions) break; List Items; if (!Actions->GetSelection(Items) && Items[0]) break; int Idx = Actions->IndexOf(Items[0]) + InsertOffset; FilterAction *Last = 0; for (auto a: Items) { Actions->Remove(a); Actions->Insert(a, Idx++); Last = a; } if (Last) { Actions->Focus(true); Last->Select(true); } break; } case IDC_NEW_FILTER_ACTION: { if (Actions) { FilterAction *n = new FilterAction(Item->GetObject()->GetStore()); Actions->Insert(n); Actions->Select(n); Actions->Focus(true); } break; } case IDC_DELETE_FILTER_ACTION: { if (Actions) { List Items; if (Actions->GetSelection(Items) && Items[0]) { int Idx = Actions->IndexOf(Items[0]); Items.DeleteObjects(); if (Idx >= (int)Actions->Length()) Idx = (int)Actions->Length() - 1; Actions->Select(Actions->ItemAt(Idx)); Actions->Focus(true); } } break; } case IDC_LAUNCH_HELP: { switch (Tab->Value()) { case 0: // Name/Index default: { Item->App->LaunchHelp("filters.html"); break; } case 1: // Conditions { Item->App->LaunchHelp("filters.html#cond"); break; } case 2: // Actions { Item->App->LaunchHelp("filters.html#actions"); break; } case 3: // Script { Item->App->LaunchHelp("filters.html#script"); break; } } break; } case IDC_FILTER_UP: { Reindex = 1; break; } case IDC_FILTER_DOWN: { Reindex = -1; break; } } if (Reindex) { // Remove holes in the indexing ScribeFolder *Folder = Item->GetFolder(); if (Folder) { Folder->ReSort(); } // Swap entries int i = (int)GetCtrlValue(IDC_FILTER_INDEX); if (i >= 0) { Filter *f = Item->GetFilterAt(i - Reindex); if (f) { int n = f->GetIndex(); f->SetIndex(Item->GetIndex()); f->SetDirty(); Item->SetIndex(n); Item->SetDirty(); f->Save(); Item->Save(); SetCtrlValue(IDC_FILTER_INDEX, Item->GetIndex()); Item->App->GetItemList()->ReSort(); Item->Update(); f->Update(); } } } return 0; } LMessage::Result FilterUi::OnEvent(LMessage *Msg) { switch (Msg->Msg()) { #ifdef WIN32 case WM_CLOSE: { Quit(); return 0; } #endif } return LWindow::OnEvent(Msg); } void LoadTree(LFilterView *v, LXmlTag *t, LTreeNode *i) { int idx = 0; for (auto c: t->Children) { if (c->GetTag()) { LFilterItem *n = 0; bool Cond = false; if (c->IsTag(ELEMENT_AND)) n = v->Create(LNODE_AND); else if (c->IsTag(ELEMENT_OR)) n = v->Create(LNODE_OR); else if (c->IsTag(ELEMENT_CONDITION)) { Cond = true; n = v->Create(LNODE_COND); } if (n) { if (Cond) { n->SetNot(c->GetAsInt(ATTR_NOT) > 0); n->SetField(c->GetAttr(ATTR_FIELD)); n->SetValue(c->GetAttr(ATTR_VALUE)); char *o = c->GetAttr(ATTR_OP); if (o) { if (IsDigit(*o)) n->SetOp(atoi(o)); else { for (int i=0; OpNames[i]; i++) { if (!_stricmp(OpNames[i], o)) { n->SetOp(i); break; } } } } } i->Insert(n, idx++); LoadTree(v, c, n); } } } } void FilterUi::OnLoad() { if (Item) { LAutoPtr r = Item->Parse(true); if (r && Actions) { for (auto c: r->Children) { FilterAction *a = new FilterAction(Item->GetObject()->GetStore()); if (a) { if (a->Set(c)) { Actions->Insert(a); } else LAssert(!"Can't convert xml to action."); } } } auto FilterName = Item->GetName(); SetCtrlName(IDC_NAME, FilterName); SetCtrlValue(IDC_FILTER_INDEX, Item->GetIndex()); SetCtrlName(IDC_SCRIPT, Item->GetScript()); SetCtrlValue(IDC_STOP_FILTERING, Item->GetStopFiltering()); SetCtrlValue(IDC_INCOMING, Item->GetIncoming()); SetCtrlValue(IDC_OUTGOING, Item->GetOutgoing()); SetCtrlValue(IDC_INTERNAL_FILTERING, Item->GetInternal()); auto Xml = Item->GetConditionsXml(); if (Conditions && Xml) { LAutoPtr x(new LXmlTag); if (x) { LMemStream p(Xml, strlen(Xml)); LXmlTree t; if (t.Read(x, &p, 0)) { Conditions->Empty(); LoadTree(Conditions, x, Conditions->GetRootNode()); if (!Conditions->GetRootNode()->GetChild()) { Conditions->SetDefault(); } } } } if (ValidStr(FilterName)) { LString s; s.Printf("%s - %s", LLoadString(IDS_FILTER), FilterName); Name(s); } } } void SaveTree(LXmlTag *t, LTreeNode *i) { for (LTreeNode *c = i->GetChild(); c; c = c->GetNext()) { LFilterItem *fi = dynamic_cast(c); if (fi) { const char *Tag = 0; bool Cond = false; switch (fi->GetNode()) { default: break; case LNODE_AND: Tag = ELEMENT_AND; break; case LNODE_OR: Tag = ELEMENT_OR; break; case LNODE_COND: Cond = true; Tag = ELEMENT_CONDITION; break; } if (Tag) { LXmlTag *n = new LXmlTag(Tag); if (n) { if (Cond) { n->SetAttr(ATTR_NOT, fi->GetNot()); n->SetAttr(ATTR_FIELD, fi->GetField()); n->SetAttr(ATTR_OP, fi->GetOp()); n->SetAttr(ATTR_VALUE, fi->GetValue()); } t->InsertTag(n); SaveTree(n, fi); } } } } } void FilterUi::OnSave() { if (Item) { Item->SetName(GetCtrlName(IDC_NAME)); Item->SetScript(GetCtrlName(IDC_SCRIPT)); Item->SetStopFiltering(GetCtrlValue(IDC_STOP_FILTERING)!=0); Item->SetIncoming(GetCtrlValue(IDC_INCOMING)!=0); Item->SetOutgoing(GetCtrlValue(IDC_OUTGOING)!=0); Item->ChkIncoming->Value(GetCtrlValue(IDC_INCOMING)); Item->ChkOutgoing->Value(GetCtrlValue(IDC_OUTGOING)); Item->ChkInternal->Value(GetCtrlValue(IDC_INTERNAL_FILTERING)); if (Conditions) { LXmlTag *x = new LXmlTag(ELEMENT_CONDITIONS); if (x) { SaveTree(x, Conditions->GetRootNode()); LXmlTree t; LStringPipe p; if (t.Write(x, &p)) { LAutoString a(p.NewStr()); Item->ConditionsCache.Reset(); Item->SetConditionsXml(a); } DeleteObj(x); } } LXmlTag x("Actions"); List Act; Actions->GetAll(Act); for (size_t i=0; i c(new LXmlTag("Action")); if (a->Get(c)) { x.InsertTag(c.Release()); } } LXmlTree t; LStringPipe p; t.Write(&x, &p); LAutoString s(p.NewStr()); Item->SetActionsXml(s); if (Item->Save()) { Item->Reindex(Item->GetFolder()); } } } int FilterUi::OnCommand(int Cmd, int Event, OsView Window) { switch (Cmd) { case IDM_SAVE: { OnSave(); break; } case IDM_SAVE_CLOSE: { OnSave(); // fall thru } case IDM_CLOSE: { Quit(); break; } case IDM_DELETE: { Item->Ui = 0; Item->OnDelete(); Item = 0; Quit(); break; } case IDM_HELP: { App->LaunchHelp("filters.html"); break; } case IDC_NEW_FILTER_ACTION: { LList *l; if (GetViewById(IDC_FILTER_ACTIONS, l)) { } break; } case IDC_DELETE_FILTER_ACTION: { break; } default: { Commands.ExecuteCallbacks(GetItem()->App, this, GetItem(), Cmd); break; } } return 0; } diff --git a/Code/ScribeFinder.cpp b/Code/ScribeFinder.cpp --- a/Code/ScribeFinder.cpp +++ b/Code/ScribeFinder.cpp @@ -1,1404 +1,1406 @@ /* ** FILE: ScribeFinder.cpp ** AUTHOR: Matthew Allen ** DATE: 3/6/1999 ** DESCRIPTION: Scribe finder tool ** ** Copyright (C) 1999, Matthew Allen ** fret@memecode.com */ #include #include #include #include #include "Scribe.h" #include "lgi/common/Edit.h" #include "lgi/common/Button.h" #include "lgi/common/CheckBox.h" #include "lgi/common/Combo.h" #include "../Resources/resdefs.h" #include "lgi/common/DragAndDrop.h" #include "lgi/common/TableLayout.h" #include "lgi/common/LgiRes.h" #define IDC_RESULTS 90 ////////////////////////////////////////////////////////////////////////////// int FindCompare(LListItem *a, LListItem *b, NativeInt Data); ////////////////////////////////////////////////////////////////////////////////// #include "LMarkColourSelect.h" LMarkColourSelect::LMarkColourSelect() : ResObject(Res_Custom) { } LArray LMarkColourSelect::GetSelected() { LArray a; for (int i=0; iX() + AnyTxt->X() + (IDM_MARK_MAX * (ColPx + Pad)) + (Pad * 3) + 2; Inf.Width.Min = None->X() + AnyTxt->X() + IDM_MARK_MAX + (Pad * 4) + 2; } else { Inf.Width.Max = Inf.Width.Min = -1; } } return true; } void LMarkColourSelect::OnPressColour(size_t i) { ColSel[i] = !ColSel[i]; if (!ColSel[i]) Any = false; Invalidate(); SendNotify(LNotifyValueChanged); } void LMarkColourSelect::SelectNone() { Any = false; memset(ColSel, 0, sizeof(ColSel)); Invalidate(); SendNotify(LNotifyValueChanged); } void LMarkColourSelect::SelectAll() { Any = true; for (int i=0; iColour(low); pDC->Box(&cli); cli.Inset(1, 1); pDC->Colour(bk); pDC->Rectangle(&cli); int x = Pad; if (None) { None->GetFont()->Colour(txt, bk); NoneRc.ZOff(None->X()-1, None->Y()-1); NoneRc.Offset(cli.x1+x, cli.y1+Pad); None->Draw(pDC, NoneRc.x1, NoneRc.y1); NoneRc.Inset(-Pad, -Pad); x += None->X() + Pad; } for (int i=0; iColour(MarkColours32[i], 32); if (ColSel[i]) pDC->Rectangle(&r); else { LRect t = r; pDC->Box(&t); t.Inset(1, 1); pDC->Box(&t); } x += r.X() + Pad; } if (AnyTxt) { AnyTxt->GetFont()->Colour(txt, bk); AnyRc.ZOff(AnyTxt->X()-1, AnyTxt->Y()-1); AnyRc.Offset(cli.x1+x, cli.y1+Pad); AnyTxt->Draw(pDC, cli.x1 + x, cli.x1 + Pad); AnyRc.Inset(-Pad, -Pad); x += AnyTxt->X() + Pad; } } class LMarkColourSelectFactory : public LViewFactory { LView *NewView(const char *Class, LRect *Pos, const char *Text) { if (!stricmp(Class, "LMarkColourSelect")) return new LMarkColourSelect(); return NULL; } } MarkColourSelectFactory; ////////////////////////////////////////////////////////////////////////////// class ResultItem : public LListItem { friend int FindCompare(LListItem *a, LListItem *b, NativeInt Data); friend class ResultList; LString Path; Thing *T; bool GetData(LArray &Data) { bool Status = false; List Objs; LList *ParentList = LListItem::Parent; if (ParentList && ParentList->GetSelection(Objs)) { for (unsigned di=0; di(i); if (Ri && Ri->T) Fmt->ThingAt(n++, Ri->T); } Status = dd.Data[0].SetBinary(Fmt->Sizeof(), Fmt); free(Fmt); } } else if (!_stricmp(LGI_FileDropFormat, dd.Format)) { LMouse m; GetList()->GetMouse(m, true); LDragDropSource *Src = dynamic_cast(GetList()); if (!Src) return false; LString::Array Files; for (auto i: Objs) { ResultItem *Ri = dynamic_cast(i); if (Ri) { Status |= Ri->T->GetDropFiles(Files); } } if (Status) { Status = Src->CreateFileDrop(&dd, m, Files); } } } } return Status; } int Type() { return T ? T->Type() : 0; } int Sizeof() { LAssert(0); return 0; } bool Serialize(LFile &f, bool Write) { LAssert(0); return 0; } public: ResultItem(ScribeWnd *App, Thing *t) { T = t; LAssert(T != NULL); auto m = t->IsMail(); if (m) { auto colour = m->GetMarkColour(); if (colour > 0) { LColour c((uint32_t)colour, 32); GetCss(true)->BackgroundColor(c.Mix(L_WORKSPACE, Mail::MarkColourMix)); } } } Thing *GetThing() { return T; } const char *GetText(int i = 0) { const char *Status = 0; int *DefFlds = T->GetDefaultFields(); if (i < 4) { Status = DefFlds ? T->GetFieldText(DefFlds[i]) : (char*)""; } else if (i == 4) { ScribeFolder *f = T->GetFolder(); if (f) { Path = f->GetPath(); } Status = Path; } return Status; } int GetImage(int Flags = 0) { return T->GetImage(Flags); } LView *DoUI() { return T->DoUI(); } }; class ResultList : public LList, public LDragDropSource { friend int FindCompare(LListItem *a, LListItem *b, NativeInt Data); int Col; bool Ascend; public: ResultList(int id, int x, int y, int cx, int cy, const char *name = "") : LList(id, x, y, cx, cy, name) { Col = -1; Ascend = true; MultiSelect(true); } void OnItemBeginDrag(LListItem *Item, LMouse &m) { Drag(this, m.Event, DROPEFFECT_MOVE); } bool GetFormats(LDragFormats &Formats) { Formats.Supports(ScribeThingList); Formats.SupportsFileDrops(); return true; } bool GetData(LArray &Data) { if (!Data.Length()) return false; List Sel; if (!GetSelection(Sel)) return false; return Sel[0]->GetData(Data); } void OnItemClick(LListItem *Item, LMouse &m) { ResultItem *i = dynamic_cast(Item); if (i) { if (m.Down() && m.Double()) { i->DoUI(); } /* else if (i->T && m.Right()) { i->T->DoContextMenu(this); } */ } } void SetSort(int col, int ascend) { Col = col; Ascend = ascend != 0; Sort(FindCompare, (NativeInt) this); for (int i=0; iMark( (Col == i) ? (Ascend) ? GLI_MARK_DOWN_ARROW : GLI_MARK_UP_ARROW : GLI_MARK_NONE); } } } void OnColumnClick(int col, LMouse &m) { if (Col != col) { SetSort(col, true); } else { SetSort(Col, !Ascend); } } }; int FindCompare(LListItem *a, LListItem *b, NativeInt Data) { ResultList *List = (ResultList*) Data; ResultItem *t1 = dynamic_cast(a); ResultItem *t2 = dynamic_cast(b); if (List && t1 && t2) { // do specific compare on mail items.. Mail *m1 = t1->T->IsMail(); Mail *m2 = t2->T->IsMail(); int Mul = (List->Ascend) ? 1 : -1; if (m1 && m2) { switch (List->Col) { case 2: { // size return Mul * (int)(m1->TotalSizeof() - m2->TotalSizeof()); break; } case 3: { // date int Sort = 0; auto d1 = m1->GetDateSent(); auto d2 = m2->GetDateSent(); if (d1 && d2) { if (*d1 < *d2) Sort = -1; if (*d1 > *d2) Sort = 1; return Mul * Sort; } break; } } } // default back to a string compare const char *A = a->GetText(List->Col); const char *B = b->GetText(List->Col); if (A && B) { return Mul * _stricmp(A, B); } } return 0; } /////////////////////////////////////////////////////////////////////// #define M_END_SEARCH (M_USER+0x145) class FindThread : public LThread, public LMutex { // Work vars ScribeWnd *App; ScribeFolder *Folder; ResultList *Results; bool InMail; int MailF; bool InContacts; int ContactF; LArray SearchText; bool Deep; bool CaseSensitive; bool MatchWord; bool Loop; LArray Colours; LAutoPtr GroupMap; // Status uint64 StartTime; int ItemsSearched; LString CurFolder; // Thread vars LViewI *Notify; bool MatchString(const char *String, char *Pattern) { if (String && Pattern) { size_t Len = strlen(Pattern); auto s = String; do { auto c = (CaseSensitive)?strstr(s, Pattern):stristr(s, Pattern); if (c) { if (MatchWord) { bool Before = c <= String || IsWordBoundry(c[-1]); bool After = c[Len] == 0 || IsWordBoundry(c[Len]); if (Before && After) return true; } else { return true; } } else break; s = c + Len; } while (s && *s); } return false; } bool MatchAddress(LDataPropI *Addr, char *Pattern, int CC = -1) { bool Status = false; if (Addr && Pattern && (CC < 0 || Addr->GetInt(FIELD_CC) == CC)) { if (Addr->GetStr(FIELD_NAME)) { Status |= MatchString(Addr->GetStr(FIELD_NAME), Pattern); } if (Addr->GetStr(FIELD_EMAIL)) { Status |= MatchString(Addr->GetStr(FIELD_EMAIL), Pattern); } // Status |= (Addr->Name) ? stristr(Addr->Name, Text) != 0 : 0; // Status |= (Addr->Addr) ? stristr(Addr->Addr, Text) != 0 : 0; } return Status; } bool MatchMail(Mail *mail, int Field = -1) { if (!mail) return false; #define StrField(fld, id) if (id == Field || Field < 0) Status |= (MatchString(fld, Text) != 0); bool Result = true; for (unsigned i=0; iTotalSizeof(); int Value = (int) (atof(StartNumber) * Scale); if (_stricmp(Op, "=") == 0) { Status = Size == Value; } else if (_stricmp(Op, "!=") == 0) { Status = Size != Value; } else if (_stricmp(Op, "<") == 0) { Status = Size < Value; } else if (_stricmp(Op, ">") == 0) { Status = Size > Value; } else if (_stricmp(Op, "<=") == 0) { Status = Size <= Value; } else if (_stricmp(Op, ">=") == 0) { Status = Size >= Value; } DeleteArray(Op); } } if (Field == FIELD_TEXT || Field < 0) { StrField(mail->GetBody(), FIELD_TEXT); StrField(mail->GetHtml(), FIELD_ALTERNATE_HTML); } StrField(mail->GetSubject(), FIELD_SUBJECT); // FIXME: // StrField(mail->GetMessageId(false), FIELD_MESSAGE_ID); StrField(mail->GetInternetHeader(), FIELD_INTERNET_HEADER); StrField(mail->GetLabel(), FIELD_LABEL); if (Field == FIELD_TO || Field == FIELD_CC || Field < 0) { GDataIt To = mail->GetTo(); for (LDataPropI *a = To->First(); a; a = To->Next()) { Status |= MatchAddress(a, Text, Field == FIELD_CC); } } if (Field == FIELD_FROM || Field < 0) { Status |= MatchAddress(mail->GetFrom(), Text); } if (Field == FIELD_REPLY || Field < 0) { Status |= MatchAddress(mail->GetObject()->GetObj(FIELD_REPLY), Text); } if (Field == FIELD_ATTACHMENTS_NAME || Field < 0) { List Files; if (mail->GetAttachments(&Files)) { for (auto a: Files) { auto FileName = a->GetName(); Status |= MatchString(FileName, Text) != 0; } } } if (Field == FIELD_MEMBER_OF_GROUP || Field < 0) { auto From = mail->GetFrom(); if (From) { auto email = From->GetStr(FIELD_EMAIL); if (email && GroupMap) { auto grps = GroupMap->Find(email); if (grps) Status |= MatchString(grps->toString(), Text); } } } Result &= Status; } if (Colours.Length()) { auto Col = mail->GetMarkColour(); Result &= Colours.HasItem((uint32_t)Col); } return Result; } bool MatchFilter(Filter *filter) { if (!filter || !filter->GetObject() || SearchText.Length() == 0) return false; LArray Strs; auto Obj = filter->GetObject(); static int Fields[] = { FIELD_FILTER_NAME, FIELD_FILTER_SCRIPT, FIELD_FILTER_CONDITIONS_XML, FIELD_FILTER_ACTIONS_XML }; for (int i=0; iGetStr(Fields[i]); if (s) Strs.Add(s); } for (auto t: SearchText) { for (auto s : Strs) if (MatchString(s, t)) return true; } return false; } bool MatchContact(Contact *contact, int Field = -1) { if (!contact || !contact->GetObject() || SearchText.Length() == 0) return false; const char *v = 0; bool Status = true; for (unsigned i=0; i= 0) { ItemFieldDef *Def = GetFieldDefById(Field); if (Def) { if ((v = contact->GetObject()->GetStr(Def->FieldId))) { Found |= (stristr(v, Text) != 0); } } } else { for (ItemFieldDef *fd = ContactFieldDefs; !Found && fd->FieldId; fd++) { if ((v = contact->GetObject()->GetStr(fd->FieldId))) { Found |= stristr(v, Text) != 0; } } } Status &= Found; } return Status; } public: char Status[256]; FindThread( ScribeWnd *app, LView *notify, ScribeFolder *folder, ResultList *results, bool mail, int mailf, bool contact, int contactf, const char *searchText, bool deep, bool case_sensitive, bool match_word, LArray colours, LAutoPtr groupMap) : LThread("FindThread.Thread"), LMutex("FindThread.Mutex") { Loop = true; Status[0] = 0; App = app; Notify = notify; Folder = folder; Results = results; InMail = mail; MailF = mailf; InContacts = contact; ContactF = contactf; Deep = deep; CaseSensitive = case_sensitive; MatchWord = match_word; Colours = colours; ItemsSearched = 0; GroupMap = groupMap; if (searchText) { const char *White = " \t\r\n"; for (auto s = searchText; s && *s; ) { while (*s && strchr(White, *s)) s++; if (*s && strchr("\'\"", *s)) { char Delim = *s++; char *e = strchr(s, Delim); if (e) { SearchText.Add( NewStr(s, e-s) ); s = e + 1; } else { SearchText.Add( NewStr(s) ); break; } } else { const char *e = s; while (*e && !strchr(White, *e)) e++; SearchText.Add( NewStr(s, e-s) ); s = *e ? e + 1 : 0; } } } Loop = true; Run(); } ~FindThread() { Loop = false; while (!IsExited()) { LSleep(10); } SearchText.DeleteArrays(); } void Search(bool i) { Loop = i; } void SetNotify(LViewI *w) { Notify = w; } bool AddThing(Thing *T) { ResultItem *Item = 0; if (T && Results) { Results->Insert(Item = new ResultItem(App, T)); } return Item != 0; } void SearchFolder(ScribeFolder *Folder) { if (!Folder) return; Store3State State = (Store3State)Folder->GetObject()->GetInt(FIELD_LOADED); Folder->LoadThings(); if (Lock(_FL)) { CurFolder = Folder->GetName(true); Unlock(); } for (auto Item : Folder->Items) { if (!Loop) break; switch ((uint32_t)Item->Type()) { case MAGIC_MAIL: { if (InMail) { bool PreLoad = Item->GetObject() != 0; Mail *m = Item->IsMail(); if (m) { if (MatchMail(m, MailF)) { PreLoad = AddThing(m); break; } ItemsSearched++; } } break; } case MAGIC_CONTACT: { if (InContacts) { bool PreLoad = Item->GetObject() != 0; Contact *c = Item->IsContact(); if (c) { if (MatchContact(c, ContactF)) { PreLoad = AddThing(c); break; } ItemsSearched++; } } break; } case MAGIC_FILTER: { bool PreLoad = Item->GetObject() != 0; Filter *f = Item->IsFilter(); if (f) { if (MatchFilter(f)) { PreLoad = AddThing(f); break; } ItemsSearched++; } break; } } } if (Deep) { for (ScribeFolder *f = Folder->GetChildFolder(); Loop && f; f = f->GetNextFolder()) { f->LoadThings(Notify); SearchFolder(f); } } if (State == Store3Unloaded) { // FIXME, should we unload the folder here? } } LString GetStatus() { char s[256]; double Sec = (double)(LCurrentTime() - StartTime) / 1000.0; double Rate = Sec != 0.0 ? ItemsSearched / Sec : 0.0; if (Lock(_FL)) { sprintf_s(s, sizeof(s), "Searching '%s', %.1f items/s.", CurFolder.Get(), Rate); Unlock(); } return s; } int Main() { StartTime = LCurrentTime(); ItemsSearched = 0; SearchFolder(Folder); if (Notify) { Notify->PostEvent(M_END_SEARCH); } return 0; } }; /////////////////////////////////////////////////////////////////////// class FindWnd : public LWindow, public LResourceLoad, public LDataEventsI { ScribeWnd *App; LEdit *Text; LButton *Search; LEdit *Folder; LCheckBox *SearchSub; LCheckBox *SearchMail; LCombo *MailField; LCheckBox *SearchContact; LCombo *ContactField; ResultList *Results; LArray MailFieldIds; char *SearchBtnText; FindThread *Thread; void OnSearch(bool Searching); public: FindWnd(ScribeWnd *app, ScribeFolder *folder); ~FindWnd(); int OnNotify(LViewI *Col, LNotification n); LMessage::Result OnEvent(LMessage *m); void OnNew(LDataFolderI *parent, LArray &new_items, int pos, bool is_new); bool OnDelete(LDataFolderI *parent, LArray &items); bool OnMove(LDataFolderI *new_parent, LDataFolderI *old_parent, LArray &items); bool OnChange(LArray &items, int FieldHint); void OnPulse(); }; FindWnd::FindWnd(ScribeWnd *app, ScribeFolder *folder) { App = app; Thread = 0; Results = 0; SearchBtnText = 0; SetQuitOnClose(false); // Setup controls LAutoString ResName; LRect r; if (LoadFromResource(IDD_FIND, this, &r, &ResName)) { SetPos(r); Name(ResName); GetViewById(IDC_TEXT, Text); GetViewById(IDOK, Search); GetViewById(IDC_FOLDER, Folder); GetViewById(IDC_SEARCH_SUB, SearchSub); GetViewById(IDC_MAIL, SearchMail); GetViewById(IDC_MAIL_FIELD, MailField); GetViewById(IDC_CONTACT, SearchContact); GetViewById(IDC_CONTACT_FIELD, ContactField); if (Search) { SearchBtnText = NewStr(Search->Name()); } LTableLayout *Tbl; if (GetViewById(IDC_TABLE, Tbl)) { auto *c = Tbl->GetCell(0, Tbl->CellY()-1); c->Add(Results = new ResultList(IDC_RESULTS, 10, 260, 400, 300, "")); } // Initialize controls if (folder) { auto FolderPath = folder->GetPath(); if (FolderPath) SetCtrlName(IDC_FOLDER, FolderPath); } else { SetCtrlName(IDC_FOLDER, "/"); } char n[256]; sprintf_s(n, sizeof(n), "(%s)", LLoadString(IDS_NONE)); if (MailField) { MailField->Insert(n); for (ItemFieldDef *Def = MailFieldDefs; Def->FieldId; Def++) { const char *FName = LLoadString(Def->FieldId); if (FName) { MailField->Insert(FName); MailFieldIds.Add(Def->FieldId); } } /* Unsupported yet... MailField->Insert(LLoadString(IDS_ATTACHMENTS_DATA)); MailFieldIds.Add(FIELD_ATTACHMENTS_DATA); */ MailField->Insert(LLoadString(IDS_ATTACHMENTS_NAME)); MailFieldIds.Add(FIELD_ATTACHMENTS_NAME); MailField->Insert(LLoadString(IDS_MEMBER_OF_GROUP)); MailFieldIds.Add(FIELD_MEMBER_OF_GROUP); } if (ContactField) { ContactField->Insert(n); for (ItemFieldDef *Def = ContactFieldDefs; Def->CtrlId; Def++) { ContactField->Insert(LLoadString(Def->FieldId)); } } if (Results) { Results->AskText(true); Results->SetImageList(App->GetIconImgList(), false); switch ((uint32_t)folder->GetItemType()) { case MAGIC_MAIL: Results->AddColumn(LLoadString(Mail::DefaultMailFields[0]), 100); Results->AddColumn(LLoadString(Mail::DefaultMailFields[1]), 100); Results->AddColumn(LLoadString(Mail::DefaultMailFields[2]), 100); Results->AddColumn(LLoadString(Mail::DefaultMailFields[3]), 100); break; case MAGIC_CONTACT: Results->AddColumn(LLoadString(Contact::DefaultContactFields[0]), 100); Results->AddColumn(LLoadString(Contact::DefaultContactFields[1]), 100); Results->AddColumn(LLoadString(Contact::DefaultContactFields[2]), 100); Results->AddColumn("", 100); break; default: Results->AddColumn("", 100); Results->AddColumn("", 100); Results->AddColumn("", 100); Results->AddColumn("", 100); break; } Results->AddColumn(LLoadString(IDS_1062), 200); } SetCtrlValue(IDC_SEARCH_SUB, false); switch ((uint32_t)folder->GetItemType()) { case MAGIC_MAIL: SetCtrlValue(IDC_MAIL, true); SetCtrlValue(IDC_CONTACT, false); break; case MAGIC_CONTACT: SetCtrlValue(IDC_MAIL, false); SetCtrlValue(IDC_CONTACT, true); break; default: SetCtrlValue(IDC_MAIL, true); SetCtrlValue(IDC_CONTACT, true); break; } MoveSameScreen(App); // Create window if (Attach(0)) { AttachChildren(); if (Search) Search->Default(true); if (Folder) Folder->Enabled(false); Visible(true); Text->Focus(true); } } App->AddStore3EventHandler(this); } FindWnd::~FindWnd() { App->RemoveStore3EventHandler(this); DeleteObj(Thread); DeleteArray(SearchBtnText); } void FindWnd::OnSearch(bool Searching) { Text->Enabled(!Searching); Folder->Enabled(!Searching); SearchSub->Enabled(!Searching); SearchMail->Enabled(!Searching); MailField->Enabled(!Searching); SearchContact->Enabled(!Searching); ContactField->Enabled(!Searching); // Results->Enabled(!Searching); MailField->Invalidate(); ContactField->Invalidate(); SetPulse(Searching ? 500 : -1); if (Searching) { Search->Name(LLoadString(IDS_CANCEL)); } else { Search->Name(SearchBtnText ? SearchBtnText : (char*)"Search"); } } int FindWnd::OnNotify(LViewI *Col, LNotification n) { switch (Col->GetId()) { case IDC_PICK_FOLDER: { if (Folder) { - FolderDlg Dlg(this, App); - if (Dlg.DoModal()) + auto Dlg = new FolderDlg(this, App); + Dlg->DoModal([this, Dlg](auto dlg, auto id) { - Folder->Name(Dlg.Get()); - } + if (id) + this->Folder->Name(Dlg->Get()); + delete dlg; + }); } break; } case IDOK: { if (Results && Folder) { if (Thread) { Thread->Search(false); Search->Name("Ending..."); } else { Results->Empty(); int MailF = -1; int ContactF = -1; if (MailField) { int Index = (int)MailField->Value(); if (Index > 0) { Index--; if (Index < (int)MailFieldIds.Length()) { MailF = MailFieldIds[Index]; } } } if (ContactField) { int Index = (int) ContactField->Value(); if (Index > 0) { ContactF = ContactFieldDefs[Index-1].FieldId; } } ScribeFolder *Root = App->GetFolder(Folder->Name()); if (Root) { auto it = Root->Items.begin(); LProgressDlg prog(this, 1000); prog.SetDescription("Get message id's..."); prog.SetRange(Root->Items.Length()); int n = 0; for (Thing *t = *it; t; t = *++it, n++) { Mail *m = t->IsMail(); if (m) { // This can't be done in the thread, so do it here m->GetMessageId(); } prog.Value(n); if (n % 10 == 0) LYield(); } LArray Colours; LMarkColourSelect *Mcs; if (GetViewById(IDC_COLOUR, Mcs)) { for (int i=0; iColSel); i++) { if (Mcs->ColSel[i]) Colours.Add(MarkColours32[i]); } } Thread = new FindThread(App, this, Root, Results, SearchMail && SearchMail->Value() > 0, MailF, SearchContact && SearchContact->Value() > 0, ContactF, Text->Name(), SearchSub && SearchSub->Value(), GetCtrlValue(IDC_FIND_CASE) != 0, GetCtrlValue(IDC_FIND_WORD) != 0, Colours, LAutoPtr(new LGroupMap(App))); OnSearch(true); } } } break; } } return 0; } LMessage::Result FindWnd::OnEvent(LMessage *m) { switch (m->Msg()) { case M_END_SEARCH: { // thread is ended, it will delete itself OnSearch(false); DeleteObj(Thread); break; } } return LWindow::OnEvent(m); } void FindWnd::OnNew(LDataFolderI *parent, LArray &new_items, int pos, bool is_new) { } bool FindWnd::OnDelete(LDataFolderI *parent, LArray &items) { if (Results) { LHashTbl, LListItem*> Map; List a; if (Results->GetAll(a)) { for (auto i: a) { Map.Add(i->GetThing()->GetObject(), i); } } for (unsigned i=0; i &items) { if (Results) { LHashTbl, LListItem*> Map; List a; if (Results->GetAll(a)) { for (auto i: a) { Map.Add(i->GetThing()->GetObject(), i); } } for (unsigned i=0; iUpdate(); } } } return true; } void FindWnd::OnPulse() { LViewI *v; if (!Thread || !GetViewById(IDC_STATUS, v)) return; v->Name(Thread->GetStatus()); v->SendNotify(LNotifyTableLayoutRefresh); } bool FindWnd::OnChange(LArray &items, int FieldHint) { return true; } LView *OpenFinder(ScribeWnd *App, ScribeFolder *Folder) { return new FindWnd(App, Folder); } diff --git a/Code/ScribeFolder.cpp b/Code/ScribeFolder.cpp --- a/Code/ScribeFolder.cpp +++ b/Code/ScribeFolder.cpp @@ -1,4191 +1,4328 @@ /* ** FILE: ScribeFolder.cpp ** AUTHOR: Matthew Allen ** DATE: 17/1/2000 ** DESCRIPTION: Scribe folder's ** ** Copyright (C) 2000-2002, Matthew Allen ** fret@memecode.com */ // Includes #include "Scribe.h" #include "lgi/common/DropFiles.h" #include "lgi/common/ProgressDlg.h" #include "lgi/common/LgiQuickSort.h" #include "lgi/common/DisplayString.h" #include "lgi/common/TextFile.h" #include "lgi/common/LgiRes.h" #include "lgi/common/FileSelect.h" #include "lgi/common/ClipBoard.h" #include "Calendar.h" #include "resdefs.h" #include "ReplicateDlg.h" ////////////////////////////////////////////////////////////////////////////// #if WINNATIVE #include "lgi/common/Com.h" #endif class LDndFilePromise #if defined(WINDOWS) #elif defined(__GTK_H__) #elif defined(MAC) #endif { LStream *Src; #if WINNATIVE FILEGROUPDESCRIPTOR Gd; #elif defined(__GTK_H__) #elif defined(MAC) #endif public: LDndFilePromise(LDragData &dd, LStream *src, LString FileName) { Src = src; #if WINNATIVE ZeroObj(Gd); Gd.cItems = 1; // CLSID Fd.clsid; // SIZEL Fd.sizel; // POINTL Fd.pointl; // FILETIME Fd.ftCreationTime; // FILETIME Fd.ftLastAccessTime; // FILETIME Fd.ftLastWriteTime; FILEDESCRIPTOR &Fd = Gd.fgd[0]; Fd.dwFlags = FD_ATTRIBUTES | FD_PROGRESSUI; Fd.dwFileAttributes = FILE_ATTRIBUTE_COMPRESSED | FILE_ATTRIBUTE_NORMAL; int64 Sz = Src->GetSize(); if (Sz >= 0) { Fd.dwFlags |= FD_FILESIZE; Fd.nFileSizeHigh = Sz >> 32; Fd.nFileSizeLow = Sz & 0xffffffff; } auto Leaf = FileName.RFind(DIR_STR); LAutoWString FnW(Utf8ToWide(Leaf >= 0 ? FileName(Leaf+1,-1) : FileName)); Strcpy(Fd.cFileName, CountOf(Fd.cFileName), FnW.Get()); LVariant &v = dd.Data[0]; v.SetBinary(sizeof(Gd), &Gd); #elif defined(__GTK_H__) LAssert(!"Not impl."); #elif LGI_COCOA LAssert(!"Not impl."); #elif LGI_CARBON // See Apple: Technical Note TN1085 // https://web.archive.org/web/20080725134839/http://developer.apple.com/technotes/tn/tn1085.html // flavorTypePromiseHFS = 'phfs' PromiseHFSFlavor Promise; ZeroObj(Promise); Promise.fileType = 'mbox'; Promise.fileCreator = '****'; Promise.promisedFlavor = kDragPromisedFlavor; // 'fssP' int Sz = sizeof(Promise); LVariant &v1 = dd.Data[0]; v1.SetBinary(sizeof(Promise), &Promise, false); // The Dnd code will add the 2nd part of the promise // There isn't the visibility into that API at this level #else #error "Impl me." #endif } ~LDndFilePromise() { #if defined(WINDOWS) #elif defined(__GTK_H__) #elif defined(MAC) #endif } #if defined(WINDOWS) #elif defined(__GTK_H__) #elif defined(MAC) #endif int AddContents(LDragData &dd) { dd.Data[0] = Src; return 1; } }; ////////////////////////////////////////////////////////////////////////////// #define PROFILE_POPULATE 0 #define PROFILE_LOAD_THINGS 0 int FoldersCurrentlyLoading = 0; char ScribeFolderObject[] = "com.memecode.Folder"; class ThingContainerPriv { public: int8 IsInbox; bool InUpdateUnread = false; LAutoPtr DsBase; LAutoPtr DsUnread; LAutoPtr FilePromise; ThingContainerPriv() { IsInbox = -1; } }; ////////////////////////////////////////////////////////////////////////////// ScribeFolder::ScribeFolder() { d = new ThingContainerPriv; } ScribeFolder::~ScribeFolder() { bool IsRoot = !GetParent(); if (CurState != FldState_Idle) { // int Cur = FoldersCurrentlyLoading; #ifdef _DEBUG LAssert(!"Can't delete folder while it's busy."); #else LgiMsg( App, "Can't unload folder while busy. Please report what\n" "you were attempting to do to fret@memecode.com", "Scribe Error", MB_OK); #endif return; } switch (GetItemType()) { case MAGIC_CALENDAR: CalendarSource::FolderDelete(this); // fall through case MAGIC_CONTACT: case MAGIC_FILTER: case MAGIC_GROUP: App->RemoveThingSrc(this); break; default: break; } if (View()) { bool IsMe = View()->GetContainer() == this; // bool IsSelect = Select(); View()->DeletePlaceHolders(); if (IsMe) { View()->SetContainer(0); View()->RemoveAll(); } } Thing *t; int i = 0; while ((t = Items[i])) { if (!t->DecRef()) i++; } Update(); EmptyFieldList(); DeleteObj(d); ScribeFolder *f = GetChildFolder(); DeleteObj(f); if (!IsRoot) { f = GetNextFolder(); DeleteObj(f); } // Don't delete 'Object' here, it's owned by the backend storage object } bool ScribeFolder::SetObject(LDataI *o, bool InDestructor, const char *File, int Line) { if (CurState != FldState_Idle) { LAssert(!"Can't set object while folder is not idle."); return false; } return LDataUserI::SetObject(o, InDestructor, File, Line); } void ScribeFolder::UpdateOsUnread() { #ifdef WIN32 if (App->GetFolder(FOLDER_INBOX) == this) { LHashTbl, int> Un(0, -1); // Pre-populate 'Un' with our email accounts List *Acc = App->GetAccounts(); if (Acc) { for (auto a: *Acc) { LVariant e = a->Identity.Email(); if (e.Str()) Un.Add(e.Str(), 0); } } // Scan folder for unread email... for (auto t : Items) { Mail *m = t->IsMail(); if (m) { if (!TestFlag(m->GetFlags(), MAIL_READ)) { ScribeAccount *a = m->GetAccountSentTo(); if (a) { LVariant Email; Email = a->Identity.Email(); if (Email.Str()) { int Cur = Un.Find(Email.Str()); Un.Add(Email.Str(), Cur + 1); } } } } } #if 0 // Set system email status LArray Ver; int Os = LGetOs(&Ver); if ((Os == LGI_OS_WIN32 || Os == LGI_OS_WIN64) && (Ver[0] > 5 || (Ver[0] == 5 && Ver[1] > 0))) { char e[256]; LgiGetExeFile(e, sizeof(e)); typedef HRESULT (__stdcall *pSHSetUnreadMailCount)(LPCWSTR pszMailAddress, DWORD dwCount,LPCWSTR pszShellExecuteCommand); LLibrary Shell32("shell32"); pSHSetUnreadMailCount SHSetUnreadMailCount = (pSHSetUnreadMailCount) Shell32.GetAddress("SHSetUnreadMailCountW"); if (SHSetUnreadMailCount) { LVariant Exe; Exe = e; char *k; for (int u=Un.First(&k); u>=0; u=Un.Next(&k)) { LVariant Email = k; SHSetUnreadMailCount(Email.WStr(), u, Exe.WStr()); } } } #endif } #endif } void ScribeFolder::SetLoadOnDemand() { if ((LoadOnDemand = new LTreeItem)) { Expanded(false); LoadOnDemand->SetText((char*)LLoadString(IDS_LOADING)); Insert(LoadOnDemand); } } -Mail *ScribeFolder::GetMessageById(char *Id) +void ScribeFolder::GetMessageById(const char *Id, std::function Callback) { if (!Id) - return NULL; - - auto s = LoadThings(); - if (s == Store3Loading) - return NULL; - - for (auto t : Items) + { + if (Callback) Callback(NULL); + return; + } + + LoadThings(NULL, [&](auto s) { - Mail *r = t->IsMail(); - if (!r) - continue; + if (s <= Store3Loading) + { + if (Callback) Callback(NULL); + return; + } + + for (auto t : Items) + { + Mail *r = t->IsMail(); + if (!r) + continue; - auto rid = r->GetMessageId(); - if (!Stricmp(rid, Id)) - return r; - } - - return NULL; + auto rid = r->GetMessageId(); + if (!Stricmp(rid, Id)) + { + if (Callback) + Callback(r); + return; + } + } + }); } bool ScribeFolder::InsertThing(Thing *t) { bool Status = false; if (t && Select() && View()) { // Filter ThingFilter *Filter = App->GetThingFilter(); t->SetFieldArray(FieldArray); if (!Filter || Filter->TestThing(t)) { if (t->GetList() != View()) { View()->Insert(t); #if WINNATIVE UpdateWindow(View()->Handle()); #endif } } Status = true; } return Status; } bool ScribeFolder::GetThreaded() { return GetObject() ? GetObject()->GetInt(FIELD_FOLDER_THREAD) != 0 : false; } void ScribeFolder::SetThreaded(bool t) { if (GetObject() && GetThreaded() ^ t) { GetObject()->SetInt(FIELD_FOLDER_THREAD, t); SetDirty(); } } ThingList *ScribeFolder::View() { return App ? App->GetMailList() : 0; } bool ScribeFolder::HasFieldId(int Id) { auto o = GetFldObj(); if (o) { for (LDataPropI *f = o->Fields().First(); f; f = o->Fields().Next()) { if (Id == f->GetInt(FIELD_ID)) { return true; } } } return false; } -bool ScribeFolder::SetFolderPerms(LView *Parent, ScribeAccessType Access, ScribePerm Perm) +void ScribeFolder::SetFolderPerms(LView *Parent, ScribeAccessType Access, ScribePerm Perm, std::function Callback) { - bool Status = false; ScribePerm Current = GetFolderPerms(Access); int Field = (Access == ScribeReadAccess) ? FIELD_FOLDER_PERM_READ : FIELD_FOLDER_PERM_WRITE; if (GetObject() && Current != Perm) { - bool Allow = App->GetAccessLevel(Parent, Current, GetPath()); - if (Allow) + App->GetAccessLevel(Parent, Current, GetPath(), [&](auto Allow) { - GetObject()->SetInt(Field, Perm); - SetDirty(); - Status = true; - } + if (Allow) + { + GetObject()->SetInt(Field, Perm); + SetDirty(); + } + if (Callback) + Callback(Allow); + }); } - else Status = true; // i.e. not changing - - return Status; + else + { + if (Callback) + Callback(true); // i.e. not changing + } } ScribePerm ScribeFolder::GetFolderPerms(ScribeAccessType Access) { int Field = (Access == ScribeReadAccess) ? FIELD_FOLDER_PERM_READ : FIELD_FOLDER_PERM_WRITE; return GetObject() ? (ScribePerm)GetObject()->GetInt(Field) : PermRequireNone; } Store3Status ScribeFolder::CopyTo(ScribeFolder *NewParent, int NewIndex) { Store3Status Copied = Store3Error; if (NewParent == GetParent()) { NewParent->GetObject()->GetStore(); } else if (GetObject() && GetObject()->GetStore() && NewParent->GetObject() && NewParent->GetObject()->GetStore()) { // Find or create the destination folder LDataFolderI *Dst = 0; auto Name = GetObject()->GetStr(FIELD_FOLDER_NAME); for (ScribeFolder *f = NewParent->GetChildFolder(); f; f = f->GetNextFolder()) { auto n = f->GetObject()->GetStr(FIELD_FOLDER_NAME); if (n && !_stricmp(n, Name)) { Dst = f->GetFldObj(); } } if (!Dst) { // Create sub-folder if ((Dst = dynamic_cast(NewParent->GetObject()->GetStore()->Create(GetObject()->Type())))) { Dst->CopyProps(*GetObject()); if (Dst->Save(NewParent->GetObject()) == Store3Error) return Store3Error; } else return Store3Error; } if (Dst) { Copied = Store3ReplicateFolders(App, Dst, GetFldObj(), true, false, 0); } } else LAssert(!"Pointer error"); return Copied; } Store3Status ScribeFolder::SetFolder(ScribeFolder *f, int Param) { Store3Status Moved = Store3Error; if (f == this) { LAssert(0); } else if (f && GetObject() && GetObject()->GetStore() && f->GetObject() && f->GetObject()->GetStore()) { if (GetObject()->GetStore() == f->GetObject()->GetStore()) { // Simple in storage movement LArray Mv; Mv.Add(GetObject()); Moved = GetObject()->GetStore()->Move(f->GetFldObj(), Mv); if (Moved && Param >= 0) { GetObject()->SetInt(FIELD_FOLDER_INDEX, Param); } } else { // Cross storage movement... // Find or create the destinate folder LDataFolderI *Dst = 0; int Idx = 0; auto Name = GetObject()->GetStr(FIELD_FOLDER_NAME); for (ScribeFolder *c = f->GetChildFolder(); c; c = c->GetNextFolder(), Idx++) { auto n = c->GetObject()->GetStr(FIELD_FOLDER_NAME); if (n && !_stricmp(n, Name)) { Dst = c->GetFldObj(); break; } } if (!Dst) { Dst = dynamic_cast(f->GetObject()->GetStore()->Create(f->Type())); } else { ScribeFolder *c = CastFolder(dynamic_cast(Dst)); if (c) { c->Remove(); Param = Idx; DeleteObj(c); } } if (Dst) { if (View()) View()->RemoveAll(); // Clean up all the items and sub-folders, they will be created by the // replication code calling "OnNew". Thing *t; while ((t = Items[0])) { t->DecRef(); Items.Delete(t); } // Delete the child folders... ScribeFolder *Cf; while ((Cf = GetChildFolder())) { DeleteObj(Cf); } // Copy ourself over... Dst->CopyProps(*GetObject()); LDataFolderI *Old = GetFldObj(); SetObject(Dst, false, _FL); // Save the object to the new store... Store3Status s = GetObject()->Save(f->GetObject()); if (s != Store3Error) { // And replicate all the children objects... Moved = Store3ReplicateFolders(App, GetFldObj(), Old, true, true, 0); } } else LAssert(!"Not a valid folder"); } if (Moved == Store3Success) { // Move LTreeItem node... f->Insert(this, Param); Select(true); LoadFolders(); // Adjust all the other indexes so that it's remembered ScribeFolder *p = GetFolder(); if (p) { int Idx = 0; for (p = p->GetChildFolder(); p; p = p->GetNextFolder()) { p->SetSortIndex(Idx++); p->SetDirty(); } } } } else LAssert(!"Pointer error"); return Moved; } -bool ScribeFolder::DeleteAllThings() +Store3Status ScribeFolder::DeleteAllThings(std::function Callback) { if (!GetFldObj()) - return false; + return Store3Error; Store3Status r = GetFldObj()->DeleteAllChildren(); if (r == Store3Error) { LAssert(!"DeleteAllChildren failed."); - return false; } else if (r == Store3Success) { LAssert(Items.Length() == 0); OnUpdateUnRead(0, true); Update(); } - return true; + return r; } -bool ScribeFolder::DeleteThing(Thing *t) +Store3Status ScribeFolder::DeleteThing(Thing *t, std::function Callback) { - bool Status = false; + Store3Status Status = Store3Error; if (t && t->GetObject()) { if (t->IsAttachment()) { #ifdef _DEBUG Mail *m = #endif t->IsAttachment()->GetOwner(); LAssert(m && Items.HasItem(m)); } if (t->GetObject()->Delete()) { t->SetParentFolder(NULL); if (View()) View()->Remove(t); } } return Status; } -Store3Status ScribeFolder::WriteThing(Thing *t) +Store3Status ScribeFolder::WriteThing(Thing *t, std::function Callback) { - Store3Status Status = Store3Error; + if (!t) + { + if (Callback) Callback(Store3Error); + return Store3Error; + } auto Path = GetFolder()->GetPath(); - bool Allow = t && (!App || App->GetAccessLevel(App, GetWriteAccess(), Path)); - if (Allow) + + auto OnAllow = [&]() { // Generic thing storage.. bool Create = !t->GetObject(); if (Create) t->SetObject(GetObject()->GetStore()->Create(t->Type()), false, _FL); if (!t->GetObject()) { - LAssert(!"No object?"); - LgiTrace("%s:%i - No object to save.\n", _FL); - return Store3Error; + LAssert(!"No object?"); + LgiTrace("%s:%i - No object to save.\n", _FL); + if (Callback) Callback(Store3Error); + return; } // saving a thing that already has an item on disk auto Obj = GetObject(); - Status = t->GetObject()->Save(Obj); + auto Status = t->GetObject()->Save(Obj); if (Status != Store3Error) { - // The ScribeWnd::OnNew will take care of inserting the item into the - // right folder, updating the unread count, any filtering etc. + // The ScribeWnd::OnNew will take care of inserting the item into the + // right folder, updating the unread count, any filtering etc. t->OnSerialize(true); + if (Callback) Callback(Status); } else { if (Create) t->SetObject(NULL, false, _FL); - LgiTrace("%s:%i - Object->Save returned %i.\n", _FL, Status); - return Store3Error; + LgiTrace("%s:%i - Object->Save returned %i.\n", _FL, Status); + if (Callback) Callback(Store3Error); } - } - - if (!Status) - { - // You need to implement the writer for this object type - LAssert(0); - } - - return Status; + }; + + if (App) + App->GetAccessLevel(App, GetWriteAccess(), Path, [&](auto Allow) + { + if (Allow) + OnAllow(); + }); + else + OnAllow(); + + return Store3Success; } int ThingContainerNameCmp(LTreeItem *a, LTreeItem *b, NativeInt d) { ScribeFolder *A = dynamic_cast(a); ScribeFolder *B = dynamic_cast(b); if (A && B) { const char *s1 = A->GetText(); const char *s2 = B->GetText(); const char *Empty = ""; return _stricmp(s1?s1:Empty, s2?s2:Empty); } else LAssert(!"Invalid objects."); return 0; } int ThingContainerIdxCmp(LTreeItem *a, LTreeItem *b, NativeInt d) { ScribeFolder *A = dynamic_cast(a); ScribeFolder *B = dynamic_cast(b); if (A && B) { int Aidx = A->GetSortIndex(); int Bidx = B->GetSortIndex(); if (Aidx >= 0 || Bidx >= 0) { return Aidx - Bidx; } } else LAssert(!"Invalid objects."); return 0; } void ScribeFolder::SortSubfolders() { int i = 0; ScribeFolder *c; LTreeItem::Items.Sort(ThingContainerNameCmp); for (c = GetChildFolder(); c; c = c->GetNextFolder()) { c->SetSortIndex(i++); c->SetDirty(); } GetTree()->UpdateAllItems(); GetTree()->Invalidate(); } void ScribeFolder::DoContextMenu(LMouse &m) { MailTree *mt = dynamic_cast(GetTree()); LScriptUi s(new LSubMenu); List Templates; bool ForceDelete = m.Shift(); bool IsTrash = false; bool IsSystem = false; if (!s.Sub) return; IsTrash = GetItemType() == MAGIC_ANY; IsSystem = App->GetFolderType(this) >= 0; if (App->GetFolderType(this) == FOLDER_OUTBOX) { s.Sub->AppendItem(LLoadString(IDS_MARK_ALL_SEND), IDM_MARK_SEND, true); } if (IsTrash) { s.Sub->AppendItem(LLoadString(IDS_EMPTY), IDM_EMPTY, true); s.Sub->AppendItem(LLoadString(IDS_MARK_ALL_READ), IDM_MARK_ALL_READ, true); } else if (!IsRoot()) { auto *LiteralNew = LLoadString(IDS_NEW); const char *Type = 0; switch (GetItemType()) { case MAGIC_MAIL: Type = LLoadString(IDS_MAIL); break; case MAGIC_CONTACT: Type = LLoadString(IDS_CONTACT); break; case MAGIC_CALENDAR: Type = LLoadString(IDS_CAL_EVENT); break; case MAGIC_FILTER: Type = LLoadString(IDS_FILTER); break; default: break; } if (Type) { char Str[256]; sprintf_s(Str, sizeof(Str), "%s %s", LiteralNew, Type); s.Sub->AppendItem(Str, IDM_NEW_EMAIL, true); } } switch (GetItemType()) { case MAGIC_CONTACT: { char Str[256]; const char *LiteralEmail = LLoadString(IDS_EMAIL); sprintf_s(Str, sizeof(Str), "%s '%s'", LiteralEmail, GetText()); s.Sub->AppendItem(Str, IDM_EMAIL_GROUP, true); ScribeFolder *f = App->GetFolder(FOLDER_TEMPLATES); if (f) { + // FIXME f->LoadThings(); auto Merge = s.Sub->AppendSub(LLoadString(IDS_MERGE_TEMPLATE)); if (Merge) { int n = 0; for (auto t: f->Items) { Mail *m = t->IsMail(); if (m) { Templates.Insert(m); Merge->AppendItem(m->GetSubject()?m->GetSubject():(char*)"(no subject)", IDM_MERGE_TEMPLATE_BASE+n++, true); } } } } else { s.Sub->AppendItem(LLoadString(IDS_MERGE_TEMPLATE), 0, false); } s.Sub->AppendItem(LLoadString(IDS_MERGE_FILE), IDM_MERGE_FILE, true); break; } case MAGIC_MAIL: { if (!IsRoot()) { s.Sub->AppendItem(LLoadString(IDC_COLLECT_SUB_MAIL), IDM_COLLECT_MAIL, true); s.Sub->AppendItem(LLoadString(IDS_MARK_ALL_READ), IDM_MARK_ALL_READ, true); if (GetObject() && GetObject()->GetInt(FIELD_STORE_TYPE) == Store3Imap) { s.Sub->AppendItem(LLoadString(IDC_UNDELETE), IDM_UNDELETE, true); s.Sub->AppendItem("Expunge", IDM_EXPUNGE, true); s.Sub->AppendItem(LLoadString(IDC_REFRESH), IDM_REFRESH, true); } } break; } default: break; } if (s.Sub->ItemAt(0)) { s.Sub->AppendSeparator(); } s.Sub->AppendItem(LLoadString(IDS_FIND), IDM_FIND, true); s.Sub->AppendSeparator(); bool HasFolderTask = App->HasFolderTasks(); bool FolderOp = !HasFolderTask && !IsRoot(); s.Sub->AppendItem(LLoadString(IDS_DELETEFOLDER), IDM_DELETE, FolderOp && ((!IsTrash && !IsSystem) || ForceDelete)); s.Sub->AppendItem(LLoadString(IDS_CREATESUBFOLDER), IDM_CREATE_SUB, !HasFolderTask && CanHaveSubFolders(GetItemType())); s.Sub->AppendItem(LLoadString(IDS_RENAMEFOLDER), IDM_RENAME, FolderOp); auto ExportMimeType = GetStorageMimeType(); s.Sub->AppendItem(LLoadString(IDS_EXPORT), IDM_EXPORT, FolderOp && ExportMimeType != NULL); s.Sub->AppendSeparator(); s.Sub->AppendItem(LLoadString(IDS_SORT_SUBFOLDERS), IDM_SORT_SUBFOLDERS, true); s.Sub->AppendItem(LLoadString(IDS_COPY_PATH), IDM_COPY_PATH, true); s.Sub->AppendItem(LLoadString(IDS_PROPERTIES), IDM_PROPERTIES, true); LArray Callbacks; if (App->GetScriptCallbacks(LFolderContextMenu, Callbacks)) { LScriptArguments Args(NULL); Args[0] = new LVariant(App); Args[1] = new LVariant(this); Args[2] = new LVariant(&s); for (auto c: Callbacks) App->ExecuteScriptCallback(*c, Args); Args.DeleteObjects(); } m.ToScreen(); LPoint Scr = _ScrollPos(); m.x -= Scr.x; m.y -= Scr.y; int Msg = s.Sub->Float(GetTree(), m.x, m.y); switch (Msg) { case IDM_NEW_EMAIL: { switch (GetItemType()) { case MAGIC_MAIL: case MAGIC_CONTACT: case MAGIC_CALENDAR: case MAGIC_FILTER: { if (App) App->CreateItem(GetItemType(), this); break; } default: break; } break; } case IDM_MARK_SEND: { for (Thing *i: Items) { Mail *m = i->IsMail(); if (m) { m->SetFlags(MAIL_READY_TO_SEND | MAIL_READ | MAIL_CREATED); m->SetDirty(); m->Update(); } } break; } case IDM_EMAIL_GROUP: { if (App) { Mail *m = dynamic_cast(App->CreateItem(MAGIC_MAIL, NULL, false)); if (m) { MailUi *UI = dynamic_cast(m->DoUI()); if (UI) { List Cts; App->GetContacts(Cts, this); for (auto c: Cts) { UI->AddRecipient(c); } } } } break; } case IDM_FIND: { ScribeFolder *Folder = (ScribeFolder*) GetTree()->Selection(); OpenFinder(App, Folder); break; } case IDM_CREATE_SUB: { if (mt) mt->OnCreateSubDirectory(this); break; } case IDM_DELETE: { if (mt) mt->OnDelete(this, ForceDelete); break; } case IDM_RENAME: { - FolderNameDlg Dlg(mt, GetName(true)); - if (Dlg.DoModal() && - ValidStr(Dlg.Name)) + auto Dlg = new FolderNameDlg(mt, GetName(true)); + Dlg->DoModal([this, Dlg, mt](auto dlg, auto id) { - // check for folder name conflicts... - ScribeFolder *ParentFolder = GetFolder(); - LString Path; - if (ParentFolder) - Path = ParentFolder->GetPath(); - if (Path) + if (id && ValidStr(Dlg->Name)) { - char s[256]; - sprintf_s(s, sizeof(s), "%s/%s", Path.Get(), Dlg.Name); - if (App->GetFolder(s)) + // check for folder name conflicts... + ScribeFolder *ParentFolder = GetFolder(); + LString Path; + if (ParentFolder) + Path = ParentFolder->GetPath(); + if (Path) { - LgiMsg(mt, LLoadString(IDS_SUBFLD_NAME_CLASH), AppName, MB_OK); - return; + char s[256]; + sprintf_s(s, sizeof(s), "%s/%s", Path.Get(), Dlg->Name); + if (App->GetFolder(s)) + { + LgiMsg(mt, LLoadString(IDS_SUBFLD_NAME_CLASH), AppName, MB_OK); + return; + } } + + // change the folders name... + OnRename(Dlg->Name); } - - // change the folders name... - OnRename(Dlg.Name); - } + delete dlg; + }); break; } case IDM_EXPORT: { LString DropName = LGetLeaf(GetDropFileName()); if (!DropName) { LgiTrace("%s:%i - Failed to create folder name.\n", _FL); break; } - LFileSelect s; - s.Name(DropName); - s.Parent(mt); - if (!s.Save()) - break; - - if (LFileExists(s.Name())) + auto s = new LFileSelect(mt); + s->Name(DropName); + s->Save([&](auto dlg, auto status) { - LString a, b; - a.Printf(LLoadString(IDS_ERROR_FILE_EXISTS), s.Name()); - b.Printf("\n%s\n", LLoadString(IDS_ERROR_FILE_OVERWRITE)); - if (LgiMsg(GetTree(), a + b, AppName, MB_YESNO) == IDNO) - break; - } - - LAutoPtr f(new LFile); - if (!f || !f->Open(s.Name(), O_WRITE)) - { - LgiTrace("%s:%i - Failed to open '%s' for writing.\n", _FL, s.Name()); - break; - } - - f->SetSize(0); - Export(AutoCast(f), ExportMimeType); + LAutoPtr mem(dlg); + if (status) + { + if (LFileExists(s->Name())) + { + LString a, b; + a.Printf(LLoadString(IDS_ERROR_FILE_EXISTS), s->Name()); + b.Printf("\n%s\n", LLoadString(IDS_ERROR_FILE_OVERWRITE)); + if (LgiMsg(GetTree(), a + b, AppName, MB_YESNO) == IDNO) + return; + } + + LAutoPtr f(new LFile); + if (!f || !f->Open(s->Name(), O_WRITE)) + { + LgiTrace("%s:%i - Failed to open '%s' for writing.\n", _FL, s->Name()); + return; + } + + f->SetSize(0); + LAutoPtr str(f.Release()); + ExportAsync(str, ExportMimeType); + } + }); break; } case IDM_EMPTY: { if (App->GetMailList()) { - App->GetMailList()->RemoveAll(); - LYield(); + App->GetMailList()->RemoveAll(); LArray Del; for (ScribeFolder *c = GetChildFolder(); c; c = c->GetNextFolder()) Del.Add(c->GetObject()); if (Del.Length()) GetObject()->GetStore()->Delete(Del, false); - DeleteAllThings(); - mt->Invalidate(); + DeleteAllThings([&](auto status) + { + mt->Invalidate(); + }); } break; } case IDM_SORT_SUBFOLDERS: { SortSubfolders(); break; } case IDM_PROPERTIES: { mt->OnProperties(this); break; } case IDM_COPY_PATH: { LClipBoard c(GetTree()); #ifdef WINDOWS LAutoWString w(Utf8ToWide(GetPath())); c.TextW(w); #else c.Text(GetPath()); #endif break; } case IDM_COLLECT_MAIL: { CollectSubFolderMail(); break; } case IDM_MARK_ALL_READ: { LArray Change; for (auto c : Items) { Mail *m = c->IsMail(); if (m) { if (!TestFlag(m->GetFlags(), MAIL_READ)) Change.Add(m->GetObject()); } } LVariant v = MAIL_READ; if (GetObject()->GetStore()->Change(Change, FIELD_FLAGS, v, OpPlusEquals) == Store3Error) { LProgressDlg Prog(GetTree(), 500); Prog.SetRange(Change.Length()); // FIXME!! // Prog.SetYieldTime(200); Prog.SetDescription("Marking email..."); for (auto c : Change) { auto t = CastThing(c); Mail *m = t ? t->IsMail() : NULL; if (m) m->SetFlags(m->GetFlags() | MAIL_READ, false, false); Prog++; if (Prog.IsCancelled()) break; } OnUpdateUnRead(0, true); } break; } case IDM_MERGE_FILE: { - LFileSelect s; - s.Parent(mt); - s.Type("Email Template", "*.txt;*.eml"); - if (s.Open()) + auto s = new LFileSelect(mt); + s->Type("Email Template", "*.txt;*.eml"); + s->Open([this](auto dlg, auto id) { - LArray Recip; - for (auto i: Items) + if (id) { - Recip.Add(new ListAddr(i->IsContact())); + LArray Recip; + for (auto i: Items) + { + Recip.Add(new ListAddr(i->IsContact())); + } + App->MailMerge(Recip, dlg->Name(), 0); + Recip.DeleteObjects(); } - App->MailMerge(Recip, s.Name(), 0); - Recip.DeleteObjects(); - } + delete dlg; + }); break; } case IDM_UNDELETE: { const char *s = DomToStr(SdUndelete); GetFldObj()->OnCommand(s); break; } case IDM_EXPUNGE: { const char *s = DomToStr(SdExpunge); GetFldObj()->OnCommand(s); break; } case IDM_REFRESH: { const char *s = DomToStr(SdRefresh); GetFldObj()->OnCommand(s); break; } default: { Mail *Template = Templates[Msg - IDM_MERGE_TEMPLATE_BASE]; if (Template) { LArray Recip; for (auto i: Items) { Recip.Add(new ListAddr(i->IsContact())); } App->MailMerge(Recip, 0, Template); Recip.DeleteObjects(); } else { // Handle any installed callbacks for menu items for (unsigned i=0; iExecuteScriptCallback(Cb, Args); } } } break; } } } bool ScribeFolder::IsInTrash() { ScribeFolder *p = this; while ((p = p->GetFolder())) { if (p->GetItemType() == MAGIC_ANY) return true; } return false; } /// This just adds certain folder types as a group ware source at the app level void ScribeFolder::OnItemType() { switch (GetItemType()) { case MAGIC_CONTACT: case MAGIC_FILTER: case MAGIC_CALENDAR: case MAGIC_GROUP: App->AddThingSrc(this); break; default: break; } } bool ScribeFolder::LoadFolders() { // static int64 Last = 0; auto FldObj = GetFldObj(); if (!FldObj) return true; CurState = FldState_Loading; FoldersCurrentlyLoading++; LHashTbl, ScribeFolder*> Loaded; for (ScribeFolder *c = GetChildFolder(); c; c = c->GetNextFolder()) { Loaded.Add(c->GetObject(), c); } LDataFolderI *s; auto &Sub = FldObj->SubFolders(); for (unsigned sIdx = 0; Sub.GetState() == Store3Loaded && sIdx < Sub.Length(); sIdx++) { if (!(s = Sub[sIdx])) break; if (!Loaded.Find(s)) { ScribeFolder *n = new ScribeFolder; if (n) { n->App = App; n->SetObject(s, false, _FL); Insert(n); SetWillDirty(false); Expanded(GetOpen() != 0); SetWillDirty(true); n->LoadFolders(); n->OnItemType(); } #if 0 int64 Now = LCurrentTime(); if (Now - Last > 500) { LYield(); Last = Now; } #endif } } LTreeItem::Items.Sort(ThingContainerIdxCmp); CurState = FldState_Idle; FoldersCurrentlyLoading--; return true; } class LoadProgressItem : public LListItem { int n; int Total; int64 Last; public: LoadProgressItem(int t) { n = 0; Total = t; Last = LCurrentTime(); } void SetPos(int i) { n = i; if (n % 32 == 0) { int64 Now = LCurrentTime(); if (Now > Last + 500) { if (Parent) { Parent->Invalidate(&Pos, true); #ifdef MAC LYield(); #endif } Last = Now; } LSleep(0); } } void OnPaint(ItemPaintCtx &Ctx) { int x = n * 150 / Total; LRect &r = Ctx; Ctx.pDC->Colour(L_FOCUS_SEL_BACK); Ctx.pDC->Rectangle(r.x1, r.y1, r.x1 + x, r.y2); Ctx.pDC->Colour(L_MED); Ctx.pDC->Rectangle(r.x1 + x + 1, r.y1, r.x1 + 150, r.y2); Ctx.pDC->Colour(L_WORKSPACE); Ctx.pDC->Rectangle(r.x1 + 151, r.y1, r.x2, r.y2); } }; class LoadingItem : public LListItem { LDataIterator *Iter; LString s; public: LoadingItem(LDataIterator *it) { _UserPtr = NULL; if ((Iter = it)) { Iter->SetProgressFn([this](ssize_t pos, ssize_t sz) { this->s.Printf("%.1f%%", (double)pos * 100 / sz); this->Update(); }); } } ~LoadingItem() { if (Iter) Iter->SetProgressFn(NULL); } bool SetText(const char *str, int i) { s = str; Update(); return true; } void OnPaint(LItem::ItemPaintCtx &Ctx) { LString msg; auto loading = LLoadString(IDS_LOADING); if (s) msg.Printf("%s %s", loading, s.Get()); else msg = loading; LDisplayString d(LSysFont, msg); LSysFont->Colour(L_TEXT, L_WORKSPACE); LSysFont->Transparent(false); d.Draw(Ctx.pDC, (Ctx.X()-d.X())/2, Ctx.y1, &Ctx); } void Select(bool b) { } }; bool ScribeFolder::UnloadThings() { bool Status = true; if (IsLoaded()) { // LgiTrace("Unloading %s, Items=%i\n", GetPath(), Items.Length()); // Emptying the item list, leave the store nodes around though Thing *t; while ((t = Items[0])) { if (t->GetDirty()) { t->Save(0); } t->SetObject(NULL, false, _FL); t->DecRef(); } Items.Empty(); IsLoaded(false); GetObject()->SetInt(FIELD_LOADED, false); } return Status; } #define DEBUG_PROF_LOAD_THINGS 0 #if DEBUG_PROF_LOAD_THINGS #define PROFILE(str) prof.Add("Add"); .Add(str) #else #define PROFILE(str) #endif - -Store3State ScribeFolder::LoadThings(LViewI *Parent) +Store3Status ScribeFolder::LoadThings(LViewI *Parent, std::function Callback) { - Store3State Status = Store3Loaded; int OldUnRead = GetUnRead(); auto FldObj = GetFldObj(); if (!FldObj) { LgiTrace("%s:%i - No folder object.\n", _FL); - return Store3Unloaded; + return Store3Error; } + if (!Parent) + Parent = App; + + auto ContinueLoading = [&]() + { + WhenLoaded(_FL, + [&]() + { + // This is called when all the Store3 objects are loaded + int Unread = OldUnRead; + if (Unread < 0) + Unread = GetUnRead(); + + Loading.Reset(); + + auto &Children = GetFldObj()->Children(); + if (Children.GetState() != Store3Loaded) + { + LAssert(!"Really should be loaded by now."); + return; + } + + for (auto c = Children.First(); c; c = Children.Next()) + { + auto t = CastThing(c); + if (t) + { + // LAssert(Items.HasItem(t)); + } + else if ((t = App->CreateThingOfType((Store3ItemTypes) c->Type(), c))) + { + t->SetObject(c, false, _FL); + t->SetParentFolder(this); + t->OnSerialize(false); + } + } + + int NewUnRead = 0; + for (auto t: Items) + { + if (t->GetFolder() != this) + { + #ifdef _DEBUG + char s[256]; + sprintf_s(s, sizeof(s), + "%s:%i - Error, thing not parented correctly: this='%s', child='%x'\n", + _FL, + GetText(0), + t->GetObject() ? t->GetObject()->Type() : 0); + printf("%s", s); + LgiMsg(App, s, AppName); + #endif + + t->SetFolder(this); + } + + Mail *m = t->IsMail(); + if (m) + NewUnRead += (m->GetFlags() & MAIL_READ) ? 0 : 1; + + t->SetFieldArray(FieldArray); + } + + if (Unread != NewUnRead) + OnUpdateUnRead(NewUnRead - Unread, false); + + Update(); + + if (d->IsInbox < 0 && App) + { + d->IsInbox = App->GetFolder(FOLDER_INBOX) == this; + if (d->IsInbox > 0) + UpdateOsUnread(); + } + + if (Callback) + Callback(Store3Success); + }, + 0); + + if (!IsLoaded()) + { + bool Ui = Tree ? Tree->InThread() : false; + if (Ui) + Tree->Capture(false); + + auto &Children = FldObj->Children(); + auto Status = Children.GetState(); + if (Status != Store3Loaded) + { + if (View() && Loading.Reset(Ui ? new LoadingItem(&Children) : NULL)) + View()->Insert(Loading); + + return Status; // Ie deferred or error... + } + + IsLoaded(true); + } + + return Store3Loaded; + }; + auto Path = GetPath(); - if (App && !App->GetAccessLevel(Parent ? Parent : App, GetReadAccess(), Path)) + if (!App || !Path) + { + LAssert(!"We should probably always have an 'App' and 'Path' ptrs..."); + return Store3Error; + } + + std::function AccessCb; + if (Callback) { + AccessCb = [&](bool Access) + { + if (Access) + ContinueLoading(); + else + Callback(Store3Error); + }; + } + + auto Access = App->GetAccessLevel( Parent, + GetReadAccess(), + Path, + AccessCb); + if (Access == Store3Error) + { // No read access: LgiTrace("%s:%i - Folder read access denied.\n", _FL); // Emptying the item list, leave the store nodes around though for (auto t: Items) - t->SetObject(NULL, false, _FL); + t->SetObject(NULL, _FL); Items.Empty(); IsLoaded(false); Update(); - return Store3Unloaded; } - - WhenLoaded(_FL, [this, OldUnRead]() + else if (Access == Store3Success) { - int Unread = OldUnRead; - if (Unread < 0) - Unread = GetUnRead(); - - Loading.Reset(); - - auto &Children = GetFldObj()->Children(); - if (Children.GetState() != Store3Loaded) - { - LAssert(!"Really should be loaded by now."); - return; - } - - for (auto c = Children.First(); c; c = Children.Next()) - { - auto t = CastThing(c); - if (t) - { - // LAssert(Items.HasItem(t)); - } - else if ((t = App->CreateThingOfType((Store3ItemTypes) c->Type(), c))) - { - t->SetObject(c, false, _FL); - t->SetParentFolder(this); - t->OnSerialize(false); - } - } - - int NewUnRead = 0; - for (auto t: Items) - { - if (t->GetFolder() != this) - { - #ifdef _DEBUG - char s[256]; - sprintf_s(s, sizeof(s), - "%s:%i - Error, thing not parented correctly: this='%s', child='%x'\n", - _FL, - GetText(0), - t->GetObject() ? t->GetObject()->Type() : 0); - printf("%s", s); - LgiMsg(App, s, AppName); - #endif - - t->SetFolder(this); - } - - Mail *m = t->IsMail(); - if (m) - NewUnRead += (m->GetFlags() & MAIL_READ) ? 0 : 1; - - t->SetFieldArray(FieldArray); - } - - if (Unread != NewUnRead) - OnUpdateUnRead(NewUnRead - Unread, false); - - Update(); - - if (d->IsInbox < 0 && App) - { - d->IsInbox = App->GetFolder(FOLDER_INBOX) == this; - if (d->IsInbox > 0) - UpdateOsUnread(); - } - }, 0); - - if (!IsLoaded()) - { - bool Ui = Tree ? Tree->InThread() : false; - if (Ui) - Tree->Capture(false); - - // auto Now = LCurrentTime(); - auto &Children = FldObj->Children(); - Status = Children.GetState(); - // printf("%s:%i - Children.GetState=%i %ims\n", _FL, Status, (int)(LCurrentTime()-Now)); - if (Status != Store3Loaded) - { - // LgiTrace("%s:%i - ScribeFolder::LoadThings(%s) incomplete...\n", _FL, Path.Get()); - - if (View() && Loading.Reset(Ui ? new LoadingItem(&Children) : NULL)) - View()->Insert(Loading); - - return Status; // Ie deferred or error... - } - - IsLoaded(true); + ContinueLoading(); } - - return Status; + + return Access; } void ScribeFolder::OnRename(char *NewName) { if (!NewName) return; int FolderType = App->GetFolderType(this); SetName(NewName, true); // Calls update too.. SetDirty(); if (FolderType >= 0) { // it's a system folder so reflect the changes in // the system folder tracking options. char KeyName[32]; sprintf_s(KeyName, sizeof(KeyName), "Folder-%i", FolderType); auto Path = GetPath(); if (Path) { LVariant v = Path.Get(); App->GetOptions()->SetValue(KeyName, v); } } } void ScribeFolder::OnDelete() { if (GetObject()) { char Msg[256]; sprintf_s(Msg, sizeof(Msg), LLoadString(IDS_DELETE_FOLDER_DLG), GetText()); int Result = LgiMsg(App, Msg, AppName, MB_YESNO); if (Result == IDYES) { if (App->GetMailList()) App->GetMailList()->RemoveAll(); ScribeFolder *Parent = GetFolder(); LArray Del; Del.Add(GetObject()); if (GetObject()->GetStore()->Delete(Del, true)) { if (Parent) Parent->Select(true); } } } } ScribeFolder *ScribeFolder::GetSubFolder(const char *Path) { ScribeFolder *Folder = 0; if (Path) { if (*Path == '/') Path++; char Name[256]; char *Sep = strchr((char*)Path, '/'); if (Sep) { ZeroObj(Name); memcpy(Name, Path, Sep-Path); } else { strcpy_s(Name, sizeof(Name), Path); } LTreeItem *Starts[2] = { GetChild(), IsRoot() ? GetNext() : 0 }; for (int s=0; !Folder && sGetNext()) { ScribeFolder *f = dynamic_cast(i); if (f) { auto n = f->GetName(true); if (n.Equals(Name)) { if (Sep) Folder = f->GetSubFolder(Sep+1); else Folder = f; break; } } } } } return Folder; } bool ScribeFolder::ReindexField(int OldIndex, int NewIndex) { if (!GetFldObj()) return false; auto &Flds = GetFldObj()->Fields(); if (GetFldObj()->Fields().Length() <= 0) return false; LDataPropI *f = Flds[OldIndex]; if (!f) return false; Flds.Delete(f); Flds.Insert(f, OldIndex < NewIndex ? NewIndex - 1 : NewIndex); int i=0; FieldArray.Length(0); for (f = Flds.First(); f; f = Flds.Next()) { auto Id = f->GetInt(FIELD_ID); FieldArray[i++] = (int)Id; } for (auto t: Items) t->SetFieldArray(FieldArray); SetDirty(); return true; } int ScribeFolder::_UnreadChildren() { int Status = 0; for (ScribeFolder *f=GetChildFolder(); f; f=f->GetNextFolder()) { int u = f->GetUnRead(); if (u > 0) Status += u; Status += f->_UnreadChildren(); } return Status; } void ScribeFolder::_PourText(LPoint &Size) { if (!d) return; const char *Text = GetText(); Size.x = Size.y = 0; if (Text && !d->DsBase) { if (GetUnRead() > 0 || ChildUnRead) { const char *StartCount = strrchr(Text, '('); ssize_t NameLen = StartCount ? StartCount-Text : strlen(Text); LFont *b = (App->GetBoldFont()) ? App->GetBoldFont() : GetTree()->GetFont(); d->DsBase.Reset(new LDisplayString(b, Text, NameLen)); d->DsUnread.Reset(new LDisplayString(GetTree()->GetFont(), StartCount)); } else { d->DsBase.Reset(new LDisplayString(GetTree()->GetFont(), Text)); } } if (d->DsBase) Size.x += d->DsBase->X(); if (d->DsUnread) Size.x += d->DsUnread->X(); Size.x += 4; Size.y = MAX(16, LSysFont->GetHeight()); } void ScribeFolder::_PaintText(LItem::ItemPaintCtx &Ctx) { if (!d) return; LRect *_Text = _GetRect(TreeItemText); if (d->DsBase) { LFont *f = d->DsBase->GetFont(); int Tab = f->TabSize(); f->TabSize(0); f->Transparent(false); f->Colour(Ctx.Fore, Ctx.TxtBack); d->DsBase->Draw(Ctx.pDC, _Text->x1 + 2, _Text->y1 + 1, _Text); if (d->DsUnread) { f = d->DsUnread->GetFont(); f->Transparent(true); LColour UnreadCol(App->GetColour(L_UNREAD_COUNT)); if ( abs ( (UnreadCol.GetGray() - Ctx.Back.GetGray()) ) < 80 ) { // too close.. use fore f->Colour(Ctx.Fore, Ctx.TxtBack); } else { // contrast ok.. use unread f->Colour(UnreadCol, Ctx.TxtBack); } d->DsUnread->Draw(Ctx.pDC, _Text->x1 + 2 + d->DsBase->X(), _Text->y1 + 1); } f->TabSize(Tab); if (Ctx.x2 > _Text->x2) { Ctx.pDC->Colour(Ctx.Back); Ctx.pDC->Rectangle(_Text->x2 + 1, Ctx.y1, Ctx.x2, Ctx.y2); } } else { Ctx.pDC->Colour(Ctx.Back); Ctx.pDC->Rectangle(&Ctx); } } bool ScribeFolder::Save(ScribeFolder *Into) { bool Status = false; if (GetObject()) { // saving a disk object Store3Status s = GetObject()->Save(); if (s != Store3Error) { Status = true; SetDirty(false); } else { LAssert(0); } } return Status; } void ScribeFolder::OnExpand(bool b) { if (b && LoadOnDemand) { DeleteObj(LoadOnDemand); if (App->ScribeState != ScribeWnd::ScribeExiting) { ScribeWnd::AppState Prev = App->ScribeState; App->ScribeState = ScribeWnd::ScribeLoadingFolders; LoadFolders(); if (App->ScribeState == ScribeWnd::ScribeExiting) { LCloseApp(); return; } App->ScribeState = Prev; } } OnUpdateUnRead(0, false); SetDirty(((uchar)b) != GetOpen()); SetOpen(b); } bool ScribeFolder::OnKey(LKey &k) { #ifndef WINDOWS // This is being done by the VK_APPS key on windows... if (k.IsContextMenu()) { if (k.Down()) { LMouse m; m.x = 5; m.y = 5; m.ViewCoords = true; m.Target = GetTree(); DoContextMenu(m); } return true; } #endif return false; } #define DefField(id, wid) \ { \ LDataPropI *Fld = Fields.Create(GetFldObj()->GetStore()); \ if (Fld) \ { \ Fld->SetInt(FIELD_ID, id); \ Fld->SetInt(FIELD_WIDTH, wid); \ Fields.Insert(Fld); \ } \ } void ScribeFolder::SetDefaultFields(bool Force) { if (!GetFldObj()) return; if (Force || GetFldObj()->Fields().Length() <= 0) { LDataIterator &Fields = GetFldObj()->Fields(); Fields.DeleteObjects(); int FolderType = App ? App->GetFolderType(this) : -1; if (FolderType == FOLDER_OUTBOX || FolderType == FOLDER_SENT) { DefField(FIELD_PRIORITY, 10); DefField(FIELD_FLAGS, 15); DefField(FIELD_TO, 150); DefField(FIELD_SUBJECT, 250); DefField(FIELD_SIZE, 80); DefField(FIELD_DATE_SENT, 100); } else { switch (GetItemType()) { case MAGIC_MAIL: { DefField(FIELD_PRIORITY, 10); DefField(FIELD_FLAGS, 10); DefField(FIELD_FROM, 150); DefField(FIELD_SUBJECT, 250); DefField(FIELD_SIZE, 80); DefField(FIELD_DATE_SENT, 100); break; } case MAGIC_CONTACT: { DefField(FIELD_FIRST_NAME, 200); DefField(FIELD_LAST_NAME, 200); DefField(FIELD_EMAIL, 300); break; } case MAGIC_CALENDAR: { DefField(FIELD_CAL_SUBJECT, 200); DefField(FIELD_CAL_START_UTC, 150); DefField(FIELD_CAL_END_UTC, 150); break; } case MAGIC_FILTER: { for (int i=0; DefaultFilterFields[i]; i++) { DefField(DefaultFilterFields[i], i == 0 ? 200 : 100); } break; } case MAGIC_GROUP: { DefField(FIELD_GROUP_NAME, 300); break; } default: break; } } // set for save SetDirty(Fields.Length() > 0); } else { // already has fields, so don't interfer with them } } LString ScribeFolder::GetPath() { LString::Array p; ScribeFolder *f = this; while (f) { p.Add(f->GetName(true)); f = dynamic_cast(f->GetParent()); } LString dir = "/"; return dir + dir.Join(p.Reverse()); } void ScribeFolder::SetName(const char *Name, bool Encode) { if (Name) { d->DsBase.Reset(); d->DsUnread.Reset(); NameCache.Reset(); if (GetObject()) GetObject()->SetStr(FIELD_FOLDER_NAME, Name); SetText(Name); // Calls update } } LString ScribeFolder::GetName(bool Decode) { if (!GetObject()) return NULL; auto In = GetObject()->GetStr(FIELD_FOLDER_NAME); if (!In) return NULL; if (Decode) return LUrlDecode(In); else return In; } void ScribeFolder::Update() { if (Tree && !Tree->InThread()) { // LgiTrace("%s:%i - Update not in thread?\n", _FL); return; } d->DsBase.Reset(); d->DsUnread.Reset(); NameCache.Reset(); LTreeItem::Update(); } const char *ScribeFolder::GetText(int i) { if (!NameCache) { LString Name = GetName(true); if (!Name) return "...loading..."; size_t NameLen = strlen(Name) + 40; NameCache.Reset(new char[NameLen]); if (NameCache) { bool ShowTotals = false; LVariant v; if (App->GetOptions()->GetValue(OPT_ShowFolderTotals, v)) ShowTotals = v.CastInt32() != 0; int c = sprintf_s(NameCache, NameLen, "%s", Name.Get()); auto ItemType = GetItemType(); if (ItemType && (ShowTotals || GetUnRead() || ChildUnRead)) { c += sprintf_s(NameCache+c, NameLen-c, " ("); if (ItemType == MAGIC_MAIL || ItemType == MAGIC_ANY) { const char *Ch = ChildUnRead ? "..." : ""; if (ShowTotals) c += sprintf_s(NameCache+c, NameLen-c, "%i%s/", GetUnRead(), Ch); else if (GetUnRead()) c += sprintf_s(NameCache+c, NameLen-c, "%i%s", GetUnRead(), Ch); else if (ChildUnRead) c += sprintf_s(NameCache+c, NameLen-c, "..."); } if (ShowTotals) { if (IsLoaded()) c += sprintf_s(NameCache+c, NameLen-c, "%zi", Items.Length()); else c += sprintf_s(NameCache+c, NameLen-c, "?"); } c += sprintf_s(NameCache+c, NameLen-c, ")"); } } } return NameCache; } int ScribeFolder::GetImage(int Flags) { if (GetItemType() == MAGIC_ANY) { return ICON_TRASH; } if (Flags) { return ICON_OPEN_FOLDER; } switch (GetItemType()) { case MAGIC_MAIL: return ICON_FOLDER_MAIL; case MAGIC_CONTACT: return ICON_FOLDER_CONTACTS; case MAGIC_FILTER: return ICON_FOLDER_FILTERS; case MAGIC_CALENDAR: return ICON_FOLDER_CALENDAR; case MAGIC_NONE: return ICON_MAILBOX; default: return ICON_CLOSED_FOLDER; } } int ScribeFolder::Sizeof() { return 0; } bool ScribeFolder::Serialize(LFile &f, bool Write) { return false; } class NullMail : public Mail { Thing &operator =(Thing &c) override { return *this; } public: NullMail(ScribeWnd *App, MContainer *c, ScribeFolder *f) : Mail(App) { SetFlags(MAIL_READ | MAIL_CREATED); Container = c; SetParentFolder(f); SetSubject((char*)LLoadString(IDS_MISSING_PARENT)); } ~NullMail() { SetParentFolder(NULL); } bool IsPlaceHolder() override { return true; } const char *GetText(int i) override { // Clear all the fields return 0; } int Compare(LListItem *Arg, ssize_t Field) override { // Use the first mail to sort into position if (Container) { MContainer *c = Container->Children.Length() ? Container->Children[0] : 0; if (c && c->Message) { return c->Message->Compare(Arg, Field); } } return -1; } void OnMouseClick(LMouse &m) override { // Disable the mouse click } bool SetDirty(bool b = true) override { // Don't set it to dirty, otherwise the dirty object // clean up code will save it into the outbox. return true; } }; template int TrashCompare(T *pa, T *pb, NativeInt Data) { ScribeFolder *f = (ScribeFolder*)Data; Thing *a = dynamic_cast(pa); Thing *b = dynamic_cast(pb); if (!a || !b) return 0; int type = a->Type() - b->Type(); if (type) return type; int col = f->GetSortCol(); int *defs = a->GetDefaultFields(); if (!defs || !defs[col]) return 0; return (f->GetSortAscend() ? 1 : -1) * a->Compare(b, defs[col]); } template int TrashCompare(LListItem *pa, LListItem *pb, NativeInt Data); int ListItemCompare(LListItem *a, LListItem *b, NativeInt Data) { ScribeFolder *f = (ScribeFolder*)Data; return (f->GetSortAscend() ? 1 : -1) * a->Compare(b, f->GetSortField()); } int ThingCompare(Thing *a, Thing *b, NativeInt Data) { ScribeFolder *f = (ScribeFolder*)Data; return (f->GetSortAscend() ? 1 : -1) * a->Compare(b, f->GetSortField()); } int ThingSorter(Thing *a, Thing *b, ThingSortParams *Params) { return (Params->SortAscend ? 1 : -1) * a->Compare(b, Params->SortField); } bool ScribeFolder::Thread() { bool Status = false; if (GetItemType() == MAGIC_MAIL) { int Thread = GetThreaded(); ThingList *Lst = View(); List m; for (auto t: Items) { Mail *e = t->IsMail(); if (e) { if (Thread) { m.Insert(e); } else if (e->Container) { DeleteObj(e->Container); } } } if (Lst && m[0]) { // Thread LArray Containers; MContainer::Thread(m, Containers); LAssert(m.Length() == Items.Length()); // Insert blank items for missing thread parents for (unsigned i=0; iMessage) { LAssert(c->Children.Length() > 1); c->Message = new NullMail(App, c, this); if (c->Message) { Lst->PlaceHolders.Insert(c->Message); if (View() && c->Message) { c->Message->SetFieldArray(FieldArray); Items.Insert(c->Message); } } } } // Sort root list LArray Containers2 = Containers; ThingSortParams Params; Params.SortAscend = GetSortAscend(); Params.SortField = GetSortField(); #if 0 for (int i=0; iMessage ? Containers[i]->Message->GetFieldText(Params.SortField) : NULL; LgiTrace("%p(%s)\n", Containers[i], Str1); } Containers.Sort(ContainerCompare); LgiQuickSort(Base, Containers2.Length(), ContainerSorter, &Params); for (int i=0; iMessage ? Containers[i]->Message->GetFieldText(Params.SortField) : NULL; char *Str2 = Containers2[i]->Message ? Containers2[i]->Message->GetFieldText(Params.SortField) : NULL; LgiTrace("%p(%s), %p(%s) - %s\n", Containers[i], Str1, Containers2[i], Str2, Containers[i]!=Containers2[i]?"DIFFERENT":""); } #else MContainer **Base = &Containers[0]; if (Containers.Length() > 1) LgiQuickSort(Base, Containers.Length(), ContainerSorter, &Params); /* for (int i=0; iMessage ? Containers[i]->Message->GetFieldText(Params.SortField) : NULL; LgiTrace("%p(%s)\n", Containers[i]->Message, Str1); } LgiTrace("\n"); */ #endif // Position and index the thread tree int Index = 0; for (unsigned i=0; iPour(Index, 0, 0, i < Containers.Length()-1, &Params); } // Sort all the items by index Items.Sort(ContainerIndexer); Status = true; /* int Idx = 0; for (Thing *t = Items.First(); t; t = Items.Next(), Idx++) { Mail *m = t->IsMail(); if (m) { char *Str = m->GetFieldText(Params.SortField); LgiTrace("%i,%i %p %s\n", Idx, m->Container ? m->Container->Index : -1, m, Str); } } */ } } else { for (auto t: Items) { Mail *e = t->IsMail(); if (e) DeleteObj(e->Container); } } return Status; } struct SortPairInt { Thing *t; uint64 ts; void SetDate(Thing *th, int sf) { t = th; auto obj = th->GetObject(); // Store3State loaded = (Store3State)obj->GetInt(FIELD_LOADED); auto dt = obj->GetDate(sf); ts = dt && dt->Year() ? dt->Ts() : 0; } void SetInt(Thing *th, int sf) { t = th; auto obj = th->GetObject(); // Store3State loaded = (Store3State)obj->GetInt(FIELD_LOADED); ts = obj->GetInt(sf); } static int Compare(SortPairInt *a, SortPairInt *b) { int64_t diff = (int64_t)a->ts - (int64_t)b->ts; if (diff < 0) return -1; return diff > 0 ? 1 : 0; } }; struct SortPairStr { Thing *t; const char *ts; void SetStr(Thing *th, int sf) { t = th; ts = th->GetObject()->GetStr(sf); } static int Compare(SortPairStr *a, SortPairStr *b) { return stricmp(a->ts ? a->ts : "", b->ts ? b->ts : ""); } }; bool ScribeFolder::SortItems() { int sf = GetSortField(); LVariantType type = GV_NULL; auto StartTs = LCurrentTime(); const static int TimeOut = 2/*sec*/ * 1000; switch (sf) { case FIELD_DATE_SENT: case FIELD_DATE_RECEIVED: type = GV_DATETIME; break; case FIELD_SUBJECT: type = GV_STRING; break; default: return false; } LArray intPairs; LArray strPairs; bool intType = type == GV_DATETIME || type == GV_INT32 || type == GV_INT64; if (intType) { intPairs.Length(Items.Length()); int n = 0; auto s = intPairs.AddressOf(); if (type == GV_DATETIME) { for (auto i: Items) { s[n++].SetDate(i, sf); if (n % 50 == 0 && LCurrentTime() - StartTs >= TimeOut) return false; } } else { for (auto i: Items) { s[n++].SetInt(i, sf); if (n % 50 == 0 && LCurrentTime() - StartTs >= TimeOut) return false; } } intPairs.Sort(SortPairInt::Compare); } else if (type == GV_STRING) { strPairs.Length(Items.Length()); int n = 0; auto s = strPairs.AddressOf(); for (auto i: Items) s[n++].SetStr(i, sf); strPairs.Sort(SortPairStr::Compare); } Items.Empty(); if (intType) { if (GetSortAscend()) for (auto i: intPairs) Items.Add(i.t); else for (auto it = intPairs.rbegin(); it != intPairs.end(); it--) Items.Add((*it).t); } else { if (GetSortAscend()) for (auto i: strPairs) Items.Add(i.t); else for (auto it = strPairs.rbegin(); it != strPairs.end(); it--) Items.Add((*it).t); } return true; } void ScribeFolder::Populate(ThingList *list) { LProfile Prof("ScribeFolder::Populate", 1000); App->OnSelect(); if (!GetFldObj() || !list) return; CurState = FldState_Populating; ScribeFolder *Prev = list->GetContainer(); bool Refresh = Prev == this; // Remove old items from list Prof.Add("Delete Placeholders"); list->DeletePlaceHolders(); list->RemoveAll(); if (!Refresh || list->GetColumns() == 0 || GetItemType() == MAGIC_FILTER) { if (Prev) { // save previous folders settings Prev->SerializeFieldWidths(); } Prof.Add("Empty cols"); LVariant GridLines; if (App->GetOptions()->GetValue(OPT_GridLines, GridLines)) { list->DrawGridLines(GridLines.CastInt32() != 0); } list->EmptyColumns(); Prof.Add("Set def fields"); bool ForceDefaultFields = GetItemType() == MAGIC_FILTER; if (GetFldObj()->Fields().Length() <= 0 || ForceDefaultFields) { SetDefaultFields(ForceDefaultFields); } // Add fields to list view int n = 0; LArray Empty; for (auto t: Items) { t->SetFieldArray(Empty); } LRect *Bounds = 0; if (App->GetIconImgList()) { Bounds = App->GetIconImgList()->GetBounds(); } switch (GetItemType()) { case MAGIC_ANY: { list->AddColumn("", 170); list->AddColumn("", 170); list->AddColumn("", 170); list->AddColumn("", 170); break; } default: { n = 0; FieldArray.Length(0); for (LDataPropI *i = GetFldObj()->Fields().First(); i; i = GetFldObj()->Fields().Next()) { int FieldId = (int)i->GetInt(FIELD_ID); const char *FName = LLoadString(FieldId); int Width = (int)i->GetInt(FIELD_WIDTH); const char *FieldText = FName ? FName : i->GetStr(FIELD_NAME); LAssert(FieldText != NULL); LItemColumn *c = list->AddColumn(FieldText, Width); if (c) { switch (i->GetInt(FIELD_ID)) { case FIELD_PRIORITY: { int x = 12; if (Bounds) { x = Bounds[ICON_PRIORITY_BLACK].X() + 7; } c->Width(x); c->Image(ICON_PRIORITY_BLACK); c->Resizable(false); break; } case FIELD_FLAGS: { int x = 14; if (Bounds) { x = Bounds[ICON_FLAGS_BLACK].X() + 7; } c->Width(x); c->Image(ICON_FLAGS_BLACK); c->Resizable(false); break; } case FIELD_SIZE: { c->TextAlign(LCss::Len(LCss::AlignRight)); break; } } } FieldArray[n++] = (int)i->GetInt(FIELD_ID); } break; } } // Add all items to list if (View()) { View()->SetContainer(this); // tell the list who we are if (GetSortCol() >= 0) { // set current sort settings View()->SetSort(GetSortCol(), GetSortAscend()); } } Prof.Add("Load things"); + // FIXME: LoadThings(); } // Filter List Is; // Do any threading/sorting LString SortMsg; if (!Thread() && GetSortField()) { SortMsg.Printf("Sorting " LPrintfInt64 " items", Items.Length()); Prof.Add(SortMsg); if (GetItemType() == MAGIC_ANY) { Items.Sort(TrashCompare, (NativeInt)this); } else { // Sort.. // if (!SortItems()) Items.Sort(ThingCompare, (NativeInt)this); } } // Do any filtering... Prof.Add("Filtering"); ThingFilter *Filter = App->GetThingFilter(); auto FilterStart = LCurrentTime(); size_t Pos = 0; for (auto t: Items) { t->SetFieldArray(FieldArray); if (!Filter || Filter->TestThing(t)) { // Add anyway... because all items are not part of list Is.Insert(t); } if ((LCurrentTime()-FilterStart) > 300) { if (!Loading && Loading.Reset(new LoadingItem(NULL))) list->Insert(Loading, 0); if (Loading) { LString s; s.Printf(LPrintfInt64 " of " LPrintfInt64 ", %.1f%%", Pos, Items.Length(), (double)Pos * 100 / Items.Length()); Loading->SetText(s); - LYield(); } FilterStart = LCurrentTime(); } Pos++; } Prof.Add("Inserting"); if (View() && Is[0]) { View()->Insert(Is, -1, true); } Prof.Add("Deleting"); list->DeletePlaceHolders(); Loading.Reset(); Prof.Add("OnSelect"); GetFldObj()->OnSelect(true); CurState = FldState_Idle; } void ScribeFolder::OnUpdateUnRead(int Offset, bool ScanItems) { if (!d->InUpdateUnread) { d->InUpdateUnread = true; int OldUnRead = GetUnRead(); d->DsBase.Reset(); d->DsUnread.Reset(); NameCache.Reset(); if (ScanItems) { if (GetItemType() == MAGIC_MAIL || GetItemType() == MAGIC_ANY) { size_t Count = 0; for (auto t: Items) { Mail *m = t->IsMail(); if (m && !TestFlag(m->GetFlags(), MAIL_READ)) Count++; } SetUnRead((int32_t)Count); } } else if (Offset != 0) { SetUnRead(GetUnRead() + Offset); } if (GetUnRead() < 0) SetUnRead(0); if (OldUnRead != GetUnRead()) { for (LTreeItem *i = GetParent(); i; i = i->GetParent()) { ScribeFolder *tc = dynamic_cast(i); if (tc && tc->GetParent()) tc->OnUpdateUnRead(0, false); } SetDirty(); if (d->IsInbox > 0) UpdateOsUnread(); } ChildUnRead = Expanded() ? 0 : _UnreadChildren(); Update(); d->InUpdateUnread = false; } } void ScribeFolder::EmptyFieldList() { if (GetFldObj()) GetFldObj()->Fields().DeleteObjects(); } void ScribeFolder::SerializeFieldWidths(bool Write) { if (GetFldObj() && View()) { // LAssert(View()->GetColumns() == Object->Fields().Length()); int Cols = MIN(View()->GetColumns(), (int)GetFldObj()->Fields().Length()); for (int i=0; iColumnAt(i); LDataPropI *f = GetFldObj()->Fields()[i]; if (c && f) { if (f->GetInt(FIELD_WIDTH) != c->Width()) { if (Write) { c->Width((int)f->GetInt(FIELD_WIDTH)); } else { f->SetInt(FIELD_WIDTH, c->Width()); SetDirty(); } } } else LAssert(0); } } } void ScribeFolder::OnProperties(int Tab) { if (GetObject()) { SerializeFieldWidths(); if (View()) { SetSort(View()->GetSortCol(), View()->GetSortAscending()); } if (OpenFolderProperties(this, Tab)) { SetDirty(); SerializeFieldWidths(true); Populate(View()); } } } ScribeFolder *ScribeFolder::CreateSubDirectory(const char *Name, int Type) { ScribeFolder *NewFolder = 0; auto ThisObj = dynamic_cast(GetObject()); if (Name && ThisObj && ThisObj->GetStore()) { LDataI *Fld = GetObject()->GetStore()->Create(MAGIC_FOLDER); if (Fld) { LDataFolderI *Obj = dynamic_cast(Fld); if (Obj) { NewFolder = new ScribeFolder; if (NewFolder) { NewFolder->App = App; NewFolder->SetObject(Obj, false, _FL); // Set name and type NewFolder->SetName(Name, true); NewFolder->GetObject()->SetInt(FIELD_FOLDER_TYPE, Type); ThisObj->SubFolders(); if (NewFolder->GetObject()->Save(ThisObj)) { Insert(NewFolder); NewFolder->SetDefaultFields(); NewFolder->OnItemType(); } else { DeleteObj(NewFolder); } } } } } return NewFolder; } bool ScribeFolder::Delete(LArray &Items, bool ToTrash) { if (!App) return false; List NotNew; LArray Del; LDataStoreI *Store = NULL; for (auto i: Items) { if (i->IsPlaceHolder()) { i->DecRef(); } else { Mail *m = i->IsMail(); if (m) NotNew.Insert(m); auto ObjStore = i->GetObject() ? i->GetObject()->GetStore() : NULL; if (!Store) Store = ObjStore; if (Store == ObjStore) Del.Add(i->GetObject()); else LAssert(!"All objects must have the same store."); } } if (NotNew.Length()) App->OnNewMail(&NotNew, false); if (Del.Length() == 0) return true; if (!Store) { LgiTrace("%s:%i - No Store?\n", _FL); return false; } if (!Store->Delete(Del, ToTrash)) { LgiTrace("%s:%i - Store.Delete failed.\n", _FL); return false; } return true; } #define MoveToStatus(idx, status) if (Status) { (*Status)[idx] = (status); } bool ScribeFolder::MoveTo(LArray &Items, bool CopyOnly, LArray *Status) { if (Items.Length() == 0) return false; if (!GetObject() || !App) return false; auto FolderItemType = GetItemType(); for (unsigned i=0; iGetObject()) { MoveToStatus(i, Store3Error); } else { auto ThingItemType = t->Type(); if (!(FolderItemType == ThingItemType || FolderItemType == MAGIC_ANY)) MoveToStatus(i, Store3Error); } } LVariant BayesInc; if (App) App->GetOptions()->GetValue(OPT_BayesIncremental, BayesInc); auto ThisFolderPath = GetPath(); auto NewBayesType = App->BayesTypeFromPath(ThisFolderPath); int NewFolderType = App->GetFolderType(this); ScribeFolder *TemplatesFolder = App->GetFolder(FOLDER_TEMPLATES); bool BuildDynMenus = this == TemplatesFolder; LArray InStoreMove; // Object in the same store... auto ThisStore = GetObject()->GetStore(); LHashTbl, bool> Map; for (unsigned i=0; iGetFolder(); LString Path; if (Old) { Path = Old->GetPath(); if (Old == TemplatesFolder) // Moving to or from the templates folder... update the menu BuildDynMenus = true; } ScribeMailType OldBayesType = BayesMailUnknown; if (BayesInc.CastInt32() && t->IsMail() && TestFlag(t->IsMail()->GetFlags(), MAIL_READ)) { OldBayesType = App->BayesTypeFromPath(t->IsMail()); } - bool Allow = (App) ? App->GetAccessLevel(App, Old->GetFolderPerms(ScribeWriteAccess), Path) : true; - if (!Allow) - { - MoveToStatus(i, Store3NoPermissions); - continue; - } - - int OldFolderType = Old ? App->GetFolderType(Old) : -1; - if ( (OldFolderType == FOLDER_TRASH || OldFolderType == FOLDER_SENT) && - NewFolderType == FOLDER_TRASH) + auto DoMove = [&]() { - // Delete for good - bool Success = Old && Old->DeleteThing(t); - MoveToStatus(i, Success ? Store3Success : Store3Error); - if (Success) - t->OnMove(); - } - else - { - // If this folder is currently selected... - if (Select()) + int OldFolderType = Old ? App->GetFolderType(Old) : -1; + if ( (OldFolderType == FOLDER_TRASH || OldFolderType == FOLDER_SENT) && + NewFolderType == FOLDER_TRASH) + { + // Delete for good + auto Success = Old ? Old->DeleteThing(t, NULL) : Store3Error; + MoveToStatus(i, Success ? Store3Success : Store3Error); + if (Success) + t->OnMove(); + } + else { - // Insert item into list - t->SetFieldArray(FieldArray); - } - - if (CopyOnly) - { - LDataI *NewT = ThisStore->Create(t->Type()); - if (NewT) + // If this folder is currently selected... + if (Select()) + { + // Insert item into list + t->SetFieldArray(FieldArray); + } + + if (CopyOnly) { - NewT->CopyProps(*t->GetObject()); - auto s = NewT->Save(GetObject()); - MoveToStatus(i, s); + LDataI *NewT = ThisStore->Create(t->Type()); + if (NewT) + { + NewT->CopyProps(*t->GetObject()); + auto s = NewT->Save(GetObject()); + MoveToStatus(i, s); + } + else + { + MoveToStatus(i, Store3Error); + } } else { - MoveToStatus(i, Store3Error); - } - } - else - { - if (NewFolderType != FOLDER_TRASH && - OldBayesType != NewBayesType) - { - App->OnBayesianMailEvent(t->IsMail(), OldBayesType, NewBayesType); - } - - // Move to this folder - auto o = t->GetObject(); - if (o && o->GetStore() == ThisStore) - { - InStoreMove.Add(o); - Map.Add(i, true); - } - else - { - // Out of store more... use the old single object method... for the moment.. - Store3Status s = t->SetFolder(this); - MoveToStatus(i, s); - if (s == Store3Success) + if (NewFolderType != FOLDER_TRASH && + OldBayesType != NewBayesType) + { + App->OnBayesianMailEvent(t->IsMail(), OldBayesType, NewBayesType); + } + + // Move to this folder + auto o = t->GetObject(); + if (o && o->GetStore() == ThisStore) + { + InStoreMove.Add(o); + Map.Add(i, true); + } + else { - // Remove from the list.. - if (Old && Old->Select() && App->MailList) - App->MailList->Remove(t); + // Out of store more... use the old single object method... for the moment.. + Store3Status s = t->SetFolder(this); + MoveToStatus(i, s); + if (s == Store3Success) + { + // Remove from the list.. + if (Old && Old->Select() && App->MailList) + App->MailList->Remove(t); - t->OnMove(); - } - else if (s == Store3Error) - { - LgiTrace("%s:%i - SetFolder failed.\n", _FL); + t->OnMove(); + } + else if (s == Store3Error) + { + LgiTrace("%s:%i - SetFolder failed.\n", _FL); + } } } } - } + }; + + if (App) + { + App->GetAccessLevel(App, Old->GetFolderPerms(ScribeWriteAccess), Path, [&](bool Allow) + { + if (Allow) + DoMove(); + else + MoveToStatus(i, Store3NoPermissions); + }); + } + else DoMove(); } + // FIXME: This code that runs at the end needs to wait for the DoMove events to complete somehow... if (InStoreMove.Length()) { auto Fld = dynamic_cast(GetObject()); if (!Fld) return false; auto s = ThisStore->Move(Fld, InStoreMove); for (unsigned i=0; iGetFolder() == this); LAssert(Items.HasItem(t)); } } } if (s <= Store3Error) return false; } if (BuildDynMenus) // Moving to or from the templates folder... update the menu App->BuildDynMenus(); return true; } int ThingFilterCompare(Thing *a, Thing *b, NativeInt Data) { Filter *A = a->IsFilter(); Filter *B = b->IsFilter(); return (A && B) ? A->GetIndex() - B->GetIndex() : 0; } void ScribeFolder::ReSort() { if (View() && Select()) { View()->SetSort(GetSortCol(), GetSortAscend()); } } void ScribeFolder::SetSort(int Col, bool Ascend, bool CanDirty) { if (GetItemType() == MAGIC_FILTER) { // Remove any holes in the indexing int i = 1; Items.Sort(ThingFilterCompare); for (auto t : Items) { Filter *f = t->IsFilter(); if (f) { if (f->GetIndex() != i) { f->SetIndex(i); } i++; } } } if (GetSortCol() != Col || GetSortAscend() != (uchar)Ascend) { GetObject()->SetInt(FIELD_SORT, (Col + 1) * (Ascend ? 1 : -1)); if (CanDirty) SetDirty(); } } int ScribeFolder::GetSortField() { int Status = 0; int Col = GetSortCol(); if (Col >= 0 && Col < (int)FieldArray.Length()) Status = FieldArray[Col]; return Status; } class FolderStream : public LStream { ScribeFolder *f; // Current index into the thing array int Idx; // Total known size of stream... int64 Sz; // A memory buffer containing just the encoded 'Thing' LMemStream Mem; LString FolderMime; public: FolderStream(ScribeFolder *folder) : Mem(512 << 10) { f = folder; Idx = 0; Sz = 0; switch (f->GetItemType()) { case MAGIC_MAIL: FolderMime = sMimeMbox; break; case MAGIC_CONTACT: FolderMime = sMimeVCard; break; case MAGIC_CALENDAR: FolderMime = sMimeVCalendar; break; default: LAssert(!"Need a mime type?"); break; } } int GetIndex() { return Idx; } int Open(const char *Str = 0,int Int = 0) { return true; } bool IsOpen() { return true; } int Close() { return 0; } int64 GetSize() { return -1; } int64 SetSize(int64 Size) { return -1; } int64 GetPos() { return -1; } int64 SetPos(int64 pos) { // This means that IStream::Seek return E_NOTIMPL, which is important // for windows to support copies of unknown size. return -1; } ssize_t Read(void *Buffer, ssize_t Size, int Flags = 0) { // Read from stream.. int64 Remaining = Mem.GetSize() - Mem.GetPos(); if (Remaining <= 0) { Thing *t = Idx < (ssize_t)f->Items.Length() ? f->Items[Idx++] : NULL; if (t) { Mem.SetSize(0); // We can't allow Export to delete the Mem object, we own it. // So create a proxy object for it. LAutoPtr cp(new LProxyStream(&Mem)); if (t->Export(cp, FolderMime)) Sz += Mem.GetSize(); else LAssert(0); Mem.SetPos(0); } else return 0; } Remaining = Mem.GetSize() - Mem.GetPos(); if (Remaining > 0) { int Common = (int)MIN(Size, Remaining); ssize_t Rd = Mem.Read(Buffer, Common); if (Rd > 0) return Rd; else LAssert(0); } return 0; } ssize_t Write(const void *Buffer, ssize_t Size, int Flags = 0) { LAssert(0); return 0; } LStreamI *Clone() { LAssert(0); return NULL; } }; // ScribeFolder drag'n'drop bool ScribeFolder::GetFormats(LDragFormats &Formats) { if (GetItemType() == MAGIC_MAIL || GetItemType() == MAGIC_CONTACT || GetItemType() == MAGIC_CALENDAR) { Formats.SupportsFileStreams(); } Formats.Supports(ScribeFolderObject); return Formats.Length() > 0; } bool ScribeFolder::OnBeginDrag(LMouse &m) { if (GetParent()) Drag(Tree, m.Event, DROPEFFECT_MOVE | DROPEFFECT_COPY); return true; } void ScribeFolder::OnEndData() { DropFileName.Empty(); } LString ScribeFolder::GetDropFileName() { LAutoString Fn(MakeFileName(GetObject()->GetStr(FIELD_FOLDER_NAME), 0)); DropFileName = Fn; if (GetItemType() == MAGIC_MAIL) DropFileName += ".mbx"; else if (GetItemType() == MAGIC_CONTACT) DropFileName += ".vcf"; else if (GetItemType() == MAGIC_CALENDAR) DropFileName += ".ics"; else if (GetItemType() == MAGIC_FILTER) DropFileName += ".xml"; return DropFileName; } bool ScribeFolder::GetData(LArray &Data) { ssize_t DataSet = 0; for (unsigned idx=0; idxSetPos(0); auto Fn = GetDropFileName(); auto MimeType = sMimeMbox; auto r = dd.AddFileStream(LGetLeaf(Fn), MimeType, s); LAssert(r); } DataSet = dd.Data.Length(); } else if (dd.IsFormat(LGI_FileDropFormat)) { LMouse m; if (App->GetMouse(m, true)) { LString::Array Files; if (GetDropFileName()) { LAutoPtr f(new LFile); if (f->Open(DropFileName, O_WRITE)) { if (GetItemType() == MAGIC_MAIL) Export(AutoCast(f), sMimeMbox); else if (GetItemType() == MAGIC_CONTACT) Export(AutoCast(f), sMimeVCard); else if (GetItemType() == MAGIC_CALENDAR) Export(AutoCast(f), sMimeVCalendar); } } if (DropFileName) { Files.Add(DropFileName.Get()); if (CreateFileDrop(&dd, m, Files)) { DataSet++; } else LgiTrace("%s:%i - CreateFileDrop failed.\n", _FL); } else LgiTrace("%s:%i - No drop file name.\n", _FL); } else LgiTrace("%s:%i - GetMouse failed.\n", _FL); } else if (dd.IsFormat(ScribeFolderObject)) { ScribeClipboardFmt *Fmt = ScribeClipboardFmt::Alloc(true, 1); if (Fmt) { Fmt->FolderAt(0, this); dd.Data[0].SetBinary(Fmt->Sizeof(), Fmt); DataSet++; free(Fmt); } } } return DataSet > 0; } void ScribeFolder::CollectSubFolderMail(ScribeFolder *To) { if (!To) To = this; - LoadThings(); - - LArray Items; - for (auto Item: Items) + LoadThings(NULL, [&](auto Status) { - if (To != this && Item->IsMail()) - Items.Add(Item); - } - To->MoveTo(Items); - - for (ScribeFolder *f = GetChildFolder(); f; f = f->GetNextFolder()) - { - f->CollectSubFolderMail(To); - } + LArray Items; + for (auto Item: Items) + { + if (To != this && Item->IsMail()) + Items.Add(Item); + } + To->MoveTo(Items); + + for (ScribeFolder *f = GetChildFolder(); f; f = f->GetNextFolder()) + { + f->CollectSubFolderMail(To); + } + }); } void ScribeFolder::OnReceiveFiles(LArray &Files) { if (Files.Length()) { for (unsigned i=0; i f(new LTextFile); if (f->Open(File, O_READ)) Import(AutoCast(f), MimeType); } } } // Import/export bool ScribeFolder::GetFormats(bool Export, LString::Array &MimeTypes) { MimeTypes.Add(sMimeMbox); if (!Export) MimeTypes.Add(sMimeMessage); return MimeTypes.Length() > 0; } class MboxParser : public LStringPipe { LStreamI *Src; int Hdrs; bool NewMsg; LArray Buf; int64 Pos; bool Eof; bool IsMessageHdr(char *c) { if (!c) return false; // check that it's a from line LToken T(c, " \r", true); if (T.Length() >= 7 && T.Length() <= 9 && !strcmp(T[0], "From")) { return true; } return false; } public: MboxParser(LStreamI *s) { Src = s; Hdrs = 0; Pos = 0; NewMsg = true; Eof = false; Buf.Length(128 << 10); } void SeekNext() { if (!NewMsg) { char b[256]; int r; while ( !NewMsg && (r = Pop(b, sizeof(b)) > 0) ) ; } } bool GetEof() { return Eof; } ssize_t Pop(char *Str, ssize_t BufSize) { while (true) { ssize_t r = LStringPipe::Pop(Str, BufSize); if (r <= 0 && Src) { r = Src ? Src->Read(&Buf[0], Buf.Length()) : 0; if (r > 0) { Push(&Buf[0], r); } else { Eof = true; return 0; } } else if (IsMessageHdr(Str)) { Pos += r; NewMsg = true; if (Hdrs++) return 0; } else { NewMsg = false; Pos += r; return r; } } return 0; } }; struct TempMsg : public LTempStream { TempMsg() : LTempStream(ScribeTempPath()) { } bool ReadMessage(LStringPipe *p) { LArray Buf(128<<10); ssize_t Bytes; bool Status = true; char *Ptr = &Buf[0]; ssize_t Len = Buf.Length(); while ( (Bytes = p->Pop(Ptr, Len)) > 0) { Status &= Write(&Buf[0], Bytes) == Bytes; } return Status; } }; class FolderTask : public LProgressDlg { protected: ScribeWnd *App = NULL; ScribeFolder *Folder = NULL; LString MimeType; LAutoPtr Stream; ThingType::IoProgress Status; ThingType::IoProgressCallback onComplete; public: // Minimum amount of time to do work. constexpr static int WORK_SLICE_MS = 130; // This should be larger then WORK_SLICE_MS to allow message loop to process constexpr static int PULSE_MS = 200; FolderTask( ScribeFolder *folder, LAutoPtr stream, LString mimeType, ThingType::IoProgressCallback cb) : LProgressDlg(folder->App), Folder(folder), Stream(stream), MimeType(mimeType), onComplete(cb), Status(Store3Success) { App = Folder->App; Ts = LCurrentTime(); SetParent(Folder->GetTree()); SetPulse(PULSE_MS); SetAlwaysOnTop(true); App->OnFolderTask(this, true); } virtual ~FolderTask() { Folder->App->OnFolderTask(this, false); if (onComplete) onComplete(&Status, Stream); } bool OnRequestClose(bool OsClose) { return true; } void OnPulse() { LProgressDlg::OnPulse(); auto StartTs = LCurrentTime(); while ( !IsCancelled() && (LCurrentTime() - StartTs) < WORK_SLICE_MS) { if (!TimeSlice()) { Quit(); break; } } } /// This should use around WORK_SLICE_MS of time and then /// \returns true if more work to do or false if finished. virtual bool TimeSlice() = 0; }; class ImportFolderTask : public FolderTask { LDataStoreI::StoreTrans trans; public: ImportFolderTask(ScribeFolder *fld, LAutoPtr in, LString mimeType, ThingType::IoProgressCallback cb) : FolderTask(fld, in, mimeType, cb) { SetDescription(LLoadString(IDS_MBOX_READING)); SetType("K"); SetScale(1.0/1024.0); SetRange(Stream->GetSize()); trans = fld->GetObject()->GetStore()->StartTransaction(); } bool TimeSlice() { auto Start = LCurrentTime(); MboxParser Parser(Stream); TempMsg *Tm; LAutoStreamI Msg(Tm = new TempMsg); while ( !Parser.GetEof() && LCurrentTime() - Start < WORK_SLICE_MS && !IsCancelled() && Tm->ReadMessage(&Parser)) { Mail *m = dynamic_cast(App->CreateItem(MAGIC_MAIL, Folder, false)); if (m) { m->OnAfterReceive(Msg); m->SetFlags(MAIL_RECEIVED|MAIL_READ); m->Save(); m->Update(); } Msg.Reset(Tm = new TempMsg); Parser.SeekNext(); Value(Stream->GetPos()); } return !Parser.GetEof(); } }; ThingType::IoProgress ScribeFolder::Import(IoProgressImplArgs) { if (Stricmp(mimeType, sMimeMbox) == 0 || Stricmp(mimeType, "text/x-mail") == 0) { // Mail box format... ThingType::IoProgress p(Store3Delayed); p.prog = new ImportFolderTask(this, stream, mimeType, cb); return p; } else if (Stricmp(mimeType, sMimeVCard) == 0) { VCard Io; Thing *t; bool Error = false; int Imported = 0; int Idx = 0; while ((t = App->CreateItem(GetItemType(), 0, false))) { Contact *c = t->IsContact(); if (!c) { t->DecRef(); Error = true; break; } if (Io.Import(c->GetObject(), stream)) { const char *First = 0, *Last = 0; c->GetField(FIELD_FIRST_NAME, First); c->GetField(FIELD_LAST_NAME, Last); LgiTrace("Import %i %s %s\n", Idx, First, Last); if (t->Save(this)) { Imported++; } else { Error = true; break; } } else { t->DecRef(); break; } Idx++; } if (Error) { LgiMsg( App, LLoadString(IDS_ERROR_IMPORT_COUNT), AppName, MB_OK, LLoadString(IDC_CONTACTS), Imported); IoProgressError("Contact import error."); } IoProgressSuccess(); } else if (Stricmp(mimeType, sMimeVCalendar) == 0) { VCal Io; Thing *t; while ((t = App->CreateItem(GetItemType(), this, false))) { if (Io.Import(t->GetObject(), stream)) { if (!t->Save(this)) IoProgressError("Contact save failed."); } else { t->DecRef(); break; } } IoProgressSuccess(); } else if (GetObject()) { Thing *t = App->CreateThingOfType(GetItemType(), GetObject()->GetStore()->Create(GetItemType())); if (!t) IoProgressError("Failed to create contact"); if (!t->Import(stream, mimeType)) { t->DecRef(); IoProgressError("Contact import failed."); } if (!t->Save(this)) { t->DecRef(); IoProgressError("Contact save failed."); } IoProgressSuccess(); } IoProgressNotImpl(); } class ExportFolderTask : public FolderTask { int Idx = 0; public: ExportFolderTask( ScribeFolder *folder, LAutoPtr out, LString mimeType, ThingType::IoProgressCallback cb) : FolderTask(folder, out, mimeType, cb) { bool Mbox = _stricmp(MimeType, sMimeMbox) == 0; // Clear the files contents Stream->SetSize(0); // Setup progress UI SetDescription(Mbox ? LLoadString(IDS_MBOX_WRITING) : (char*)"Writing..."); SetRange(Folder->Items.Length()); switch (Folder->GetItemType()) { case MAGIC_MAIL: SetType(LLoadString(IDS_EMAIL)); break; case MAGIC_CALENDAR: SetType(LLoadString(IDS_CALENDAR)); break; case MAGIC_CONTACT: SetType(LLoadString(IDS_CONTACT)); break; case MAGIC_GROUP: SetType("Groups"); break; default: SetType("Objects"); break; } SetPulse(PULSE_MS); SetAlwaysOnTop(true); } bool TimeSlice() { auto Start = LCurrentTime(); while ( LCurrentTime() - Start < WORK_SLICE_MS && !IsCancelled()) { if (Idx >= (ssize_t)Folder->Items.Length()) return false; // Process all the container's items Thing *t = Folder->Items[Idx++]; if (!t) return false; LAutoPtr wrapper(new LProxyStream(Stream)); if (!t->Export(wrapper, MimeType)) { Status.status = Store3Error; Status.errMsg = "Error exporting items."; return false; } Value(Idx); } return true; } /* void OnPulse() { LProgressDlg::OnPulse(); PostEvent(M_EXPORT_NEXT); } LMessage::Result OnEvent(LMessage *Msg) { if (Msg->Msg() == M_EXPORT_NEXT) { auto StartTs = LCurrentTime(); while ( !IsCancelled() && (LCurrentTime() - StartTs) < WORK_SLICE_MS) { if (Idx >= (ssize_t)Folder->Items.Length()) { Quit(); break; } // Process all the container's items Thing *t = Folder->Items[Idx++]; if (t) { LAutoPtr wrapper(new LProxyStream(Out)); if (t->Export(wrapper, MimeType)) { Value(Idx); } else { Status.status = Store3Error; Status.errMsg = "Error exporting items."; if (!onComplete) LgiMsg(this, "%s", AppName, MB_OK, Status.errMsg.Get()); Quit(); } } } return 0; } return LProgressDlg::OnEvent(Msg); } */ }; // This is the mime type used to storage objects on disk const char *ScribeFolder::GetStorageMimeType() { auto Type = GetItemType(); switch (Type) { case MAGIC_MAIL: return sMimeMbox; case MAGIC_CALENDAR: return sMimeVCalendar; case MAGIC_CONTACT: return sMimeVCard; case MAGIC_FILTER: return sMimeXml; default: LgiTrace("%s:%i - Unsupported storage type: %s\n", _FL, Store3ItemTypeName(Type)); break; } return NULL; } -ThingType::IoProgress ScribeFolder::Export(IoProgressImplArgs) +void ScribeFolder::ExportAsync(LAutoPtr f, const char *MimeType, std::function Callback) { - IoProgress ErrStatus(Store3Error); - if (!mimeType) + if (!MimeType) + { + LAssert(!"No Mimetype"); + if (Callback) Callback(NULL); + return; + } + + LoadThings(NULL, [&](auto Status) { - ErrStatus.errMsg = "No mimetype."; - if (cb) cb(&ErrStatus, NULL); - return ErrStatus; + if (Status == Store3Success) + { + auto Task = new FolderExportTask(f, this, MimeType); + if (Callback) Callback(Task); + } + else if (Callback) + Callback(NULL); + }); +} + +void ScribeFolder::Export(LStreamI &f, const char *MimeType, std::function Callback) +{ + if (!MimeType) + { + if (Callback) Callback(Store3Error); + return; } + + LoadThings(NULL, [&](auto Status) + { + if (Status != Store3Loaded) + { + if (Callback) Callback(Status); + return; + } + + bool Mbox = _stricmp(MimeType, sMimeMbox) == 0; + LProgressDlg Dlg(App); - if (!LoadThings()) - { - ErrStatus.errMsg = "Failed to load things."; - if (cb) cb(&ErrStatus, NULL); - return ErrStatus; - } - - IoProgress Status(Store3Delayed); - Status.prog = new ExportFolderTask(this, stream, mimeType, cb); - return Status; + App->OnFolderTask(&Dlg, true); + + // Clear the files contents + f.SetSize(0); + + // Setup progress UI + Dlg.SetDescription(Mbox ? LLoadString(IDS_MBOX_WRITING) : (char*)"Writing..."); + Dlg.Invalidate((LRect*)0, true); + Dlg.SetRange(Items.Length()); + Dlg.SetType(LLoadString(IDS_EMAIL)); + + // Process all the container's items + int Error = 0; + for (auto i: Items) + { + if (!i->Export(f, MimeType)) + Error++; + + Dlg.Value(Dlg.Value()+1); + Dlg.Invalidate((LRect*)0, true); + } + + // all done + App->OnFolderTask(&Dlg, false); + + if (Callback) Callback(Error ? Store3Error : Store3Success); + }); } size_t ScribeFolder::Length() { if (GetItemType() == MAGIC_MAIL) { ThingList *v = View(); if (v) return v->Length(); } if (IsLoaded()) return Items.Length(); return GetItems(); } ssize_t ScribeFolder::IndexOf(Mail *m) { if (GetItemType() == MAGIC_MAIL) { ThingList *v = View(); if (v) return v->IndexOf(m); return Items.IndexOf(m); } return -1; } Mail *ScribeFolder::operator [](size_t i) { if (GetItemType() == MAGIC_MAIL) { ThingList *v = View(); if (v) return dynamic_cast(v->ItemAt(i)); Thing *t = Items[i]; if (t) return t->IsMail(); } return NULL; } bool ScribeFolder::GetVariant(const char *Name, LVariant &Value, const char *Array) { ScribeDomType Fld = StrToDom(Name); switch (Fld) { case SdType: // Type: Int32 { Value = GetObject()->Type(); break; } case SdName: // Type: String { Value = GetName(true).Get(); break; } case SdPath: // Type: String { auto p = GetPath(); if (p) Value = p; else return false; break; } case SdUnread: // Type: Int32 { Value = GetUnRead(); break; } case SdLength: // Type: Int32 { Value = (int32)Items.Length(); break; } case SdItem: // Type: Thing[] { - if (Items.Length() == 0) - LoadThings(); - + Value.Empty(); + + LoadThings(); // Use in sync mode, no callback + + // This call back HAS to set value one way or another... if (Array) { - Value.Empty(); - bool IsNumeric = true; for (auto *v = Array; *v; v++) { if (!IsDigit(*v)) { IsNumeric = false; break; } } if (IsNumeric) { int Idx = atoi(Array); if (Idx >= 0 && Idx < (ssize_t)Items.Length()) + { Value = (LDom*) Items[Idx]; + return true; + } } else // Is message ID? { for (auto t : Items) { Mail *m = t->IsMail(); if (!m) break; auto Id = m->GetMessageId(); if (Id && !strcmp(Id, Array)) { Value = (LDom*)t; - break; + return true; } } } } - else + else if (Value.SetList()) { - if (Value.SetList()) - { - for (auto t : Items) - { - Value.Value.Lst->Insert(new LVariant((LDom*)t)); - } - } + for (auto t : Items) + Value.Value.Lst->Insert(new LVariant((LDom*)t)); + return true; } break; } case SdItemType: // Type: Int32 { Value = GetItemType(); break; } case SdScribe: // Type: ScribeWnd { Value = (LDom*)App; break; } case SdChild: // Type: ScribeFolder { Value = (LDom*)GetChildFolder(); break; } case SdNext: // Type: ScribeFolder { Value = (LDom*)GetNextFolder(); break; } case SdSelected: // Type: Thing[] { if (!Select() || !Value.SetList()) return false; List a; if (!App->GetMailList()->GetSelection(a)) return false; for (auto t: a) { Value.Value.Lst->Insert(new LVariant(t)); } break; } case SdExpanded: // Type: Boolean { Value = Expanded(); break; } default: { return false; } } return true; } bool ScribeFolder::SetVariant(const char *Name, LVariant &Value, const char *Array) { ScribeDomType Fld = StrToDom(Name); switch (Fld) { case SdName: // Type: String { char *s = Value.CastString(); if (ValidStr(s)) OnRename(s); else return false; break; } case SdExpanded: // Type: Boolean { Expanded(Value.CastInt32() != 0); break; } default: return false; } return true; } bool ScribeFolder::CallMethod(const char *MethodName, LVariant *ReturnValue, LArray &Args) { ScribeDomType m = StrToDom(MethodName); switch (m) { case SdLoad: // Type: () { - *ReturnValue = LoadThings(App); + LoadThings(App, [&](auto Status) + { + *ReturnValue = Status == Store3Success; + }); + + // Convert the async LoadFolders call back to sync. + WaitForVariant(*ReturnValue); return true; } case SdSelect: // Type: () { Select(true); return true; } case SdImport: // Type: (String FileName, String MimeType) { *ReturnValue = false; if (Args.Length() != 2) LgiTrace("%s:%i - Error: expecting 2 arguments to 'Import'.\n", _FL); else { auto FileName = Args[0]->Str(); LAutoPtr f(new LFile); if (f->Open(FileName, O_READ)) { auto p = Import(AutoCast(f), Args[1]->Str()); *ReturnValue = p.status; } else LgiTrace("%s:%i - Error: Can't open '%s' for reading.\n", _FL, FileName); } break; } case SdExport: // Type: (String FileName, String MimeType) { *ReturnValue = false; if (Args.Length() != 2) LgiTrace("%s:%i - Error: expecting 2 arguments to 'Export'.\n", _FL); else { auto FileName = Args[0]->Str(); LAutoPtr f(new LFile); if (f->Open(FileName, O_WRITE)) { auto p = Export(AutoCast(f), Args[1]->Str()); *ReturnValue = p.status; } - else LgiTrace("%s:%i - Error: Can't open '%s' for writing.\n", _FL, FileName); + break; + } + + Export(f, Args[1]->Str(), [&](auto Status) + { + *ReturnValue = Status == Store3Success; + }); + + WaitForVariant(*ReturnValue); } break; } default: break; } return false; } void ScribeFolder::OnRethread() { if (GetThreaded()) { Thread(); } } diff --git a/Code/ScribeFolderDlg.cpp b/Code/ScribeFolderDlg.cpp --- a/Code/ScribeFolderDlg.cpp +++ b/Code/ScribeFolderDlg.cpp @@ -1,226 +1,229 @@ #include "Scribe.h" #include "ScribeFolderDlg.h" #include "resdefs.h" #include "lgi/common/LgiRes.h" #include "lgi/common/FileSelect.h" //////////////////////////////////////////////////////////////////////////// class RecentItem : public LListItem { ScribeFolderDlg *Dlg; public: RecentItem(ScribeFolderDlg *dlg, char *FileName) { Dlg = dlg; SetText(FileName); } void OnSelect() { Dlg->SetCtrlName(IDC_EXISTING_FOLDER, GetText(0)); } void OnMouseClick(LMouse &m) { if (Dlg && m.Double()) { Dlg->PostEvent(M_CHANGE, IDOK); } } }; //////////////////////////////////////////////////////////////////////////// ScribeFolderDlg::ScribeFolderDlg(ScribeWnd *app) { SetParent(App = app); Create = false; if (LoadFromResource(IDD_NO_FOLDER)) { // Load MRU LList *Recent; if (GetViewById(IDC_FOLDER_HISTORY, Recent)) { for (int i=0; i<10; i++) { char Key[32]; LVariant f; sprintf_s(Key, sizeof(Key), "FolderMru.%i", i); if (App->GetOptions()->GetValue(Key, f)) { Recent->Insert(new RecentItem(this, f.Str())); } } } // Look at folders... LArray Current; int Exists = 0; LXmlTag *MailStores = App->GetOptions()->LockTag(OPT_MailStores, _FL); if (MailStores) { for (auto t: MailStores->Children) { char *File = t->GetAttr(OPT_MailStoreLocation); if (ValidStr(File)) { Current.Add(NewStr(File)); Exists += LFileExists(File) ? 1 : 0; } } App->GetOptions()->Unlock(); } if (Current.Length()) { char Msg[256]; if (Exists) { // another app has it open SetCtrlValue(IDC_ACTION, 1); sprintf_s(Msg, sizeof(Msg), LLoadString(IDS_ERROR_CANT_OPEN_FOLDERS), Current[0]); SetCtrlName(IDC_MESSAGE, Msg); } else { // doesn't exist... SetCtrlValue(IDC_ACTION, 0); sprintf_s(Msg, sizeof(Msg), LLoadString(IDS_ERROR_FOLDERS_DONT_EXIST), Current[0]); SetCtrlName(IDC_MESSAGE, Msg); SetCtrlName(IDC_NEW_FOLDER, Current[0]); } } else { // no file actually specified... this is most likely because it's // a new installation. char Def[MAX_PATH_LEN] = ""; LAutoString Base = App->GetDataFolder(); if (LMakePath(Def, sizeof(Def), Base, "Folders.mail3")) SetCtrlName(IDC_NEW_FOLDER, Def); else LgiTrace("%s:%i - LMakePath failed.\n", _FL); SetCtrlValue(IDC_ACTION, 0); SetCtrlName(IDC_MESSAGE, LLoadString(IDS_ENTER_FILE_NAME_FOR_FOLDERS)); } Current.DeleteArrays(); LViewI *w = FindControl(IDC_ACTION); LNotification note(LNotifyValueChanged); if (w) OnNotify(w, note); } MoveToCenter(); } int ScribeFolderDlg::OnNotify(LViewI *Ctrl, LNotification n) { switch (Ctrl->GetId()) { case IDC_BROWSE_NEW: { - LFileSelect Select; + auto Select = new LFileSelect(this); auto Txt = GetCtrlName(IDC_NEW_FOLDER); if (Txt) { char Def[MAX_PATH_LEN]; strcpy_s(Def, sizeof(Def), Txt); LTrimDir(Def); - Select.InitialDir(Def); + Select->InitialDir(Def); } else { - Select.InitialDir(LGetExePath()); + Select->InitialDir(LGetExePath()); } - Select.Parent(this); - Select.Type("v3 Mail Store", "*.sqlite"); - Select.Type("All Files", LGI_ALL_FILES); + Select->Type("v3 Mail Store", "*.sqlite"); + Select->Type("All Files", LGI_ALL_FILES); - if (Select.Save()) + Select->Save([&](auto dlg, auto status) { - char Def[MAX_PATH_LEN]; - strcpy_s(Def, sizeof(Def), Select.Name()); - char *d = strrchr(Def, DIR_CHAR); - if (d) + if (status) { - char *e = strrchr(d, '.'); - if (!e) + char Def[MAX_PATH_LEN]; + strcpy_s(Def, sizeof(Def), Select->Name()); + char *d = strrchr(Def, DIR_CHAR); + if (d) { - if (Select.SelectedType() == 0) + char *e = strrchr(d, '.'); + if (!e) { - strcat(d, ".mail3"); + if (Select->SelectedType() == 0) + strcat(d, ".mail3"); } - else break; + + SetCtrlName(IDC_NEW_FOLDER, Def); } - - SetCtrlName(IDC_NEW_FOLDER, Def); + else LgiMsg(this, "Error: Invalid path.", AppName); } - else LgiMsg(this, "Error: Invalid path.", AppName); - } + delete dlg; + }); break; } case IDC_BROWSE_EXISTING: { - LFileSelect Select; + auto Select = new LFileSelect(this); char Def[300]; auto Txt = GetCtrlName(IDC_EXISTING_FOLDER); if (Txt) { strcpy_s(Def, sizeof(Def), Txt); LTrimDir(Def); - Select.InitialDir(Def); + Select->InitialDir(Def); } else { - Select.InitialDir(LGetExePath()); + Select->InitialDir(LGetExePath()); } - Select.Parent(this); - Select.Type("v3 Mail Store", "*.sqlite"); - Select.Type("All Files", LGI_ALL_FILES); + Select->Type("v3 Mail Store", "*.sqlite"); + Select->Type("All Files", LGI_ALL_FILES); - if (Select.Open()) + Select->Open([this](auto dlg, auto id) { - if (LFileExists(Select.Name()) || LDirExists(Select.Name())) - SetCtrlName(IDC_EXISTING_FOLDER, Select.Name()); - else - LgiMsg(this, LLoadString(IDS_ERROR_FOLDERS_DONT_EXIST), AppName, MB_OK, Select.Name()); - } + if (id) + { + if (LFileExists(dlg->Name()) || LDirExists(dlg->Name())) + SetCtrlName(IDC_EXISTING_FOLDER, dlg->Name()); + else + LgiMsg(this, LLoadString(IDS_ERROR_FOLDERS_DONT_EXIST), AppName, MB_OK, dlg->Name()); + } + delete dlg; + }); break; } // case IDC_ACTION: case IDC_CREATE_FOLDER: case IDC_EXISTING: { Create = GetCtrlValue(IDC_CREATE_FOLDER) != 0; SetCtrlEnabled(IDC_NEW_FOLDER, Create); SetCtrlEnabled(IDC_BROWSE_NEW, Create); SetCtrlEnabled(IDC_EXISTING_FOLDER, !Create); SetCtrlEnabled(IDC_BROWSE_EXISTING, !Create); SetCtrlEnabled(IDC_FOLDER_HISTORY, !Create); break; } case IDOK: { Create = GetCtrlValue(IDC_CREATE_FOLDER) != 0; FolderFile.Reset(NewStr(GetCtrlName(Create ? IDC_NEW_FOLDER : IDC_EXISTING_FOLDER))); LgiTrace("In IDOK, FolderFile=%s\n", FolderFile.Get()); // fall thru } case IDCANCEL: { EndModal(Ctrl->GetId()); break; } } return 0; } diff --git a/Code/ScribeFolderProp.cpp b/Code/ScribeFolderProp.cpp --- a/Code/ScribeFolderProp.cpp +++ b/Code/ScribeFolderProp.cpp @@ -1,349 +1,367 @@ /* ** FILE: ScribeFolderProp.cpp ** AUTHOR: Matthew Allen ** DATE: 17/5/1999 ** DESCRIPTION: Scribe folder properties dialog ** ** Copyright (C) 1999, Matthew Allen ** fret@memecode.com */ #include #include #include #include #include "Scribe.h" #include "lgi/common/TextLabel.h" #include "lgi/common/ProgressDlg.h" #include "resdefs.h" #include "lgi/common/TabView.h" #include "lgi/common/LgiRes.h" ////////////////////////////////////////////////////////////////////////////// class LFolderInfo : public LListItem { public: ScribeFolder *Folder; uint64 Size; }; int FolderInfo_Compare(LListItem *a, LListItem *b, NativeInt Data) { LFolderInfo *A = dynamic_cast(a); LFolderInfo *B = dynamic_cast(b); if (A && B) { return (int) ((int64)B->Size - (int64)A->Size); } return 0; } ////////////////////////////////////////////////////////////////////////////// #define M_INIT_DONE (M_USER + 1000) class FolderPropertiesDlg : public LDialog { // Data ScribeFolder *Folder; // Controls LTabView *Tab; // LTabPage *DetailTab; LList *Usage; LView *Txt; // Scanning portion.. bool Loop; public: bool RePopulate; FolderPropertiesDlg(ScribeFolder *folder, int InitialTab) { RePopulate = false; Loop = true; Txt = 0; Folder = folder; - if (Folder) + if (!Folder) { - SetParent(Folder->App); + LAssert(!"No folder."); + return; + } - if (LoadFromResource(IDD_FOLDER_PROPS)) - { - MoveToCenter(); + SetParent(Folder->App); - GetViewById(IDC_TAB, Tab); - GetViewById(IDC_FOLDER_MSG, Txt); - GetViewById(IDC_USAGE, Usage); - } + if (LoadFromResource(IDD_FOLDER_PROPS)) + { + MoveToCenter(); - auto Path = Folder->GetPath(); - SetCtrlName(IDC_PATH, Path); + GetViewById(IDC_TAB, Tab); + GetViewById(IDC_FOLDER_MSG, Txt); + GetViewById(IDC_USAGE, Usage); + } + + auto Path = Folder->GetPath(); + SetCtrlName(IDC_PATH, Path); - ScribePerm p = Folder->GetFolderPerms(ScribeReadAccess); - if (p == PermRequireAdmin) - { - SetCtrlEnabled(IDC_FPR_NONE, false); - SetCtrlEnabled(IDC_FPR_USER, false); - } - else - { - SetCtrlEnabled(IDC_FPR_ADMIN, false); - } - SetCtrlValue(IDC_FOLDER_READ, p); + ScribePerm p = Folder->GetFolderPerms(ScribeReadAccess); + if (p == PermRequireAdmin) + { + SetCtrlEnabled(IDC_FPR_NONE, false); + SetCtrlEnabled(IDC_FPR_USER, false); + } + else + { + SetCtrlEnabled(IDC_FPR_ADMIN, false); + } + SetCtrlValue(IDC_FOLDER_READ, p); - p = Folder->GetFolderPerms(ScribeWriteAccess); - if (p == PermRequireAdmin) - { - SetCtrlEnabled(IDC_FPW_NONE, false); - SetCtrlEnabled(IDC_FPW_USER, false); - } - else - { - SetCtrlEnabled(IDC_FPW_ADMIN, false); - } - SetCtrlValue(IDC_FOLDER_WRITE, p); - - DoModal(); + p = Folder->GetFolderPerms(ScribeWriteAccess); + if (p == PermRequireAdmin) + { + SetCtrlEnabled(IDC_FPW_NONE, false); + SetCtrlEnabled(IDC_FPW_USER, false); } + else + { + SetCtrlEnabled(IDC_FPW_ADMIN, false); + } + SetCtrlValue(IDC_FOLDER_WRITE, p); } ~FolderPropertiesDlg() { LAssert(Loop == false); } void OnCreate() { PostEvent(M_INIT_DONE); } bool OnRequestClose(bool OsClose) { if (Loop) { Loop = false; return false; } return true; } int OnNotify(LViewI *Ctrl, LNotification n) { switch (Ctrl->GetId()) { case IDOK: { - if (!Folder->SetFolderPerms(this, ScribeReadAccess, (ScribePerm) GetCtrlValue(IDC_FOLDER_READ))) - break; + auto Err = [&]() + { + LgiMsg(this, "Failed to set permissions.", AppName); + return false; + }; + + Folder->SetFolderPerms(this, ScribeReadAccess, (ScribePerm) GetCtrlValue(IDC_FOLDER_READ), [&](auto status) + { + if (!status) + return Err(); - if (!Folder->SetFolderPerms(this, ScribeWriteAccess, (ScribePerm) GetCtrlValue(IDC_FOLDER_WRITE))) - break; + this->Folder->SetFolderPerms(this, ScribeWriteAccess, (ScribePerm) this->GetCtrlValue(IDC_FOLDER_WRITE), [&](auto status) + { + if (!status) + return Err(); + + EndModal(1); - // fall thru + return true; + }); + + return true; + }); + break; } case IDCANCEL: { if (Loop) Loop = false; else EndModal(0); break; } } return 0; } void AddMimeSeg(LDataPropI *Ptr, Counter &c, uint64 Size) { // Add this one... LDataI *Seg = dynamic_cast(Ptr); if (Seg) { c.Inc(Seg->Type()); Size += Seg->Size(); // Add the children... GDataIt Children = Seg->GetList(FIELD_MIME_SEG); if (Children) { for (LDataPropI *Child = Children->First(); Child; Child = Children->Next()) { AddMimeSeg(Child, c, Size); } } } } void Count(LDataFolderI *f, Counter &c, int depth = 0) { uint64 Size = f->Size(); c.Inc(f->Type()); LDataIterator &fc = f->Children(); for (unsigned i=0; Loop && iType()); Size += t->Size(); if (t->Type() == MAGIC_MAIL) { AddMimeSeg(t->GetObj(FIELD_MIME_SEG), c, Size); } } if (i % 30 == 0) LYield(); } c.Add(1, Size); #ifdef _DEBUG char s[256]; memset(s, '\t', depth); s[depth] = 0; LgiTrace("%sCounting %s (size=%i)\n", s, f->GetStr(FIELD_FOLDER_NAME), Size); #endif for (unsigned n=0; Loop && nSubFolders().Length(); n++) { LDataFolderI *s = f->SubFolders()[n]; Count(s, c, depth + 1); LYield(); } } void Run() { Counter c; LYield(); // Do count LDataFolderI *f = Folder->GetFldObj(); c.Inc(f->Type()); c.Add(1, f->Size()); LDataIterator &Children = f->Children(); for (unsigned i=0; Loop && iType()); c.Add(1, t->Size()); LYield(); } for (ScribeFolder *Child = Folder->GetChildFolder(); Loop && Child; Child = Child->GetNextFolder()) { uint64 Old = c.GetTypeCount(1); Count(Child->GetFldObj(), c); if (Usage) { LFolderInfo *i = new LFolderInfo; if (i) { i->Folder = Child; i->Size = c.GetTypeCount(1) - Old; char Size[32]; LFormatSize(Size, sizeof(Size), i->Size); i->SetText(Child->GetText(), 0); i->SetText(Size, 1); Usage->Insert(i); } } LYield(); } int64 Used = c.GetTypeCount(1); // 64 bytes in the header // post count tallying if (Loop && Usage) { // set the percent for (auto it = Usage->begin(); Loop && it != Usage->end(); it++) { LFolderInfo *i = dynamic_cast(*it); if (!i) continue; char Str[32]; sprintf_s(Str, sizeof(Str), "%.1f", (double)(int64)i->Size * 100 / Used ); i->SetText(Str, 2); LYield(); } // sort the items Usage->Sort(FolderInfo_Compare); Usage->ResizeColumnsToContent(); } // other props LString MsgSize = LFormatSize(Used); char Msg[512]; const char *Format = LLoadString(IDS_FOLDER_PROPERTIES_DLG); int ch = sprintf_s(Msg, sizeof(Msg), Format?Format:"", (int) c.GetTypeCount(MAGIC_MAIL), (int) c.GetTypeCount(MAGIC_CONTACT), (int) c.GetTypeCount(MAGIC_FOLDER), MsgSize.Get()); if (Loop && !Folder->GetParent()) { LMailStore *Ms = Folder->App->GetDefaultMailStore(); if (Ms) { char File[32], Unused[32]; uint64 FileSize = Ms->Store->Size(); LFormatSize(File, sizeof(File), FileSize); LFormatSize(Unused, sizeof(Unused), FileSize-Used); sprintf_s(Msg+ch, sizeof(Msg)-ch, LLoadString(IDS_FOLDER_PROPERTIES_COMPACT), File, (int) ((Used*100)/FileSize), Unused); } } if (Txt) { Txt->Name(Msg); Txt->SendNotify(LNotifyTableLayoutRefresh); } Loop = false; } LMessage::Param OnEvent(LMessage *Msg) { if (Msg->Msg() == M_INIT_DONE) Run(); return LDialog::OnEvent(Msg); } }; ////////////////////////////////////////////////////////////////////////////// bool OpenFolderProperties(ScribeFolder *Parent, int Tab) { FolderPropertiesDlg Dlg(Parent, Tab); return Dlg.RePopulate; } diff --git a/Code/ScribeFolderTree.cpp b/Code/ScribeFolderTree.cpp --- a/Code/ScribeFolderTree.cpp +++ b/Code/ScribeFolderTree.cpp @@ -1,808 +1,817 @@ /* ** FILE: ScribeFolderTree.cpp ** AUTHOR: Matthew Allen ** DATE: 17/1/2000 ** DESCRIPTION: Scribe Folder Tree ** ** Copyright (C) 2000-2002, Matthew Allen ** fret@memecode.com */ // Includes #include "Scribe.h" #include "lgi/common/DropFiles.h" #include "lgi/common/TextLabel.h" #include "lgi/common/RtfHtml.h" #include "resdefs.h" #include "ScribeListAddr.h" #include "lgi/common/ProgressDlg.h" #if WINNATIVE #include "OutlookDropSupport.cpp" #endif #include "lgi/common/LgiRes.h" ////////////////////////////////////////////////////////////////////////////// int FolderSorter(ScribeFolder *a, ScribeFolder *b, int d) { auto A = a->GetName(true); auto B = b->GetName(true); return A && B ? _stricmp(A, B) : 0; } ////////////////////////////////////////////////////////////////////////////// MailTree::MailTree(ScribeWnd *app) : LTree(100, 0, 0, 100, 100, "") { App = app; LastHit = 0; LastWasRoot = -1; Sunken(false); } MailTree::~MailTree() { } void MailTree::OnCreate() { SetWindow(this); } ssize_t MailTree::Sizeof() { LAssert(0); return 0; } bool MailTree::Serialize(LFile &f, bool Write) { bool Status = false; LAssert(0); return Status; } #ifdef _DEBUG const char *GetCompareHdrs(LDataI *Mail, LDataI *a) { auto s = a->GetStr(FIELD_INTERNET_HEADER); if (!s && Mail) s = Mail->GetStr(FIELD_INTERNET_HEADER); return s; } void CompareTrimWhite(LArray &a) { while (a.Length() > 0 && strchr(WhiteSpace, a[a.Length()-1])) a.Length(a.Length()-1); a.Add(0); } char *CompareChar(char a) { static char buf[4][16]; static int next = 0; char *b = buf[next++]; if (next >= 4) next = 0; if (a < ' ' || ((uint8_t)a) >= 0x80) sprintf_s(b, 16, "0x%2.2X", a); else sprintf_s(b, 16, "'%c'", a); return b; } struct SegMap { LDataI *a, *b; }; bool CompareSegments(LDataI *MailA, LDataI *MailB, LDataI *a, LDataI *b, LStream &p) { const char *s1, *s2; // Check headers s1 = GetCompareHdrs(MailA, a); s2 = GetCompareHdrs(MailB, b); if (s1 && s2 && strcmp(s1, s2)) { p.Print("Seg.Headers(%p,%p),", a, b); return false; } else if ((s1 != 0) ^ (s2 != 0)) { p.Print("Seg.HeaderPtrs,"); return false; } // Check data LAutoStreamI da = a->GetStream(_FL); LAutoStreamI db = b->GetStream(_FL); if (da && db) { LAutoString TransferEncoding(s1?InetGetHeaderField(s1, "Content-Transfer-Encoding"):0); bool IsText = true; if (TransferEncoding && !_stricmp(TransferEncoding, "base64")) IsText = false; if (IsText) { // Text compare LArray bufa, bufb; if (bufa.Length((uint32_t) da->GetSize()) && bufb.Length((uint32_t) db->GetSize())) { da->Read(&bufa[0], bufa.Length()); db->Read(&bufb[0], bufb.Length()); CompareTrimWhite(bufa); CompareTrimWhite(bufb); char *ta = &bufa[0], *tb = &bufb[0]; do { if (*ta != *tb) { p.Print("Seg.TextBody(%s != %s @ %i, %p, %p),", CompareChar(*ta), CompareChar(*tb), ta - &bufa[0], a, b); return false; } ta++; tb++; if (*ta == '\r') ta++; if (*tb == '\r') tb++; } while (*ta && *tb); } else { p.Print("Seg.TextMemAlloc(" LPrintfInt64 "," LPrintfInt64 "),", da->GetSize(), db->GetSize()); return false; } } else { // Binary compare if (da->GetSize() != db->GetSize()) { p.Print("Seg.DataSize(" LPrintfInt64 "," LPrintfInt64 "),", da->GetSize(), db->GetSize()); return false; } else { char bufa[1024], bufb[1024]; for (int64 i=0; iGetSize(); ) { ssize_t ra = da->Read(bufa, sizeof(bufa)); ssize_t rb = db->Read(bufb, sizeof(bufb)); if (ra == rb) { if (memcmp(bufa, bufb, ra)) { p.Print("Seg.DataCmp,"); return false; } i += ra; } else { p.Print("Seg.DataRead,"); return false; } } } } } else if ((da != 0) ^ (db != 0)) { p.Print("Seg.Data(%p[%p,%I64i],%p[%p,%I64i]),", a, da.Get(), da ? da->GetSize() : 0, b, db.Get(), db ? db->GetSize() : 0); return false; } GDataIt ac = a->GetList(FIELD_MIME_SEG); GDataIt bc = b->GetList(FIELD_MIME_SEG); if (ac && bc) { if (ac->Length() != bc->Length()) { p.Print("Seg.ChildLength,"); return false; } else { LArray Map; LDataI *Seg; for (Seg = dynamic_cast(ac->First()); Seg; Seg = dynamic_cast(ac->Next())) { Map.New().a = Seg; } for (Seg = dynamic_cast(bc->First()); Seg; Seg = dynamic_cast(bc->Next())) { auto hdr_b = Seg->GetStr(FIELD_INTERNET_HEADER); bool Matched = false; for (unsigned i=0; iGetStream(_FL); LAutoStreamI db = Seg->GetStream(_FL); if (da && db) { int64 size_a = da->GetSize(); int64 size_b = db->GetSize(); int64 diff = size_a - size_b; if (diff < 0) diff = -diff; if (diff == 0) Exact = true; else if (diff < 8) Close = true; } // What about headers? auto hdr_a = m.a->GetStr(FIELD_INTERNET_HEADER); if (hdr_a && hdr_b) { if (_stricmp(hdr_a, hdr_b)) { Close = false; Exact = false; } } if (Close || Exact) { // Match m.b = Seg; Matched = true; break; } } } if (!Matched) { for (unsigned i=0; iGetStr(FIELD_INTERNET_HEADER); if (hdr_a && hdr_b) { if (!_stricmp(hdr_a, hdr_b)) { // Match m.b = Seg; break; } } } } } } unsigned i; for (i=0; iGetStream(_FL); p.Print("%s%p - %s (%I64i)\r\n", sp, s, ContentType.Get(), Data?Data->GetSize():-1); GDataIt c = s->GetList(FIELD_MIME_SEG); if (c) { for (LDataI *a = dynamic_cast(c->First()); a; a = dynamic_cast(c->Next())) { CompareDumpSegs(p, 0, a, depth+1); } } if (mail) p.Print("\r\n"); } #endif void MailTree::OnItemClick(LTreeItem *Item, LMouse &m) { if (m.Down() && m.IsContextMenu()) { Select(Item); ScribeFolder *t = dynamic_cast(Item); if (t) t->DoContextMenu(m); } } void MailTree::OnItemSelect(LTreeItem *Item) { if (LastWasRoot ^ (int8)Item->IsRoot()) { if (Item->IsRoot()) { if (App->GetItemList()) App->GetItemList()->RemoveAll(); App->SetListPane(new DynamicHtml(App, "title.html")); } else { App->SetListPane(new ThingList(App)); } } if (Things() && !Item->IsRoot()) { ScribeFolder *C = dynamic_cast(Item); if (C) { C->Populate(Things()); App->SetCtrlValue(IDM_THREAD, C->GetThreaded()); App->OnFolderSelect(C); } } else { App->SetCtrlValue(IDM_THREAD, false); } LastWasRoot = Item ? Item->IsRoot() : false; } void MailTree::OnCreateSubDirectory(ScribeFolder *Item) { // setup type list... Store3ItemTypes Type[] = { MAGIC_MAIL, MAGIC_CONTACT, MAGIC_FILTER, MAGIC_CALENDAR, MAGIC_GROUP }; int i=0; for (int n=0; nGetItemType()) { i = n; break; } } // do ui.. bool Enable[] = { Item->CanHaveSubFolders(MAGIC_MAIL), Item->CanHaveSubFolders(MAGIC_CONTACT), Item->CanHaveSubFolders(MAGIC_FILTER), Item->CanHaveSubFolders(MAGIC_CALENDAR), Item->CanHaveSubFolders(MAGIC_GROUP) }; CreateSubFolderDlg Dlg(this, i, Enable); if (ValidStr(Dlg.SubName) && Dlg.SubType >= 0) { // check the name doesn't conflict.. auto Path = Item->GetPath(); if (Path) { char s[256]; sprintf_s(s, sizeof(s), "%s/%s", Path.Get(), Dlg.SubName); if (App->GetFolder(s)) { LgiMsg(this, LLoadString(IDS_SUBFLD_NAME_CLASH), AppName, MB_OK); return; } } // insert the folder... Item->CreateSubDirectory(Dlg.SubName, Type[Dlg.SubType]); } } void MailTree::OnDelete(ScribeFolder *Item, bool Force) { if (Item) { int FolderType = App->GetFolderType(Item); if (!Force && FolderType >= 0) { char Msg[256]; sprintf_s(Msg, sizeof(Msg), LLoadString(IDS_DELETE_SYS_FOLDER), DefaultFolderNames[FolderType]); LgiMsg(this, Msg, AppName, MB_OK); } else { Item->OnDelete(); } } } void MailTree::OnProperties(ScribeFolder *Item) { if (Item) { Item->OnProperties(); } } LMessage::Result MailTree::OnEvent(LMessage *Msg) { return LTree::OnEvent(Msg); } extern char ScribeFolderObject[]; int MailTree::WillAccept(LDragFormats &Formats, LPoint Pt, int KeyState) { int Status = DROPEFFECT_NONE; LastHit = ItemAtPoint(Pt.x, Pt.y); if (LastHit) { List Accepted; Formats.Supports(ScribeThingList); Formats.Supports(ScribeFolderObject); Formats.SupportsFileDrops(); #if WINNATIVE Formats.Supports(CFSTR_FILEDESCRIPTOR); #endif #ifdef MAC Formats.Supports(LGI_StreamDropFormat); #endif if (Formats.GetSupported().Length()) { SelectDropTarget(LastHit); Status = KeyState & LGI_EF_CTRL ? DROPEFFECT_COPY : DROPEFFECT_MOVE; } } return Status; } static int FolderItemCmp(LTreeItem *a, LTreeItem *b, NativeInt UserData) { ScribeFolder *ta = dynamic_cast(a); ScribeFolder *tb = dynamic_cast(b); if (ta && tb) { int64 IndexA = ta->GetObject()->GetInt(FIELD_FOLDER_INDEX); int64 IndexB = tb->GetObject()->GetInt(FIELD_FOLDER_INDEX); return (int)IndexA - (int)IndexB; } return 0; } int MailTree::OnDrop(LArray &Data, LPoint Pt, int KeyState) { int Status = DROPEFFECT_NONE; ScribeFolder *Leaf = dynamic_cast(LastHit); #if WINNATIVE LString FileDescFmt = CFSTR_FILEDESCRIPTOR; #endif SelectDropTarget(0); if (App) App->SetLastDrop(); for (unsigned idx=0; idxLength(); ScribeFolder *Root = dynamic_cast(ItemAt(0)); for (ssize_t i=0; iLength(); i++) { ScribeFolder *Folder = Fmt->FolderAt(i); if (Folder) { if (Folder == Leaf || Folder == Root || Leaf->GetItemType() == MAGIC_ANY) { // they're dragging onto themselves or // dragging the whole mail tree around or // dragging a folder into the trash // just quit out now... return DROPEFFECT_NONE; } - - int Res = 2; + bool CopyOp = (KeyState & LGI_EF_CTRL) != 0; - if (Leaf->GetFolder() != NULL) + + auto FinishFolderOp = [&](int Res) { - LAlert Dlg( this, - LLoadString(CopyOp ? IDS_COPY_FOLDER : IDS_MOVE_FOLDER), - LLoadString(IDS_MOVE_ATTACH), - LLoadString(IDS_NEXT_FOLDER), - LLoadString(IDS_SUB_FOLDER), - LLoadString(IDS_CANCEL)); - Res = Dlg.DoModal(); - } - // else it's a top level folder, which doesn't have a "next" option. - - int SystemFolder = App->GetFolderType(Folder); - auto OldPath = Folder->GetPath(); - Store3Status Status = Store3Error; - ScribeFolder *OldParent = Folder->GetFolder(); - ScribeFolder *NewParent = NULL; - int Index = 0; + int SystemFolder = App->GetFolderType(Folder); + auto OldPath = Folder->GetPath(); + Store3Status Status = Store3Error; + ScribeFolder *OldParent = Folder->GetFolder(); + ScribeFolder *NewParent = NULL; + int Index = 0; - if (Res == 1) - { - // Attach next - NewParent = Leaf->GetFolder(); - if (NewParent) + if (Res == 1) { - // Work out the index - for (LTreeItem *Item = NewParent->GetChild(); Item && Item!=Leaf; Item=Item->GetNext()) + // Attach next + NewParent = Leaf->GetFolder(); + if (NewParent) { + // Work out the index + for (LTreeItem *Item = NewParent->GetChild(); Item && Item!=Leaf; Item=Item->GetNext()) + { + Index++; + } Index++; } - Index++; } - } - else if (Res == 2) - { - // Attach child - NewParent = Leaf; - } - else continue; - - if (SystemFolder >= 0 && - NewParent->GetObject()->GetStore() != OldParent->GetObject()->GetStore()) - { - LgiMsg(App, "Can't move system folders to a different mail store.", AppName, MB_OK); - break; - } + else if (Res == 2) + { + // Attach child + NewParent = Leaf; + } + else return; - if (CopyOp) - { - // Copy - Status = Folder->CopyTo(NewParent, Index); - } - else - { - LDataFolderI *fo = Folder->GetFldObj(); - if (NewParent == OldParent) + if (SystemFolder >= 0 && + NewParent->GetObject()->GetStore() != OldParent->GetObject()->GetStore()) { - // Re-index only... - // int64 OldIndex = fo->GetInt(FIELD_FOLDER_INDEX); - if (fo->SetInt(FIELD_FOLDER_INDEX, Index)) - { - // Change the UI to match... - NewParent->Sort(FolderItemCmp); - Status = Store3Success; - } + LgiMsg(App, "Can't move system folders to a different mail store.", AppName, MB_OK); + return; + } + + if (CopyOp) + { + // Copy + Status = Folder->CopyTo(NewParent, Index); } else { - // Move - Status = Folder->SetFolder(NewParent, Index); - if (Status) + LDataFolderI *fo = Folder->GetFldObj(); + if (NewParent == OldParent) { - NewParent->Sort(FolderItemCmp); + // Re-index only... + // int64 OldIndex = fo->GetInt(FIELD_FOLDER_INDEX); + if (fo->SetInt(FIELD_FOLDER_INDEX, Index)) + { + // Change the UI to match... + NewParent->Sort(FolderItemCmp); + Status = Store3Success; + } + } + else + { + // Move + Status = Folder->SetFolder(NewParent, Index); + if (Status) + { + NewParent->Sort(FolderItemCmp); + } } } - } - - if (Status == Store3Success && - SystemFolder >= 0 && - !CopyOp) + + if (Status == Store3Success && + SystemFolder >= 0 && + !CopyOp) + { + // Update the system path location if it's changed... + auto NewPath = Folder->GetPath(); + if (OldPath && + NewPath && + strcmp(OldPath, NewPath) != 0) + { + LVariant v; + v = NewPath; + LString SysFolderName; + SysFolderName.Printf("Folder-%i", SystemFolder); + App->GetOptions()->SetValue(SysFolderName, v); + } + } + }; + + int Res = 2; + if (Leaf->GetFolder() != NULL) { - // Update the system path location if it's changed... - auto NewPath = Folder->GetPath(); - if (OldPath && - NewPath && - strcmp(OldPath, NewPath) != 0) + auto Dlg = new LAlert( this, + LLoadString(CopyOp ? IDS_COPY_FOLDER : IDS_MOVE_FOLDER), + LLoadString(IDS_MOVE_ATTACH), + LLoadString(IDS_NEXT_FOLDER), + LLoadString(IDS_SUB_FOLDER), + LLoadString(IDS_CANCEL)); + Dlg->DoModal([this, Dlg, FinishFolderOp](auto dlg, auto Res) { - LVariant v; - v = NewPath; - LString SysFolderName; - SysFolderName.Printf("Folder-%i", SystemFolder); - App->GetOptions()->SetValue(SysFolderName, v); - } + if (Res > 0) + FinishFolderOp(Res); + delete dlg; + }); } + else FinishFolderOp(Res); } } } else if (ScribeClipboardFmt::IsThing(v.Value.Binary.Data, v.Value.Binary.Length)) { ScribeClipboardFmt *Fmt = (ScribeClipboardFmt*) v.Value.Binary.Data; LDataStoreI::StoreTrans Trans = Leaf->GetObject()->GetStore()->StartTransaction(); bool CopyOnly = (KeyState & LGI_EF_CTRL) != 0; Count = Fmt->Length(); LArray Items; for (ssize_t i=0; iThingAt(i); if (Thg) Items.Add(Thg); } if (Items.Length()) { LArray ItemStatus; Leaf->MoveTo(Items, CopyOnly, &ItemStatus); for (auto s: ItemStatus) if (s == Store3Error) Errors++; } } else LAssert(!"Unknown drop format."); App->Update(); if (Errors) { LgiMsg(this, LLoadString(IDS_MOVE_ERROR), AppName, MB_OK, Errors, Count); } else { Status = DROPEFFECT_COPY; } // We don't need to process any other data types... we're done. break; } else if (dd.IsFileDrop()) { if (dd.Data.Length() > 0) { LDropFiles Files(dd.Data[0]); Leaf->OnReceiveFiles(Files); Status = DROPEFFECT_COPY; } } #if WINNATIVE else if (_stricmp(dd.Format, FileDescFmt) == 0) { if (dd.Data.Length() == 0 || dd.Data[0].Type != GV_BINARY) continue; // Get file list... LString::Array Files; FILEGROUPDESCRIPTOR *FileGroup = (FILEGROUPDESCRIPTOR*) dd.Data[0].Value.Binary.Data; if (OnDropFileGroupDescriptor(FileGroup, Files)) { // Process dropped files LArray FileLst; for (auto &f : Files) FileLst.Add(f.Get()); Leaf->OnReceiveFiles(FileLst); // Clean up for (auto f : Files) { FileDev->Delete(f, false); } } else if (DataObject) { for (int i=0; true; i++) { FORMATETC Format; Format.cfFormat = RegisterClipboardFormat(CFSTR_FILECONTENTS); Format.dwAspect = DVASPECT_CONTENT; Format.lindex = i; Format.tymed = TYMED_ISTORAGE; Format.ptd = 0; STGMEDIUM Medium; ZeroObj(Medium); HRESULT res = DataObject->GetData(&Format, &Medium); if (SUCCEEDED(res)) { if (Medium.tymed == TYMED_ISTORAGE) { OutlookIStorage Dec(App, Medium.pstg, false); int Type = Dec.GetType(); if (Type == Leaf->GetItemType()) { switch (Type) { case MAGIC_MAIL: { Mail *m = Dec.GetMail(); if (m) { m->App = App; m->Save(Leaf); Status = DROPEFFECT_COPY; } break; } case MAGIC_CONTACT: { Contact *c = Dec.GetContact(); if (c) { c->App = App; c->Save(Leaf); Status = DROPEFFECT_COPY; } break; } } } } ReleaseStgMedium(&Medium); } else break; } } } #endif } return Status; } diff --git a/Code/ScribeGroup.cpp b/Code/ScribeGroup.cpp --- a/Code/ScribeGroup.cpp +++ b/Code/ScribeGroup.cpp @@ -1,1310 +1,1313 @@ #include "Scribe.h" #include "lgi/common/TextView3.h" #include "resdefs.h" #include "ScribeListAddr.h" #include "lgi/common/DisplayString.h" #include "lgi/common/LgiRes.h" #include "lgi/common/FileSelect.h" #include "AddressSelect.h" ItemFieldDef GroupFieldDefs[] = { {ContactGroupName, SdName, GV_STRING, FIELD_GROUP_NAME, IDC_GROUP_NAME}, {ContactGroupList, SdList, GV_STRING, FIELD_GROUP_LIST, IDC_GROUP_LIST}, {ContactGroupDateModified, SdDateModified, GV_DATETIME, FIELD_DATE_MODIFIED, 0}, {0} }; int DefaultGroupFields[] = { FIELD_GROUP_NAME, FIELD_DATE_MODIFIED, 0, 0 }; //////////////////////////////////////////////////////////////////////////////////////// class LAddressEdit; struct AddressMeta { int Id; bool Referenced; LAddressEdit *Edit; ListAddr Addr; LString Text; AddressMeta(LAddressEdit *edit, int id = -1); ~AddressMeta(); bool Matched() { return Addr.Length() == 1; } void Ins(); bool IsResolved(); bool IsUnique(); void OpenContact(); }; class LAddressEdit : public LTextView3 { friend struct AddressMeta; LHashTbl,AddressMeta*> Map; LArray Items; int CreateId() { int Id; while (Map.Find(Id = LRand(1000))) ; return Id; } AddressMeta *GetMeta(LStyle &s) { return Map.Find(s.Data.CastInt32()); } bool UpdateMeta(LStyle &Style) { auto m = GetMeta(Style); if (!m) return false; m->Addr.SetText(m->Text); // LgiTrace("UpdateMeta '%s' -> %i\n", m->Text.Get(), m->Addr.Length()); if (m->Addr.Length() > 1) { // Matched more than one recip Style.Fore.Rgb(255, 0, 0); Style.Font = Underline; } else if (m->Addr.Length()) { // Matched a single recip Style.Fore = LColour(L_BLACK); Style.Font = Underline; } else { // No match Style.Fore.Rgb(0xb0, 0xb0, 0xb0); Style.Font = GetFont(); Style.Decor = LCss::TextDecorSquiggle; Style.DecorColour = LColour::Red; } return true; } public: bool AllowPour; ScribeWnd *App; LAddressEdit(LRect *p, const char *t); void OnPaint(LSurface *pDC); void PourStyle(size_t Start, ssize_t Length); bool Insert(size_t At, const char16 *Data, ssize_t Len); bool Delete(size_t At, ssize_t Len); bool GetStyles(List &Styles); int WillAccept(LDragFormats &Formats, LPoint Pt, int KeyState); int OnDrop(LArray &Data, LPoint Pt, int KeyState); bool OnStyleClick(LStyle *style, LMouse *m); bool OnStyleMenu(LStyle *style, LSubMenu *m); void OnStyleMenuClick(LStyle *style, int i); }; class GroupUi : public ThingUi, public LResourceLoad { ContactGroup *Item; LAddressEdit *Edit; public: GroupUi(ContactGroup *item); ~GroupUi(); int OnNotify(LViewI *c, LNotification n); void ResolveAll(); void OnDirty(bool Dirty) {} void OnLoad(); void OnSave(); int WillAccept(LDragFormats &Formats, LPoint Pt, int KeyState); int OnDrop(LArray &Data, LPoint Pt, int KeyState); void Add(char *Email); }; //////////////////////////////////////////////////////////////////////////////////////// class ContactGroupPrivate { public: }; ContactGroup::ContactGroup(ScribeWnd *app, LDataI *object) : Thing(app, object) { DefaultObject(object); d = new ContactGroupPrivate; Ui = 0; } ContactGroup::~ContactGroup() { DeleteObj(d); } Thing &ContactGroup::operator =(Thing &c) { LAssert(0); return *this; } char *ContactGroup::GetDropFileName() { if (!DropFileName) { LAssert(!"FIXME"); } return DropFileName; } bool ContactGroup::GetDropFiles(LString::Array &Files) { LAssert(!"FIXME"); return false; } LString::Array ContactGroup::GetAddresses() { LString::Array Addrs; LVariant l; if (GetVariant(ContactGroupList, l)) { LToken t(l.Str()); for (unsigned i=0; i &a) { bool Status = false; LVariant l; if (GetVariant(ContactGroupList, l)) { LToken t(l.Str()); for (unsigned i=0; iGetStr(FIELD_GROUP_NAME); break; } case SdList: // Type: String[] { if (Array) { LToken t(GetObject()->GetStr(FIELD_GROUP_LIST), ", \r\n"); int Idx = atoi(Array); if (Idx >= 0 && Idx < (int)t.Length()) { Value = t[Idx]; } } else { Value = GetObject()->GetStr(FIELD_GROUP_LIST); } break; } case SdType: // Type: Int32 { Value = GetObject()->Type(); break; } case SdDateModified: { Value = GetObject()->GetDate(FIELD_DATE_MODIFIED); break; } case SdUsedTs: { Value = &UsedTs; break; } default: { return false; } } return true; } bool ContactGroup::CallMethod(const char *MethodName, LVariant *ReturnValue, LArray &Args) { if (GetObject() && !Stricmp((char*)MethodName, (char*)"AddAddress")) { int Added = 0; LString Addrs = GetObject()->GetStr(FIELD_GROUP_LIST); LString::Array a = Addrs.SplitDelimit(WhiteSpace); for (unsigned i=0; iStr(); if (AddrToAdd) { bool HasAddr = false; for (unsigned n=0; nSetStr(FIELD_GROUP_LIST, Lst); SetDirty(); UsedTs.SetNow(); if (ReturnValue) *ReturnValue = true; } else if (ReturnValue) { *ReturnValue = false; } return true; } return Thing::CallMethod(MethodName, ReturnValue, Args); } bool ConvertList(ContactGroup *g, LArray &a) { List Addrs; if (g->GetAddresses(Addrs)) { for (auto e: Addrs) { ListAddr *la = new ListAddr(g->App, e, (char*)0); if (la) { la->OnFind(); a.Add(la); } } Addrs.DeleteArrays(); } return a.Length() > 0; } void ContactGroup::OnMouseClick(LMouse &m) { LListItem::OnMouseClick(m); if (m.IsContextMenu()) { // open the right click menu LScriptUi s(new LSubMenu); if (s.Sub) { List Templates; s.Sub->AppendItem(LLoadString(IDS_OPEN), IDM_OPEN, true); s.Sub->AppendItem(LLoadString(IDS_DELETE), IDM_DELETE, true); ScribeFolder *f = App->GetFolder(FOLDER_TEMPLATES); if (f) { auto Merge = s.Sub->AppendSub(LLoadString(IDS_MERGE_TEMPLATE)); if (Merge) { int n = 0; for (auto t: f->Items) { Mail *m = t->IsMail(); if (m) { Templates.Insert(m); Merge->AppendItem(m->GetSubject()?m->GetSubject():(char*)"(no subject)", IDM_MERGE_TEMPLATE_BASE+n++, true); } } } } else { s.Sub->AppendItem(LLoadString(IDS_MERGE_TEMPLATE), 0, false); } s.Sub->AppendItem(LLoadString(IDS_MERGE_FILE), IDM_MERGE_FILE, true); if (GetList()->GetMouse(m, true)) { LArray Callbacks; if (App->GetScriptCallbacks(LThingContextMenu, Callbacks)) { LScriptArguments Args(NULL); Args[0] = new LVariant(App); Args[1] = new LVariant(this); Args[2] = new LVariant(&s); for (unsigned i=0; iExecuteScriptCallback(*Callbacks[i], Args); } } int Msg; switch (Msg = s.Sub->Float(GetList(), m.x, m.y)) { case IDM_OPEN: { DoUI(); break; } case IDM_DELETE: { LVariant ConfirmDelete; App->GetOptions()->GetValue(OPT_ConfirmDelete, ConfirmDelete); if (!ConfirmDelete.CastInt32() || LgiMsg(GetList(), LLoadString(IDS_DELETE_ASK), AppName, MB_YESNO) == IDYES) { List Del; LList *ParentList = LListItem::Parent; if (ParentList && ParentList->GetSelection(Del)) { for (auto i: Del) { Filter *m = dynamic_cast(i); if (m) m->OnDelete(); } } } break; } case IDM_MERGE_FILE: { - LFileSelect s; - s.Parent(App); - s.Type("Email Template", "*.txt;*.eml"); - if (s.Open()) + auto s = new LFileSelect(App); + s->Type("Email Template", "*.txt;*.eml"); + s->Open([this](auto dlg, auto status) { - LArray Recip; - if (ConvertList(this, Recip)) - App->MailMerge(Recip, s.Name(), 0); - Recip.DeleteObjects(); - } + if (status) + { + LArray Recip; + if (ConvertList(this, Recip)) + App->MailMerge(Recip, dlg->Name(), 0); + Recip.DeleteObjects(); + } + delete dlg; + }); break; } default: { if (Msg >= IDM_MERGE_TEMPLATE_BASE && Msg - IDM_MERGE_TEMPLATE_BASE < (ssize_t)Templates.Length()) { Mail *Template = Templates[Msg - IDM_MERGE_TEMPLATE_BASE]; if (Template) { LArray Recip; if (ConvertList(this, Recip)) App->MailMerge(Recip, 0, Template); Recip.DeleteObjects(); } } else { // Handle any installed callbacks for menu items for (unsigned i=0; iExecuteScriptCallback(Cb, Args); } } } break; } } } DeleteObj(s.Sub); } } else if (m.Down() && m.Double()) { if (!Ui) { Ui = new GroupUi(this); } } } void ContactGroup::OnSerialize(bool Write) { if (Write) { UsedTs.SetNow(); } else if (!UsedTs.IsValid()) { auto m = GetObject()->GetDate(FIELD_DATE_MODIFIED); if (m) UsedTs = *m; } } ThingUi *ContactGroup::DoUI(MailContainer *c) { if (!Ui) Ui = new GroupUi(this); else Ui->Visible(true); return Ui; } int ContactGroup::Compare(LListItem *Arg, ssize_t Field) { ContactGroup *cg = dynamic_cast(Arg); if (!cg) return 0; switch (Field) { case FIELD_DATE_MODIFIED: { auto a = GetObject()->GetDate((int)Field); auto b = cg->GetObject()->GetDate((int)Field); if (a && b) return a->Compare(b); break; } default: { auto a = GetFieldText((int)Field); auto b = cg->GetFieldText((int)Field); if (a && b) return _stricmp(a, b); break; } } return 0; } bool ContactGroup::Save(ScribeFolder *Into) { bool Status = false; // Pre save checks // Save if (!GetFolder() && App) SetParentFolder(App->GetFolder(FOLDER_GROUPS)); if (GetFolder()) { Status = GetFolder()->WriteThing(this) != Store3Error; if (Status) SetDirty(false); } return Status; } int *ContactGroup::GetDefaultFields() { return DefaultGroupFields; } const char *ContactGroup::GetFieldText(int Field) { switch (Field) { case FIELD_DATE_MODIFIED: { auto d = GetObject()->GetDate(Field); if (d) DateCache = d->Local().Get(); else DateCache = LLoadString(IDS_NONE); return DateCache; } } return GetObject() ? GetObject()->GetStr(Field) : 0; } const char *ContactGroup::GetText(int i) { if (FieldArray.Length()) { if (i >= 0 && i < (int)FieldArray.Length()) return GetFieldText(FieldArray[i]); } else if (i < CountOf(DefaultGroupFields)) { return GetFieldText(DefaultGroupFields[i]); } return 0; } //////////////////////////////////////////////////////////////////////////////////////// GroupUi::GroupUi(ContactGroup *item) : ThingUi(item, "Contact Group") { Item = item; Edit = 0; LRect p; LAutoString n; if (LoadFromResource(IDD_GROUP, this, &p, &n)) { SetPos(p); Name(n); MoveSameScreen(App); if (GetViewById(IDC_GROUP_LIST, Edit)) { Edit->App = App; } if (Attach(0)) { AttachChildren(); OnLoad(); Visible(true); SetWindow(this); } } } GroupUi::~GroupUi() { Item->Ui = 0; } extern bool SerializeUi(ItemFieldDef *Defs, LDataI *Object, LViewI *View, bool ToUi); void GroupUi::OnLoad() { SerializeUi(GroupFieldDefs, Item->GetObject(), this, true); auto GrpName = Item->GetObject()->GetStr(FIELD_GROUP_NAME); if (ValidStr(GrpName)) { LString s; s.Printf("%s - %s", LLoadString(IDD_GROUP), GrpName); Name(s); } } void GroupUi::ResolveAll() { // Turn all the references into email addresses List Styles; if (Edit && Edit->GetStyles(Styles)) { for (auto a: Styles) { if (!a->IsResolved() && a->IsUnique()) { a->Ins(); } } } } void GroupUi::OnSave() { ResolveAll(); // Save the group of contacts Item->SetDirty(); SerializeUi(GroupFieldDefs, Item->GetObject(), this, false); Item->Save(); Item->Update(); } int GroupUi::OnNotify(LViewI *c, LNotification n) { switch (c->GetId()) { case IDOK: { OnSave(); PostEvent(M_CLOSE); break; } } return 0; } int GroupUi::WillAccept(LDragFormats &Formats, LPoint Pt, int KeyState) { Formats.Supports(ScribeThingList); Formats.SupportsFileDrops(); return Formats.GetSupported().Length() ? DROPEFFECT_COPY : DROPEFFECT_NONE; } void GroupUi::Add(char *Email) { ResolveAll(); if (Edit) { List Styles; Edit->GetStyles(Styles); for (auto a: Styles) { if (a->Addr.sAddr && _stricmp(a->Addr.sAddr, Email) == 0) { LgiTrace("%s:%i - '%s' already in group\n", _FL, Email); return; } } char16 NewLine[] = { '\n', 0 }; char16 *e = Utf8ToWide(Email); if (e) { auto Len = StrlenW(Edit->NameW()); if (Len) Edit->Insert(Len, NewLine, 1); Edit->Insert(Len + 1, e, StrlenW(e)); } } } int GroupUi::OnDrop(LArray &Data, LPoint Pt, int KeyState) { int Status = DROPEFFECT_NONE; for (unsigned n=0; nIsBinary() && ScribeClipboardFmt::IsThing(Data->Value.Binary.Data, Data->Value.Binary.Length)) { ScribeClipboardFmt *Fmt = (ScribeClipboardFmt*)Data->Value.Binary.Data; for (unsigned i=0; iLength(); i++) { Contact *c = Fmt->ThingAt(i) ? Fmt->ThingAt(i)->IsContact() : NULL; if (c) { auto Email = c->GetAddrAt(0); if (Email) { Add(Email); Status = DROPEFFECT_COPY; } else LgiTrace("%s:%i - No addr\n", _FL); } else LgiTrace("%s:%i - Not a contact\n", _FL); } } } } else if (dd.IsFileDrop()) { LgiTrace("%s:%i - Impl file drop here\n", _FL); } } return Status; } //////////////////////////////////////////////////////////////////////////////////////// #define EDIT_OPEN_CONTACT 100 #define EDIT_DELETE 101 #define EDIT_ADDR_BASE 1000 #define GEDIT 'GEDT' AddressMeta::AddressMeta(LAddressEdit *edit, int id) : Addr(edit->App) { Edit = edit; Id = id < 0 ? edit->CreateId() : id; Referenced = false; LAssert(!Edit->Map.Find(Id)); Edit->Map.Add(Id, this); } AddressMeta::~AddressMeta() { LAssert(Edit->Map.Find(Id)); Edit->Map.Delete(Id, true); } bool AddressMeta::IsUnique() { return Addr.Length() == 1; } bool AddressMeta::IsResolved() { bool Status = false; if (IsUnique()) { RecipientItem *r = Addr[0]; if (r && ValidStr(r->GetEmail())) { Contact *c = r->GetContact(); if (c) { auto Emails = c->GetEmails(); for (auto e: Emails) { if (e.Equals(Text)) { Status = true; break; } } } else { Status = _stricmp(Text, r->GetEmail()) == 0; } } } return Status; } void AddressMeta::Ins() { RecipientItem *r = Addr[0]; if (r) { const char *n = Addr.sAddr; if (!n) n = r->GetEmail(); LAutoWString Name(Utf8ToWide(n)); if (Name) { /* auto v = Style->View; Edit->AllowPour = false; v->Delete(Style->Start, Style->Len); Edit->AllowPour = true; Style->Len = StrlenW(Name); auto sl = Style->End(); // This may delete this object... so don't use anything local afterwards v->Insert(Style->Start, Name, Style->Len); // No local! v->Invalidate(); v->SetCaret(sl, false, false); */ } } } void AddressMeta::OpenContact() { if (Addr.Length() == 1) { RecipientItem *r = Addr[0]; if (r && r->GetContact()) { r->GetContact()->DoUI(); } } } LAddressEdit::LAddressEdit(LRect *p, const char *t) : LTextView3(-1, 0, 0, 100, 100, 0) { App = NULL; AllowPour = true; if (p) SetPos(*p); Sunken(true); } void LAddressEdit::OnPaint(LSurface *pDC) { LTextView3::OnPaint(pDC); #if 0// def _DEBUG pDC->ClipRgn(0); char s[256]; sprintf_s(s, sizeof(s), "%i styles", Style.Length()); LSysFont->Colour(Rgb24(0, 0, 255), 24); LSysFont->Transparent(true); LDisplayString ds(LSysFont, s); ds.Draw(pDC, 2, Y()-20); #endif } void LAddressEdit::PourStyle(size_t Start, ssize_t Length) { if (AllowPour) { const char *Delim = " \t\r\n,;"; LUnrolledList Old, New; Old.Swap(Style); for (auto i : Map) { i.value->Referenced = false; } LHashTbl,LStyle*> Hash; for (auto &i : Old) { LAssert(i.Data.Type != GV_NULL); Hash.Add(i.Start, &i); } // Generate the new list... for (int i=0; i 0) { // Insert new style LAssert(App != NULL); auto &s = New.New().Construct(this, STYLE_ADDRESS); s.Start = Start; s.Len = Len; } } // Match the old and new lists, merging unchanged entries from // the old list and taking changed entries from the new list. AddressMeta *m; for (auto &n : New) { // Find matching old entry LString s(Text + n.Start, n.Len); auto o = Hash.Find(n.Start); if (o && o->Len == n.Len) { // Carry over the data ref id LAssert(o->Data.Type != GV_NULL); n.Data = o->Data; n.Font = o->Font; n.Fore = o->Fore; n.Back = o->Back; n.Decor = o->Decor; n.DecorColour = o->DecorColour; auto m = GetMeta(n); if (m) { // LgiTrace("Existing meta @ %i '%s'\n", n.Start, m->Text.Get()); m->Referenced = true; } } else { // Create a new entry if ((m = new AddressMeta(this))) { n.Data = m->Id; m->Referenced = true; m->Text.SetW(Text + n.Start, n.Len); UpdateMeta(n); // LgiTrace("Creating meta @ %i '%s' with id=%i\n", n.Start, m->Text.Get(), m->Id); } } } // Clear out unreferenced meta objects for (auto i : Map) { if (i.value->Referenced == false) { // LgiTrace("Deleting unreferenced meta '%s'\n", i.value->Text.Get()); delete i.value; } } #ifdef _DEBUG for (auto &s : New) { LAssert(s.Data.Type != GV_NULL); } #endif Style.Swap(New); // Update LRect r(0, Y()-20, 100, Y()); Invalidate(&r); #if 0 printf("Styles:\n"); for (GEditAddress *e=(GEditAddress*)Style.First(); e; e=(GEditAddress*)Style.Next()) { printf("\tStart=%i Len=%i Text=%.*S\n", e->Start, e->Len, e->Len, Text+e->Start); } #endif } } bool LAddressEdit::Insert(size_t At, const char16 *Data, ssize_t Len) { for (auto &s : Style) { LAssert(s.Data.Type != GV_NULL); if (s.Start > (ssize_t)At) { /*auto m =*/ GetMeta(s); // LgiTrace("Insert adding to start: %i->%i '%s'\n", s.Start, s.Start + Len, m ? m->Text.Get() : NULL); s.Start += Len; } } return LTextView3::Insert(At, Data, Len); } bool LAddressEdit::Delete(size_t at, ssize_t Len) { ssize_t At = (ssize_t)at; for (auto &s : Style) { LAssert(s.Data.Type != GV_NULL); AddressMeta *m = GetMeta(s); if (s.Overlap(At, Len) && m && m->Matched()) { // extend delete region to include the whole styled addr if (At > s.Start) { Len += At - s.Start; At = s.Start; } if (At + Len < s.Start + s.Len) { Len = s.Start + s.Len - At; } // LgiTrace("Delete len change: %i:%i '%s'\n", s.Start, s.Len, m ? m->Text.Get() : NULL); } if (s.Start > At) { s.Start -= Len; // LgiTrace("Delete start change: %i:%i '%s'\n", s.Start, s.Len, m ? m->Text.Get() : NULL); } } return LTextView3::Delete(At, Len); } bool LAddressEdit::GetStyles(List &Styles) { /* Styles.Empty(); for (GEditAddress *s = (GEditAddress*) Style.First(); s; s = (GEditAddress*) Style.Next()) { Styles.Insert(s); } return Styles.First() != 0; */ return false; } int LAddressEdit::WillAccept(LDragFormats &Formats, LPoint Pt, int KeyState) { LWindow *w = GetWindow(); LDragDropTarget *t = dynamic_cast(w); if (t) { return t->WillAccept(Formats, Pt, KeyState); } return DROPEFFECT_NONE; } bool LAddressEdit::OnStyleClick(LStyle *style, LMouse *ms) { auto m = GetMeta(*style); if (!m) return false; if (ms->Double() && ms->Left()) { m->OpenContact(); return true; } return false; } bool LAddressEdit::OnStyleMenu(LStyle *style, LSubMenu *sub) { auto m = GetMeta(*style); if (!m) return false; if (m->Addr.Length() == 1) { sub->AppendItem("Open Contact", EDIT_OPEN_CONTACT, true); sub->AppendItem("Delete", EDIT_DELETE, true); } else { Items.DeleteObjects(); AddressBrowseLookup(App, Items, m->Text); if (Items.Length()) { int Idx = 0; for (auto i : Items) { LString n; n.Printf("%s %s <%s>", i->First.Get(), i->Last.Get(), i->Email.Get()); sub->AppendItem(n, EDIT_ADDR_BASE + Idx++); } } else { sub->AppendItem("No results", -1, false); } } sub->AppendSeparator(); /* if (IsOk() && Addr.Length() > 0) { if (Addr.Length() == 1) { m->AppendItem("Open Contact", EDIT_OPEN_CONTACT, true); m->AppendItem("Delete", EDIT_DELETE, true); m->AppendSeparator(); } char s[256]; for (int i=0; iGetContact(); if (c) { LAutoString Email; for (int n=0; (Email = c->GetAddrAt(n)); n++) { char *First = 0, *Last = 0; s[0] = 0; c->Get(OPT_First, First); c->Get(OPT_Last, Last); if (First) { if (*s) strcat(s, " "); strcat(s, First); } if (Last) { if (*s) strcat(s, " "); strcat(s, Last); } int len = (int)strlen(s); sprintf_s(s+len, sizeof(s)-len, " <%s>", (char*)Email); int k = (i << 8) + n; m->AppendItem( s, EDIT_ADDR_BASE + k, ValidStr(Email)); LgiTrace("Adding contact '%s' %i:%i (%k)\n", s, i, n, k); } } else { char *Name = Addr[i]->GetName(); char *Email = Addr[i]->GetEmail(); sprintf_s(s, sizeof(s), "%s <%s>", Name, Email); m->AppendItem( s, EDIT_ADDR_BASE + (i << 8), ValidStr(Email)); } } return true; } */ return false; } void LAddressEdit::OnStyleMenuClick(LStyle *style, int i) { auto m = GetMeta(*style); if (!m) return; switch (i) { case EDIT_OPEN_CONTACT: { m->OpenContact(); break; } case EDIT_DELETE: { // This will delete us, don't access anything local afterwards Delete(style->Start, style->Len); // Update the edit and get outta here Invalidate(); return; break; } default: { BrowseItem *r = Items[i - EDIT_ADDR_BASE]; if (r) { Contact *c = Contact::LookupEmail(r->Email); if (c) { style->Fore = LColour(L_TEXT); m->Addr.SetWho(new RecipientItem(c), -1); LAutoWString wEmail(Utf8ToWide(r->Email)); auto Start = style->Start; auto Len = style->Len; Delete(Start, Len); Insert(Start, wEmail, Strlen(wEmail.Get())); } } } } } int LAddressEdit::OnDrop(LArray &Data, LPoint Pt, int KeyState) { LWindow *w = GetWindow(); LDragDropTarget *t = dynamic_cast(w); if (t) { return t->OnDrop(Data, Pt, KeyState); } return DROPEFFECT_NONE; } class LAddressEditFactory : public LViewFactory { public: LView *NewView(const char *Class, LRect *Pos, const char *Text) { if (strcmp(Class, "LAddressEdit") == 0) { return new LAddressEdit(Pos, Text); } return 0; } } AddressEditFactory; ////////////////////////////////////////////////////////////////////////////// LGroupMap::LGroupMap(ScribeWnd *app) : App(app) { auto srcs = App->GetThingSources(MAGIC_GROUP); for (auto s: srcs) { s->LoadThings(); for (auto i: s->Items) Index(i->IsGroup()); } } LGroupMap::~LGroupMap() { DeleteObjects(); } void LGroupMap::Index(ContactGroup *grp) { if (!grp) return; for (auto email: grp->GetAddresses()) { auto a = Find(email); if (!a) { a = new LGroupMapArray; Add(email, a); } a->Add(grp); } } diff --git a/Code/ScribeListAddr.cpp b/Code/ScribeListAddr.cpp --- a/Code/ScribeListAddr.cpp +++ b/Code/ScribeListAddr.cpp @@ -1,1180 +1,1182 @@ #include "Scribe.h" #include "resdefs.h" #include "lgi/common/ClipBoard.h" #include "ScribeListAddr.h" #include "lgi/common/LgiRes.h" #define IDM_ALT_EMAIL_BASE 100000 //////////////////////////////////////////////////////////////////////////// void RecipientItem::_New() { c = 0; g = 0; // e = 0; Buf[0] = 0; } RecipientItem::RecipientItem(RecipientItem *item) { _New(); c = item->c; // e = item->e; g = item->g; } RecipientItem::RecipientItem(Contact *contact) { _New(); c = contact; } RecipientItem::RecipientItem(ContactGroup *group) { _New(); g = group; } const char *RecipientItem::GetName() { if (c) { if (!Buf[0]) { const char *First = 0, *Last = 0; c->Get(OPT_First, First); c->Get(OPT_Last, Last); if (First) strcpy_s(Buf, sizeof(Buf), First); if (Last) { if (Buf[0]) strcat(Buf, " "); strcat(Buf, Last); } } return Buf; } /* if (e) { e->GetValue(PropToStr(PropName), Name); return Name.Str(); } */ return 0; } const char *RecipientItem::GetFirst() { const char *s = 0; if (c) { c->Get(OPT_First, s); } else if (g) { LVariant Name; if (g->GetVariant(ContactGroupName, Name) && Name.Str()) { static char Buf[64]; strcpy_s(Buf, sizeof(Buf), Name.Str()); s = Buf; } } return s; } const char *RecipientItem::GetLast() { const char *s = 0; if (c) c->Get(OPT_Last, s); return s; } const char *RecipientItem::GetEmail() { const char *s = 0; if (c) c->Get(OPT_Email, s); return s; } const char *RecipientItem::GetAttribute(char *Attr) { const char *s = 0; if (c) c->Get(Attr, s); return s; } void RecipientItem::DoUI(LView *Parent) { if (c) c->DoUI(); else if (g) g->DoUI(); } //////////////////////////////////////////////////////////////////////////// #define IDM_WHO 1000 ListAddr::ListAddr(ScribeWnd *app) { App = app; Loaded = false; } ListAddr::ListAddr(ScribeWnd *app, LDataPropI *Prop) { App = app; Loaded = true; if (Prop) { sName = Prop->GetStr(FIELD_NAME); sAddr = Prop->GetStr(FIELD_EMAIL); CC = (EmailAddressType)Prop->GetInt(FIELD_CC); Status = (uint8_t) Prop->GetInt(FIELD_STATUS); OnFind(); } } ListAddr::ListAddr(Contact *c) { Loaded = true; App = NULL; if (c) { App = c->App; Who.Insert(new RecipientItem(c)); OnFound(); } else LAssert(0); } ListAddr::ListAddr(ContactGroup *g) { Loaded = true; App = NULL; if (g) { App = g->App; Who.Insert(new RecipientItem(g)); OnFound(); } } ListAddr::ListAddr(ScribeWnd *app, RecipientItem *c) { Loaded = true; App = app; if (c) { Who.Insert(c); OnFound(); } } ListAddr::ListAddr(ScribeWnd *app, AddressDescriptor *a, List *Cache) { Loaded = true; App = app; ListAddr *la = dynamic_cast(a); if (la) { sName = la->sName; sAddr = la->sAddr; Status = la->Status; CC = la->CC; for (auto r: *la) { Who.Insert(new RecipientItem(r)); } } else if (a) { sName = a->sName; sAddr = a->sAddr; CC = a->CC; Status = a->Status; OnFind(Cache); } } ListAddr::ListAddr(ScribeWnd *app, const char *Email, const char *Nm, List *Cache) { Loaded = true; App = app; sName = Nm; sAddr = Email; OnFind(Cache); } ListAddr::~ListAddr() { _Delete(); } const char *ListAddr::GetStr(int id) { switch (id) { case FIELD_NAME: return sName; case FIELD_EMAIL: return sAddr; } LAssert(0); return 0; } Store3Status ListAddr::SetStr(int id, const char *str) { switch (id) { case FIELD_NAME: if (sName.Get() != str) sName = str; break; case FIELD_EMAIL: if (sAddr.Get() != str) sAddr = str; break; default: LAssert(0); return Store3Error; } return Store3Success; } int64 ListAddr::GetInt(int id) { switch (id) { case FIELD_CC: return CC; case FIELD_STATUS: return Status; case FIELD_COLOUR: { LCss *Css = GetCss(); if (!Css) return -1; LCss::ColorDef c = Css->Color(); return c.Type == LCss::ColorRgb ? c.Rgb32 : -1; } } LAssert(0); return -1; } Store3Status ListAddr::SetInt(int id, int64 i) { switch (id) { case FIELD_COLOUR: { LCss *Css = GetCss(true); if (!Css) return Store3Error; if (i) Css->Color(LColour((uint32_t)i, 32)); else Css->Color(LCss::ColorDef(LCss::ColorInherit)); break; } default: { LAssert(0); return Store3Error; } } return Store3Success; } LDataPropI &ListAddr::operator =(LDataPropI &p) { AddressDescriptor *a = dynamic_cast(&p); if (a) { CopyFrom(*a); } else LAssert(0); return *this; } void ListAddr::CopyFrom(AddressDescriptor &a) { _Delete(); sName = a.sName; sAddr = a.sAddr; CC = a.CC; Status = a.Status; } int ListAddr::Length() { if (Loaded) { OnFind(0, true); Loaded = true; } #if 0 if (Who.Length() > 1) { LgiTrace("name=%s,%s\n", Name, Addr); for (RecipientItem *ri=Who.First(); ri; ri=Who.Next()) { Contact *c = ri->GetContact(); if (c) { ScribeFolder *t = c->GetFolder(); if (t) { LAutoString p = t->GetPath(); LgiTrace("p=%s\n", p.Get()); } else LgiTrace("t=0\n"); } else LgiTrace("c=0\n"); } } #endif return (int)Who.Length(); } void ListAddr::Delete(RecipientItem *i) { Who.Delete(i); } void ListAddr::SetWho(RecipientItem *i, int Idx) { Who.Delete(i); Who.DeleteObjects(); Who.Insert(i); if (Idx >= 0) { Contact *c = Who[0]->GetContact(); if (c) { auto a = c->GetAddrAt(Idx); sAddr = a.Get(); } } OnFound(); Loaded = true; } bool ListAddr::Serialize(LString &s, bool write) { Store3Status Status = Store3Error; static const char *Delim = "\'\"<>,"; if (write) { // ListAddr -> String LString name = LUrlEncode(sName, Delim); LString email = LUrlEncode(sAddr, Delim); if (name && email) s.Printf("'%s' <%s>", name.Get(), email.Get()); else if (name) s.Printf("'%s'", name.Get()); else if (email) s.Printf("<%s>", email.Get()); else return false; Status = Store3Success; } else { // String -> ListAddr char *c = s; while (*c) { while (*c && strchr(WhiteSpace, *c)) c++; if (*c == '\'' || *c == '<') { char del = *c++; char *e = strchr(c, del=='<'?'>':del); if (!e) break; *e = 0; LString d = LUrlDecode(c); if (d) { if (del == '<') { if (SetStr(FIELD_EMAIL, d)) Status = Store3Success; } else { if (SetStr(FIELD_NAME, d)) Status = Store3Success; } } c = e + 1; } else break; } } return Status >= Store3Delayed; } RecipientItem *ListAddr::operator [](int i) { if (!Loaded) { Loaded = true; OnFind(0, true); } return Who[i]; } List::I ListAddr::begin() { if (!Loaded) { Loaded = true; OnFind(0, true); } return Who.begin(); } List::I ListAddr::end() { return Who.end(); } char *ListAddr::MakeName(const char *Delim, bool LocalTime) { static char Buf[512]; if (sAddr && sName) { RecipientItem *i = Who[0]; Contact *c = i ? i->GetContact() : 0; char *Local = LocalTime ? (c ? c->GetLocalTime() : 0) : 0; if (Local) { sprintf_s(Buf, sizeof(Buf), "%s%s%s <%s> (%s)", Delim, sName.Get(), Delim, sAddr.Get(), Local); DeleteArray(Local); } else { sprintf_s(Buf, sizeof(Buf), "%s%s%s <%s>", Delim, sName.Get(), Delim, sAddr.Get()); } } else if (sAddr) { sprintf_s(Buf, sizeof(Buf), "<%s>", sAddr.Get()); } else if (sName) { sprintf_s(Buf, sizeof(Buf), "%s%s%s", Delim, sName.Get(), Delim); } else { return 0; } return Buf; } const char *ListAddr::GetText(int i) { AddressList *al = dynamic_cast(Parent); if (al) { switch (i) { case 0: { switch (CC) { case MAIL_ADDR_TO: return (char*)"To:"; case MAIL_ADDR_CC: return (char*)"Cc:"; case MAIL_ADDR_BCC: return (char*)"Bcc:"; default: break; } break; } case 1: { return MakeName(); break; } } } else { switch (i) { case 0: { if (sAddr) return sAddr; break; } case 1: { if (sName) return sName; break; } } } return LListItem::GetText(i); } bool ListAddr::SetText(const char *s, int i) { if (s && !i) { sAddr = s; OnFind(); } else { return LListItem::SetText(s, i); } return true; } int ListAddr::GetImage(int Flags) { auto Whose = Who.Length(); if (Whose == 1) { RecipientItem *i = Who[0]; if (i && i->GetGroup()) { return ICON_CLOSED_FOLDER; } return ICON_CONTACT; } if (Whose > 1) return ICON_UNKNOWN; return -1; } void ListAddr::OnFound(bool Persist) { if (Who.Length() == 1) { RecipientItem *c = Who[0]; if (c) { auto First = c->GetFirst(); auto Last = c->GetLast(); bool ValidEmail = false; if (sAddr) { Contact *Con = c->GetContact(); if (Con) { ValidEmail = Con->HasEmail(sAddr); } else { auto Email = c->GetEmail(); if (Email) ValidEmail = _stricmp(Email, sAddr) == 0; } } if (c->GetGroup()) { sName.Empty(); sAddr = First; } else { if (!ValidEmail) { sAddr = c->GetEmail(); } if (First || Last) { auto first = (First) ? First : ""; auto last = (Last) ? Last : ""; if (!sName || !Persist) sName.Printf("%s %s", first, last); } } Update(); } } } void ListAddr::OnFind(List *Cache, bool Persist) { if (sAddr) { // Strip off mailto... LString s = sAddr.Strip(); if (s.Find(MailToStr) == 0) sAddr = s(7,-1); } { List MatchLast; List MatchNick; if (!Cache) { Cache = App->GetEveryone(); } Who.DeleteObjects(); // search scribe's contact database for matches auto Lst = Cache->begin(); // ssize_t Len = Lst.Length(); int Iters = 0; if (sAddr) { for (Contact *c = *Lst; c; c = *++Lst, Iters++) { if (strchr(sAddr, '@')) { if (c->HasEmail(sAddr)) Who.Insert(new RecipientItem(c)); } else { const char *First = 0; const char *Last = 0; const char *Nick = 0; c->Get(OPT_First, First); c->Get(OPT_Last, Last); c->Get(OPT_Nick, Nick); char *Space = strchr(sAddr, ' '); if (Space) { ssize_t Len = Space - sAddr.Get(); if (First && Last) { if (_strnicmp(sAddr, First, Len) == 0 && _stricmp(Space+1, Last) == 0) { Who.Insert(new RecipientItem(c)); } } else if (First) { if (_stricmp(sAddr, First) == 0) { Who.Insert(new RecipientItem(c)); } } } else { if (First && _stricmp(sAddr, First) == 0) { Who.Insert(new RecipientItem(c)); } if (Last && _stricmp(sAddr, Last) == 0) { MatchLast.Insert(new RecipientItem(c)); } if (Nick && _stricmp(sAddr, Nick) == 0) { MatchNick.Insert(new RecipientItem(c)); } } } } } // search the groups for names if (sName) { ContactGroup *g = LookupContactGroup(App, sName); if (g) Who.Insert(new RecipientItem(g)); } // look at results if (Who.Length() == 0) { if (MatchLast.Length() > 0) { Who = MatchLast; } else if (MatchNick.Length() > 0) { Who = MatchNick; } } } OnFound(Persist); } void ListAddr::AddToContacts(bool Ui, ScribeFolder *Folder) { Contact *c = (Contact*) App->CreateItem(MAGIC_CONTACT, Folder, false); if (c) { char *NameStr = 0; if (sAddr && strchr(sAddr, '@')) { c->Set(OPT_Email, sAddr); NameStr = sName; } else { NameStr = sAddr; } char Buffer[256]; if (NameStr && strlen(NameStr) < sizeof(Buffer)) { strcpy_s(Buffer, sizeof(Buffer), NameStr); char *Space = strchr(Buffer, ' '); if (Space) { // has first and last *Space++ = 0; c->Set(OPT_Last, Space); } // else just the first name c->Set(OPT_First, Buffer); } c->Save(); if (Ui) c->DoUI(); Who.Insert(new RecipientItem(c)); Update(); /* if (LListItem::Parent) { LListItem::Parent->Invalidate(); } */ } } void ListAddr::OnMouseClick(LMouse &m) { AddressList *al = dynamic_cast(Parent); if (!al) { return; } if (m.IsContextMenu()) { auto RClick = new LSubMenu; if (RClick) { RClick->SetImageList(App->GetIconImgList(), false); if (Who.Length() > 1) { int i=0; for (auto c: Who) { RClick->AppendItem(c->GetName(), IDM_WHO + i, true); i++; } } else { // Allow user to select amoungst multiple email addresses if (Who.Length() == 1) { Contact *c = Who[0]->GetContact(); if (c && c->GetAddrCount() > 1) { auto Emails = c->GetEmails(); int i = 0; for (auto e: Emails) RClick->AppendItem(e, IDM_ALT_EMAIL_BASE+i++, true); RClick->AppendSeparator(); } } auto Item = RClick->AppendItem("&To:", IDM_TO, true); if (Item && CC == 0) Item->Checked(true); Item = RClick->AppendItem("&Cc:", IDM_CC, true); if (Item && CC == 1) Item->Checked(true); Item = RClick->AppendItem("&Bcc:", IDM_BCC, true); if (Item && CC == 2) Item->Checked(true); } RClick->AppendSeparator(); RClick->AppendItem(LLoadString(IDS_COPY), IDM_COPY, true); RClick->AppendSeparator(); RClick->AppendItem(LLoadString(IDS_EDIT), IDM_EDIT, true); RClick->AppendItem(LLoadString(IDS_DELETE), IDM_DELETE, true); if (sAddr) { if (Who.Length() == 1) { RecipientItem *r = Who[0]; auto i = RClick->AppendItem(LLoadString(IDS_OPEN), IDM_OPEN, true); if (i) { if (r->GetContact()) { i->Icon(ICON_CONTACT); } else if (r->GetGroup()) { i->Icon(ICON_CLOSED_FOLDER); } } } else if (Who.Length() < 1) { RClick->AppendItem(LLoadString(IDS_ADD_CONTACTS), IDM_NEW_CONTACT, true); } } m.ToScreen(); int Msg = RClick->Float(LListItem::Parent, m.x, m.y); switch (Msg) { case IDM_TO: case IDM_CC: case IDM_BCC: { List Sel; LList *ParentList = LListItem::Parent; if (ParentList && ParentList->GetSelection(Sel)) { int Val = 0; switch (Msg) { case IDM_CC: Val = 1; break; case IDM_BCC: Val = 2; break; } for (auto It = Sel.rbegin(); It != Sel.end(); It--) { auto c = dynamic_cast(*It); if (c) { c->CC = (EmailAddressType)Val; c->Update(); } } } break; } case IDM_COPY: { if (LListItem::GetList()) { List Sel; if (LListItem::GetList()->GetSelection(Sel)) { LStringPipe p(256); int i = 0; for (auto la: Sel) { p.Print("%s%s", i?EOL_SEQUENCE:"", la->MakeName("\"", false)); i++; } LAutoString s(p.NewStr()); if (s) { LClipBoard Clip(Parent); Clip.Text(s); LAutoWString w(Utf8ToWide(s)); if (w) Clip.TextW(w, false); } } } break; } case IDM_EDIT: { - LInput Dlg(Parent, sAddr); - if (Dlg.DoModal() == IDOK) + auto Dlg = new LInput(Parent, sAddr); + Dlg->DoModal([this, Dlg](auto dlg, auto id) { - sAddr = Dlg.GetStr(); + if (id == IDOK) + { + sAddr = Dlg->GetStr(); - OnFind(); - Update(); + OnFind(); + Update(); - if (LListItem::GetList()) - { - LListItem::GetList()->OnNotify(LListItem::GetList(), LNotifyItemChange); + if (LListItem::GetList()) + LListItem::GetList()->OnNotify(LListItem::GetList(), LNotifyItemChange); } - } + delete dlg; + }); break; } case IDM_DELETE: { List Del; LList *ParentList = LListItem::Parent; if (ParentList && ParentList->GetSelection(Del)) { for (auto It = Del.rbegin(); It != Del.end(); It--) { auto c = dynamic_cast(*It); if (c) ParentList->Delete(c); } ParentList->Invalidate(); } break; } case IDM_NEW_CONTACT: { AddToContacts(true); break; } case IDM_OPEN: { RecipientItem *c = Who[0]; if (c) { c->DoUI(Parent); } break; } default: { if (Msg >= IDM_WHO && Msg < IDM_WHO + Who.Length()) { RecipientItem *c = Who[Msg - IDM_WHO]; Who.Delete(c); Who.DeleteObjects(); Who.Insert(c); OnFound(); if (LListItem::Parent) LListItem::Parent->Invalidate(); } // Action the change address menu if (Msg >= IDM_ALT_EMAIL_BASE && Who.Length() == 1) { Contact *c = Who[0]->GetContact(); if (c && c->GetAddrCount() > 1) { int Index = Msg - IDM_ALT_EMAIL_BASE; if (Index < c->GetAddrCount()) { sAddr = c->GetAddrAt(Index); Update(); } } } break; } } DeleteObj(RClick); } } else if (m.Double() && m.Left()) { if (Who.Length() == 1) { RecipientItem *c = Who[0]; if (c) { c->DoUI(Parent); } } } } bool ListAddr::SetVariant(const char *VarName, LVariant &Value, const char *Array) { if (!VarName) return false; if (_stricmp(VarName, "Text") == 0) { LAutoString n, a; DecodeAddrName(Value.Str(), n, a, 0); sName = n.Get(); sAddr = a.Get(); return true; } return false; return true; } bool ListAddr::GetVariant(const char *VarName, LVariant &Value, const char *Array) { ScribeDomType Fld = StrToDom(VarName); switch (Fld) { case SdText: // Type: String { Value = Print().Get(); break; } case SdName: // Type: String { Value = sName; break; } case SdEmail: // Type: String { Value = sAddr; break; } case SdType: // Type: Int32 { Value = (int)CC; break; } case SdContact: // Type: Contact { OnFind(0, true); RecipientItem *Ri = Who[0]; if (Ri && Ri->GetContact()) { Value = Ri->GetContact(); } else return false; break; } case SdGroups: // Type: String[] { OnFind(0, true); RecipientItem *Ri = Who[0]; if (Ri && Ri->GetContact()) { return Ri->GetContact()->GetVariant(VarName, Value, Array); } else if (sAddr) { if (Value.SetList()) { ScribeFolder *g = App->GetFolder(FOLDER_GROUPS); if (g) { for (auto t: g->Items) { ContactGroup *Grp = t->IsGroup(); if (Grp) { List GrpAddr; if (Grp->GetAddresses(GrpAddr)) { for (auto a: GrpAddr) { if (_stricmp(a, sAddr) == 0) { auto Name = Grp->GetFieldText(FIELD_GROUP_NAME); if (Name) Value.Value.Lst->Insert(new LVariant(Name)); } } GrpAddr.DeleteArrays(); } } } } } } else return false; break; } default: { return false; } } return true; } char *ListAddr::Copy() { return NewStr(MakeName("\"", false)); } void ListAddr::Paste(char *s) { _Delete(); LAutoString a, n; DecodeAddrName(s, n, a, 0); sName = n.Get(); sAddr = a.Get(); } void ListAddr::_Delete() { AddressDescriptor::_Delete(); Who.DeleteObjects(); } diff --git a/Code/ScribeMail.cpp b/Code/ScribeMail.cpp --- a/Code/ScribeMail.cpp +++ b/Code/ScribeMail.cpp @@ -1,10220 +1,10234 @@ /* ** FILE: ScribeMail.cpp ** AUTHOR: Matthew Allen ** DATE: 11/11/98 ** DESCRIPTION: Scribe Mail Object and UI ** ** Copyright (C) 1998-2003, Matthew Allen ** fret@memecode.com */ #include #include #include #include #include #include #include "Scribe.h" #include "ScribePageSetup.h" #include "lgi/common/NetTools.h" #include "lgi/common/Popup.h" #include "lgi/common/ColourSelect.h" #include "lgi/common/TextView3.h" #include "lgi/common/Html.h" #include "lgi/common/Combo.h" #include "lgi/common/Edit.h" #include "lgi/common/Button.h" #include "lgi/common/TextLabel.h" #include "lgi/common/CheckBox.h" #include "lgi/common/TabView.h" #include "lgi/common/Input.h" #include "lgi/common/ClipBoard.h" #include "lgi/common/TableLayout.h" #include "lgi/common/DisplayString.h" #include "lgi/common/ThreadEvent.h" #include "lgi/common/GdcTools.h" #include "lgi/common/Charset.h" #include "../src/common/Coding/ScriptingPriv.h" #include "PrintPreview.h" #include "ScribeListAddr.h" #include "PrintContext.h" #include "lgi/common/LgiRes.h" #include "Encryption/GnuPG.h" #include "ObjectInspector.h" #include "lgi/common/EventTargetThread.h" #include "Store3Common.h" #include "Tables.h" #include "Calendar.h" #include "CalendarView.h" #include "AddressSelect.h" #include "Store3Imap/ScribeImap.h" #include "lgi/common/TextConvert.h" #include "lgi/common/FileSelect.h" #include "resdefs.h" #include "resource.h" #include "lgi/common/Printer.h" #include "lgi/common/SubProcess.h" #define SAVE_HEADERS 0 static char MsgIdEncodeChars[] = "%/"; static char ScribeReplyClass[] = "scribe_reply"; static char ScribeReplyStyles[] = "margin-left: 0.5em;\n" "padding-left: 0.5em;\n" "border-left: 1px solid #ccc;"; char DefaultTextReplyTemplate[] = { "---------- Original Message ----------\n" "To: <>\n" "From: <>\n" "Subject: \n" "Date: \n" "\n" "\n" "\n" "\n" }; char DefaultHtmlReplyTemplate[] = { "\n" "\n" "\n" "\n" "\n" "

---------- Original Message ----------
\n" " To: <?mail.tohtml?>
\n" " From: <?mail.fromhtml?>
\n" " Subject: <?mail.subject?>
\n" " Date: <?mail.datesent?>
\n" "
\n" " <?mail.bodyastext quote=scribe.quote?>
\n" " <?cursor?>
\n" " <?mail.sig[html]?>

\n" "\n" "\n" }; class DepthCheck { int &i; public: constexpr static int MaxDepth = 5; DepthCheck(int &val) : i(val) { i++; if (!*this) LAssert(!"Recursion?"); } ~DepthCheck() { i--; } operator bool() const { return i < MaxDepth; } }; void CollectAttachments(LArray *Attachments, LArray *Related, LDataI **Text, LDataI **Html, LDataPropI *d, Store3MimeType *ParentMime = NULL) { if (!d) return; Store3MimeType Mt(d->GetStr(FIELD_MIME_TYPE)); auto FileName = d->GetStr(FIELD_NAME); if (!Mt) Mt = "text/plain"; // printf("Collect %s %s\n", (char*)Mt, FileName); LDataI *Att = dynamic_cast(d); if (ParentMime && Att && ParentMime->IsRelated() && Related) { auto Id = d->GetStr(FIELD_CONTENT_ID); if (ValidStr(Id)) { if (Related) Related->Add(Att); Att = NULL; } else if (Mt.IsHtml() && Html) { *Html = Att; Att = NULL; } } if (Att) { if (ValidStr(FileName)) { if (Attachments) Attachments->Add(Att); } else if (Mt.IsHtml()) { if (Html) *Html = Att; } else if (Mt.IsPlainText()) { if (Text) *Text = Att; } else if (!Mt.IsMultipart()) { if (Attachments) Attachments->Add(Att); } /* if (d->GetInt(FIELD_SIZE) < (512 << 10)) a.Add(dynamic_cast(d)); */ } GDataIt It = d->GetList(FIELD_MIME_SEG); if (It) { for (LDataPropI *i=It->First(); i; i=It->Next()) CollectAttachments(Attachments, Related, Text, Html, i, Mt.IsMultipart() ? &Mt : NULL); } } void RemoveReturns(char *s) { // Delete out the '\r' chars. char *In = s; char *Out = s; while (*In) { if (*In != '\r') { *Out++ = *In; } In++; } *Out++ = 0; } class XmlSaveStyles : public LXmlTree { void OnParseComment(LXmlTag *Ref, const char *Comment, ssize_t Bytes) { if (Ref && Ref->IsTag("style")) { Ref->SetContent(Comment, Bytes); } } public: XmlSaveStyles(int flags) : LXmlTree(flags) { } }; bool ExtractHtmlContent(LString &OutHtml, LString &Charset, LString &Styles, const char *InHtml) { if (!InHtml) return false; XmlSaveStyles t(GXT_NO_ENTITIES | GXT_NO_DOM | GXT_NO_HEADER); LXmlTag r; LMemStream mem(InHtml, strlen(InHtml), false); if (!t.Read(&r, &mem)) return false; bool InHead = false; LStringPipe Style; r.Children.SetFixedLength(false); for (auto It = r.Children.begin(); It != r.Children.end(); ) { LXmlTag *c = *It; if (c->IsTag("style")) { if (ValidStr(c->GetContent())) Style.Print("%s\n", c->GetContent()); c->Parent = NULL; r.Children.Delete(It); DeleteObj(c); } else if (c->IsTag("/head")) { InHead = false; c->Parent = NULL; r.Children.Delete(It); DeleteObj(c); } else if (c->IsTag("body")) { // We remove this tag, but KEEP the content... if any if (ValidStr(c->GetContent())) { c->SetTag(NULL); It++; } else { // No content, remove entirely. c->Parent = NULL; r.Children.Delete(It); DeleteObj(c); } } else if (InHead || c->IsTag("html") || c->IsTag("/html") || c->IsTag("/body") || c->IsTag("/style")) { c->Parent = NULL; r.Children.Delete(It); DeleteObj(c); } else if (c->IsTag("head")) { InHead = true; c->Parent = NULL; r.Children.Delete(It); DeleteObj(c); } else It++; } LStringPipe p; t.Write(&r, &p); OutHtml = p.NewGStr(); Styles = Style.NewGStr(); #if 0 LgiTrace("InHtml=%s\n", InHtml); LgiTrace("OutHtml=%s\n", OutHtml.Get()); LgiTrace("Styles=%s\n", Styles.Get()); #endif return true; } ////////////////////////////////////////////////////////////////////////////// char MailToStr[] = "mailto:"; char SubjectStr[] = "subject="; char ContentTypeDefault[] = "Content-type: text/plain; charset=us-ascii"; extern LString HtmlToText(const char *Html, const char *InitialCharSet); extern LString TextToHtml(const char *Txt, const char *Charset); class ImageResizeThread : public LEventTargetThread { LOptionsFile *Opts; public: class Job { #ifdef __GTK_H__ /* This object may not exist when the worker is finished. However LAppInst->PostEvent can handle that so we'll allow it, so long as it's never used in the Sink->PostEvent form. */ LViewI *Sink; #else OsView Sink; #endif public: LString FileName; LAutoStreamI Data; void SetSink(LViewI *v) { #if !LGI_VIEW_HANDLE Sink = v; #else Sink = v->Handle(); #endif } bool PostEvent(int Msg, LMessage::Param a = 0, LMessage::Param b = 0) { #ifdef __GTK_H__ return LAppInst->PostEvent(Sink, Msg, a, b); #else return LPostEvent(Sink, Msg, a, b); #endif } }; ImageResizeThread(LOptionsFile *opts) : LEventTargetThread("ImageResize") { Opts = opts; } void Resize(LAutoPtr &Job) { LVariant Qual = 80, Px = 1024, SizeLimit = 200, v; Opts->GetValue(OPT_ResizeJpegQual, Qual); Opts->GetValue(OPT_ResizeMaxPx, Px); Opts->GetValue(OPT_ResizeMaxKb, SizeLimit); LAutoStreamI Input = Job->Data; LAutoStreamI MemBuf(new GMemFile(4 << 10)); int64 FileSize = Input->GetSize(); LStream *sImg = dynamic_cast(Input.Get()); LAutoPtr Img(GdcD->Load(sImg, Job->FileName)); if (Img) { int iPx = Px.CastInt32(); int iKb = SizeLimit.CastInt32(); if (Img->X() > iPx || Img->Y() > iPx || FileSize >= iKb << 10) { // Create a JPEG filter auto Jpeg = LFilterFactory::New(".jpg", FILTER_CAP_WRITE, NULL); if (Jpeg) { // Re-sample the image... double XScale = (double) Img->X() / iPx; double YScale = (double) Img->Y() / iPx; // double Aspect = (double) Img->X() / Img->Y(); double Scale = XScale > YScale ? XScale : YScale; if (Scale > 1.0) { int Nx = (int)(Img->X() / Scale + 0.001); int Ny = (int)(Img->Y() / Scale + 0.001); LAutoPtr ResizedImg(new LMemDC(Nx, Ny, Img->GetColourSpace())); if (ResizedImg) { if (ResampleDC(ResizedImg, Img)) { Img = ResizedImg; } } } // Compress the image.. LXmlTag Props; Props.SetValue(LGI_FILTER_QUALITY, Qual); Props.SetValue(LGI_FILTER_SUBSAMPLE, v = 1); // 2x2 Jpeg->Props = &Props; if (Jpeg->WriteImage(dynamic_cast(MemBuf.Get()), Img) == LFilter::IoSuccess) { Job->Data = MemBuf; } } } } Job->PostEvent(M_RESIZE_IMAGE, (LMessage::Param)Job.Get()); Job.Release(); // Do this after the post event... so the deref doesn't crash. } LMessage::Result OnEvent(LMessage *Msg) { switch (Msg->Msg()) { case M_RESIZE_IMAGE: { LAutoPtr j((Job*)Msg->A()); if (j) Resize(j); break; } } return 0; } }; #include "lgi/common/RichTextEdit.h" #include "../src/common/Widgets/Editor/RichTextEditPriv.h" class MailRendererScript : public LThread, public LCancel, public LDom { Mail *m; LScriptCallback *cb; public: MailRendererScript(Mail *ml, LScriptCallback *script) : m(ml), cb(script), LThread("MailRendererScript") { Run(); } ~MailRendererScript() { Cancel(true); while (!IsExited()) LSleep(1); } int Main() { LVirtualMachine Vm; LScriptArguments Args(&Vm); Args.New() = new LVariant(m->App); Args.New() = new LVariant(m); Args.New() = new LVariant((LDom*)this); m->App->ExecuteScriptCallback(*cb, Args); Args.DeleteObjects(); return 0; } bool CallMethod(const char *MethodName, LVariant *Ret, LArray &Args) { ScribeDomType Method = StrToDom(MethodName); *Ret = false; switch (Method) { case SdGetTemp: { *Ret = ScribeTempPath(); break; } case SdExecute: { *Ret = false; if (Args.Length() >= 3) { auto Dir = Args[0]->Str(); auto Exe = Args[1]->Str(); auto Arg = Args[2]->Str(); if (Dir && Exe && Arg) { LSubProcess p(Exe, Arg); p.SetInitFolder(Dir); if (p.Start()) { char Buf[256]; LStringPipe Out; Out.Print("%s %s\n", Exe, Arg); ssize_t Rd; while (p.IsRunning() && !IsCancelled()) { Rd = p.Read(Buf, sizeof(Buf)); if (Rd < 0) break; if (Rd == 0) LSleep(1); else Out.Write(Buf, Rd); } while (!IsCancelled() && (Rd = p.Read(Buf, sizeof(Buf))) > 0) Out.Write(Buf, Rd); // LgiTrace("%s:%i - Process:\n%s\n", _FL, Out.NewGStr().Get()); if (p.IsRunning()) p.Kill(); else *Ret = true; } } } break; } case SdSetHtml: { if (!IsCancelled() && Args.Length() > 0) { LView *Ui = m->GetUI(); if (!Ui) { // Maybe hasn't finished opening the UI? LSleep(100); Ui = m->GetUI(); } if (!Ui) Ui = m->App; if (Ui) Ui->PostEvent(M_SET_HTML, (LMessage::Param)new LString(Args[0]->Str())); } break; } default: LAssert(!"Unsupported call."); return false; } return true; } }; class MailPrivate { LAutoPtr Resizer; Mail *m; public: struct HtmlBody { LString Html, Charset, Styles; }; LAutoPtr Body; LAutoPtr Renderer; LString MsgIdCache; LString DomainCache; int InSetFlags = 0; int InSetFlagsCache = 0; MailPrivate(Mail *mail) { m = mail; } void OnSave() { Resizer.Reset(); } HtmlBody *GetBody() { if (!Body && !Body.Reset(new HtmlBody)) return NULL; if (ValidStr(m->GetHtml())) { ExtractHtmlContent( Body->Html, Body->Charset, Body->Styles, m->GetHtml()); } else if (ValidStr(m->GetBody())) { Body->Charset = m->GetCharSet(); Body->Html = TextToHtml(m->GetBody(), Body->Charset); } return Body; } bool AddResizeImage(Attachment *File) { if (!File || !m->GetUI()) return false; if (!Resizer) Resizer.Reset(new ImageResizeThread(m->App->GetOptions())); if (!Resizer) return false; LAutoStreamI Obj = File->GetObject()->GetStream(_FL); if (!Obj) return false; ImageResizeThread::Job *j = new ImageResizeThread::Job; if (!j) return false; // The user interface will handle the response... j->SetSink(m->GetUI()); j->FileName = File->GetName(); // Make a complete copy of the stream... j->Data.Reset(new LMemStream(Obj, 0, -1)); // Post the work over to the thread... Resizer->PostEvent(M_RESIZE_IMAGE, (LMessage::Param)j); // Mark the image resizing File->SetIsResizing(true); return true; } }; bool Mail::ResizeImage(Attachment *a) { return d->AddResizeImage(a); } AttachmentList::AttachmentList(int id, int x, int y, int cx, int cy, MailUi *ui) : LList(id, x, y, cx, cy, 0) { Ui = ui; } AttachmentList::~AttachmentList() { RemoveAll(); } void AttachmentList::OnItemClick(LListItem *Item, LMouse &m) { LList::OnItemClick(Item, m); if (!Item && m.IsContextMenu() && Ui) { LSubMenu RClick; RClick.AppendItem(LLoadString(IDS_ATTACH_FILE), IDM_OPEN, true); switch (RClick.Float(this, m)) { case IDM_OPEN: { Ui->PostEvent(M_COMMAND, IDM_ATTACH_FILE, 0); break; } } } } bool AttachmentList::OnKey(LKey &k) { if (k.vkey == LK_DELETE) { if (k.Down()) { List s; if (GetSelection(s)) { Attachment *a = dynamic_cast(s[0]); if (a) { a->OnDeleteAttachment(this, true); } } } return true; } return LList::OnKey(k); } ////////////////////////////////////////////////////////////////////////////// int Strnlen(const char *s, int n) { int i = 0; if (s) { if (n < 0) { while (*s++) { i++; } } else { while (*s++ && n-- > 0) { i++; } } } return i; } ////////////////////////////////////////////////////////////////////////////// MailContainer::~MailContainer() { MailContainerIter *i; while ((i = Iters[0])) { Iters.Delete(i); if (i->Container) { i->Container = 0; } } } MailContainerIter::MailContainerIter() { Container = 0; } MailContainerIter::~MailContainerIter() { if (Container) { Container->Iters.Delete(this); } } void MailContainerIter::SetContainer(MailContainer *c) { Container = c; if (Container) { Container->Iters.Insert(this); } } ////////////////////////////////////////////////////////////////////////////// uint32_t MarkColours32[IDM_MARK_MAX] = { Rgb32(255, 0, 0), // red Rgb32(255, 166, 0), // orange Rgb32(255, 222, 0), // yellow Rgb32(0, 0xa0, 0), // green Rgb32(0, 0xc0, 255),// cyan Rgb32(0, 0, 255), // blue Rgb32(192, 0, 255), // purple Rgb32(0, 0, 0) // black }; ItemFieldDef MailFieldDefs[] = { {"To", SdTo, GV_STRING, FIELD_TO}, {"From", SdFrom, GV_STRING, FIELD_FROM}, {"Subject", SdSubject, GV_STRING, FIELD_SUBJECT}, {"Size", SdSize, GV_INT64, FIELD_SIZE}, {"Received Date", SdReceivedDate, GV_DATETIME, FIELD_DATE_RECEIVED}, {"Send Date", SdSendDate, GV_DATETIME, FIELD_DATE_SENT}, {"Body", SdBody, GV_STRING, FIELD_TEXT}, {"Internet Header", SdInternetHeader, GV_STRING, FIELD_INTERNET_HEADER, IDC_INTERNET_HEADER}, {"Message ID", SdMessageID, GV_STRING, FIELD_MESSAGE_ID}, {"Priority", SdPriority, GV_INT32, FIELD_PRIORITY}, {"Flags", SdFlags, GV_INT32, FIELD_FLAGS}, {"Html", SdHtml, GV_STRING, FIELD_ALTERNATE_HTML}, {"Label", SdLabel, GV_STRING, FIELD_LABEL}, {"From Contact", SdContact, GV_STRING, FIELD_FROM_CONTACT_NAME}, {"File", SdFile, GV_STRING, FIELD_CACHE_FILENAME}, {"ImapFlags", SdImapFlags, GV_STRING, FIELD_CACHE_FLAGS}, {"ImapSeq", SdFile, GV_INT32, FIELD_IMAP_SEQ}, {"ImapUid", SdImapFlags, GV_INT32, FIELD_SERVER_UID}, {"ReceivedDomain", SdReceivedDomain, GV_STRING, FIELD_RECEIVED_DOMAIN}, {"MessageId", SdMessageId, GV_STRING, FIELD_MESSAGE_ID}, {0} }; ////////////////////////////////////////////////////////////////////////////// class LIdentityItem : public LListItem { ScribeWnd *App; ScribeAccount *Acc; char *Txt; public: LIdentityItem(ScribeWnd *app, ScribeAccount *acc) { App = app; Acc = acc; Txt = 0; LVariant e, n; if (Acc) { n = Acc->Identity.Name(); e = Acc->Identity.Email(); } else { LAssert(!"No account specified"); } if (e.Str() && n.Str()) { char t[256]; sprintf_s(t, sizeof(t), "%s <%s>", n.Str(), e.Str()); Txt = NewStr(t); } else if (e.Str()) { Txt = NewStr(e.Str()); } else if (n.Str()) { Txt = NewStr(n.Str()); } else { Txt = NewStr("(error)"); } } ~LIdentityItem() { DeleteArray(Txt); } ScribeAccount *GetAccount() { return Acc; } const char *GetText(int i) { switch (i) { case 0: { return Txt; break; } } return 0; } }; class LIdentityDropDrop : public LPopup { ScribeWnd *App; Mail *Email; LList *Lst; public: LIdentityDropDrop(ScribeWnd *app, Mail *mail, LView *owner) : LPopup(owner) { App = app; Email = mail; LRect r(0, 0, 300, 100); SetPos(r); Children.Insert(Lst = new LList(IDC_LIST, 2, 2, X()-4, Y()-4)); if (Lst) { Lst->SetParent(this); Lst->AddColumn("Identity", Lst->GetClient().X()); Lst->MultiSelect(false); if (App) { Lst->Insert(new LIdentityItem(App, 0)); for (auto a : *App->GetAccounts()) { if (a->Identity.Name().Str()) { Lst->Insert(new LIdentityItem(App, a)); } } /* for (LListItem *i = List->First(); i; i = List->Next()) { LIdentityItem *Item = dynamic_cast(i); if (Item) { char *IdEmail = a->Send.IdentityEmail(); if (Email && IdEmail && Email->From->Addr && _stricmp(Email->From->Addr, IdEmail) == 0) { Item->Select(true); } } } */ } } } void OnPaint(LSurface *pDC) { LRect r(GetClient()); LWideBorder(pDC, r, DefaultRaisedEdge); } int OnNotify(LViewI *c, LNotification n) { switch (c->GetId()) { case IDC_LIST: { if (n.Type == LNotifyItemClick) { Visible(false); LIdentityItem *NewFrom = dynamic_cast(Lst->GetSelected()); if (Email && NewFrom) { // ScribeAccount *a = NewFrom->GetAccount(); if (Email->GetUI()) { LList *FromList; if (GetViewById(IDC_FROM, FromList)) { // Change item data /* FIXME DeleteArray(Email->From->Name); DeleteArray(Email->From->Addr); DeleteArray(Email->Reply->Name); DeleteArray(Email->Reply->Addr); if (a) { Email->From->Addr = NewStr(a->Send.IdentityEmail().Str()); Email->From->Name = NewStr(a->Send.IdentityName().Str()); Email->Reply->Addr = NewStr(a->Send.IdentityReplyTo().Str()); if (Email->Reply->Addr) { Email->Reply->Name = NewStr(Email->From->Name); } } else { LVariant e, n, r; App->GetOptions()->GetValue(OPT_EmailAddr, e); App->GetOptions()->GetValue(OPT_UserName, n); App->GetOptions()->GetValue(OPT_ReplyToEmail, n); Email->From->Addr = NewStr(e.Str()); Email->From->Name = NewStr(n.Str()); Email->Reply->Addr = NewStr(r.Str()); if (Email->Reply->Addr) { Email->Reply->Name = NewStr(Email->From->Name); } } // Change UI FromList->Empty(); LDataPropI *na = new LDataPropI(Email->From); if (na) { na->CC = MAIL_ADDR_FROM; FromList->Insert(na); } */ } } } } break; } } return 0; } }; ////////////////////////////////////////////////////////////////////////////// LSubMenu* BuildMarkMenu( LSubMenu *MarkMenu, MarkedState MarkState, uint32_t SelectedMark, bool None, bool All, bool Select) { int SelectedIndex = -1; // Build image list LImageList *ImgLst = new LImageList(16, 16); if (ImgLst && ImgLst->Create(16 * CountOf(MarkColours32), 16, System32BitColourSpace)) { // ImgLst->Colour(1); ImgLst->Colour(L_MED); ImgLst->Rectangle(); for (int i=0; iColour(L_LOW); ImgLst->Box(i*16+1, 0, i*16+15, 14); SelectedIndex = i; } ImgLst->Colour(MarkColours32[i], 32); ImgLst->Rectangle(i*16+3, 2, i*16+13, 12); } } // Build Submenu if (MarkMenu && ImgLst) { ImgLst->Update(-1); MarkMenu->SetImageList(ImgLst); LMenuItem *Item = NULL; if (None) { Item = MarkMenu->AppendItem(LLoadString(IDS_NONE), Select ? IDM_SELECT_NONE : IDM_UNMARK, MarkState != MS_None); } if (All) { Item = MarkMenu->AppendItem(LLoadString(IDS_ALL), Select ? IDM_SELECT_ALL : IDM_MARK_ALL, true); } if (Item) { MarkMenu->AppendSeparator(); } for (int i=0; iAppendItem(s, ((Select) ? IDM_MARK_SELECT_BASE : IDM_MARK_BASE) + i, (MarkState != 1) || (i != SelectedIndex)); if (Item) { Item->Icon(i); } } } return MarkMenu; } ////////////////////////////////////////////////////////////////////////////// char *WrapLines(char *Str, int Len, int WrapColumn) { if (Str && Len > 0 && WrapColumn > 0) { LMemQueue Temp; int LastWhite = -1; int StartLine = 0; int XPos = 0; int i; for (i=0; Str[i] && i= WrapColumn && Len > 0) { Temp.Write((uchar*) Str+StartLine, Len); Temp.Write((uchar*) "\n", 1); XPos = 0; StartLine = StartLine + Len + 1; LastWhite = -1; } else { LastWhite = i; XPos++; } } else if (Str[i] == '\t') { XPos = ((XPos + 7) / 8) * 8; } else if (Str[i] == '\n') { Temp.Write((uchar*) Str+StartLine, i - StartLine + 1); XPos = 0; StartLine = i+1; } else { XPos++; } } Temp.Write((uchar*) Str+StartLine, i - StartLine + 1); int WrapLen = (int)Temp.GetSize(); char *Wrapped = new char[WrapLen+1]; if (Wrapped) { Temp.Read((uchar*) Wrapped, WrapLen); Wrapped[WrapLen] = 0; return Wrapped; } } return Str; } char *DeHtml(const char *Str) { char *r = 0; if (Str) { LMemQueue Buf; char Buffer[256]; auto s = Str; while (s && *s) { // Search for start of next tag const char *Start = s; const char *End = Start; for (; *End && *End != '<'; End++); // Push pre-tag data onto pipe size_t Len = End-Start; for (size_t i=0; i, ItemFieldDef*> Lut(256); if (Lut.Length() == 0) { for (ItemFieldDef **l=FieldLists; *l; l++) { for (ItemFieldDef *i = *l; i->FieldId; i++) { if (i->Option) Lut.Add(i->Option, i); } } } return (ItemFieldDef*) Lut.Find(Name); } return 0; } static bool FieldLutInit = false; static LArray IdToFieldLut; ItemFieldDef *GetFieldDefById(int Id) { if (!FieldLutInit) { FieldLutInit = true; for (ItemFieldDef **l=FieldLists; *l; l++) { for (ItemFieldDef *i = *l; i->FieldId; i++) { IdToFieldLut[i->FieldId] = i; } } } if (Id >= 0 && Id < (int)IdToFieldLut.Length()) return IdToFieldLut[Id]; return 0; } ////////////////////////////////////////////////////////////////////////////// void Log(char *File, char *Str, ...) { #if defined WIN32 const char *DefFile = "c:\\temp\\list.txt"; #else const char *DefFile = "/home/list.txt"; #endif if (Str) { LFile f; if (f.Open((File) ? File : DefFile, O_WRITE)) { char Buf[1024]; va_list Arg; va_start(Arg, Str); vsprintf_s(Buf, sizeof(Buf), Str, Arg); va_end(Arg); f.Seek(0, SEEK_END); f.Write(Buf, (int)strlen(Buf)); } } else { LFile f; if (f.Open((File) ? File : DefFile, O_WRITE)) { f.SetSize(0); } } } char *NewPropStr(LOptionsFile *Options, char *Name) { LVariant n; if (Options->GetValue(Name, n) && n.Str() && strlen(n.Str()) > 0) { return NewStr(n.Str()); } return 0; } ////////////////////////////////////////////////////////////////////////////// // Columns of controls #define MAILUI_Y 0 #define IDM_REMOVE_GRTH 1000 #define IDM_REMOVE_GRTH_SP 1001 #define IDM_REMOVE_HTML 1002 #define IDM_CONVERT_B64_TO_BIN 1003 #define IDM_CONVERT_BIN_TO_B64 1004 #define RECIP_SX 500 #define ADD_X (RECIP_X + RECIP_SX + 10) #ifdef MAC #define ADD_RECIP_BTN_X 36 #else #define ADD_RECIP_BTN_X 20 #endif #define CONTENT_BORDER 3 #if defined WIN32 #define DLG_X 15 #define DLG_Y 30 #else #define DLG_X 6 #define DLG_Y 6 #endif MailUi::MailUi(Mail *item, MailContainer *container) : ThingUi(item, LLoadString(IDS_MAIL_MESSAGE)), WorkingDlg(NULL), Sx(0), Sy(0), CmdAfterResize(NULL), MissingCaps(NULL), BtnPrev(NULL), BtnNext(NULL), BtnSend(NULL), BtnSave(NULL), BtnSaveClose(NULL), BtnAttach(NULL), BtnReply(NULL), BtnReplyAll(NULL), BtnForward(NULL), BtnBounce(NULL), GpgUi(NULL), ToPanel(NULL), Entry(NULL), Browse(NULL), SetTo(NULL), To(NULL), Remove(NULL), FromPanel(NULL), FromList(NULL), FromCbo(NULL), ReplyToPanel(NULL), ReplyToChk(NULL), ReplyToCbo(NULL), SubjectPanel(NULL), Subject(NULL), CalendarPanel(NULL), CalPanelStatus(NULL), Tab(NULL), TabText(NULL), TextView(NULL), TabHtml(NULL), HtmlView(NULL), TabAttachments(NULL), Attachments(NULL), TabHeader(NULL), Header(NULL) { // Init everything to 0 Container = container; if (!item || !item->App) { LAssert(!"Invalid ptrs"); return; } AddMode = MAIL_ADDR_TO; CurrentEditCtrl = -1; MetaFieldsDirty = IgnoreShowImgNotify = HtmlCtrlDirty = TextCtrlDirty = TextLoaded = HtmlLoaded = false; // This allows us to hook iconv conversion events LFontSystem::Inst()->Register(this); // This allows us to hook missing image library events GdcD->Register(this); // Read/Write access bool ReadOnly = !TestFlag(GetItem()->GetFlags(), MAIL_CREATED | MAIL_BOUNCE); int MinButY = 0; // Get position LRect r(150, 150, 800, 750); SetPos(r); int FontHeight = GetFont()->GetHeight(); LVariant v; LOptionsFile *Options = App ? App->GetOptions() : 0; if (Options) { if (Options->GetValue("MailUI.Pos", v)) { r.SetStr(v.Str()); } } SetPos(r); MoveSameScreen(App); Name(LLoadString(IDS_MAIL_MESSAGE)); #if WINNATIVE CreateClassW32("Scribe::MailUi", LoadIcon(LProcessInst(), MAKEINTRESOURCE(IDI_MAIL))); #endif bool IsCreated = TestFlag(GetItem()->GetFlags(), MAIL_CREATED); if (Attach(0)) { DropTarget(true); // Setup main toolbar Commands.Toolbar = App->LoadToolbar(this, App->GetResourceFile(ResToolbarFile), App->GetToolbarImgList()); if (Commands.Toolbar) { Commands.Toolbar->Raised(false); Commands.Toolbar->Attach(this); BtnSend = Commands.Toolbar->AppendButton(RemoveAmp(LLoadString(IDS_SEND)), IDM_SEND_MSG, TBT_PUSH, !ReadOnly, IMG_SEND); Commands.Toolbar->AppendSeparator(); BtnSave = Commands.Toolbar->AppendButton(RemoveAmp(LLoadString(IDS_SAVE)), IDM_SAVE, TBT_PUSH, !ReadOnly, IMG_SAVE); BtnSaveClose = Commands.Toolbar->AppendButton(RemoveAmp(LLoadString(IDS_SAVE_CLOSE)), IDM_SAVE_CLOSE, TBT_PUSH, !ReadOnly, IMG_SAVE_AND_CLOSE); Commands.Toolbar->AppendSeparator(); Commands.Toolbar->AppendButton(RemoveAmp(LLoadString(IDS_DELETE)), IDM_DELETE_MSG, TBT_PUSH, true, IMG_TRASH); Commands.Toolbar->AppendButton(RemoveAmp(LLoadString(IDS_SPAM)), IDM_DELETE_AS_SPAM, TBT_PUSH, true, IMG_DELETE_SPAM); BtnAttach = Commands.Toolbar->AppendButton(RemoveAmp(LLoadString(IDS_ATTACH_FILE)), IDM_ATTACH_FILE, TBT_PUSH, !ReadOnly, IMG_ATTACH_FILE); Commands.Toolbar->AppendSeparator(); BtnReply = Commands.Toolbar->AppendButton(RemoveAmp(LLoadString(IDS_REPLY)), IDM_REPLY, TBT_PUSH, ReadOnly, IMG_REPLY); BtnReplyAll = Commands.Toolbar->AppendButton(RemoveAmp(LLoadString(IDS_REPLYALL)), IDM_REPLY_ALL, TBT_PUSH, ReadOnly, IMG_REPLY_ALL); BtnForward = Commands.Toolbar->AppendButton(RemoveAmp(LLoadString(IDS_FORWARD)), IDM_FORWARD, TBT_PUSH, ReadOnly, IMG_FORWARD); BtnBounce = Commands.Toolbar->AppendButton(RemoveAmp(LLoadString(IDS_BOUNCE)), IDM_BOUNCE, TBT_PUSH, ReadOnly, IMG_BOUNCE); Commands.Toolbar->AppendSeparator(); BtnPrev = Commands.Toolbar->AppendButton(RemoveAmp(LLoadString(IDS_PREV_MSG)), IDM_PREV_MSG, TBT_PUSH, true, IMG_PREV_ITEM); BtnNext = Commands.Toolbar->AppendButton(RemoveAmp(LLoadString(IDS_NEXT_MSG)), IDM_NEXT_MSG, TBT_PUSH, true, IMG_NEXT_ITEM); Commands.Toolbar->AppendSeparator(); Commands.Toolbar->AppendButton(RemoveAmp(LLoadString(IDS_HIGH_PRIORITY)), IDM_HIGH_PRIORITY, TBT_TOGGLE, true, IMG_HIGH_PRIORITY); Commands.Toolbar->AppendButton(RemoveAmp(LLoadString(IDS_LOW_PRIORITY)), IDM_LOW_PRIORITY, TBT_TOGGLE, true, IMG_LOW_PRIORITY); Commands.Toolbar->AppendSeparator(); Commands.Toolbar->AppendButton(RemoveAmp(LLoadString(IDS_READ_RECEIPT)), IDM_READ_RECEIPT, TBT_TOGGLE, true, IMG_READ_RECEIPT); Commands.Toolbar->AppendSeparator(); Commands.Toolbar->AppendButton(RemoveAmp(LLoadString(IDS_PRINT)), IDM_PRINT, TBT_PUSH, true, IMG_PRINT); for (LViewI *w: Commands.Toolbar->IterateViews()) MinButY = MAX(MinButY, w->Y()); Commands.Toolbar->Customizable(App->GetOptions(), "MailWindowToolbar"); Commands.SetupCallbacks(App, this, GetItem(), LThingUiToolbar); } // Setup to/from panels LVariant AlwaysShowFrom; if (IsCreated) App->GetOptions()->GetValue(OPT_MailShowFrom, AlwaysShowFrom); bool ShowFrom = !IsCreated && !TestFlag(GetItem()->GetFlags(), MAIL_BOUNCE); LDisplayString Recip(LSysFont, LLoadString(IDS_RECIPIENTS)); LAutoPtr ReplyChk(new LCheckBox(IDC_USE_REPLY_TO, 21, #ifdef MAC 1, #else 6, #endif -1, -1, "Reply To")); int ReplyChkPx = ReplyChk ? ReplyChk->X() : 0; int RECIP_X = 20 + MAX(ReplyChkPx, Recip.X()) + 10; int EditHeight = FontHeight + 6; LVariant HideGpg; if (!item->App->GetOptions()->GetValue(OPT_HideGnuPG, HideGpg) || HideGpg.CastInt32() == 0) { GpgUi = new MailUiGpg( App, this, 21, RECIP_X, !ReadOnly && !TestFlag(GetItem()->GetFlags(), MAIL_SENT)); if (GpgUi) GpgUi->Attach(this); } ToPanel = new LPanel(LLoadString(IDS_TO), FontHeight * 8, !ShowFrom); if (ToPanel) { int Cy = 4; ToPanel->AddView(SetTo = new LCombo(IDC_SET_TO, 20, Cy, 60, EditHeight, 0)); if (SetTo) { SetTo->Insert(LLoadString(IDS_TO)); SetTo->Insert(LLoadString(IDS_CC)); SetTo->Insert(LLoadString(IDS_BCC)); if (SetTo->GetPos().x2 + 10 > RECIP_X) RECIP_X = SetTo->GetPos().x2 + 10; } ToPanel->AddView(Entry = new LEdit(IDC_ENTRY, RECIP_X, Cy, RECIP_SX, EditHeight, "")); Cy = SetTo->GetPos().y2 + 5; ToPanel->AddView(To = new AddressList(App, IDC_TO, RECIP_X, Cy, RECIP_SX, ToPanel->GetOpenSize() - Cy - 6)); if (To) To->SetImageList(App->GetIconImgList(), false); ToPanel->AddView(new LTextLabel(IDC_STATIC, 20, Cy, -1, -1, LLoadString(IDS_RECIPIENTS))); ToPanel->Raised(false); ToPanel->Attach(this); } FromPanel = new LPanel(LLoadString(IDS_FROM), FontHeight + 13, AlwaysShowFrom.CastInt32() || ShowFrom); if (FromPanel) { FromPanel->AddView(new LTextLabel(IDC_STATIC, 21, #ifdef MAC 8, #else 4, #endif -1, -1, LLoadString(IDS_FROM))); if (ShowFrom) { FromPanel->AddView(FromList = new AddressList(App, IDC_FROM, RECIP_X, 2, RECIP_SX, EditHeight)); } else { FromPanel->AddView(FromCbo = new LCombo(IDC_FROM, RECIP_X, 2, RECIP_SX, EditHeight, 0)); } if (IsCreated) { LCheckBox *Chk; auto Label = LLoadString(IDS_ALWAYS_SHOW); FromPanel->AddView(Chk = new LCheckBox(IDC_SHOW_FROM, ADD_X, #ifdef MAC 1, #else 6, #endif -1, -1, Label)); if (Chk) Chk->Value(AlwaysShowFrom.CastInt32()); } FromPanel->Raised(false); FromPanel->Attach(this); } ReplyToPanel = new LPanel("Reply To:", FontHeight + 13, false); if (ReplyToPanel) { ReplyToPanel->Raised(false); ReplyToPanel->AddView(ReplyToChk = ReplyChk.Release()); ReplyToPanel->AddView(ReplyToCbo = new LCombo(IDC_REPLY_TO_ADDR, RECIP_X, 2, RECIP_SX, EditHeight, 0)); ReplyToPanel->Attach(this); } SubjectPanel = new LPanel(LLoadString(IDS_SUBJECT), -(LSysFont->GetHeight() + 16)); if (SubjectPanel) { SubjectPanel->Raised(false); SubjectPanel->AddView( new LTextLabel(IDC_STATIC, 21, 8, -1, -1, LLoadString(IDS_SUBJECT))); SubjectPanel->AddView(Subject = new LEdit(IDC_SUBJECT, RECIP_X, 4, RECIP_SX, EditHeight, "")); SubjectPanel->Attach(this); } CalendarPanel = new LPanel(LLoadString(IDC_CALENDAR), -(LSysFont->GetHeight() + 16), false); if (CalendarPanel) { CalendarPanel->Raised(false); LTableLayout *t = new LTableLayout(IDC_TABLE); if (t) { t->GetCss(true)->Margin(LCss::Len(LCss::LenPx, LTableLayout::CellSpacing)); t->GetCss()->Width(LCss::LenAuto); t->GetCss()->Height(LCss::LenAuto); CalendarPanel->AddView(t); auto c = t->GetCell(0, 0); c->Width(LCss::Len(LCss::LenPx, RECIP_X - (LTableLayout::CellSpacing * 2))); c->PaddingLeft(LCss::Len(LCss::LenPx, 21 - LTableLayout::CellSpacing)); c->Debug = true; c->Add(new LTextLabel(IDC_STATIC, 0, 0, -1, -1, LLoadString(IDS_CALENDAR))); c = t->GetCell(1, 0); c->Add(new LButton(IDC_ADD_CAL_EVENT, 0, 0, -1, -1, LLoadString(IDS_ADD_CAL))); c = t->GetCell(2, 0); c->Add(new LButton(IDC_ADD_CAL_EVENT_POPUP, 0, 0, -1, -1, LLoadString(IDS_ADD_CAL_POPUP))); c = t->GetCell(3, 0); c->Add(CalPanelStatus = new LTextLabel(IDC_STATIC, 0, 0, -1, -1, NULL)); } CalendarPanel->Attach(this); } Tab = new LTabView(IDC_MAIL_UI_TABS, 0, 0, 1000, 1000, 0); if (Tab) { Tab->GetCss(true)->PaddingTop("3px"); // Don't attach text and html controls here, because OnLoad will do it later... TabText = Tab->Append(LLoadString(IDS_TEXT)); TabHtml = Tab->Append("HTML"); TabAttachments = Tab->Append(LLoadString(IDS_ATTACHMENTS)); TabHeader = Tab->Append(LLoadString(IDS_INTERNETHEADER)); if (TabAttachments) { TabAttachments->Append(Attachments = new AttachmentList(IDC_ATTACHMENTS, CONTENT_BORDER, CONTENT_BORDER, 200, 200, this)); if (Attachments) { Attachments->AddColumn(LLoadString(IDS_FILE_NAME), 160); Attachments->AddColumn(LLoadString(IDS_SIZE), 80); Attachments->AddColumn(LLoadString(IDS_MIME_TYPE), 100); Attachments->AddColumn(LLoadString(IDS_CONTENT_ID), 250); } } if (TabHeader) { TabHeader->Append(Header = new LTextView3( IDC_INTERNET_HEADER, CONTENT_BORDER, CONTENT_BORDER, 100, 20)); if (Header) { Header->Sunken(true); } } LTabPage *Fields = Tab->Append(LLoadString(IDS_FIELDS)); if (Fields) { Fields->LoadFromResource(IDD_MAIL_FIELDS); LTableLayout *l; if (GetViewById(IDC_TABLE, l)) { l->SetPourLargest(false); } } Tab->Attach(this); // Colours LColourSelect *Colour; if (GetViewById(IDC_COLOUR, Colour)) { LArray c32; for (int i=0; iSetColourList(&c32); } else LAssert(!"No colour control?"); } PourAll(); if (Commands.Toolbar) { if (ToPanel) ToPanel->SetClosedSize(Commands.Toolbar->Y()-1); if (FromPanel) FromPanel->SetClosedSize(Commands.Toolbar->Y()-1); if (ReplyToPanel) ReplyToPanel->SetClosedSize(Commands.Toolbar->Y()-1); } SetIcon("About64px.png"); OnLoad(); _Running = true; Visible(true); RegisterHook(this, LKeyEvents); LResources::StyleElement(this); } } MailUi::~MailUi() { DeleteObj(TextView); if (Attachments) { Attachments->RemoveAll(); } LOptionsFile *Options = (GetItem() && App) ? App->GetOptions() : 0; if (Options) { LRect p = GetPos(); if (p.x1 >= 0 && p.y1 >= 0) { LVariant v = p.GetStr(); Options->SetValue("MailUI.Pos", v); } } Tab = 0; if (GetItem()) { GetItem()->Ui = NULL; } else LgiTrace("%s:%i - Error: no item to clear UI ptr?\n", _FL); // We delete the LView here because objects // need to have their virtual tables intact _Delete(); } Mail *MailUi::GetItem() { return _Item ? _Item->IsMail() : 0; } void MailUi::SetItem(Mail *m) { if (_Item) { Mail *old = _Item->IsMail(); if (old) old->Ui = NULL; else LAssert(0); _Item = NULL; } if (m) { _Item = m; m->Ui = this; } } bool MailUi::SetDirty(bool Dirty, bool Ui) { bool b = ThingUi::SetDirty(Dirty, Ui); if (MetaFieldsDirty && !Dirty) { Mail *m = GetItem(); if (m) { LColourSelect *Colour; if (GetViewById(IDC_COLOUR, Colour)) { uint32_t Col = (uint32_t)Colour->Value(); m->SetMarkColour(Col); } else LgiTrace("%s:%i - Can't find IDC_COLOUR\n", _FL); auto s = GetCtrlName(IDC_LABEL); m->SetLabel(s); if (m->GetObject()->GetInt(FIELD_STORE_TYPE) != Store3Imap) // Imap knows to save itself. m->SetDirty(); m->Update(); } MetaFieldsDirty = false; } return b; } #define IDM_FILTER_BASE 2000 #define IDM_CHARSET_BASE 3000 void AddActions(LSubMenu *Menu, List &Filters, LArray Folders) { if (!Menu) return; auto StartLen = Menu->Length(); for (auto Folder: Folders) { for (ScribeFolder *f=Folder->GetChildFolder(); f; f=f->GetNextFolder()) { auto Sub = Menu->AppendSub(f->GetName(true)); if (Sub) { auto Item = Sub->GetParent(); if (Item) Item->Icon(ICON_CLOSED_FOLDER); Sub->SetImageList(Menu->GetImageList(), false); f->LoadThings(); AddActions(Sub, Filters, {f}); } } List a; Folder->LoadThings(); for (auto t: Folder->Items) { a.Insert(t->IsFilter()); } a.Sort(FilterCompare); for (auto i: a) { LVariant Name; if (i->GetVariant("Name", Name) && Name.Str()) { char n[256]; strcpy_s(n, sizeof(n), Name.Str()); for (char *s=n; *s; s++) { if (*s == '&') { memmove(s + 1, s, strlen(s)+1); s++; } } auto Item = Menu->AppendItem(n, (int) (IDM_FILTER_BASE + Filters.Length()), true); if (Item) { Item->Icon(ICON_FILTER); Filters.Insert(i); } } } } if (StartLen == Menu->Length()) { char s[64]; sprintf_s(s, sizeof(s), "(%s)", LLoadString(IDS_EMPTY)); Menu->AppendItem(s, 0, false); } } bool MailUi::OnViewKey(LView *v, LKey &k) { if (k.Down() && k.CtrlCmd() && !k.Alt()) { switch (k.vkey) { case LK_RETURN: { PostEvent(M_COMMAND, IDM_SEND_MSG); return true; } case LK_UP: { SeekMsg(-1); return true; } case LK_DOWN: { SeekMsg(1); return true; } } switch (k.c16) { case 'p': case 'P': { - App->ThingPrint(GetItem(), NULL, this); + App->ThingPrint(NULL, GetItem(), NULL, this); break; } case 'f': case 'F': { if (Tab->Value() == 0) { if (TextView) - TextView->DoFind(); + TextView->DoFind(NULL); } else if (Tab->Value() == 1) { if (HtmlView) - HtmlView->DoFind(); + HtmlView->DoFind(NULL); } break; } case 'r': case 'R': { OnCommand(IDM_REPLY, 0, NULL); return true; } case 'w': case 'W': { if (OnRequestClose(false)) Quit(); return true; } case 's': case 'S': { if (IsDirty()) { SetDirty(false); } return true; } case 't': case 'T': { Tab->Value(0); if (TextView) TextView->Focus(true); return true; } case 'h': case 'H': { Tab->Value(1); if (HtmlView) HtmlView->Focus(true); return true; } } } return ThingUi::OnViewKey(v, k); } void MailUi::SerializeText(bool FromCtrl) { if (GetItem() && TextView) { if (FromCtrl) { GetItem()->SetBody(TextView->Name()); } else { TextView->Name(GetItem()->GetBody()); } } } const char *FilePart(const char *Uri) { const char *Dir = Uri + strlen(Uri) - 1; while (Dir > Uri && Dir[-1] != '/' && Dir[-1] != '\\') { Dir--; } return Dir; } void MailUi::OnAttachmentsChange() { Attachments->ResizeColumnsToContent(); if (GetItem()) { TabAttachments->GetCss(true)->FontBold(GetItem()->Attachments.Length() > 0); TabAttachments->OnStyleChange(); } } bool MailUi::NeedsCapability(const char *Name, const char *Param) { if (!InThread()) { PostEvent(M_NEEDS_CAP, (LMessage::Param)NewStr(Name)); } else { if (!Name) return false; if (Caps.Find(Name)) return true; Caps.Add(Name, true); char msg[256]; LArray Actions; LAutoPtr Back; if (!_stricmp(Name, "RemoteContent")) { Actions.Add(LLoadString(IDS_ALWAYS_SHOW_REMOTE_CONTENT)); Actions.Add(LLoadString(IDS_SHOW_REMOTE_CONTENT)); Back.Reset(new LColour(L_LOW)); strcpy_s(msg, sizeof(msg), LLoadString ( IDS_REMOTE_CONTENT_MSG, "To protect your privacy Scribe has blocked the remote content in this message." )); } else { Actions.Add(LLoadString(IDS_INSTALL)); int ch = 0; for (auto k : Caps) ch += sprintf_s(msg+ch, sizeof(msg)-ch, "%s%s", ch?", ":"", k.key); ch += sprintf_s(msg+ch, sizeof(msg)-ch, " is required to display this content."); } if (!MissingCaps) { MissingCaps = new MissingCapsBar(this, &Caps, msg, App, Actions, Back); auto c = IterateViews(); auto Idx = c.IndexOf(GpgUi); AddView(MissingCaps, (int)Idx+1); AttachChildren(); OnPosChange(); } else { MissingCaps->SetMsg(msg); } } return true; } void MailUi::OnCloseInstaller() { if (MissingCaps) { DeleteObj(MissingCaps); PourAll(); } } void MailUi::OnInstall(LCapabilityTarget::CapsHash *Caps, bool Status) { } void MailUi::OnChildrenChanged(LViewI *Wnd, bool Attaching) { if (Wnd == (LViewI*)MissingCaps && !Attaching) { MissingCaps = NULL; PourAll(); } } LDocView *MailUi::GetDoc(const char *MimeType) { if (!MimeType) { MimeType = sTextPlain; LVariant v; if (App->GetOptions()->GetValue(OPT_DefaultAlternative, v) && v.CastInt32() > 0) MimeType = sTextHtml; } LAssert(MimeType != NULL); if (!_stricmp(MimeType, sTextPlain)) return TextView; else if (!_stricmp(MimeType, sTextHtml)) return HtmlView; else LAssert(!"Invalid mime type."); return NULL; } bool MailUi::SetDoc(LDocView *v, const char *MimeType) { if (!MimeType) { MimeType = sTextPlain; LVariant v; if (App->GetOptions()->GetValue(OPT_DefaultAlternative, v) && v.CastInt32() > 0) MimeType = sTextHtml; } LAssert(MimeType != NULL); if (!_stricmp(MimeType, sTextPlain)) { LTextView3 *Txt = static_cast(v); if (Txt) { if (Txt != TextView) { DeleteObj(TextView); TextView = Txt; if (TabText && !TextView->IsAttached()) TabText->Append(TextView); } TextLoaded = true; if (_Running) TabText->Select(); } else { LAssert(!"Invalid ctrl."); return false; } } else if (!_stricmp(MimeType, sTextHtml)) { LDocView *Html = dynamic_cast(v); if (Html) { if (Html != HtmlView) { DeleteObj(HtmlView); HtmlView = Html; LCapabilityClient *cc = dynamic_cast(v); if (cc) cc->Register(this); if (TabHtml && !HtmlView->IsAttached()) TabHtml->Append(HtmlView); } HtmlLoaded = true; if (_Running) TabHtml->Select(); } else { LAssert(!"Invalid ctrl."); return false; } } else { LAssert(!"Invalid mime type."); return false; } return true; } bool MailUi::IsWorking(int Set) { if (!GetItem()) return false; // Are any of the attachments busy doing something? LDataI *AttachPoint = GetItem() && Set >= 0 ? GetItem()->GetFileAttachPoint() : NULL; List Attachments; if (GetItem()->GetAttachments(&Attachments)) { // Attachment *Match = NULL; for (auto a: Attachments) { if (Set >= 0) { a->SetIsResizing(Set != 0); if (AttachPoint) { a->GetObject()->Save(AttachPoint); } } else if (a->GetIsResizing()) { return true; } } } // Check rich text control as well... auto Rte = dynamic_cast(HtmlView); if (Rte) { if (Rte->IsBusy()) { return true; } } return false; } class BusyPanel : public LPanel { public: BusyPanel() : LPanel("Working...", LSysFont->GetHeight() << 1) { LViewI *v; LCss::ColorDef Bk(LColour(255, 128, 0)); GetCss(true)->BackgroundColor(Bk); AddView(v = new LTextLabel(IDC_STATIC, 30, 5, -1, -1, "Still resizing images...")); v->GetCss(true)->BackgroundColor(Bk); AddView(v = new LButton(IDCANCEL, 200, 3, -1, -1, LLoadString(IDS_CANCEL))); v->GetCss(true)->NoPaintColor(Bk); } }; void MailUi::SetCmdAfterResize(int Cmd) { if (CmdAfterResize == 0) { CmdAfterResize = Cmd; if (!WorkingDlg) { WorkingDlg = new BusyPanel; if (WorkingDlg) { AddView(WorkingDlg, 1); AttachChildren(); OnPosChange(); } } } } bool MailUi::OnRequestClose(bool OsClose) { bool Working = IsWorking(); if (Working) { SetCmdAfterResize(IDM_SAVE_CLOSE); return false; } return ThingUi::OnRequestClose(OsClose); } void MailUi::OnChange() { if (!IsDirty()) OnLoad(); } struct MailUiNameAddr { LString Name, Addr; MailUiNameAddr(const char *name = 0, const char *addr = 0) { Name = name; Addr = addr; } }; void MailUi::OnLoad() { bool Edit = false; bool ReadOnly = true; Mail *Item = GetItem(); _Running = false; if (Item && Item->App) { Edit = TestFlag(Item->GetFlags(), MAIL_CREATED); ReadOnly = !TestFlag(Item->GetFlags(), MAIL_CREATED | MAIL_BOUNCE); if (Entry) { Entry->Name(""); } if (To) { To->OnInit(Item->GetTo()); } if (FromCbo) { LHashTbl, MailUiNameAddr*> ReplyToAddrs; const char *Template = "%s <%s>"; FromCbo->Empty(); int Idx = -1; LVariant DefName, DefAddr; // LOptionsFile *Opts = Item->Window->GetOptions(); for (auto a : *Item->App->GetAccounts()) { LVariant Name = a->Identity.Name(); LVariant Addr = a->Identity.Email(); LVariant ReplyTo = a->Identity.ReplyTo(); if (!a->IsValid() || a->Send.Disabled()) continue; if (ReplyTo.Str()) { if (!ReplyToAddrs.Find(ReplyTo.Str())) ReplyToAddrs.Add(ReplyTo.Str(), new MailUiNameAddr(Name.Str(), ReplyTo.Str())); } else if (Addr.Str()) { if (!ReplyToAddrs.Find(Addr.Str())) ReplyToAddrs.Add(Addr.Str(), new MailUiNameAddr(Name.Str(), Addr.Str())); } if (Name.Str() && Addr.Str()) { if (!DefAddr.Str() || _stricmp(DefAddr.Str(), Addr.Str())) { auto FromAddr = Item->GetFromStr(FIELD_EMAIL); if (FromAddr && _stricmp(Addr.Str(), FromAddr) == 0) { Idx = (int)FromCbo->Length(); } LString p; p.Printf(Template, Name.Str(), Addr.Str()); int Id = a->Receive.Id(); int CurLen = (int)FromCbo->Length(); LAssert(Id != 0); FromAccountId[CurLen] = Id; FromCbo->Insert(p); } } } if (Idx < 0) { auto FromName = Item->GetFromStr(FIELD_NAME); auto FromAddr = Item->GetFromStr(FIELD_EMAIL); if (FromAddr) { LStringPipe p; if (FromName) p.Print(Template, FromName, FromAddr); else p.Print("<%s>", FromAddr); LAutoString s(p.NewStr()); FromAccountId[FromCbo->Length()] = -1; Idx = (int)FromCbo->Length(); FromCbo->Insert(s); FromCbo->Value(Idx); } } else { FromCbo->Value(Idx); } if (ReplyToAddrs.Length() > 0 && ReplyToCbo) { auto CurAddr = Item->GetReply() ? Item->GetReply()->GetStr(FIELD_EMAIL) : NULL; int CurIdx = -1; // for (MailUiNameAddr *na = ReplyToAddrs.First(); na; na = ReplyToAddrs.Next()) for (auto na : ReplyToAddrs) { char s[256]; sprintf_s(s, sizeof(s), Template, na.value->Name.Get(), na.value->Addr.Get()); if (CurAddr && !_stricmp(na.value->Addr, CurAddr)) CurIdx = (int)ReplyToCbo->Length(); ReplyToCbo->Insert(s); } if (CurIdx >= 0) { ReplyToCbo->Value(CurIdx); ReplyToChk->Value(true); } else { ReplyToChk->Value(false); } } ReplyToAddrs.DeleteObjects(); } else if (FromList) { FromList->Empty(); ListAddr *na = new ListAddr(App, Item->GetFrom()); if (na) { na->CC = MAIL_ADDR_FROM; na->OnFind(); FromList->Insert(na); } } if (Subject) { Subject->Name(Item->GetSubject()); char Title[140]; auto Subj = Item->GetSubject(); if (Subj) sprintf_s(Title, sizeof(Title), "%s - %.100s", LLoadString(IDS_MAIL_MESSAGE), Subj); else sprintf_s(Title, sizeof(Title), "%s", LLoadString(IDS_MAIL_MESSAGE)); Title[sizeof(Title)-1] = 0; for (char *s = Title; *s; s++) if (*s == '\n' || *s == '\r') *s = ' '; Name(Title); } int64_t Rgb32 = Item->GetMarkColour(); SetCtrlValue(IDC_COLOUR, Rgb32 > 0 ? Rgb32 : -1); SetCtrlName(IDC_LABEL, Item->GetLabel()); char Date[256]; Item->GetDateReceived()->Get(Date, sizeof(Date)); SetCtrlName(IDC_RECEIVED_DATE, Date); Item->GetDateSent()->Get(Date, sizeof(Date)); SetCtrlName(IDC_SENT_DATE, Date); TextLoaded = false; HtmlLoaded = false; auto TextContent = Item->GetBody(); auto HtmlContent = Item->GetHtml(); Sx = Sy = -1; LDocView *DocView = NULL; if (TabText && (DocView = Item->CreateView(this, sTextPlain, true, -1, !Edit))) { if (DocView == HtmlView) { // CreateView converted the text to HTML to embed Emojis. If we have // actual HTML content it'll overwrite the text portion, so we need // to move the HTML control to the text tab to leave room for actual HTML. DeleteObj(TextView); TextView = HtmlView; HtmlView = NULL; TextView->Detach(); TabText->Append(TextView); TextLoaded = true; HtmlLoaded = false; } if (!TextView && Edit) { // What the? Force creation of control... LAssert(!"Must have an edit control."); LDocView *Dv = App->CreateTextControl(IDC_TEXT_VIEW, sTextPlain, Edit, GetItem()); if (Dv) SetDoc(Dv, sTextPlain); LAssert(TextView != NULL); } if (TextView) { TextView->Visible(true); // This needs to be below the resize of the control so that // any wrapping has already been done and thus the scroll // bar is laid out already. if (Item->Cursor > 0) TextView->SetCaret(Item->Cursor, false); TabText->GetCss(true)->FontBold(ValidStr(TextContent)); TabText->OnStyleChange(); } } bool ValidHtml = ValidStr(HtmlContent); if (TabHtml && Item->CreateView(this, sTextHtml, true, -1, !Edit) && HtmlView) { HtmlView->Visible(true); if (Item->Cursor > 0) HtmlView->SetCaret(Item->Cursor, false); TabHtml->GetCss(true)->FontBold(ValidHtml); TabHtml->OnStyleChange(); } LVariant DefTab; Item->App->GetOptions()->GetValue(Edit ? OPT_EditControl : OPT_DefaultAlternative, DefTab); CurrentEditCtrl = (Edit || ValidHtml) && DefTab.CastInt32(); Tab->Value(CurrentEditCtrl); HtmlCtrlDirty = !ValidHtml; TextCtrlDirty = !ValidStr(TextContent); if (CalendarPanel) { auto CalEvents = GetItem()->GetCalendarAttachments(); CalendarPanel->Open(CalEvents.Length() > 0); } OnPosChange(); if (Attachments) { Attachments->RemoveAll(); List Files; if (Item->GetAttachments(&Files)) { for (auto a: Files) Attachments->Insert(a); } OnAttachmentsChange(); } if (Header) { LAutoString Utf((char*)LNewConvertCp("utf-8", Item->GetInternetHeader(), "iso-8859-1")); Header->Name(Utf); Header->SetEnv(Item); } bool Update = (Item->GetFlags() & MAIL_READ) == 0 && (Item->GetFlags() & MAIL_CREATED) == 0; if (Update) { Item->SetFlags(Item->GetFlags() | MAIL_READ); } if (Commands.Toolbar) { int p = Item->GetPriority(); Commands.Toolbar->SetCtrlValue(IDM_HIGH_PRIORITY, p < MAIL_PRIORITY_NORMAL); Commands.Toolbar->SetCtrlValue(IDM_LOW_PRIORITY, p > MAIL_PRIORITY_NORMAL); Commands.Toolbar->SetCtrlValue(IDM_READ_RECEIPT, TestFlag(Item->GetFlags(), MAIL_READ_RECEIPT)); } } if (Item->GetFlags() & (MAIL_CREATED | MAIL_BOUNCE)) { if (Entry) Entry->Focus(true); } else { if (TextView) TextView->Focus(true); } if (BtnPrev && BtnNext) { if (Item && Container) { /* int Items = Item->GetList()->Length(); int i = Item->GetList()->IndexOf(Item); */ auto Items = Container->Length(); auto i = Container->IndexOf(Item); BtnPrev->Enabled(i < (ssize_t)Items - 1); BtnNext->Enabled(i > 0); } else { BtnPrev->Enabled(false); BtnNext->Enabled(false); } } if (BtnSend) BtnSend->Enabled(!ReadOnly); if (BtnSave) BtnSave->Enabled(!ReadOnly); if (BtnSaveClose) BtnSaveClose->Enabled(!ReadOnly); if (BtnAttach) BtnAttach->Enabled(!ReadOnly); if (BtnReply) BtnReply->Enabled(ReadOnly); if (BtnReplyAll) BtnReplyAll->Enabled(ReadOnly); if (BtnForward) BtnForward->Enabled(true); if (BtnBounce) BtnBounce->Enabled(ReadOnly); if (Commands.Toolbar) { Commands.Toolbar->SetCtrlEnabled(IDM_HIGH_PRIORITY, Edit); Commands.Toolbar->SetCtrlEnabled(IDM_LOW_PRIORITY, Edit); Commands.Toolbar->SetCtrlEnabled(IDM_READ_RECEIPT, Edit); } _Running = true; } void MailUi::OnSave() { if (!GetItem()) return; if (GetItem()->GetFlags() & MAIL_SENT) { // Save a copy instead of over writing the original sent email Mail *Copy = new Mail(App, GetItem()->GetObject()->GetStore()->Create(MAGIC_MAIL)); if (Copy) { *Copy = *_Item; Copy->SetFlags(MAIL_READ | MAIL_CREATED, true); Copy->SetFolder(GetItem()->GetFolder()); Copy->SetDateSent(0); SetItem(Copy); } } Mail *Item = GetItem(); if (To) { To->OnSave(Item->GetObject()->GetStore(), Item->GetTo()); } LMailStore *AccountMailStore = NULL; if (FromCbo && Item->GetFrom() && FromAccountId.Length() > 0) { int64 CboVal = FromCbo->Value(); LAssert(CboVal < (ssize_t)FromAccountId.Length()); int AccountId = FromAccountId[(int)CboVal]; LDataPropI *Frm = Item->GetFrom(); if (AccountId < 0) { // From is a literal address, not an account ID. This can happen when bouncing email. Mailto mt(App, FromCbo->Name()); if (mt.To.Length() == 1) { AddressDescriptor *a = mt.To[0]; if (a) { Frm->SetStr(FIELD_NAME, a->sName); Frm->SetStr(FIELD_EMAIL, a->sAddr); } } else LAssert(0); } else if (AccountId > 0) { ScribeAccount *a = Item->App->GetAccountById(AccountId); if (a) { Frm->SetStr(FIELD_NAME, a->Identity.Name().Str()); Frm->SetStr(FIELD_EMAIL, a->Identity.Email().Str()); } else LAssert(!"From account missing."); // Find the associated mail store for this account. Hopefully we can put any new // mail into the mail store that the account is using. // // Check for IMAP mail store? AccountMailStore = a->Receive.GetMailStore(); if (!AccountMailStore) { // Nope... what about a receive path? LVariant DestFolder = a->Receive.DestinationFolder(); if (ValidStr(DestFolder.Str())) { AccountMailStore = Item->App->GetMailStoreForPath(DestFolder.Str()); } } } else LAssert(!"No account id."); } LDataPropI *ReplyObj = Item->GetReply(); if (ReplyToCbo != NULL && ReplyObj != NULL && ReplyToChk != NULL && ReplyToChk->Value()) { Mailto mt(App, ReplyToCbo->Name()); if (mt.To.Length() == 1) { AddressDescriptor *a = mt.To[0]; if (a && a->sAddr) { if (a->sName) ReplyObj->SetStr(FIELD_NAME, a->sName); ReplyObj->SetStr(FIELD_EMAIL, a->sAddr); } } } Item->SetSubject(Subject->Name()); Item->SetLabel(GetCtrlName(IDC_LABEL)); auto c32 = GetCtrlValue(IDC_COLOUR); Item->SetMarkColour(c32); LDocView *Ctrl = CurrentEditCtrl ? HtmlView : TextView; if (Ctrl) { // Delete all existing data... Item->SetBody(0); Item->SetBodyCharset(0); Item->SetHtml(0); Item->SetHtmlCharset(0); const char *MimeType = Ctrl->GetMimeType(); const char *Charset = Ctrl->GetCharset(); if (!_stricmp(MimeType, sTextHtml)) { LArray Media; // Set the HTML part LString HtmlFormat; if (!Ctrl->GetFormattedContent("text/html", HtmlFormat, &Media)) HtmlFormat = Ctrl->Name(); Item->SetHtml(HtmlFormat); Item->SetHtmlCharset(Charset); // Also set a text version for the alternate LString TxtFormat; if (!Ctrl->GetFormattedContent(sTextPlain, TxtFormat)) { TxtFormat = HtmlToText(Item->GetHtml(), Charset); } if (TxtFormat) { Item->SetBody(TxtFormat); Item->SetBodyCharset(Charset); } auto Obj = Item->GetObject(); // This clears any existing multipart/related objects... Obj->SetObj(FIELD_HTML_RELATED, NULL); if (Media.Length() > 0) { // Make a table of existing attachments so that we don't duplicate // these new ones. LArray Objs; LHashTbl,LDataI*> Map; if (GetItem()->GetAttachmentObjs(Objs)) { for (auto i : Objs) { auto Cid = i->GetStr(FIELD_CONTENT_ID); if (Cid) Map.Add(Cid, i); } } // If there are media attachments, splice them into the MIME tree. // This should go after setting the text part so that the right // MIME alternative structure is generated. auto Store = Obj->GetStore(); for (auto &Cm : Media) { LDataI *a = Store->Create(MAGIC_ATTACHMENT); if (a) { LAssert(Cm.Valid()); auto Existing = Map.Find(Cm.Id); if (Existing) { // Delete the existing attachment Thing *t = CastThing(Existing); Attachment *a = t ? t->IsAttachment() : NULL; if (a) { // Delete both the Attachment and it's store object... auto it = GetItem(); it->DeleteAttachment(a); } else { // There is Attachment object for the LDataI.... but we can // still delete it from the store. LArray del; del.Add(Existing); Existing->GetStore()->Delete(del, false); } } LgiTrace("Adding related: %s %s " LPrintfInt64 "\n", Cm.FileName.Get(), Cm.MimeType.Get(), Cm.Stream->GetSize()); a->SetStr(FIELD_CONTENT_ID, Cm.Id); a->SetStr(FIELD_NAME, Cm.FileName); a->SetStr(FIELD_MIME_TYPE, Cm.MimeType); a->SetStream(Cm.Stream); Obj->SetObj(FIELD_HTML_RELATED, a); } } } } else { auto Text = Ctrl->Name(); Item->SetBody(Text); Item->SetBodyCharset(Charset); } } #if SAVE_HEADERS char *Headers = Header ? Header->Name() : 0; if (Headers) { DeleteArray(Item->InternetHeader); Item->InternetHeader = NewStr(Headers); } #endif Item->GetMessageId(true); Item->CreateMailHeaders(); Item->Update(); ScribeFolder *Folder = Item->GetFolder(); // Now get the associated outbox for this mail ScribeFolder *Outbox = Item->App->GetFolder(FOLDER_OUTBOX, AccountMailStore); auto Fld = Folder ? Folder : Outbox; LAssert(Fld != NULL); bool Status = Fld ? Item->Save(Fld) : false; if (Status) { LArray c; c.Add(Item->GetObject()); Item->App->SetContext(_FL); Item->App->OnChange(c, 0); } } bool MailUi::AddRecipient(AddressDescriptor *Addr) { if (Addr && To) { ListAddr *La = dynamic_cast(Addr); if (La) { To->Insert(La); return true; } } return false; } bool MailUi::AddRecipient(Contact *c) { ListAddr *La = new ListAddr(c); if (La) { To->Insert(La); return true; } return false; } bool MailUi::AddRecipient(const char *Email, const char *Name) { ListAddr *La = new ListAddr(App, Email, Name); if (La) { To->Insert(La); return true; } return false; } bool MailUi::SeekMsg(int delta) { bool Status = false; if (Container) { Mail *Item = GetItem(); auto Index = Container->IndexOf(Item); Mail *Next = Index >= 0 ? (*Container)[Index + delta] : 0; if (Next) { SetDirty(false); // called OnSave() if necessary _Running = false; if (Item->Ui) { // close any existing user interface if (Item->Ui != this) { Item->Ui->Quit(); } else { Item->Ui = 0; } } if (Header) Header->SetEnv(0); Caps.Empty(); SetItem(Next); Item = GetItem(); // select this item in the list if (Item->GetList()) { Item->GetList()->Select(Item); Item->ScrollTo(); } Status = true; OnLoad(); _Running = true; } } return Status; } int MailUi::OnCommand(int Cmd, int Event, OsView From) { if (GpgUi) { int r = GpgUi->OnCommand(Cmd, Event, From); if (r) return r; } switch (Cmd) { case IDM_READ_RECEIPT: { if (Commands.Toolbar) { int f = GetItem()->GetFlags(); if (Commands.Toolbar->GetCtrlValue(IDM_READ_RECEIPT)) { SetFlag(f, MAIL_READ_RECEIPT); } else { ClearFlag(f, MAIL_READ_RECEIPT); } GetItem()->SetFlags(f); } break; } case IDM_HIGH_PRIORITY: { if (Commands.Toolbar) { GetItem()->SetPriority(Commands.Toolbar->GetCtrlValue(IDM_HIGH_PRIORITY) ? MAIL_PRIORITY_HIGH : MAIL_PRIORITY_NORMAL); SetDirty(true); Commands.Toolbar->SetCtrlValue(IDM_HIGH_PRIORITY, GetItem()->GetPriority() < MAIL_PRIORITY_NORMAL); Commands.Toolbar->SetCtrlValue(IDM_LOW_PRIORITY, GetItem()->GetPriority() > MAIL_PRIORITY_NORMAL); } break; } case IDM_LOW_PRIORITY: { if (Commands.Toolbar) { GetItem()->SetPriority(Commands.Toolbar->GetCtrlValue(IDM_LOW_PRIORITY) ? MAIL_PRIORITY_LOW : MAIL_PRIORITY_NORMAL); SetDirty(true); Commands.Toolbar->SetCtrlValue(IDM_HIGH_PRIORITY, GetItem()->GetPriority() < MAIL_PRIORITY_NORMAL); Commands.Toolbar->SetCtrlValue(IDM_LOW_PRIORITY, GetItem()->GetPriority() > MAIL_PRIORITY_NORMAL); } break; } case IDM_PREV_MSG: { SeekMsg(1); break; } case IDM_NEXT_MSG: { SeekMsg(-1); break; } case IDM_SEND_MSG: { bool Working = IsWorking(); if (Working) { SetCmdAfterResize(Cmd); break; } if (!GetItem() || !App) { LAssert(!"Missing item or window ptr."); break; } // Normal save bool IsInPublicFolder = GetItem()->GetFolder() && GetItem()->GetFolder()->IsPublicFolders(); if (IsInPublicFolder) { auto i = GetItem(); LDateTime n; n.SetNow(); i->SetDateSent(&n); i->Update(); } OnDataEntered(); OnSave(); SetDirty(false, false); GetItem()->Send(true); Quit(); return 0; } case IDM_DELETE_MSG: { LVariant ConfirmDelete, DelDirection; App->GetOptions()->GetValue(OPT_ConfirmDelete, ConfirmDelete); App->GetOptions()->GetValue(OPT_DelDirection, DelDirection); int WinAction = DelDirection.CastInt32() - 1; // -1 == Next, 0 == Close, 1 == Prev if (!ConfirmDelete.CastInt32() || LgiMsg(this, LLoadString(IDS_DELETE_ASK), AppName, MB_YESNO) == IDYES) { Mail *Del = GetItem()->GetObject() ? GetItem() : 0; if (Del) { if (!Del->GetObject()->IsOnDisk()) Del = 0; } if (!WinAction || !SeekMsg(WinAction)) { SetItem(0); PostEvent(M_CLOSE); } if (Del && Del->GetObject()) { Del->OnDelete(); } } break; } case IDM_DELETE_AS_SPAM: { LVariant DelDirection; App->GetOptions()->GetValue(OPT_DelDirection, DelDirection); int WinAction = DelDirection.CastInt32() - 1; // -1 == Next, 0 == Close, 1 == Prev if (GetItem()) { Mail *Del = GetItem()->GetObject() ? GetItem() : 0; if (!WinAction || !SeekMsg(WinAction)) { SetItem(0); PostEvent(M_CLOSE); } if (Del) { Del->DeleteAsSpam(this); } } break; } case IDM_SAVE: { OnDataEntered(); SetDirty(false, false); break; } case IDM_SAVE_CLOSE: { bool Working = IsWorking(); if (Working) { SetCmdAfterResize(Cmd); break; } OnDataEntered(); SetDirty(false, false); // fall thru } case IDM_CLOSE: { Quit(); return 0; } case IDM_REPLY: case IDM_REPLY_ALL: { App->MailReplyTo(GetItem(), Cmd == IDM_REPLY_ALL); SetDirty(false); PostEvent(M_CLOSE); break; } case IDM_FORWARD: { App->MailForward(GetItem()); if (IsDirty()) OnSave(); PostEvent(M_CLOSE); break; } case IDM_BOUNCE: { App->MailBounce(GetItem()); OnSave(); PostEvent(M_CLOSE); break; } case IDM_PRINT: { if (GetItem() && App) { if (IsDirty()) { OnDataEntered(); OnSave(); } - App->ThingPrint(GetItem(), 0, this); + App->ThingPrint(NULL, GetItem(), 0, this); } break; } case IDM_ATTACH_FILE: { - LFileSelect Select; - - Select.Parent(this); - Select.MultiSelect(true); - Select.Type("All files", LGI_ALL_FILES); - - if (Select.Open()) - { - Mail *m = GetItem(); - if (m) - { - for (size_t i=0; iMultiSelect(true); + Select->Type("All files", LGI_ALL_FILES); + Select->Open([this](auto dlg, auto status) + { + if (status) + { + Mail *m = GetItem(); + if (m) { - char File[MAX_PATH_LEN]; - if (!LResolveShortcut(Select[i], File, sizeof(File))) + for (size_t i=0; iLength(); i++) { - strcpy_s(File, sizeof(File), Select[i]); - } - - Attachment *a = m->AttachFile(this, File); - if (a && Attachments) - { - Attachments->Insert(a); - Attachments->ResizeColumnsToContent(); + char File[MAX_PATH_LEN]; + if (!LResolveShortcut((*dlg)[i], File, sizeof(File))) + { + strcpy_s(File, sizeof(File), (*dlg)[i]); + } + + Attachment *a = m->AttachFile(this, File); + if (a && Attachments) + { + Attachments->Insert(a); + Attachments->ResizeColumnsToContent(); + } } } } - } + delete dlg; + }); break; } default: { Commands.ExecuteCallbacks(App, this, GetItem(), Cmd); break; } } return LWindow::OnCommand(Cmd, Event, From); } LArray Mail::GetCalendarAttachments() { List Attachments; if (!GetAttachments(&Attachments)) return false; LArray Cal; for (auto a: Attachments) { LString Mt = a->GetMimeType(); if (Mt.Equals("text/calendar")) Cal.Add(a); } return Cal; } bool MailUi::AddCalendarEvent(bool AddPopupReminder) { LString Msg; auto Result = GetItem()->AddCalendarEvent(this, AddPopupReminder, &Msg); auto css = CalPanelStatus->GetCss(true); if (Result) css->Color(LCss::ColorInherit); else css->Color(LColour::Red); CalPanelStatus->Name(Msg); return Result; } bool Mail::AddCalendarEvent(LViewI *Parent, bool AddPopupReminder, LString *Msg) { LString Err, s; auto Cal = GetCalendarAttachments(); int NewEvents = 0, DupeEvents = 0, Processed = 0, Cancelled = 0, NotMatched = 0; ScribeFolder *Folder = NULL; if (Cal.Length() == 0) { Err = "There are no attached events to add."; goto OnError; } Folder = App->GetFolder(FOLDER_CALENDAR); if (!Folder) { Err = "There no calendar folder to save to."; goto OnError; } for (auto a: Cal) { auto Event = App->CreateThingOfType(MAGIC_CALENDAR); if (Event) { LString Mt = a->GetMimeType(); LAutoPtr Data(a->GotoObject(_FL)); if (Data) { if (Event->Import(AutoCast(Data), Mt)) { auto c = Event->IsCalendar(); auto obj = c ? c->GetObject() : NULL; if (!obj) continue; if (AddPopupReminder) { LString s; s.Printf("%g,%i,%i,", 10.0, CalMinutes, CalPopup); obj->SetStr(FIELD_CAL_REMINDERS, s); } // Is it a cancellation? auto Status = obj->GetStr(FIELD_CAL_STATUS); auto IsCancel = Stristr(Status, "CANCELLED") != NULL; if (!IsCancel) { // Does the folder already have a copy of this event? bool AlreadyAdded = false; for (auto t: Folder->Items) { auto Obj = t->IsCalendar(); if (Obj && *Obj == *c) { AlreadyAdded = true; break; } } if (AlreadyAdded) { DupeEvents++; } else { // Write the event to the folder auto Status = Folder->WriteThing(Event); if (Status > Store3Error) { NewEvents++; Event = NULL; } } } else { // Cancellation processing auto Uid = obj->GetStr(FIELD_UID); Thing *Match = NULL; for (auto t: Folder->Items) { auto tCal = t->IsCalendar(); if (tCal && tCal->GetObject()) { auto tUid = tCal->GetObject()->GetStr(FIELD_UID); if (!Stricmp(Uid, tUid)) { Match = t; break; } } } if (Match) { if (!Parent || LgiMsg(Parent, "Delete cancelled event?", "Calendar", MB_YESNO) == IDYES) { auto f = Match->GetFolder(); LArray items; items.Add(Match); f->Delete(items, true); Cancelled++; } } else NotMatched++; } } else LgiTrace("%s:%i - vCal event import failed.\n", _FL); } else LgiTrace("%s:%i - GotoObject failed.\n", _FL); if (Event) Event->DecRef(); } else LgiTrace("%s:%i - CreateThingOfType failed.\n", _FL); } Processed = NewEvents + DupeEvents; if (Processed != Cal.Length()) { Err.Printf("There were errors processing %i events, check the console.", (int)Cal.Length() - Processed); goto OnError; } if (NewEvents || DupeEvents) s.Printf("%i new events, %i duplicates.", NewEvents, DupeEvents); else s.Printf("%i events cancelled, %i not matched.", Cancelled, NotMatched); if (Msg) *Msg = s; if (Processed > 0) { for (auto v: CalendarView::CalendarViews) v->OnContentsChanged(); } return true; OnError: if (Msg) *Msg = Err; return false; } int MailUi::OnNotify(LViewI *Col, LNotification n) { if (dynamic_cast(Col)) { Sx = Sy = -1; OnPosChange(); return 0; } if (GpgUi) { if (n.Type == LNotifyItemDelete && Col == (LViewI*)GpgUi) { GpgUi = NULL; } else { int r = GpgUi->OnNotify(Col, n); if (r) return r; } } int CtrlId = Col->GetId(); switch (CtrlId) { case IDC_MAIL_UI_TABS: { Mail *Item = GetItem(); if (!Item) break; // bool Edit = TestFlag(Item->GetFlags(), MAIL_CREATED); if (n.Type == LNotifyValueChanged) { switch (Col->Value()) { case 0: // Text tab { if (!TextLoaded) { Item->CreateView(this, sTextPlain, true, -1); OnPosChange(); } if (CurrentEditCtrl == 1) { if (HtmlView && TextView && TextCtrlDirty) { // Convert HTML to Text here... TextCtrlDirty = false; auto Html = HtmlView->Name(); if (Html) { LString Txt = HtmlToText(Html, HtmlView->GetCharset()); if (Txt) TextView->Name(Txt); } } CurrentEditCtrl = 0; } break; } case 1: // Html tab { if (!HtmlLoaded) { Item->CreateView(this, sTextHtml, true, -1); OnPosChange(); } if (CurrentEditCtrl == 0) { if (HtmlView && TextView && HtmlCtrlDirty) { // Convert Text to HTML here... HtmlCtrlDirty = false; auto Text = TextView->Name(); if (Text) { LString Html = TextToHtml(Text, TextView->GetCharset()); if (Html) HtmlView->Name(Html); } } CurrentEditCtrl = 1; } break; } default: // Do nothing on other tabs.. break; } } else if (n.Type == LNotifyItemClick) { LMouse m; if (!Col->GetMouse(m)) break; int TabIdx = Tab->HitTest(m); if (TabIdx == 0 || TabIdx == 1) { if (Item && m.IsContextMenu()) { LSubMenu s; s.AppendItem(LLoadString(IDS_DELETE), IDM_DELETE); m.ToScreen(); int Cmd = s.Float(this, m.x, m.y, false); if (Cmd == IDM_DELETE) { if (TabIdx == 0) // Txt { Item->SetBody(NULL); Item->SetBodyCharset(NULL); TextView->Name(NULL); if (TabText) { TabText->GetCss(true)->FontBold(false); TabText->OnStyleChange(); } TextCtrlDirty = false; } else // HTML { Item->SetHtml(NULL); Item->SetHtmlCharset(NULL); HtmlView->Name(NULL); if (TabHtml) { TabHtml->GetCss(true)->FontBold(false); TabHtml->OnStyleChange(); } HtmlCtrlDirty = false; } } } } } break; } case IDC_FROM: { if (_Running && FromCbo && n.Type == LNotifyValueChanged) SetDirty(true); break; } case IDC_SHOW_FROM: { LVariant Show = Col->Value();; App->GetOptions()->SetValue(OPT_MailShowFrom, Show); break; } case IDC_LAUNCH_HTML: { char File[MAX_PATH_LEN]; if (GetItem() && GetItem()->WriteAlternateHtml(File)) { LExecute(File); } break; } case IDC_ENTRY: { if (Entry) { if (ValidStr(Entry->Name())) { if (!Browse) { Browse = new AddressBrowse(App, Entry, To, SetTo); } } if (Browse) { Browse->OnNotify(Entry, n); } if (n.Type == LNotifyReturnKey) { OnDataEntered(); } } break; } case IDC_SET_TO: { if (SetTo) { AddMode = (int)SetTo->Value(); } break; } case IDC_SEND: { OnCommand(IDM_SEND_MSG, 0, #if LGI_VIEW_HANDLE Col->Handle() #else (OsView)NULL #endif ); break; } #if SAVE_HEADERS case IDC_INTERNET_HEADER: #endif case IDC_TEXT_VIEW: case IDC_HTML_VIEW: { Mail *Item = GetItem(); if (!Item) break; bool Edit = TestFlag(Item->GetFlags(), MAIL_CREATED); if ( ( n.Type == LNotifyDocChanged || n.Type == LNotifyCharsetChanged || n.Type == LNotifyFixedWidthChanged || (!IgnoreShowImgNotify && n.Type == LNotifyShowImagesChanged) ) && _Running ) { if (GetItem()) GetItem()->OnNotify(Col, n); SetDirty(true); if (Edit) { if (CtrlId == IDC_TEXT_VIEW) { CurrentEditCtrl = 0; HtmlCtrlDirty = true; TabText->GetCss(true)->FontBold(true); TabText->OnStyleChange(); } else if (CtrlId == IDC_HTML_VIEW) { CurrentEditCtrl = 1; TextCtrlDirty = true; TabHtml->GetCss(true)->FontBold(true); TabHtml->OnStyleChange(); } // LgiTrace("%s:%i - OnNotify: TextLoaded=%i, HtmlLoaded=%i\n", _FL, TextLoaded, HtmlLoaded); } } break; } case IDC_ATTACHMENTS: { if (n.Type == LNotifyItemInsert || n.Type == LNotifyItemDelete) { // fall thru } else { break; } } case IDC_TO: { if ( _Running && ( n.Type == LNotifyItemInsert || n.Type == LNotifyItemDelete || n.Type == LNotifyItemChange ) ) { SetDirty(true); } break; } case IDC_COLOUR: { if (_Running && GetItem()) { MetaFieldsDirty = true; OnDirty(true); } break; } case IDC_LABEL: { if (_Running) { MetaFieldsDirty = true; OnDirty(true); } break; } case IDC_SUBJECT: { if (_Running) { SetDirty(true); } break; } case IDCANCEL: { if (CmdAfterResize) { int Cmd = CmdAfterResize; CmdAfterResize = 0; IsWorking(false); OnCommand(Cmd, 0, NULL); } break; } case IDC_ADD_CAL_EVENT: { AddCalendarEvent(false); break; } case IDC_ADD_CAL_EVENT_POPUP: { AddCalendarEvent(true); break; } } return 0; } void MailUi::OnDataEntered() { auto Name = Entry->Name(); if (ValidStr(Name)) { List New; // Decode the entries Mailto mt(App, Name); New = mt.To; mt.To.Empty(); if (mt.Subject && !ValidStr(GetCtrlName(IDC_SUBJECT))) { SetCtrlName(IDC_SUBJECT, mt.Subject); } // Add the new entries List Cache; App->GetContacts(Cache); AddressDescriptor *ad; while ((ad = New[0])) { New.Delete(ad); ListAddr *t = dynamic_cast(ad); if (t) { t->CC = (EmailAddressType)GetCtrlValue(IDC_SET_TO); t->OnFind(&Cache); To->Insert(t, 0); } else { DeleteObj(ad); } } // Clear the entry box for the next one Entry->Name(""); Entry->Select(-1, -1); } } void MailUi::OnPosChange() { LWindow::OnPosChange(); if (Tab && (Sx != X() || Sy != Y())) { Sx = X(); Sy = Y(); LRect r = Tab->GetCurrent()->GetClient(); r.Inset(CONTENT_BORDER, CONTENT_BORDER); if (TextView) TextView->SetPos(r, true); if (HtmlView) HtmlView->SetPos(r, true); if (Attachments) Attachments->SetPos(r, true); if (Header) Header->SetPos(r, true); } } void MailUi::OnPaint(LSurface *pDC) { LCssTools Tools(this); Tools.PaintContent(pDC, GetClient()); } void MailUi::OnPulse() { if (IsDirty() && TextView && GetItem()) { // Ui -> Object OnSave(); // Object -> Disk GetItem()->Save(0); } else { SetPulse(); } } void MailUi::OnDirty(bool Dirty) { SetCtrlEnabled(IDM_SAVE, Dirty); SetCtrlEnabled(IDM_SAVE_CLOSE, Dirty); if (Dirty) { SetPulse(60 * 1000); // every minute } else { SetPulse(); } } bool MailUi::CallMethod(const char *Name, LVariant *Dst, LArray &Arg) { ScribeDomType Method = StrToDom(Name); *Dst = false; switch (Method) { case SdShowRemoteContent: // Type: () if (HtmlView) { bool Always = Arg.Length() > 0 ? Arg[0]->CastBool() : false; if (Always && GetItem()) { auto From = GetItem()->GetFrom(); if (From) App->RemoteContent_AddSender(From->GetStr(FIELD_EMAIL), true); else LgiTrace("%s:%i - No from address.\n", _FL); } IgnoreShowImgNotify = true; HtmlView->SetLoadImages(true); IgnoreShowImgNotify = false; PostEvent(M_UPDATE); *Dst = true; } break; case SdSetHtml: // Type: (String Html) if (HtmlView) { if (Arg.Length() > 0) { HtmlView->Name(Arg[0]->Str()); *Dst = true; } } break; default: return false; } return true; } LMessage::Result MailUi::OnEvent(LMessage *Msg) { switch (Msg->Msg()) { case M_SET_HTML: { LAutoPtr s((LString*)Msg->A()); if (s && HtmlView) HtmlView->Name(*s); break; } case M_NEEDS_CAP: { LAutoString c((char*)Msg->A()); NeedsCapability(c); return 0; } case M_RESIZE_IMAGE: { LAutoPtr Job((ImageResizeThread::Job*)Msg->A()); if (Job && GetItem()) { // Find the right attachment... List Attachments; if (GetItem()->GetAttachments(&Attachments)) { Attachment *Match = NULL; for (auto a: Attachments) { LString Nm = a->GetName(); if (Nm.Equals(Job->FileName)) { Match = a; break; } } if (Match) { if (Job->Data) Match->Set(Job->Data); auto Mt = Match->GetMimeType(); if (_stricmp(Mt, "image/jpeg")) { auto Name = Match->GetName(); char *Ext = LGetExtension(Name); if (Ext) *Ext = 0; LString NewName = Name; NewName += "jpg"; Match->SetName(NewName); Match->SetMimeType("image/jpeg"); } Match->SetIsResizing(false); LDataI *AttachPoint = GetItem()->GetFileAttachPoint(); if (AttachPoint) { // Do final save to the mail store... Match->GetObject()->Save(AttachPoint); } else { LAssert(0); Match->DecRef(); Match = NULL; } if (CmdAfterResize) { bool Working = IsWorking(); if (!Working) { // All resizing work is done... OnCommand(CmdAfterResize, 0, NULL); return 0; } } } else LAssert(!"No matching attachment image to store resized image in."); } } break; } #if WINNATIVE case WM_COMMAND: { LAssert((NativeInt)Commands.Toolbar != 0xdddddddd); if (Commands.Toolbar && Commands.Toolbar->Handle() == (HWND)Msg->b) { return LWindow::OnEvent(Msg); } break; } #endif } return LWindow::OnEvent(Msg); } void MailUi::OnReceiveFiles(LArray &Files) { List Att; GetItem()->GetAttachments(&Att); for (unsigned i=0; iGetName(), f) == 0) { int Result = LgiMsg(this, LLoadString(IDS_ATTACH_WARNING_DLG), LLoadString(IDS_ATTACHMENTS), MB_YESNO); if (Result == IDNO) { Add = false; break; } } } if (Add) { Attachment *a = GetItem()->AttachFile(this, Path); if (a) { Attachments->Insert(a); Attachments->ResizeColumnsToContent(); } } } } ////////////////////////////////////////////////////////////////////////////// bool Mail::PreviewLines = false; bool Mail::RunMailPipes = true; List Mail::NewMailLst; bool Mail::AdjustDateTz = true; LHashTbl,Mail*> Mail::MessageIdMap; Mail::Mail(ScribeWnd *app, LDataI *object) : Thing(app, object) { DefaultObject(object); d = new MailPrivate(this); _New(); } Mail::~Mail() { if (GetObject()) { auto Id = GetObject()->GetStr(FIELD_MESSAGE_ID); if (Id) MessageIdMap.Delete(Id); } NewMailLst.Delete(this); _Delete(); DeleteObj(d); } void Mail::_New() { SendAttempts = 0; Container = 0; FlagsCache = -1; PreviewCacheX = 0; Cursor = 0; ParentFile = 0; PreviousMail = 0; NewEmail = NewEmailNone; Ui = 0; TotalSizeCache = -1; } void Mail::_Delete() { if (ParentFile) { ParentFile->SetMsg(0); } UnloadAttachments(); PreviewCache.DeleteObjects(); DeleteObj(Container); if (Ui) Ui->PostEvent(M_CLOSE); if (Ui) Ui->SetItem(0); } bool Mail::AppendItems(LSubMenu *Menu, const char *Param, int Base) { auto Remove = Menu->AppendSub(LLoadString(IDS_REMOVE)); if (Remove) { Remove->AppendItem("'>'", IDM_REMOVE_GRTH, true); Remove->AppendItem("'> '", IDM_REMOVE_GRTH_SP, true); Remove->AppendItem(LLoadString(IDS_HTML_TAGS), IDM_REMOVE_HTML, true); } auto FilterMenu = Menu->AppendSub(LLoadString(IDS_FILTER)); if (FilterMenu) { FilterMenu->SetImageList(App->GetIconImgList(), false); Actions.Empty(); AddActions(FilterMenu, Actions, App->GetThingSources(MAGIC_FILTER)); } auto Convert = Menu->AppendSub(LLoadString(IDS_CONVERT_SELECTION)); if (Convert) { Convert->AppendItem("To Base64", IDM_CONVERT_BIN_TO_B64, true); Convert->AppendItem("To Binary", IDM_CONVERT_B64_TO_BIN, true); } if (!TestFlag(GetFlags(), MAIL_CREATED)) { auto Charset = Menu->AppendSub(LLoadString(L_CHANGE_CHARSET)); if (Charset) { int n=0; for (LCharset *c = LGetCsList(); c->Charset; c++, n++) Charset->AppendItem(c->Charset, IDM_CHARSET_BASE + n, c->IsAvailable()); } } return true; } bool Mail::OnMenu(LDocView *View, int Id, void *Context) { const char *RemoveStr = 0; switch (Id) { case IDM_REMOVE_GRTH: { RemoveStr = ">"; break; } case IDM_REMOVE_GRTH_SP: { RemoveStr = "> "; break; } case IDM_REMOVE_HTML: { auto s = View ? View->Name() : 0; if (s) { auto n = DeHtml(s); if (n) { View->Name(n); DeleteArray(n); } } break; } default: { if (Id >= IDM_CHARSET_BASE) { int n=0; LCharset *c; for (c = LGetCsList(); c->Charset; c++, n++) { if (Id - IDM_CHARSET_BASE == n) { break; } } if (c->Charset) { SetBodyCharset((char*)c->Charset); SetDirty(); if (GetBodyCharset() && Ui) { Ui->OnLoad(); } } } else if (Id >= IDM_FILTER_BASE) { Filter *Action = Actions[Id-IDM_FILTER_BASE]; if (Action) { // Save the message... SetDirty(false); // Hide the mail window... if (Ui) Ui->Visible(false); // Do the action bool Stop; Mail *This = this; Action->DoActions(This, Stop); if (This != this) break; if (Ui) DeleteObj(Ui); return true; } } break; } #ifdef _DEBUG case IDM_CONVERT_BIN_TO_B64: { char *t = View->GetSelection(); if (t) { size_t In = strlen(t); size_t Len = BufferLen_BinTo64(In); char *B64 = new char[Len+1]; if (B64) { ConvertBinaryToBase64(B64, Len, (uchar*)t, In); B64[Len] = 0; char Temp[256]; ConvertBase64ToBinary((uchar*)Temp, sizeof(Temp), B64, Len); char16 *Str = Utf8ToWide(B64); if (Str) { LTextView3 *Tv = dynamic_cast(View); if (Tv) { Tv->DeleteSelection(); Tv->Insert(Tv->GetCaret(), Str, Len); } DeleteArray(Str); } DeleteArray(B64); } } break; } case IDM_CONVERT_B64_TO_BIN: { char *t = View->GetSelection(); if (t) { size_t In = strlen(t); size_t Len = BufferLen_64ToBin(In); char *Bin = new char[Len+1]; if (Bin) { ssize_t Out = ConvertBase64ToBinary((uchar*)Bin, Len, t, In); Bin[Out] = 0; char16 *Str = Utf8ToWide(Bin, Out); if (Str) { LTextView3 *Tv = dynamic_cast(View); if (Tv) { Tv->DeleteSelection(); Tv->Insert(Tv->GetCaret(), Str, Out); } DeleteArray(Str); } DeleteArray(Bin); } } break; } #endif } if (RemoveStr) { size_t TokenLen = strlen(RemoveStr); auto s = View ? View->Name() : 0; if (s) { LMemQueue Temp; auto Start = s; const char *End; while (*Start) { // seek to EOL for (End = Start; *End && *End != '\n'; End++); End++; ssize_t Len = End - Start; if (_strnicmp(Start, RemoveStr, TokenLen) == 0) { Temp.Write((uchar*)Start + TokenLen, Len - TokenLen); } else { Temp.Write((uchar*)Start, Len); } Start = End; } int Size = (int)Temp.GetSize(); char *Buf = new char[Size+1]; if (Buf) { Temp.Read((uchar*) Buf, Size); Buf[Size] = 0; View->Name(Buf); DeleteArray(Buf); } } } return true; } LDocumentEnv::LoadType Mail::GetContent(LoadJob *&j) { if (!j) return LoadError; LUri Uri(j->Uri); if ( Uri.sProtocol && ( !_stricmp(Uri.sProtocol, "http") || !_stricmp(Uri.sProtocol, "https") || !_stricmp(Uri.sProtocol, "ftp") ) ) { // We don't check OPT_HtmlLoadImages here because it's done elsewhere: // - ScribeWnd::CreateTextControl calls LHtml::SetLoadImages with the value from OPT_HtmlLoadImages // - LTag::LoadImage checks LHtml::GetLoadImages // // If there is a remote job here, it's because it's probably whitelisted. if (!Worker) Worker = App->GetImageLoader(); if (!Worker) return LoadError; Worker->AddJob(j); j = 0; return LoadDeferred; } else if (Uri.sProtocol && !_stricmp(Uri.sProtocol, "file")) { if (!_strnicmp(Uri.sPath, "//", 2)) { // This seems to hang the windows CreateFile function... } else { // Is it a local file then? if (j->pDC.Reset(GdcD->Load(Uri.sPath))) { return LoadImmediate; } } } else { List Files; if (GetAttachments(&Files)) { auto Dir = FilePart(j->Uri); Attachment *a = NULL; for (auto It = Files.begin(); It != Files.end(); It++) { a = *It; if (_strnicmp(j->Uri, "cid:", 4) == 0) { char *ContentId = j->Uri + 4; auto AttachmentId = a->GetContentId(); if (AttachmentId) { if (AttachmentId[0] == '<') { auto s = AttachmentId + 1; auto e = strrchr(s, '>'); if (e) { ssize_t len = e - s; if (strlen(ContentId) == len && !strncmp(s, ContentId, len)) break; } } else if (!strcmp(AttachmentId, ContentId)) { break; } } } else { auto Name = a->GetName(); if (Name) { auto NameDir = FilePart(Name); if (_stricmp(NameDir, Dir) == 0) { break; } } } } if (a) { j->MimeType = a->GetMimeType(); j->ContentId = a->GetContentId(); if (j->Pref == LoadJob::FmtStream) { j->Filename = a->GetName(); j->Stream = a->GetObject()->GetStream(_FL); return LoadImmediate; } else { char *Tmp = ScribeTempPath(); if (Tmp) { auto File = a->GetName(); auto Ext = LGetExtension(File); char s[MAX_PATH_LEN] = ""; LString part; do { if (part.Printf("%x.%s", LRand(), Ext) < 0) return LoadError; if (!LMakePath(s, sizeof(s), Tmp, part)) return LoadError; } while (LFileExists(s)); if (a->SaveTo(s, true)) { if (j->Pref == LoadJob::FmtFilename) { j->Filename = s; return LoadImmediate; } else { int Promote = GdcD->SetOption(GDC_PROMOTE_ON_LOAD, 0); j->pDC.Reset(GdcD->Load(s)); j->Filename = a->GetName(); GdcD->SetOption(GDC_PROMOTE_ON_LOAD, Promote); FileDev->Delete(s, false); return LoadImmediate; } } } } } } } return LoadError; } class MailCapabilities : public LLayout { LArray MissingCaps; LDocView *Doc; LButton *Install; public: MailCapabilities(LDocView *d) { Doc = d; Install = 0; } const char *GetClass() { return "MailCapabilities"; } void OnPosChange() { LRect c = GetClient(); if (MissingCaps.Length()) { if (!Install) { if ((Install = new LButton(IDOK, 0, 0, -1, -1, "Install"))) Install->Attach(this); } LRect r = c; r.x1 = r.x2 - Install->X(); r.y2 -= 7; Install->SetPos(r); } } void OnPaint(LSurface *pDC) { LRect cli = GetClient(); if (MissingCaps.Length()) { char Msg[256]; int c = sprintf_s(Msg, sizeof(Msg), "This content requires "); for (unsigned i=0; iTransparent(false); LSysFont->Colour(L_TEXT, L_MED); ds.Draw(pDC, cli.x1, cli.y1, &cli); } else { pDC->Colour(L_MED); pDC->Rectangle(); } } }; LDocView *Mail::CreateView( MailViewOwner *Owner, LString MimeType, bool Sunken, size_t MaxBytes, bool NoEdit) { bool Created = TestFlag(GetFlags(), MAIL_CREATED); bool Edit = NoEdit ? false : Created; bool ReadOnly = !Created; LAutoString Mem; LVariant DefAlt; App->GetOptions()->GetValue(OPT_DefaultAlternative, DefAlt); auto TextBody = GetBody(); auto TextCharset = GetBodyCharset(); auto HtmlBody = GetHtml(); auto HtmlCharset = GetHtmlCharset(); const char *CtrlType = NULL; if (!MimeType) { bool TextValid = TextBody != NULL; bool HtmlValid = HtmlBody != NULL; if (TextValid && HtmlValid) MimeType = DefAlt.CastInt32() ? sTextHtml : sTextPlain; else if (TextValid) MimeType = sTextPlain; else if (HtmlValid) MimeType = sTextHtml; else return NULL; } #ifdef WINDOWS if (DefAlt.CastInt32() == 2 && MimeType == sTextHtml) CtrlType = sApplicationInternetExplorer; else #endif CtrlType = MimeType; const char *Content, *Charset; if (MimeType == sTextHtml) { Content = HtmlBody; Charset = HtmlCharset; } else { Content = TextBody; Charset = TextCharset; } // Emoji check LVariant NoEmoji; App->GetOptions()->GetValue(OPT_NoEmoji, NoEmoji); // Check if the control needs changing LDocView *View = Owner->GetDoc(MimeType); if (View) { const char *ViewMimeType = View->GetMimeType(); if (MimeType != ViewMimeType) { Owner->SetDoc(NULL, ViewMimeType); View = NULL; } } if (!View) { View = App->CreateTextControl( MimeType == sTextHtml ? IDC_HTML_VIEW : IDC_TEXT_VIEW, MimeType, Edit, this); } if (View) { // Control setup View->Sunken(Sunken); View->SetReadOnly(ReadOnly); View->SetEnv(this); LVariant UseCid = true; View->SetValue(LDomPropToString(HtmlImagesLinkCid), UseCid); LVariant LoadImages; App->GetOptions()->GetValue(OPT_HtmlLoadImages, LoadImages); bool AppLoadImages = LoadImages.CastInt32() != 0; bool MailLoadImages = TestFlag(GetFlags(), MAIL_SHOW_IMAGES); const char *SenderAddr = GetFrom() ? GetFrom()->GetStr(FIELD_EMAIL) : NULL; auto SenderStatus = App->RemoteContent_GetSenderStatus(SenderAddr); View->SetLoadImages ( SenderStatus != RemoteNeverLoad && ( AppLoadImages || MailLoadImages || SenderStatus == RemoteAlwaysLoad ) ); // Attach control Owner->SetDoc(View, MimeType); LCharset *CsInfo = LGetCsInfo(Charset); // Check for render scripts LArray Renderers; LString RenderMsg = "Rendering..."; if (App->GetScriptCallbacks(LRenderMail, Renderers)) { for (auto r: Renderers) { LVirtualMachine Vm; LScriptArguments Args(&Vm); Args.New() = new LVariant(App); Args.New() = new LVariant(this); Args.New() = new LVariant((void*)NULL); bool Status = App->ExecuteScriptCallback(*r, Args); Args.DeleteObjects(); if (Status) { auto Ret = Args.GetReturn(); if (Ret->IsString() || Ret->CastInt32()) { if (Ret->IsString()) RenderMsg = Ret->Str(); d->Renderer.Reset(new MailRendererScript(this, r)); break; } } } } if (d->Renderer) { LString Nm; if (MimeType.Equals(sTextHtml)) Nm.Printf("%s", RenderMsg.Get()); else Nm = RenderMsg; View->Name(Nm); } else { // Send the data to the control size_t ContentLen = Content ? strlen(Content) : 0; Html1::LHtml *Html = dynamic_cast(View); if (MimeType.Equals(sTextHtml)) { if (CsInfo) { int OverideDocCharset = *Charset == '>' ? 1 : 0; View->SetCharset(Charset + OverideDocCharset); if (Html) Html->SetOverideDocCharset(OverideDocCharset != 0); } else { View->SetCharset(0); if (Html) Html->SetOverideDocCharset(0); } View->Name(Content); } else { LAutoPtr Utf32((uint32_t*)LNewConvertCp("utf-32", Content, Charset ? Charset : (char*)"utf-8", MaxBytes > 0 ? MIN(ContentLen, MaxBytes) : ContentLen)); if (Utf32) { int Len = 0; while (Utf32[Len]) Len++; #if 0 LFile f; if (f.Open("c:\\temp\\utf32.txt", O_WRITE)) { uchar bom[4] = { 0xff, 0xfe, 0, 0 }; f.Write(bom, 4); f.Write(Utf32, Len * sizeof(uint32)); f.Close(); } #endif } LAutoWString Wide; Wide.Reset((char16*)LNewConvertCp(LGI_WideCharset, Content, Charset ? Charset : (char*)"utf-8", MaxBytes > 0 ? MIN(ContentLen, MaxBytes) : ContentLen)); if (Wide) { View->NameW(Wide); } else { // Fallback... try and show something at least LAutoString t(NewStr(Content, MaxBytes > 0 ? MIN(ContentLen, MaxBytes) : ContentLen)); if (t) { uint8_t *i = (uint8_t*)t.Get(); while (*i) { if (*i & 0x80) *i &= 0x7f; i++; } View->Name(t); } else View->NameW(0); } View->SetFixedWidthFont(TestFlag(GetFlags(), MAIL_FIXED_WIDTH_FONT)); } } } return View; } bool Mail::OnNavigate(LDocView *Parent, const char *Uri) { if (Uri) { if ( _strnicmp(Uri, "mailto:", 7) == 0 || ( strchr(Uri, '@') && !strchr(Uri, '/') ) ) { // Mail address return App->CreateMail(0, Uri, 0) != 0; } else { return LDefaultDocumentEnv::OnNavigate(Parent, Uri); } } return false; } void Mail::Update() { TotalSizeCache = -1; LListItem::Update(); } void ParseIdList(char *In, List &Out) { if (!In) return; while (*In && strchr(WhiteSpace, *In)) In++; if (*In == '<') { // Standard msg-id list.. for (char *s=In; s && *s; ) { s = strchr(s, '<'); if (!s) break; while (*s == '<') s++; char *e = strchr(s, '>'); if (e) { Out.Insert(NewStr(s, e-s)); s = e + 1; } else break; } } else { // Non compliant msg-id list... const char Delim[] = ", \t\r\n"; for (char *s=In; s && *s; ) { if (strchr(Delim, *s)) s++; else { char *Start = s; while (*s && !strchr(Delim, *s)) s++; Out.Insert(NewStr(Start, s - Start)); } } } } void Base36(char *Out, uint64 In) { while (In) { int p = (int)(In % 36); if (p < 10) { *Out++ = '0' + p; } else { *Out++ = 'A' + p - 10; } In /= 36; } *Out++ = 0; } void Mail::ClearCachedItems() { TotalSizeCache = -1; PreviewCacheX = -1; PreviewCache.DeleteObjects(); Attachment *a; int i = 0; while ((a = Attachments[i])) { if (!a->DecRef()) i++; } } void Mail::NewRecipient(char *Email, char *Name) { LDataPropI *a = GetTo()->Create(GetObject()->GetStore()); if (a) { a->SetStr(FIELD_EMAIL, Email); a->SetStr(FIELD_NAME, Name); GetTo()->Insert(a); } } void Mail::PrepSend() { // Set flags and other data SetDirty(); int OldFlags = GetFlags(); SetFlags((OldFlags | MAIL_READY_TO_SEND) & ~MAIL_SENT); // we want to send now... // Check we're in the Outbox ScribeFolder *OutBox = App->GetFolder(FOLDER_OUTBOX, GetObject()); if (OutBox) { ScribeFolder *f = GetFolder(); if (!f || f != OutBox) { LArray Items; Items.Add(this); OutBox->MoveTo(Items); } } } bool Mail::Send(bool Now) { // Check for any "on before send" callbacks: bool AllowSend = true; LArray Callbacks; if (App->GetScriptCallbacks(LMailOnBeforeSend, Callbacks)) { for (unsigned i=0; AllowSend && iExecuteScriptCallback(c, Args)) { if (!Args.GetReturn()->CastInt32()) AllowSend = false; } Args.DeleteObjects(); } } } if (!AllowSend) return false; // Set the ready to send flag.. bool IsInPublicFolder = GetFolder() && GetFolder()->IsPublicFolders(); if (!IsInPublicFolder) { PrepSend(); if (Now) { // Kick off send thread if relevant LVariant Offline; App->GetOptions()->GetValue(OPT_WorkOffline, Offline); if (!Offline.CastInt32() && !IsInPublicFolder) { App->PostEvent(M_COMMAND, IDM_SEND_MAIL, (LMessage::Param)Handle()); } } } return true; } bool Mail::MailMessageIdMap(bool Add) { if (Add) { auto LoadState = GetLoaded(); if (LoadState < Store3Headers) { LAssert(!"Not loaded yet."); LStackTrace("MailMessageIdMap msg not loaded yet: %i\n", LoadState); return false; } // char *ObjId = GetObject()->GetStr(FIELD_MESSAGE_ID); auto Id = GetMessageId(true); if (!Id) { LAssert(!"No message ID? Impossible!"); GetMessageId(true); return false; } #if 0 LgiTrace("MailMessageIdMap(%i) Old=%s Id=%s Mail=%p\n", Add, ObjId, Id, this); #endif return MessageIdMap.Add(Id, this); } else { auto Id = GetMessageId(); #if 0 LgiTrace("MailMessageIdMap(%i) Id=%s Mail=%p\n", Add, Id, this); #endif return MessageIdMap.Delete(Id); } } Mail *Mail::GetMailFromId(const char *Id) { Mail *m = MessageIdMap.Find(Id); // LgiTrace("GetMailFromId(%s)=%p\n", Id, m); return m; } bool Mail::SetMessageId(const char *MsgId) { if (LAppInst->InThread()) { LAssert(GetObject() != NULL); auto OldId = GetObject()->GetStr(FIELD_MESSAGE_ID); if (OldId) MessageIdMap.Delete(OldId); LAutoString m(NewStr(MsgId)); LAutoString s(TrimStr(m, "<>")); if (GetObject()->SetStr(FIELD_MESSAGE_ID, s)) SetDirty(); return s != 0; } else { // No no no NO NON NOT NADA. LAssert(0); } return false; } LAutoString Mail::GetThreadIndex(int TruncateChars) { LAutoString Id; LAutoString Raw(InetGetHeaderField(GetInternetHeader(), "Thread-Index")); if (Raw) { Id = ConvertThreadIndex(Raw, TruncateChars); } return Id; } const char *Mail::GetMessageId(bool Create) { LAssert(GetObject() != NULL); d->MsgIdCache = GetObject()->GetStr(FIELD_MESSAGE_ID); if (!d->MsgIdCache) { bool InThread = GetCurrentThreadId() == LAppInst->GetGuiThreadId(); LAutoString Header(InetGetHeaderField(GetInternetHeader(), "Message-ID")); if (Header) { LAssert(InThread); if (InThread) { List Ids; ParseIdList(Header, Ids); SetMessageId(Ids[0]); Ids.DeleteArrays(); d->MsgIdCache = GetObject()->GetStr(FIELD_MESSAGE_ID); } } if (!d->MsgIdCache && Create) { LAssert(InThread); if (InThread) { auto FromEmail = GetFromStr(FIELD_EMAIL); const char *At = FromEmail ? strchr(FromEmail, '@') : 0; if (!At) { LVariant Email; if (App->GetOptions()->GetValue(OPT_Email, Email) && Email.Str()) { At = strchr(Email.Str(), '@'); } else { At = "@domain.com"; } } if (At) { char m[96], a[32], b[32]; Base36(a, LCurrentTime()); Base36(b, LRand(RAND_MAX)); sprintf_s(m, sizeof(m), "<%s.%i%s%s>", a, LRand(RAND_MAX), b, At); if (GetObject()->SetStr(FIELD_MESSAGE_ID, m)) SetDirty(); d->MsgIdCache = GetObject()->GetStr(FIELD_MESSAGE_ID); } else LgiTrace("%s:%i - Error, no '@' in %s.\n", _FL, FromEmail); } else LgiTrace("%s:%i - Error, not in thread.\n", _FL); } } else { d->MsgIdCache = d->MsgIdCache.Strip("<>").Strip(); } LAssert ( (!d->MsgIdCache && !Create) || (d->MsgIdCache && !strchr(d->MsgIdCache, '\n')) ); return d->MsgIdCache; } bool Mail::GetReferences(List &Ids) { LAutoString References(InetGetHeaderField(GetInternetHeader(), "References")); if (References) { ParseIdList(References, Ids); } LAutoString InReplyTo(InetGetHeaderField(GetInternetHeader(), "In-Reply-To")); if (InReplyTo) { List To; ParseIdList(InReplyTo, To); bool Has = false; char *r = To[0]; if (r) { for (auto h: Ids) { if (!strcmp(h, r)) Has = true; } if (!Has) { To.Delete(r); Ids.Insert(r); } } To.DeleteArrays(); } if (Ids.Length() == 0) { LAutoString Id = GetThreadIndex(5); if (Id) { size_t Len = strlen(Id); size_t Bytes = Len >> 1; if (Bytes >= 22) { Ids.Insert(Id.Release()); } } } return Ids.Length() > 0; } LString AddrToHtml(LDataPropI *a) { LString s; if (a) { auto Name = a->GetStr(FIELD_NAME); auto Addr = a->GetStr(FIELD_EMAIL); LXmlTree t; LAutoString eName(t.EncodeEntities(Name)); LAutoString eAddr(t.EncodeEntities(Addr)); if (Name && Addr) s.Printf("%s", eAddr.Get(), eName.Get()); else if (Name) s = eName.Get(); else if (Addr) s.Printf("%s", eAddr.Get(), eAddr.Get()); } return s; } LDataPropI *FindMimeSeg(LDataPropI *s, char *Type) { if (!s) return 0; const char *Mt = s->GetStr(FIELD_MIME_TYPE); if (!Mt) Mt = "text/plain"; if (!_stricmp(Type, Mt)) return s; GDataIt c = s->GetList(FIELD_MIME_SEG); if (!c) return 0; for (LDataPropI *i=c->First(); i; i=c->Next()) { LDataPropI *Child = FindMimeSeg(i, Type); if (Child) return Child; } return 0; } bool Mail::GetAttachmentObjs(LArray &Objs) { if (!GetObject()) return false; CollectAttachments(&Objs, NULL, NULL, NULL, GetObject()->GetObj(FIELD_MIME_SEG)); return Objs.Length() > 0; } void DescribeMime(LStream &p, LDataI *Seg) { auto Mt = Seg->GetStr(FIELD_MIME_TYPE); p.Print("%s", Mt); auto Cs = Seg->GetStr(FIELD_CHARSET); if (Cs) p.Print(" - %s", Cs); p.Print("
\n"); GDataIt c = Seg->GetList(FIELD_MIME_SEG); if (c && c->First()) { p.Print("
\n"); for (LDataPropI *i=c->First(); i; i=c->Next()) { LDataI *a = dynamic_cast(i); if (a) DescribeMime(p, a); } p.Print("
\n"); } } bool Mail::GetVariant(const char *Name, LVariant &Value, const char *Array) { ScribeDomType Fld = StrToDom(Name); switch (Fld) { case SdFrom: // Type: ListAddr { Value = GetFrom(); break; } case SdFromHtml: // Type: String { LString s = AddrToHtml(GetFrom()); if (s.Length() == 0) return false; Value = s; break; } case SdContact: // Type: Contact { if (!GetFrom()) return false; auto Addr = GetFrom()->GetStr(FIELD_EMAIL); if (!Addr) return false; Contact *c = Contact::LookupEmail(Addr); if (!c) return App->GetVariant("NoContact", Value); Value = (LDom*)c; break; } case SdTo: // Type: ListAddr[] { if (Array) { GDataIt To = GetTo(); int Idx = atoi(Array); bool Create = false; if (Idx < 0) { Create = true; Idx = -Idx; } LDataPropI *a = Idx < (int)To->Length() ? (*To)[Idx] : 0; if (!a && Create) { if ((a = To->Create(GetObject()->GetStore()))) { To->Insert(a); } } Value = a; } else if (Value.SetList()) { GDataIt To = GetTo(); for (LDataPropI *a=To->First(); a; a=To->Next()) { LVariant *Recip = new LVariant; if (Recip) { *Recip = a; Value.Value.Lst->Insert(Recip); } } } else return false; break; } case SdToHtml: // Type: String { if (GetTo()) { LStringPipe p; for (LDataPropI *t=GetTo()->First(); t; t=GetTo()->Next()) { if (p.GetSize()) p.Write((char*)", ", 2); LString s = AddrToHtml(t); if (s) p.Write(s, s.Length()); } Value.Type = GV_STRING; Value.Value.String = p.NewStr(); } break; } case SdSubject: // Type: String { Value = GetSubject(); break; } case SdBody: // Type: String { if (Array) { auto Body = GetBody(); LAutoString h; for (auto c = Body; c && *c; ) { while (*c && strchr(WhiteSpace, *c)) c++; auto Start = c; while (*c && *c != ':' && *c != '\n') c++; if (*c == ':') { if (Start[0] == '\"' && c[1] == '\"') c++; LString s(Start, c - Start); s = s.Strip("\'\" \t[]<>{}:"); if (s.Equals(Array)) { // Match... c++; while (*c && strchr(WhiteSpace, *c)) c++; Start = c; while (*c && *c != '\n') c++; while (c > Start && strchr(WhiteSpace, c[-1])) c--; h.Reset(NewStr(Start, c - Start)); break; } else { while (*c && *c != '\n') c++; } } if (*c == '\n') c++; } if (h) { if (GetBodyCharset()) { char *u = (char*)LNewConvertCp("utf-8", h, GetBodyCharset()); if (u) { Value.OwnStr(u); } else { Value.OwnStr(h.Release()); } } else { Value.OwnStr(h.Release()); } } } else { Value = GetBody(); } break; } case SdBodyAsText: // Type: String { size_t MaxSize = ValidStr(Array) ? atoi(Array) : -1; if (ValidStr(GetBody()) && ValidStr(GetHtml())) { LVariant Def; App->GetOptions()->GetValue(OPT_DefaultAlternative, Def); if (Def.CastInt32()) { goto DoHtml; } goto DoText; } else if (ValidStr(GetBody())) { DoText: LAutoString CharSet = GetCharSet(); auto Txt = GetBody(); if (CharSet) { size_t TxtLen = strlen(Txt); Value.OwnStr((char*)LNewConvertCp("utf-8", Txt, CharSet, MIN(TxtLen, MaxSize))); } else Value = Txt; } else if (ValidStr(GetHtml())) { DoHtml: auto v = HtmlToText(GetHtml(), GetHtmlCharset()); Value = v.Get(); } else return false; break; } case SdBodyAsHtml: // Type: String { // int MaxSize = ValidStr(Array) ? atoi(Array) : -1; MailPrivate::HtmlBody *b = d->GetBody(); if (!b) return false; LStringPipe p; p.Print("
\n%s\n
\n", ScribeReplyClass, b->Html.Get()); Value.OwnStr(p.NewStr()); LgiTrace("Value=%s\n", Value.Str()); break; } case SdHtmlHeadFields: // Type: String { MailPrivate::HtmlBody *b = d->GetBody(); if (!b) return false; LStringPipe p; if (ValidStr(b->Charset)) p.Print("\t\n", b->Charset.Get()); p.Print("\t"); Value.OwnStr(p.NewStr()); break; } case SdMessageID: // Type: String { Value = GetMessageId(); return true; } case SdInternetHeaders: // Type: String { Value = GetInternetHeader(); break; } case SdInternetHeader: // Type: String[] { if (!Array || !GetInternetHeader()) return false; LAutoString s(InetGetHeaderField(GetInternetHeader(), Array)); if (s) Value = s; else Value.Empty(); break; } case SdPriority: // Type: Int32 { Value = (int)GetPriority(); break; } case SdHtml: // Type: String { Value = GetHtml(); break; } case SdFlags: // Type: Int32 { if (Array) { int Flag = StringToMailFlag(Array); Value = (GetFlags() & Flag) != 0; } else { Value = (int)GetFlags(); } break; } case SdFolder: // Type: ScribeFolder { ScribeFolder *f = GetFolder(); if (!f) return false; Value = (LDom*)f; break; } case SdScribe: // Type: ScribeWnd { Value = (LDom*)App; break; } case SdMail: // Type: Mail { Value = PreviousMail; break; } case SdDateSent: // Type: DateTime { Value = GetDateSent(); break; } case SdDateReceived: // Type: DateTime { Value = GetDateReceived(); break; } case SdSig: // Type: String { bool Type = false; if (Array && stristr(Array, "html")) Type = true; Value = GetSig(Type); break; } case SdSize: // Type: Int64 { Value = TotalSizeof(); break; } case SdLabel: // Type: String { Value = GetLabel(); break; } case SdAttachments: // Type: Int32 { LArray Lst; GetAttachmentObjs(Lst); Value = (int)Lst.Length(); break; } case SdAttachment: // Type: Attachment[] { List Files; if (GetAttachments(&Files) && Array) { int i = atoi(Array); Value = Files[i]; if (Value.Type == GV_DOM) return true; } LAssert(!"Not a valid attachment?"); return false; } case SdMimeTree: // Type: String { LDataI *Root = dynamic_cast(GetObject()->GetObj(FIELD_MIME_SEG)); if (!Root) return false; LStringPipe p(256); DescribeMime(p, Root); Value.OwnStr(p.NewStr()); break; } case SdUi: // Type: LView { Value = Ui; break; } case SdType: // Type: Int32 { Value = GetObject()->Type(); break; } case SdSelected: // Type: Bool { Value = Select(); break; } case SdRead: // Type: Bool { Value = (GetFlags() & MAIL_READ) != 0; break; } case SdShowImages: // Type: Bool { Value = (GetFlags() & MAIL_SHOW_IMAGES) != 0; break; } case SdColour: // Type: Int32 { Value = GetMarkColour(); break; } case SdReceivedDomain: // Type: String { Value = GetFieldText(FIELD_RECEIVED_DOMAIN); break; } default: { return false; } } return true; } #define DomSetStr(Dom, Fld) \ case Dom: \ if (GetObject() && Value.Type == GV_STRING) \ GetObject()->SetStr(Fld, Value.Str()); \ else \ LAssert(!"Missing object or type err"); \ break; #define DomSetInt(Dom, Fld) \ case Dom: \ if (GetObject() && Value.Type == GV_INT32) \ GetObject()->SetInt(Fld, Value.Value.Int); \ else \ LAssert(!"Missing object or type err"); \ break; #define DomSetDate(Dom, Fld) \ case Dom: \ if (GetObject() && Value.Type == GV_DATETIME) \ GetObject()->SetDate(Fld, Value.Value.Date); \ else \ LAssert(!"Missing object or type err"); \ break; bool Mail::SetVariant(const char *Name, LVariant &Value, const char *Array) { ScribeDomType Fld = StrToDom(Name); switch (Fld) { DomSetStr(SdSubject, FIELD_SUBJECT) DomSetStr(SdMessageID, FIELD_MESSAGE_ID) DomSetStr(SdInternetHeaders, FIELD_INTERNET_HEADER) DomSetInt(SdPriority, FIELD_PRIORITY) DomSetDate(SdDateSent, FIELD_DATE_SENT) DomSetDate(SdDateReceived, FIELD_DATE_RECEIVED) case SdBody: { if (!SetBody(Value.Str())) return false; break; } case SdHtml: { if (!SetHtml(Value.Str())) return false; break; } case SdRead: { if (Value.CastInt32()) SetFlags(GetFlags() | MAIL_READ); else SetFlags(GetFlags() & ~MAIL_READ); break; } case SdColour: { auto u32 = Value.IsNull() ? 0 : (uint32_t)Value.CastInt32(); if (!SetMarkColour(u32)) return false; break; } case SdShowImages: { if (Value.CastInt32()) SetFlags(GetFlags() | MAIL_SHOW_IMAGES); else SetFlags(GetFlags() & ~MAIL_SHOW_IMAGES); break; } case SdSelected: { Select(Value.CastInt32() != 0); break; } case SdLabel: { if (!SetLabel(Value.Str())) return false; break; } case SdFlags: { if (Array) { int Flag = StringToMailFlag(Array); if (Value.CastInt32()) // Set SetFlags(GetFlags() | Flag); else SetFlags(GetFlags() & ~Flag); } else { SetFlags(Value.CastInt32()); } break; } default: { return false; } } SetDirty(); Update(); return true; } bool Mail::CallMethod(const char *MethodName, LVariant *ReturnValue, LArray &Args) { ScribeDomType Fld = StrToDom(MethodName); switch (Fld) { default: break; case SdSend: // Type: ([Bool SendNow = true]) { bool Now = Args.Length() > 0 ? Args[0]->CastInt32() != 0 : true; bool Result = Send(Now); if (ReturnValue) *ReturnValue = Result; return true; } case SdAddCalendarEvent: // Type: ([Bool AddPopupReminder]) { bool AddPopupReminder = Args.Length() > 0 ? Args[0]->CastInt32() != 0 : true; bool Result = AddCalendarEvent(NULL, AddPopupReminder, NULL); if (ReturnValue) *ReturnValue = Result; return true; } case SdGetRead: // Type: () { *ReturnValue = (GetFlags() & MAIL_READ) != 0; return true; } case SdSetRead: // Type: (Bool IsRead = true) { bool Rd = Args.Length() ? Args[0]->CastInt32() != 0 : true; auto Flags = GetFlags(); if (Rd) SetFlags(Flags | MAIL_READ); else SetFlags(Flags & ~MAIL_READ); *ReturnValue = true; return true; } case SdBayesianChange: // Type: (int SpamWordOffset, int HamWordOffset) { if (Args.Length() != 2) { LgiTrace("%s:%i - Invalid arg count, expecting (int SpamWordOffset, int HamWordOffset)\n", _FL); *ReturnValue = false; return true; } auto SpamWordOffset = Args[0]->CastInt32(); auto HamWordOffset = Args[1]->CastInt32(); if (SpamWordOffset > 1) App->OnBayesianMailEvent(this, BayesMailUnknown, BayesMailSpam); else if (SpamWordOffset < 0) App->OnBayesianMailEvent(this, BayesMailSpam, BayesMailUnknown); if (HamWordOffset > 1) App->OnBayesianMailEvent(this, BayesMailUnknown, BayesMailHam); else if (HamWordOffset < 1) App->OnBayesianMailEvent(this, BayesMailHam, BayesMailUnknown); break; } case SdBayesianScore: // Type: () { double Result; if (App->IsSpam(Result, this)) { *ReturnValue = Result; return true; } break; } case SdSearchHtml: // Type: (String SearchExpression) { if (Args.Length() != 2) { LgiTrace("%s:%i - Method needs 1 argument.\n", _FL); *ReturnValue = false; return true; } auto Html = GetHtml(); if (!Html) { LgiTrace("%s:%i - No HTML to parse.\n", _FL); *ReturnValue = false; return true; } // auto Cs = GetHtmlCharset(); SearchHtml(ReturnValue, Html, Args[0]->Str(), Args[1]->Str()); return true; } case SdDeleteAsSpam: // Type: () { DeleteAsSpam(App); return true; } } return Thing::CallMethod(MethodName, ReturnValue, Args); } char *Mail::GetDropFileName() { if (!DropFileName) { LString Subj = GetSubject(); Subj = Subj.Strip(" \t\r\n."); DropFileName.Reset(MakeFileName(ValidStr(Subj) ? Subj.Get() : (char*)"Untitled", "eml")); } return DropFileName; } bool Mail::GetDropFiles(LString::Array &Files) { if (!GetDropFileName()) return false; if (!LFileExists(DropFileName)) { LAutoPtr F(new LFile); if (F->Open(DropFileName, O_WRITE)) { F->SetSize(0); if (!Export(AutoCast(F), sMimeMessage)) return false; } else return false; } if (!LFileExists(DropFileName)) return false; Files.Add(DropFileName.Get()); return true; } OsView Mail::Handle() { return #if LGI_VIEW_HANDLE (Ui) ? Ui->Handle() : #endif NULL; } Thing &Mail::operator =(Thing &t) { Mail *m = t.IsMail(); if (m) { #define CopyStr(dst, src) \ { DeleteArray(dst); dst = NewStr(src); } /* for (LDataPropI *a = m->GetTo()->First(); a; a = m->GetTo()->Next()) { LDataPropI *NewA = GetTo()->Create(Object->GetStore()); if (NewA) { *NewA = *a; GetTo()->Insert(NewA); } } */ if (GetObject() && m->GetObject()) { GetObject()->CopyProps(*m->GetObject()); } else LAssert(!"This shouldn't happen right?"); Update(); } return *this; } bool Mail::HasAlternateHtml(Attachment **Attach) { // New system of storing alternate HTML in a Mail object field. if (GetHtml()) return true; // Old way of storing alternate HTML, in an attachment. // pre v1.53 List Files; if (GetAttachments(&Files)) { for (auto a: Files) { if ((_stricmp(a->GetText(0), "attachment.html") == 0 || _stricmp(a->GetText(0), "index.html") == 0) && (a->GetMimeType() ? _stricmp(a->GetMimeType(), "text/html") == 0 : 1)) { if (Attach) { *Attach = a; } return true; } } } // None found return false; } char *Mail::GetAlternateHtml(List *Refs) { char *Status = 0; Attachment *Attach = 0; if (HasAlternateHtml(&Attach)) { if (GetHtml()) { // New system of storing alternate HTML in a Mail object field. Status = NewStr(GetHtml()); } else if (Attach) { // Old way of storing alternate HTML, in an attachment. // pre v1.53 char *Ptr; ssize_t Size; if (Attach->Get(&Ptr, &Size)) { Status = NewStr((char*)Ptr, Size); } } if (Status && Refs) { // Turn all the cid: references into file names... LStringPipe Out; char *n; for (char *s=Status; *s; s=n) { n = stristr(s, "\"cid:"); if (n) { // Extract cid n++; Out.Push(s, n-s); s = n += 4; while (*n && !strchr("\"\' >", *n)) n++; char *Cid = NewStr(s, n-s); if (Cid) { // Find attachment List Files; if (GetAttachments(&Files)) { for (auto a: Files) { if (a->GetContentId() && strcmp(a->GetContentId(), Cid) == 0) { Refs->Insert(a); Out.Push(a->GetName()); } } } } } else { Out.Push(s); break; } } DeleteArray(Status); Status = Out.NewStr(); } } return Status; } bool Mail::WriteAlternateHtml(char *DstFile, int DstFileLen) { bool Status = false; char *Tmp = ScribeTempPath(); List Refs; char *Html; if (Tmp && (Html = GetAlternateHtml(&Refs))) { char FileName[256]; LMakePath(FileName, sizeof(FileName), Tmp, "Alt.html"); if (DstFile) { strcpy_s(DstFile, DstFileLen, FileName); } LFile f; if (f.Open(FileName, O_WRITE)) { size_t Len = strlen(Html); Status = f.Write(Html, Len) == Len; f.Close(); } DeleteArray(Html); for (auto a: Refs) { LMakePath(FileName, sizeof(FileName), Tmp, a->GetName()); FileDev->Delete(FileName, false); a->SaveTo(FileName); } } return Status; } bool Mail::DeleteAttachment(Attachment *File) { bool Status = false; if (File) { if (Attachments.HasItem(File)) { Attachments.Delete(File); if (File->GetObject()) { auto r = File->GetObject()->Delete(); if (r > Store3Error) { auto o = File->GetObject(); if (o->IsOrphan()) o = NULL; // SetObject will delete... File->SetObject(NULL, false, _FL); DeleteObj(o); } } File->DecRef(); File = NULL; if (Attachments.Length() == 0) { // Remove attachments flag SetFlags(GetFlags() & (~MAIL_ATTACHMENTS)); } Update(); if (Ui) { Ui->OnAttachmentsChange(); } } } return Status; } bool Mail::UnloadAttachments() { for (auto it = Attachments.begin(); it != Attachments.end(); ) { Attachment *a = *it; if (!a->DecRef()) it++; } return true; } bool Mail::GetAttachments(List *Files) { bool Status = false; if (Files) { LArray Lst; if (GetAttachmentObjs(Lst)) { LHashTbl, bool> Loaded; for (size_t i=0; iGetObject(), true); } // Load attachments for (unsigned i=0; iSetOwner(this); Attachments.Insert(k); } } } } for (auto a: Attachments) { if (a->GetObject()->UserData != a) { LAssert(!"Wut?"); } Files->Insert(a); } Status = true; } else { Status = Attachments.Length() > 0; } return Status; } int64 Mail::TotalSizeof() { if (TotalSizeCache < 0) { int Size = GetObject() ? (int)GetObject()->GetInt(FIELD_SIZE) : -1; if (Size >= 0) { TotalSizeCache = Size; } else { TotalSizeCache = ((GetBody()) ? strlen(GetBody()) : 0) + ((GetHtml()) ? strlen(GetHtml()) : 0); List Attachments; if (GetAttachments(&Attachments)) { for (auto a: Attachments) { TotalSizeCache += a->GetSize(); } } } } return TotalSizeCache; } bool Mail::_GetListItems(List &l, bool All) { LList *ParentList = LListItem::Parent; l.Empty(); if (All) { if (ParentList) { ParentList->GetAll(l); } else { l.Insert(this); } } else { if (ParentList) { ParentList->GetSelection(l); } else if (Select()) { l.Insert(this); } } return l.Length() > 0; } void Mail::GetThread(List &Thread) { MContainer *c; for (c=Container; c->Parent; c=c->Parent); List Stack; Stack.Insert(c); while ((c=Stack[0])) { Stack.Delete(c); for (unsigned i=0; iChildren.Length(); i++) Stack.Insert(c->Children[i]); if (c->Message && !Thread.HasItem(c->Message)) { Thread.Insert(c->Message); } } } void Mail::SetListRead(bool Read) { List Sel; if (!_GetListItems(Sel, false)) return; LArray a; for (auto t: Sel) { auto m = dynamic_cast(t); if (!m) continue; bool isRead = (m->GetFlags() & MAIL_READ) != 0; if (Read ^ isRead) a.Add(m->GetObject()); } if (!a.Length()) return; auto Store = a[0]->GetStore(); if (!Store) { LAssert(0); return; } LVariant read = MAIL_READ; Store->Change(a, FIELD_FLAGS, read, Read ? OpPlusEquals : OpMinusEquals); } void SetFolderCallback(LInput *Dlg, LViewI *EditCtrl, void *Param) { ScribeWnd *App = (ScribeWnd*) Param; auto Str = EditCtrl->Name(); - LAutoPtr Select(new FolderDlg(Dlg, App, MAGIC_MAIL, 0, Str)); - if (Select && Select->DoModal()) - { - EditCtrl->Name(Select->Get()); - } + auto Select = new FolderDlg(Dlg, App, MAGIC_MAIL, 0, Str); + Select->DoModal([&](auto dlg, auto id) + { + if (id) + EditCtrl->Name(Select->Get()); + delete dlg; + }); } void Mail::DoContextMenu(LMouse &m, LView *p) { #ifdef _DEBUG LAutoPtr Prof(new LProfile("Mail::DoContextMenu")); Prof->HideResultsIfBelow(100); #endif if (!p) p = Parent; // open the right click menu MarkedState MarkState = MS_None; // Pre-processing for marks List Sel; if (_GetListItems(Sel, false)) { int Marked = 0; for (auto t: Sel) { Mail *m = dynamic_cast(t); if (m) { if (m->GetMarkColour()) { Marked++; } } } if (Marked == 1) { MarkState = MS_One; } else if (Marked) { MarkState = MS_Multiple; } } #ifdef _DEBUG Prof->Add("CreateMenu"); #endif // Create menu LScriptUi s(new LSubMenu); if (s.Sub) { /* Keyboard shortcuts: o - Open d - Delete x - Export e - Set read u - Set unread r - Reply a - Reply all f - Forward b - Bounce m - Mark s - Select marked c - Create filter i - Inspect p - Properties */ LMenuItem *i; s.Sub->SetImageList(App->GetIconImgList(), false); s.Sub->AppendItem(LLoadString(IDS_OPEN), IDM_OPEN, true); i = s.Sub->AppendItem(LLoadString(IDS_DELETE), IDM_DELETE, true); i->Icon(ICON_TRASH); s.Sub->AppendItem(LLoadString(IDS_EXPORT), IDM_EXPORT, true); int AttachBaseMsg = 10000; #ifdef _DEBUG Prof->Add("GetAttachments"); #endif List AttachLst; if (GetAttachments(&AttachLst) && AttachLst[0]) { LSubMenu *Attachments = s.Sub->AppendSub(LLoadString(IDS_SAVE_ATTACHMENT_AS)); if (Attachments) { int n=0; for (auto a: AttachLst) { auto Name = a->GetName(); Attachments->AppendItem(Name?Name:(char*)"Attachment.txt", AttachBaseMsg+(n++), true); } } } s.Sub->AppendSeparator(); #ifdef _DEBUG Prof->Add("Read/Unread"); #endif i = s.Sub->AppendItem(AddAmp(LLoadString(IDS_SET_READ), 'e'), IDM_SET_READ, true); i->Icon(ICON_READ_MAIL); i = s.Sub->AppendItem(AddAmp(LLoadString(IDS_SET_UNREAD), 'u'), IDM_SET_UNREAD, true); i->Icon(ICON_UNREAD_MAIL); s.Sub->AppendSeparator(); #ifdef _DEBUG Prof->Add("Reply/Bounce"); #endif i = s.Sub->AppendItem(AddAmp(LLoadString(IDS_REPLY), 'r'), IDM_REPLY, true); i->Icon(ICON_FLAGS_REPLY); s.Sub->AppendItem(AddAmp(LLoadString(IDS_REPLYALL), 'a'), IDM_REPLY_ALL, true); i = s.Sub->AppendItem(AddAmp(LLoadString(IDS_FORWARD), 'f'), IDM_FORWARD, true); i->Icon(ICON_FLAGS_FORWARD); i = s.Sub->AppendItem(AddAmp(LLoadString(IDS_BOUNCE), 'b'), IDM_BOUNCE, true); i->Icon(ICON_FLAGS_BOUNCE); s.Sub->AppendSeparator(); #ifdef _DEBUG Prof->Add("Thread"); #endif if (App->GetCtrlValue(IDM_THREAD)) { auto Thread = s.Sub->AppendSub(LLoadString(IDS_THREAD)); if (Thread) { Thread->AppendItem(LLoadString(IDS_THREAD_SELECT), IDM_SELECT_THREAD, true); Thread->AppendItem(LLoadString(IDS_THREAD_DELETE), IDM_DELETE_THREAD, true); Thread->AppendItem(LLoadString(IDS_THREAD_IGNORE), IDM_IGNORE_THREAD, true); s.Sub->AppendSeparator(); } } #ifdef _DEBUG Prof->Add("Mark"); #endif auto MarkMenu = s.Sub->AppendSub(AddAmp(LLoadString(IDS_MARK), 'm')); if (MarkMenu) { BuildMarkMenu(MarkMenu, MarkState, (uint32_t)GetMarkColour(), true); } MarkMenu = s.Sub->AppendSub(AddAmp(LLoadString(IDS_SELECT_MARKED), 's')); if (MarkMenu) { BuildMarkMenu(MarkMenu, MS_Multiple, 0, true, true, true); } s.Sub->AppendSeparator(); s.Sub->AppendItem(AddAmp(LLoadString(IDS_INSPECT), 'i'), IDM_INSPECT, true); s.Sub->AppendItem(LLoadString(IDS_PROPERTIES), IDM_PROPERTIES, true); #ifdef _DEBUG Prof->Add("ScriptCallbacks"); #endif LArray Callbacks; if (App->GetScriptCallbacks(LThingContextMenu, Callbacks)) { LScriptArguments Args(NULL); Args[0] = new LVariant(App); Args[1] = new LVariant(this); Args[2] = new LVariant(&s); for (auto c: Callbacks) App->ExecuteScriptCallback(*c, Args); Args.DeleteObjects(); } m.ToScreen(); int Result; #ifdef _DEBUG Prof.Reset(); #endif int Btn = 0; if (m.Left()) Btn = LSubMenu::BtnLeft; else if (m.Right()) Btn = LSubMenu::BtnRight; Result = s.Sub->Float(p, m.x, m.y); switch (Result) { case IDM_OPEN: { DoUI(); break; } case IDM_DELETE: { LVariant ConfirmDelete = false; App->GetOptions()->GetValue(OPT_ConfirmDelete, ConfirmDelete); if (!ConfirmDelete.CastInt32() || LgiMsg(GetList(), LLoadString(IDS_DELETE_ASK), AppName, MB_YESNO) == IDYES) { if (_GetListItems(Sel, false)) { int Index = -1; LList *TheList = LListItem::Parent; LArray Items; for (auto s: Sel) { Mail *m = dynamic_cast(s); if (m) { if (Index < 0) Index = TheList->IndexOf(m); Items.Add(m); } } GetFolder()->Delete(Items, true); if (Index >= 0) { LListItem *i = TheList->ItemAt(Index); if (i) i->Select(true); } } } break; } case IDM_EXPORT: { - ExportAll(Parent, sMimeMessage); + ExportAll(Parent, sMimeMessage, NULL); break; } case IDM_REPLY: case IDM_REPLY_ALL: { App->MailReplyTo(this, Result == IDM_REPLY_ALL); SetDirty(false); break; } case IDM_FORWARD: { App->MailForward(this); SetDirty(false); break; } case IDM_BOUNCE: { App->MailBounce(this); SetDirty(false); break; } case IDM_SET_READ: { SetListRead(true); break; } case IDM_SET_UNREAD: { SetListRead(false); break; } case IDM_INSPECT: { OnInspect(); break; } case IDM_PROPERTIES: { OnProperties(); break; } case IDM_SELECT_THREAD: { if (_GetListItems(Sel, false)) { List Thread; for (auto s: Sel) { Mail *m = dynamic_cast(s); if (m) m->GetThread(Thread); } for (auto m: Thread) { m->Select(true); } } break; } case IDM_DELETE_THREAD: { if (_GetListItems(Sel, false)) { List Thread; for (auto s: Sel) { Mail *m = dynamic_cast(s); if (m) m->GetThread(Thread); } for (auto m: Thread) { m->OnDelete(); } } break; } case IDM_IGNORE_THREAD: { if (_GetListItems(Sel, false)) { List Thread; for (auto s: Sel) { Mail *m = dynamic_cast(s); if (m) m->GetThread(Thread); } for (auto m: Thread) { m->SetFlags(m->GetFlags() | MAIL_IGNORE | MAIL_READ); } } break; } case IDM_UNMARK: case IDM_SELECT_NONE: case IDM_SELECT_ALL: default: { if (Result == IDM_UNMARK || (Result >= IDM_MARK_BASE && Result < IDM_MARK_BASE+CountOf(MarkColours32))) { bool Marked = Result != IDM_UNMARK; if (_GetListItems(Sel, false)) { COLOUR Col32 = Marked ? MarkColours32[Result - IDM_MARK_BASE] : 0; for (auto s: Sel) { Mail *m = dynamic_cast(s); if (m) { if (m->SetMarkColour(Col32)) { if (m->GetObject()->GetInt(FIELD_STORE_TYPE) != Store3Imap) // Imap knows to save itself. m->SetDirty(); } m->Update(); } } } } else if (Result == IDM_SELECT_NONE || Result == IDM_SELECT_ALL || (Result >= IDM_MARK_SELECT_BASE && Result < IDM_MARK_SELECT_BASE+CountOf(MarkColours32))) { bool None = Result == IDM_SELECT_NONE; bool All = Result == IDM_SELECT_ALL; uint32_t c32 = MarkColours32[Result - IDM_MARK_SELECT_BASE]; if (_GetListItems(Sel, true)) { for (auto s: Sel) { Mail *m = dynamic_cast(s); if (!m) break; auto CurCol = m->GetMarkColour(); if (None) { m->Select(CurCol <= 0); } else { if (CurCol > 0) { if (All) { m->Select(true); } else { m->Select(CurCol && CurCol == c32); } } else { m->Select(false); } } } } } else if (Result >= AttachBaseMsg && Result < AttachBaseMsg + (ssize_t)AttachLst.Length()) { // save attachment as... Attachment *a = AttachLst.ItemAt(Result - AttachBaseMsg); if (a) { a->OnSaveAs(Parent); } } else { // Handle any installed callbacks for menu items for (unsigned i=0; iExecuteScriptCallback(Cb, Args); } } } break; } } DeleteObj(s.Sub); } } void Mail::OnMouseClick(LMouse &m) { if (m.Down()) { if (!m.IsContextMenu()) { if (m.Double()) { // open the UI for the Item DoUI(); } } else { DoContextMenu(m); } } } void Mail::OnCreate() { LOptionsFile *Options = App->GetOptions(); LVariant v; if (GetObject()) GetObject()->SetInt(FIELD_FLAGS, MAIL_CREATED); // Get identity and set from info ScribeAccount *Ident = App->GetCurrentAccount(); if (Ident) { v = Ident->Identity.Name(); GetFrom()->SetStr(FIELD_NAME, v.Str()); v = Ident->Identity.Email(); GetFrom()->SetStr(FIELD_EMAIL, v.Str()); v = Ident->Identity.ReplyTo(); if (v.Str()) GetReply()->SetStr(FIELD_REPLY, v.Str()); } else { LAssert(!"No identity selected"); } LVariant EditCtrl; App->GetOptions()->GetValue(OPT_EditControl, EditCtrl); LAutoString Sig = GetSig(EditCtrl.CastInt32() != 0, Ident); if (!Sig && EditCtrl.CastInt32()) { Sig = GetSig(false, Ident); SetBody(Sig); } else if (EditCtrl.CastInt32()) SetHtml(Sig); else SetBody(Sig); LVariant ClipRecip; if (Options->GetValue(OPT_RecipientFromClipboard, ClipRecip) && ClipRecip.CastInt32()) { LClipBoard Clip(App); char *Txt = Clip.Text(); if (Txt && strchr(Txt, '@') && strlen(Txt) < 100) { for (char *s=Txt; *s; s++) { if (*s == '\n' || *s == '\r') { *s = 0; } } Mailto mt(App, Txt); for (auto a: mt.To) { LDataPropI *OldA = dynamic_cast(a); if (OldA) { LDataPropI *NewA = GetTo()->Create(GetObject()->GetStore()); if (NewA) { NewA->CopyProps(*OldA); GetTo()->Insert(NewA); } } } mt.To.Empty(); if (mt.Subject && !ValidStr(GetSubject())) { SetSubject(mt.Subject); } /* if (_strnicmp(Txt, MailToStr, 7) == 0) { char *Question = strchr(Txt + 7, '?'); if (Question) { *Question = 0; Question++; int Len = strlen(SubjectStr); if (_strnicmp(Question, SubjectStr, Len) == 0) { Subject = NewStr(Question + Len); } } La->Addr = NewStr(Txt + 7); } else { La->Addr = NewStr(Txt); } To.Insert(La); */ } } Update(); } void Mail::CreateMailHeaders() { LStringPipe Hdrs(256); MailProtocol Protocol; LVariant HideId; App->GetOptions()->GetValue(OPT_HideId, HideId); Protocol.ProgramName = GetFullAppName(!HideId.CastInt32()); LDataI *Obj = GetObject(); if (Obj && ::CreateMailHeaders(App, Hdrs, Obj, &Protocol)) { LAutoString FullHdrs(Hdrs.NewStr()); Obj->SetStr(FIELD_INTERNET_HEADER, FullHdrs); } else LAssert(!"CreateMailHeaders failed."); } bool Mail::OnBeforeSend(ScribeEnvelope *Out) { if (!Out) { LAssert(!"No output envelope."); return false; } // First check the email from address... if (!ValidStr(GetFrom()->GetStr(FIELD_EMAIL))) { LOptionsFile *Options = App->GetOptions(); if (Options) { ScribeAccount *Ident = App->GetAccounts()->ItemAt(App->GetCurrentIdentity()); if (Ident) { LVariant v = Ident->Identity.Email(); GetFrom()->SetStr(FIELD_EMAIL, v.Str()); v = Ident->Identity.Name(); GetFrom()->SetStr(FIELD_NAME, v.Str()); } } } Out->From = GetFromStr(FIELD_EMAIL); GDataIt To = GetTo(); ContactGroup *Group = NULL; for (LDataPropI *t = To->First(); t; t = To->Next()) { LString Addr = t->GetStr(FIELD_EMAIL); if (LIsValidEmail(Addr)) Out->To.New() = Addr; else if ((Group = LookupContactGroup(App, Addr))) { LString::Array a = Group->GetAddresses(); Out->To.Add(a); } } LDataPropI *Root = GetObject()->GetObj(FIELD_MIME_SEG); if (!Root) { LAssert(!"No root element."); return false; } // Check the headers have been created.. if (!Root->GetStr(FIELD_INTERNET_HEADER)) CreateMailHeaders(); Out->MsgId = GetMessageId(true); // Convert the mime stream LMime Mime(ScribeTempPath()); LTempStream Buf(ScribeTempPath()); Store3ToGMime(&Mime, Root); // Do the encode if (!Mime.Text.Encode.Push(&Buf)) { LAssert(!"Mime encode failed."); return false; } Out->References = GetReferences(); Out->FwdMsgId = GetFwdMsgId(); Out->BounceMsgId = GetBounceMsgId(); // Read the resulting string into the output envelope int Sz = (int)Buf.GetSize(); Out->Rfc822.Length(Sz); Buf.SetPos(0); Buf.Read(Out->Rfc822.Get(), Sz); Out->Rfc822.Get()[Sz] = 0; return true; } void Mail::OnAfterSend() { int f = GetFlags(); f |= MAIL_SENT | MAIL_READ; // set sent flag f &= ~MAIL_READY_TO_SEND; // clear read to send flag // LgiTrace("Setting flags: %x\n", f); SetFlags(f); LDateTime n; n.SetNow(); SetDateSent(&n); // LString s = GetDateSent()->Get(); // LgiTrace("Setting sent date: %s\n", s.Get()); Update(); SetDirty(); if (App) { ScribeFolder *Sent = App->GetFolder(FOLDER_SENT, GetObject()); if (Sent) { LArray Items; Items.Add(this); Sent->MoveTo(Items); } } } bool Mail::OnBeforeReceive() { return true; } enum MimeDecodeMode { MODE_FIELDS, MODE_WAIT_MSG, MODE_SEGMENT, MODE_WAIT_TYPE, MODE_WAIT_BOUNDRY }; #define MF_UNKNOWN 1 #define MF_SUBJECT 2 #define MF_TO 3 #define MF_FROM 4 #define MF_REPLY_TO 5 #define MF_CONTENT_TYPE 6 #define MF_CC 7 #define MF_CONTENT_TRANSFER_ENCODING 9 #define MF_DATE_SENT 10 #define MF_PRIORITY 11 #define MF_COLOUR 12 #define MF_DISPOSITIONNOTIFICATIONTO 13 void MungCharset(Mail *Msg, bool &HasRealCs, ScribeAccount *Acc) { if (ValidStr(Msg->GetBodyCharset())) { HasRealCs = true; } if (Acc) { // LCharset *Cs = 0; if (Msg->GetBodyCharset() && stristr(Msg->GetBodyCharset(), "ascii") && Acc->Receive.AssumeAsciiCharset().Str()) { Msg->SetBodyCharset(Acc->Receive.AssumeAsciiCharset().Str()); HasRealCs = false; } else if (!ValidStr(Msg->GetBodyCharset()) || !LGetCsInfo(Msg->GetBodyCharset())) { Msg->SetBodyCharset(Acc->Receive.Assume8BitCharset().Str()); HasRealCs = false; } } } bool Mail::OnAfterReceive(LStreamI *Msg) { if (!Msg) return false; // Clear the codepage setting here so that we can // check later, down the bottom we must set it to // the default if it's not set anywhere along the // way. SetBodyCharset(0); if (GetObject() && !GetObject()->SetRfc822(Msg)) { LAssert(!"Failed to set mime content."); return false; } // Now parse them into the meta fields... GetObject()->ParseHeaders(); ScribeAccount *Acc = GetAccountSentTo(); LAutoString ContentType; // Fill out any Contact's TimeZone if missing LAutoString DateStr(InetGetHeaderField(GetInternetHeader(), "Date")); LDateTime DateObj; if (DateStr && DateObj.Decode(DateStr)) { double SenderTz = DateObj.GetTimeZoneHours(); if (SenderTz != 0.0) { ListAddr *Sender = dynamic_cast(GetFrom()); if (Sender) { auto It = Sender->begin(); if (*It) { Contact *c = (*It)->GetContact(); if (c) { const char *Tz = ""; if (!c->Get(OPT_TimeZone, Tz) || atof(Tz) != SenderTz) { char s[32]; sprintf_s(s, sizeof(s), "%.1f", SenderTz); c->Set(OPT_TimeZone, s); c->SetDirty(true); } } } } } } auto BodyText = GetBody(); if (BodyText) { if (!GetBodyCharset()) { if (Acc && Acc->Receive.Assume8BitCharset().Str()) { SetBodyCharset(Acc->Receive.Assume8BitCharset().Str()); } else { SetBodyCharset("us-ascii"); } } LStringPipe NewBody; LArray Files; if (DecodeUuencodedAttachment(GetObject()->GetStore(), Files, &NewBody, BodyText)) { LDataI *AttachPoint = GetFileAttachPoint(); if (AttachPoint) { for (unsigned i=0; iSetStr(FIELD_MIME_TYPE, sAppOctetStream); Files[i]->Save(AttachPoint); } SetDirty(!TestFlag(GetFlags(), MAIL_ATTACHMENTS)); SetFlags(GetFlags() | MAIL_ATTACHMENTS); LAutoString n(NewBody.NewStr()); SetBody(n); Update(); if (Ui) Ui->OnAttachmentsChange(); } } } LDateTime n; n.SetNow(); SetDateReceived(&n); Update(); SetDirty(); // Call any 'after receive' callbacks LArray Callbacks; if (App->GetScriptCallbacks(LMailOnAfterReceive, Callbacks)) { for (auto c: Callbacks) { if (!c->Func) continue; LVirtualMachine Vm; LScriptArguments Args(&Vm); Args.New() = new LVariant(App); Args.New() = new LVariant(this); App->ExecuteScriptCallback(*c, Args); Args.DeleteObjects(); } } return true; } ThingUi *Mail::GetUI() { return Ui; } LVariant Mail::GetServerUid() { LVariant v; if (GetObject()) { auto Type = (Store3Backend)GetObject()->GetInt(FIELD_STORE_TYPE); if (Type == Store3Imap) v = GetObject()->GetInt(FIELD_SERVER_UID); else v = GetObject()->GetStr(FIELD_SERVER_UID); } else LAssert(!"No object."); return v; } bool Mail::SetServerUid(LVariant &v) { if (!GetObject()) return false; Store3Status s; if (GetObject()->GetInt(FIELD_STORE_TYPE) == Store3Imap) s = GetObject()->SetInt(FIELD_SERVER_UID, v.CastInt32()); else s = GetObject()->SetStr(FIELD_SERVER_UID, v.Str()); return s > Store3Error; } bool Mail::SetObject(LDataI *o, bool IsDestructor, const char *File, int Line) { bool b = LDataUserI::SetObject(o, IsDestructor, File, Line); if (b) { // Clear out stale attachment objects... UnloadAttachments(); } return b; } bool Mail::SetUI(ThingUi *new_ui) { MailUi *NewMailUi = dynamic_cast(new_ui); if (NewMailUi == Ui) return true; if (Ui) { LWindow *w = Ui; Ui->SetItem(0); w->Quit(); LAssert(Ui == NULL); } if (new_ui) { Ui = dynamic_cast(new_ui); if (Ui) Ui->SetItem(this); else LAssert(!"Incorrect object."); } return true; } ThingUi *Mail::DoUI(MailContainer *c) { if (App && !Ui) { if (App->InThread()) { Ui = new MailUi(this, c ? c : GetFolder()); } else { App->PostEvent(M_SCRIBE_OPEN_THING, (LMessage::Param)this, 0); } } if (Ui) { #if WINNATIVE SetActiveWindow(Ui->Handle()); #endif } return Ui; } int Mail::Compare(LListItem *t, ssize_t Field) { Thing *T = (Thing*)t->_UserPtr; Mail *m = T ? T->IsMail() : 0; if (m) { static int Fields[] = {FIELD_FROM, FIELD_SUBJECT, FIELD_SIZE, FIELD_DATE_RECEIVED}; if (Field < 0) { auto i = -Field - 1; if (i >= 0 && i < CountOf(Fields)) { Field = Fields[i]; } } LVariant v1, v2; const char *s1 = "", *s2 = ""; switch (Field) { case 0: { break; } case FIELD_TO: { LDataPropI *a1 = GetTo()->First(); if (a1) { if (a1->GetStr(FIELD_NAME)) s1 = a1->GetStr(FIELD_NAME); else if (a1->GetStr(FIELD_EMAIL)) s1 = a1->GetStr(FIELD_EMAIL); } LDataPropI *a2 = m->GetTo()->First(); if (a2) { if (a2->GetStr(FIELD_NAME)) s2 = a2->GetStr(FIELD_NAME); else if (a2->GetStr(FIELD_EMAIL)) s2 = a2->GetStr(FIELD_EMAIL); } break; } case FIELD_FROM: { LDataPropI *f1 = GetFrom(); LDataPropI *f2 = m->GetFrom(); if (f1->GetStr(FIELD_NAME)) s1 = f1->GetStr(FIELD_NAME); else if (f1->GetStr(FIELD_EMAIL)) s1 = f1->GetStr(FIELD_EMAIL); if (f2->GetStr(FIELD_NAME)) s2 = f2->GetStr(FIELD_NAME); else if (f2->GetStr(FIELD_EMAIL)) s2 = f2->GetStr(FIELD_EMAIL); break; } case FIELD_SUBJECT: { s1 = GetSubject(); s2 = m->GetSubject(); break; } case FIELD_SIZE: { return (int) (TotalSizeof() - m->TotalSizeof()); break; } case FIELD_DATE_SENT: { auto Sent1 = GetDateSent(); auto Sent2 = m->GetDateSent(); if (!Sent1 || !Sent2) break; return Sent1->Compare(Sent2); break; } case FIELD_DATE_RECEIVED: { return GetDateReceived()->Compare(m->GetDateReceived()); break; } case FIELD_PRIORITY: { return GetPriority() - m->GetPriority(); break; } case FIELD_FLAGS: { int Mask = MAIL_FORWARDED | MAIL_REPLIED | MAIL_READ; return (GetFlags() & Mask) - (m->GetFlags() & Mask); break; } case FIELD_LABEL: { if (GetLabel()) s1 = GetLabel(); if (m->GetLabel()) s2 = m->GetLabel(); break; } case FIELD_MESSAGE_ID: { s1 = GetMessageId(); s2 = m->GetMessageId(); break; } case FIELD_FROM_CONTACT_NAME: { s1 = GetFieldText(FIELD_FROM_CONTACT_NAME); s2 = m->GetFieldText(FIELD_FROM_CONTACT_NAME); break; } case FIELD_SERVER_UID: { v1 = GetServerUid(); v2 = m->GetServerUid(); if (v1.Str() && v2.Str()) { s1 = v1.Str(); s2 = v2.Str(); } else { auto diff = v1.CastInt64() - v2.CastInt64(); if (diff < 0) return -1; return diff > 1; } } default: { s1 = GetObject() ? GetObject()->GetStr((int)Field) : 0; s2 = m->GetObject() ? m->GetObject()->GetStr((int)Field) : 0; break; } } const char *Empty = ""; return _stricmp(s1?s1:Empty, s2?s2:Empty); } return -1; } class MailPropDlg : public LDialog { List Lst; LViewI *Table = NULL; struct FlagInfo { int Flag = 0, Ctrl = 0; void Set(int f, int c) { Flag = f; Ctrl = c; } }; LArray Flags; public: MailPropDlg(LView *Parent, List &lst) { SetParent(Parent); Lst = lst; Flags.New().Set(MAIL_SENT, IDC_SENT); Flags.New().Set(MAIL_RECEIVED, IDC_RECEIVED); Flags.New().Set(MAIL_CREATED, IDC_CREATED); Flags.New().Set(MAIL_FORWARDED, IDC_FORWARDED); Flags.New().Set(MAIL_REPLIED, IDC_REPLIED); Flags.New().Set(MAIL_ATTACHMENTS, IDC_HAS_ATTACH); Flags.New().Set(MAIL_READ, IDC_READ); Flags.New().Set(MAIL_READY_TO_SEND, IDC_READY_SEND); if (LoadFromResource(IDD_MAIL_PROPERTIES)) { GetViewById(IDC_TABLE, Table); LAssert(Table != NULL); SetCtrlEnabled(IDC_OPEN_INSPECTOR, Lst.Length() == 1); for (auto &i: Flags) { int Set = 0; for (auto m: Lst) { if (m->GetFlags() & i.Flag) Set++; } if (Set == Lst.Length()) { SetCtrlValue(i.Ctrl, LCheckBox::CheckOn); } else if (Set) { LCheckBox *Cb; if (GetViewById(i.Ctrl, Cb)) Cb->ThreeState(true); SetCtrlValue(i.Ctrl, LCheckBox::CheckPartial); } } SetCtrlEnabled(IDC_HAS_ATTACH, false); char Msg[512] = ""; if (Lst.Length() == 1) { Mail *m = Lst[0]; auto mObj = m->GetObject(); if (mObj) { auto dt = mObj->GetDate(FIELD_DATE_RECEIVED); sprintf_s( Msg, sizeof(Msg), LLoadString(IDS_MAIL_PROPS_DLG), LFormatSize(mObj->GetInt(FIELD_SIZE)).Get(), dt ? dt->Get().Get() : LLoadString(IDS_NONE), mObj->GetStr(FIELD_DEBUG)); SetCtrlName(IDC_MSG_ID, mObj->GetStr(FIELD_MESSAGE_ID)); } } else { sprintf_s(Msg, sizeof(Msg), LLoadString(IDS_MULTIPLE_ITEMS), Lst.Length()); SetCtrlEnabled(IDC_MSG_ID, false); } SetCtrlName(IDC_MAIL_DESCRIPTION, Msg); MoveSameScreen(Parent); } } void OnPosChange() { if (Table) { LRect r = GetClient(); r.Inset(LTableLayout::CellSpacing, LTableLayout::CellSpacing); Table->SetPos(r); } } int OnNotify(LViewI *Ctr, LNotification n) { switch (Ctr->GetId()) { case IDC_OPEN_INSPECTOR: { if (Lst.Length()) Lst[0]->OnInspect(); break; } case IDOK: { for (auto &i: Flags) { auto v = GetCtrlValue(i.Ctrl); LAssert(v >= 0); // the control couldn't be found... check the .lr8 file for (auto m: Lst) { if (v == 1) m->SetFlags(m->GetFlags() | i.Flag); else if (v == 0) m->SetFlags(m->GetFlags() & ~i.Flag); } } // fall thru } case IDCANCEL: { EndModal(Ctr->GetId()); break; } } return 0; } }; void Mail::OnInspect() { new ObjectInspector(App, this); } void Mail::OnProperties(int Tab) { List Sel; if (GetList()->GetSelection(Sel)) { List Lst; for (auto i: Sel) { Lst.Insert(dynamic_cast(i)); } if (Lst[0]) { - MailPropDlg Dlg(GetList(), Lst); - if (Dlg.DoModal() == IDOK) - { - SetDirty(); - Update(); - } + auto Dlg = new MailPropDlg(GetList(), Lst); + Dlg->DoModal([this, Dlg](auto dlg, auto id) + { + if (id == IDOK) + { + SetDirty(); + Update(); + } + delete dlg; + }); } } } int Mail::GetImage(int SelFlags) { if (TestFlag(GetFlags(), MAIL_READY_TO_SEND) && !TestFlag(GetFlags(), MAIL_SENT)) { return ICON_UNSENT_MAIL; } if (GetFlags() & MAIL_READ) { return (GetFlags() & MAIL_ATTACHMENTS) ? ICON_READ_ATT_MAIL : ICON_READ_MAIL; } else { return (GetFlags() & MAIL_ATTACHMENTS) ? ICON_UNREAD_ATT_MAIL : ICON_UNREAD_MAIL; } return ICON_READ_MAIL; } int *Mail::GetDefaultFields() { return DefaultMailFields; } void Mail::DeleteAsSpam(LView *View) { // Set read.. SetFlags(GetFlags() | MAIL_READ | MAIL_BAYES_SPAM, true); // Remove from NewMail NewMailLst.Delete(this); LVariant DeleteOnServer, DeleteAttachments, SetRead; auto Opts = App->GetOptions(); if (!Opts->GetValue(OPT_BayesDeleteOnServer, DeleteOnServer)) DeleteOnServer = false; if (!Opts->GetValue(OPT_BayesDeleteAttachments, DeleteAttachments)) DeleteAttachments = false; if (!Opts->GetValue(OPT_BayesSetRead, SetRead)) SetRead = false; if (DeleteOnServer.CastBool()) { // Tell the account it's spam... so that any further connects can // delete it off the server. ScribeAccount *a = GetAccountSentTo(); if (a) { auto ServerUid = GetServerUid(); if (ServerUid.Str()) { a->Receive.DeleteAsSpam(ServerUid.Str()); SetServerUid(ServerUid = NULL); } else { LAutoString Uid(InetGetHeaderField(GetInternetHeader(), "X-UIDL")); if (Uid) a->Receive.DeleteAsSpam(Uid); } } else { #if 0 // def _DEBUG if (LgiMsg(Ui?(LView*)Ui:(LView*)App, "Debug: GetAccountSentTo failed. Debug?", AppName, MB_YESNO) == IDYES) { LAssert(0); } #endif } } if (DeleteAttachments.CastBool()) { // Delete all attachments... they're useless and more than likely // just virii anyway. List Files; if (GetAttachments(&Files)) { for (auto a: Files) { DeleteAttachment(a); } Files.Empty(); } } if (SetRead.CastInt32() != 0) { SetFlags(GetFlags() | MAIL_READ); } // Move it to the spam folder if it exists. auto FolderPath = GetFolder()->GetPath(); LToken Parts(FolderPath, "/"); if (Parts.Length() == 0) LgiMsg(View, "Error: No folder path?", AppName); else { LString SpamPath; LString SpamLeaf = "Spam"; SpamPath.Printf("/%s/%s", Parts[0], SpamLeaf.Get()); ScribeFolder *Spam = App->GetFolder(SpamPath); if (!Spam) { LMailStore *Ms = App->GetMailStoreForPath(FolderPath); if (!Ms) { if (GetFolder()->GetObject()->GetInt(FIELD_STORE_TYPE) == Store3Imap) { Ms = App->GetDefaultMailStore(); if (Ms && Ms->Root) { Spam = Ms->Root->GetSubFolder(SpamLeaf); if (!Spam) Spam = Ms->Root->CreateSubDirectory(SpamLeaf, MAGIC_MAIL); } } else LgiMsg(View, "Error: Couldn't get mail store for '%s'.", AppName, MB_OK, FolderPath.Get()); } else Spam = Ms->Root->CreateSubDirectory("Spam", MAGIC_MAIL); } if (Spam && Spam != GetFolder()) { LArray Items; Items.Add(this); if (!Spam->MoveTo(Items)) { LgiMsg(View, "Error: Couldn't move email to spam folder.", AppName); } } } } void Mail::SetFlagsCache(int64_t NewFlags, bool IgnoreReceipt, bool UpdateScreen) { if (FlagsCache == NewFlags) return; DepthCheck Depth(d->InSetFlagsCache); bool Read1 = TestFlag(FlagsCache, MAIL_READ); bool Read2 = TestFlag(NewFlags, MAIL_READ); bool ChangeRead = Read1 ^ Read2; // LgiTrace("%p::SetFlagsCache: %s -> %s\n", this, EmailFlagsToStr(FlagsCache).Get(), EmailFlagsToStr(StoreFlags).Get()); FlagsCache = NewFlags; if (ChangeRead && App) { if (Read2) { // Becoming read List Objs; Objs.Insert(this); App->OnNewMail(&Objs, false); PreviewCache.DeleteObjects(); // Read receipt if (!IgnoreReceipt && !TestFlag(NewFlags, MAIL_CREATED) && TestFlag(NewFlags, MAIL_READ_RECEIPT)) { LAutoString Header(InetGetHeaderField(GetInternetHeader(), "Disposition-Notification-To")); if (Header) { LAutoString Name, Addr; DecodeAddrName(Header, Name, Addr, 0); if (Addr) { if (LgiMsg( Ui != 0 ? (LView*)Ui : (LView*)App, LLoadString(IDS_RECEIPT_ASK), AppName, MB_YESNO, GetSubject(), GetFrom()->GetStr(FIELD_NAME), GetFrom()->GetStr(FIELD_EMAIL), (char*)Name, (char*)Addr) == IDYES) { Mail *m = new Mail(App); if (m) { m->App = App; LDataPropI *ToAddr = m->GetTo()->Create(m->GetObject()->GetStore()); if (ToAddr) { ToAddr->SetStr(FIELD_NAME, Name); ToAddr->SetStr(FIELD_EMAIL, Addr); m->GetTo()->Insert(ToAddr); } m->OnReceipt(this); m->Save(); App->Send(); } } } } } LVariant Inc; if (App->GetOptions()->GetValue(OPT_BayesIncremental, Inc) && Inc.CastInt32()) { // Incremental bayesian update App->OnBayesianMailEvent(this, BayesMailUnknown, BayesMailHam); } } if (UpdateScreen) { // Changing read status ScribeFolder *t = GetFolder(); if (t) t->OnUpdateUnRead(0, true); } } if (UpdateScreen || ChangeRead) { Update(); } } uint32_t Mail::GetFlags() { if (GetObject()) { // Make sure the objects are in sync... auto StoreFlags = GetObject()->GetInt(FIELD_FLAGS); if (FlagsCache < 0) FlagsCache = StoreFlags; else if (FlagsCache != StoreFlags) SetFlagsCache(StoreFlags, false, true); } return (uint32_t)FlagsCache; } void Mail::SetFlags(ulong NewFlags, bool IgnoreReceipt, bool UpdateScreen) { if (FlagsCache == NewFlags) return; DepthCheck SetFlagsDepth(d->InSetFlags); if (GetObject()) { Store3Status Result = GetObject()->SetInt(FIELD_FLAGS, NewFlags); if (Result == Store3Success && GetObject()->IsOnDisk()) { // Imap mail shouldn't fall in here. SetDirty(); } } else { LAssert(0); return; } SetFlagsCache(NewFlags, IgnoreReceipt, UpdateScreen); } const char *Mail::GetFieldText(int Field) { static char Buf[512]; switch (Field) { case FIELD_TO: { size_t ch = 0; Buf[0] = 0; GDataIt To = GetTo(); for (LDataPropI *a=To->First(); a; a=To->Next()) { if (ch > 0) ch += sprintf_s(Buf+ch, sizeof(Buf)-ch, ", "); auto Name = a->GetStr(FIELD_NAME); auto Addr = a->GetStr(FIELD_EMAIL); auto n = Name ? Name : Addr; if (!n) continue; // Is the buffer too small? size_t n_len = strlen(n); if (ch + n_len + 8 >= sizeof(Buf)) { // Yes... just truncate it with "..." and bail. ch += sprintf_s(Buf+ch, sizeof(Buf)-ch, "..."); break; } ch += sprintf_s(Buf+ch, sizeof(Buf)-ch, "%s", n); LAssert(ch < sizeof(Buf) - 1); } return Buf; break; } case FIELD_FROM_CONTACT_NAME: { static bool InMethod = false; if (!InMethod) { InMethod = true; LDataPropI *la = GetFrom(); if (la) { auto Email = la->GetStr(FIELD_EMAIL); Contact *c = Contact::LookupEmail(Email); if (c) { const char *f = 0, *l = 0; c->Get(OPT_First, f); c->Get(OPT_Last, l); if (f && l) sprintf_s(Buf, sizeof(Buf), "%s %s", f, l); else if (f) strcpy_s(Buf, sizeof(Buf), f); else if (l) strcpy_s(Buf, sizeof(Buf), l); else Buf[0] = 0; InMethod = false; return Buf; } } InMethod = false; } else { LAssert(0); } // fall through } case FIELD_FROM: { LDataPropI *f = GetFrom(); auto n = f->GetStr(FIELD_NAME); if (n) return n; auto e = f->GetStr(FIELD_EMAIL); if (e) return e; break; } case FIELD_SUBJECT: return GetSubject(); case FIELD_SIZE: { if (TotalSizeCache < 0) TotalSizeof(); if (OptionSizeInKiB) { int ch = sprintf_s(Buf, sizeof(Buf), "%.0f KiB", (double)TotalSizeCache/1024.0); if (ch > 4 + 3) { int digits = ch - 4; char *s = Buf + ((digits % 3) ? digits % 3 : 3); char *e = Buf + ch - 4; while (s < e) { memmove(s + 1, s, strlen(s)+1); *s = ','; s += 3; } } } else LFormatSize(Buf, sizeof(Buf), TotalSizeCache); return Buf; } case FIELD_DATE_SENT: { auto DateSent = GetDateSent(); if (DateSent->Year()) { LDateTime dt = *DateSent; if (GetObject() && GetObject()->GetInt(FIELD_STORE_TYPE) == Store3Imap) { ValidateImapDate(dt); } if (AdjustDateTz) { if (dt.GetTimeZone() != LDateTime::SystemTimeZone()) dt.SetTimeZone(LDateTime::SystemTimeZone(), true); } if (ShowRelativeDates) { auto rel = RelativeTime(dt); strcpy_s(Buf, sizeof(Buf), rel ? rel : "#error:RelativeTime"); } else { dt.Get(Buf, sizeof(Buf)); } } else { sprintf_s(Buf, sizeof(Buf), "(%s)", LLoadString(IDS_NONE, "None")); } return Buf; } case FIELD_DATE_RECEIVED: { auto DateReceived = GetDateReceived(); if (DateReceived->Year()) { LDateTime dt = *DateReceived; if (AdjustDateTz) { if (dt.GetTimeZone() != LDateTime::SystemTimeZone()) dt.SetTimeZone(LDateTime::SystemTimeZone(), true); } dt.Get(Buf, sizeof(Buf)); if (ShowRelativeDates) { auto rel = RelativeTime(dt); strcpy_s(Buf, sizeof(Buf), rel ? rel : "#error:RelativeTime"); } else { dt.Get(Buf, sizeof(Buf)); } } else { sprintf_s(Buf, sizeof(Buf), "(%s)", LLoadString(IDS_NONE, "None")); } return Buf; } case FIELD_TEXT: return GetBody(); case FIELD_MESSAGE_ID: return GetMessageId(); case FIELD_INTERNET_HEADER: return GetInternetHeader(); case FIELD_ALTERNATE_HTML: return GetHtml(); case FIELD_LABEL: return GetLabel(); case FIELD_PRIORITY: case FIELD_FLAGS: return 0; case FIELD_SERVER_UID: sprintf_s(Buf, sizeof(Buf), "%i", GetObject() ? (int)GetObject()->GetInt(Field) : -1); return Buf; case FIELD_RECEIVED_DOMAIN: { if (!d->DomainCache) { if (!GetObject()) return NULL; auto hdr = GetObject()->GetStr(FIELD_INTERNET_HEADER); if (!hdr) return NULL; for (auto ln: LString(hdr).SplitDelimit("\n")) { if (ln.Find("Received: from") == 0) { auto p = ln.SplitDelimit(); if (p.Length() > 2) { auto t = p[2]; if (t.Find(".") > 0) d->DomainCache = t.Strip("[]"); } } } } return d->DomainCache; } default: return GetObject() ? GetObject()->GetStr(Field) : NULL; } return 0; } int Mail::DefaultMailFields[] = { /* FIELD_FLAGS, FIELD_PRIORITY, */ FIELD_FROM, FIELD_SUBJECT, FIELD_SIZE, FIELD_DATE_SENT }; const char *Mail::GetText(int i) { if (FieldArray.Length()) { if (i >= 0 && i < (int)FieldArray.Length()) return GetFieldText(FieldArray[i]); } else if (i < CountOf(DefaultMailFields)) { return GetFieldText(DefaultMailFields[i]); } return NULL; } LDataI *Mail::GetFileAttachPoint() { if (!GetObject()) { LAssert(!"No object ptr."); return 0; } // Check existing root node for "multipart/mixed"? LDataI *r = dynamic_cast(GetObject()->GetObj(FIELD_MIME_SEG)); if (!r) { // Create one... r = GetObject()->GetStore()->Create(MAGIC_ATTACHMENT); if (!r) { LAssert(!"No MIME segment ptr."); return NULL; } r->SetStr(FIELD_MIME_TYPE, sMultipartMixed); if (!GetObject()->SetObj(FIELD_MIME_SEG, r)) { LAssert(!"Can't set MIME segment."); return NULL; } } auto Mt = r->GetStr(FIELD_MIME_TYPE); if (Mt && !_stricmp(Mt, sMultipartMixed)) { // Yes is it mixed... return that... return r; } // No, a different type of segment, make the parent seg a "mixed", and attach the old segment to the mixed LDataI *Mixed = GetObject()->GetStore()->Create(MAGIC_ATTACHMENT); if (!Mixed) { LAssert(!"Failed to create MAGIC_ATTACHMENT."); return 0; } // Set the type... Mixed->SetStr(FIELD_MIME_TYPE, sMultipartMixed); // Reparent the content to the mixed if (!r->Save(Mixed)) { LAssert(!"Can't reparent the content into the mixed seg."); return 0; } // Reparent the mixed to the mail object if (!Mixed->Save(GetObject())) { LAssert(!"Can't reparent the mixed seg into the mail object."); return 0; } return Mixed; } bool Mail::AttachFile(Attachment *File) { bool Status = false; if (File && !Attachments.HasItem(File)) { LDataI *AttachPoint = GetFileAttachPoint(); if (AttachPoint) { if (File->GetObject()->Save(AttachPoint)) { File->App = App; File->SetOwner(this); Attachments.Insert(File); Status = true; SetDirty(!TestFlag(GetFlags(), MAIL_ATTACHMENTS)); SetFlags(GetFlags() | MAIL_ATTACHMENTS); Update(); if (Ui) { Ui->OnAttachmentsChange(); } } } else { File->DecRef(); } } return Status; } Attachment *Mail::AttachFile(LView *Parent, const char *FileName) { LString MimeType = ScribeGetFileMimeType(FileName); LVariant Resize = false; if (MimeType && !_strnicmp(MimeType, "image/", 6)) { // Check if we have to do a image resize App->GetOptions()->GetValue(OPT_ResizeImgAttachments, Resize); } LDataI *AttachPoint = GetFileAttachPoint(); if (!AttachPoint) { LAssert(!"No attachment point in MIME heirarchy for file"); return NULL; } Attachment *File = new Attachment( App, GetObject()->GetStore()->Create(MAGIC_ATTACHMENT), FileName); if (File) { File->App = App; if (Resize.CastInt32() || File->GetSize() > 0) { File->SetOwner(this); Attachments.Insert(File); if (Resize.CastInt32()) d->AddResizeImage(File); else File->GetObject()->Save(AttachPoint); SetFlags(GetFlags() | MAIL_ATTACHMENTS); Update(); if (Ui) Ui->OnAttachmentsChange(); } else { LgiMsg(Parent, LLoadString(IDS_ERROR_FILE_EMPTY), AppName); File->DecRef(); File = NULL; } } return File; } bool Mail::Save(ScribeFolder *Into) { bool Status = false; if (!Into) { if (GetFolder()) Into = GetFolder(); else Into = App->GetFolder(FOLDER_OUTBOX); } if (Into) { ScribeFolder *Old = GetFolder(); if (!GetFolder()) { SetParentFolder(Into); } else if (GetFolder() != Into) { // If this fails, you should really by using ScribeFolder::MoveTo to move // the item from it's existing location to the new folder... LAssert(!"Use MoveTo instead."); return false; } Store3Status Result = Into->WriteThing(this); if (Result != Store3Error) { Status = true; SetDirty(false); if (Into && Into == App->GetFolder(FOLDER_TEMPLATES, NULL, true)) { // saving into the templates folder... update the menu App->BuildDynMenus(); } if (Status && DropFileName) { FileDev->Delete(DropFileName, false); DropFileName.Reset(); } } else { SetParentFolder(Old); } } // This frees the resizer thread... // so they aren't hanging around pointlessly d->OnSave(); return Status; } struct WrapQuoteBlock { int Depth; LArray Text; }; void WrapAndQuote( LStream &Pipe, const char *Quote, int Wrap, const char *Text, const char *Cp, const char *MimeType) { int IsHtml = MimeType && !_stricmp(MimeType, sTextHtml); LAutoString In((char*) LNewConvertCp("utf-8", Text, Cp ? Cp : (char*)"utf-8")); const char *QuoteStr = Quote; if (In) { size_t QuoteChars = Strlen(QuoteStr); char NewLine[8]; int NewLineLen = sprintf_s(NewLine, sizeof(NewLine), "%s", IsHtml ? "
\n" : "\n"); // Two step process, parse all the input into an array of paragraph blocks and then output each block // in wrapped form LArray Para; // 1) Parse all input into paragraphs char *i = In; while (*i) { // Parse out the next line... char *e = i; while (*e && *e != '\n') e++; ssize_t len = e - i; int depth = 0; for (int n=0; n') depth++; else if (i[n] != ' ' || i[n] != '\t') break; } if (Para.Length()) { // Can this be added to the previous paragraph? WrapQuoteBlock &Prev = Para[Para.Length()-1]; if (Prev.Depth == depth) { // Yes?! Prev.Text.Add(NewStr(i, len)); i = *e ? e + 1 : e; continue; } } // Create a new paragraph WrapQuoteBlock &New = Para.New(); New.Depth = depth; New.Text.Add(NewStr(i, len)); // Move current position to next line i = *e ? e + 1 : e; } // 2) Output all paragraphs const char *PrefixCharacters = "> \t"; for (unsigned n=0; n 0); if (WordLen == 0) break; // This won't end well... if (Wrap > 0) { // Check if we can put this word on the current line without making it longer than 'Wrap' if (CharPos + WordLen > Wrap) { // No it won't fit... so emit [NewLine][QuoteStr][Prefix] Pipe.Write(NewLine, NewLineLen); if (QuoteStr) Pipe.Write(QuoteStr, QuoteChars * sizeof(*QuoteStr)); Pipe.Write(Prefix, PrefixChars * sizeof(*Prefix)); // Now we are ready to write more words... CharPos = QuoteChars + PrefixChars; } // else Yes it fits... } // Write out the word... if (IsHtml && Strnchr(Start, '<', WordLen)) { for (auto *c = Start; c < Start + WordLen; c++) if (*c == '<') Pipe.Print("<"); else if (*c == '>') Pipe.Print(">"); else Pipe.Write(c, sizeof(*c)); } else Pipe.Write(Start, WordLen * sizeof(*Start)); CharPos += WordLen; Start = End; } if (HardNewLine) { Pipe.Write(NewLine, NewLineLen); if (QuoteStr) Pipe.Write(QuoteStr, QuoteChars * sizeof(*QuoteStr)); Pipe.Write(Prefix, PrefixChars * sizeof(*Prefix)); CharPos = QuoteChars + PrefixChars; } } // Finish the paragraph Pipe.Write(NewLine, NewLineLen); } } } void Mail::WrapAndQuote(LStringPipe &p, const char *Quote, int WrapAt) { LString Mem; LAutoString Cp; if (!ValidStr(GetBody())) Mem = HtmlToText(GetHtml(), GetHtmlCharset()); else Cp = GetCharSet(); ::WrapAndQuote(p, Quote, WrapAt > 0 ? WrapAt : 76, Mem ? Mem.Get() : GetBody(), Cp); } LAutoString Mail::GetSig(bool HtmlVersion, ScribeAccount *Account) { LAutoString r; LVariant Xml; if (!Account) Account = GetAccountSentTo(); if (Account) { if (HtmlVersion) Xml = Account->Identity.HtmlSig(); else Xml = Account->Identity.TextSig(); } if (Xml.Str()) { r = App->ProcessSig(this, Xml.Str(), HtmlVersion ? sTextHtml : sTextPlain); } return r; } void Mail::ProcessTextForResponse(Mail *From, LOptionsFile *Options, ScribeAccount *Account) { LStringPipe Temp; LVariant QuoteStr, Quote, WrapAt; Options->GetValue(OPT_QuoteReply, Quote); Options->GetValue(OPT_WrapAtColumn, WrapAt); Options->GetValue(OPT_QuoteReplyStr, QuoteStr); From->WrapAndQuote(Temp, Quote.CastInt32() ? QuoteStr.Str() : NULL, WrapAt.CastInt32()); LAutoString s = From->GetSig(false, Account); if (s) Temp.Write(s, strlen(s)); s.Reset(Temp.NewStr()); SetBody(s); SetBodyCharset("utf-8"); } ScribeAccount *Mail::GetAccountSentTo() { ScribeAccount *Account = App->GetAccountById(GetAccountId()); if (!Account) { // Older style lookup based on to address... for (auto a : *App->GetAccounts()) { LVariant e = a->Identity.Email(); if (ValidStr(e.Str())) { for (LDataPropI *t=GetTo()->First(); t; t=GetTo()->Next()) { auto Addr = t->GetStr(FIELD_EMAIL); if (ValidStr(Addr)) { if (_stricmp(Addr, e.Str()) == 0) { Account = a; break; } } } } } } return Account; } void Mail::OnReceipt(Mail *m) { if (m) { SetFlags(MAIL_CREATED | MAIL_READY_TO_SEND); ScribeAccount *MatchedAccount = m->GetAccountSentTo(); if (!MatchedAccount) { MatchedAccount = App->GetAccounts()->ItemAt(App->GetCurrentIdentity()); } if (MatchedAccount) { GetFrom()->SetStr(FIELD_NAME, MatchedAccount->Identity.Name().Str()); GetFrom()->SetStr(FIELD_EMAIL, MatchedAccount->Identity.Email().Str()); } char Sent[64]; m->GetDateReceived()->Get(Sent, sizeof(Sent)); char Read[64]; LDateTime t; t.SetNow(); t.Get(Read, sizeof(Read)); char s[256]; sprintf_s(s, sizeof(s), LLoadString(IDS_RECEIPT_BODY), GetFrom()->GetStr(FIELD_NAME), GetFrom()->GetStr(FIELD_EMAIL), Sent, m->GetSubject(), Read); SetBody(s); sprintf_s(s, sizeof(s), "Read: %s", m->GetSubject() ? m->GetSubject() : (char*)""); SetSubject(s); } } LString Mail::GetMailRef() { LString Ret; if (auto MsgId = GetMessageId(true)) { LAssert(!strchr(MsgId, '\n')); ScribeFolder *Fld = GetFolder(); LString FldPath; if (Fld) FldPath = Fld->GetPath(); if (FldPath) { LUri u; auto a = u.EncodeStr(MsgId, MsgIdEncodeChars); if (a) Ret.Printf("%s/%s", FldPath.Get(), a.Get()); } else { Ret = MsgId; } } return Ret; } void Mail::OnReply(Mail *m, bool All, bool MarkOriginal) { if (!m) return; SetWillDirty(false); LVariant ReplyAllSetting = MAIL_ADDR_TO; if (All) { App->GetOptions()->GetValue(OPT_DefaultReplyAllSetting, ReplyAllSetting); } if (MarkOriginal) { // source email has been replyed to m->SetFlags(m->GetFlags() | MAIL_READ); } // this email is ready to send SetFlags(GetFlags() | MAIL_READ | MAIL_CREATED); LDataPropI *ToAddr = GetTo()->Create(GetObject()->GetStore()); if (ToAddr) { bool CopyStatus; auto From = m->GetFrom(); auto Reply = m->GetReply(); auto ReplyTo = Reply->GetStr(FIELD_EMAIL); if (ReplyTo) CopyStatus = ToAddr->CopyProps(*Reply); else CopyStatus = ToAddr->CopyProps(*From); LAssert(CopyStatus); ToAddr->SetInt(FIELD_CC, ReplyAllSetting.CastInt32()); GetTo()->Insert(ToAddr); } LVariant UserName, EmailAddr, ReplyTo; ScribeAccount *a = App->GetCurrentAccount(); if (a) { UserName = a->Identity.Name(); EmailAddr = a->Identity.Email(); ReplyTo = a->Identity.ReplyTo(); } else LAssert(!"No current account."); if (All) { for (LDataPropI *a = m->GetTo()->First(); a; a = m->GetTo()->Next()) { bool Same = App->IsMyEmail(a->GetStr(FIELD_EMAIL)); bool AlreadyThere = false; for (LDataPropI *r = GetTo()->First(); r; r=GetTo()->Next()) { if (_stricmp(r->GetStr(FIELD_EMAIL), a->GetStr(FIELD_EMAIL)) == 0) { AlreadyThere = true; break; } } if (!Same && !AlreadyThere) { LDataPropI *New = GetTo()->Create(GetObject()->GetStore()); if (New) { New->SetInt(FIELD_CC, ReplyAllSetting.CastInt32()); New->SetStr(FIELD_EMAIL, a->GetStr(FIELD_EMAIL)); New->SetStr(FIELD_NAME, a->GetStr(FIELD_NAME)); GetTo()->Insert(New); } } } } ScribeAccount *MatchedAccount = m->GetAccountSentTo(); if (MatchedAccount && MatchedAccount->Identity.Email().Str()) { GetFrom()->SetStr(FIELD_NAME, MatchedAccount->Identity.Name().Str()); GetFrom()->SetStr(FIELD_EMAIL, MatchedAccount->Identity.Email().Str()); ReplyTo = MatchedAccount->Identity.ReplyTo(); if (ReplyTo.Str()) { GetReply()->SetStr(FIELD_NAME, MatchedAccount->Identity.Name().Str()); GetReply()->SetStr(FIELD_EMAIL, ReplyTo.Str()); } } else { GetFrom()->SetStr(FIELD_NAME, UserName.Str()); GetFrom()->SetStr(FIELD_EMAIL, EmailAddr.Str()); if (ValidStr(ReplyTo.Str())) { GetReply()->SetStr(FIELD_NAME, UserName.Str()); GetReply()->SetStr(FIELD_EMAIL, ReplyTo.Str()); } } char Re[32]; sprintf_s(Re, sizeof(Re), "%s:", LLoadString(IDS_REPLY_PREFIX)); auto SrcSubject = (m->GetSubject()) ? m->GetSubject() : ""; if (_strnicmp(SrcSubject, Re, strlen(Re)) != 0) { size_t Len = strlen(SrcSubject) + strlen(Re) + 2; char *s = new char[Len]; if (s) { sprintf_s(s, Len, "%s %s", Re, SrcSubject); SetSubject(s); DeleteArray(s); } } else { SetSubject(m->GetSubject()); } const char *EditMimeType = App->EditCtrlMimeType(); LAutoString Xml = App->GetReplyXml(EditMimeType); if (ValidStr(Xml)) { RemoveReturns(Xml); PreviousMail = m; LString Body = App->ProcessReplyForwardTemplate(m, this, Xml, Cursor, EditMimeType); LVariant EditCtrl; if (App->GetOptions()->GetValue(OPT_EditControl, EditCtrl) && EditCtrl.CastInt32()) { SetHtml(Body); } else { SetBody(Body); SetBodyCharset("utf-8"); } PreviousMail = 0; } else { ProcessTextForResponse(m, App->GetOptions(), MatchedAccount); } // Reference SetReferences(0); auto MailRef = m->GetMailRef(); if (MailRef) SetReferences(MailRef); GetMessageId(true); SetWillDirty(true); ScribeFolder *Folder = m->GetFolder(); if (Folder && Folder->IsPublicFolders()) { SetParentFolder(Folder); } } bool Mail::OnForward(Mail *m, bool MarkOriginal, int WithAttachments) { if (!m) { LAssert(!"No mail object."); LgiTrace("%s:%i - No mail object.\n", _FL); return false; } // ask about attachments? List Attach; if (m->GetAttachments(&Attach) && Attach.Length() > 0) { if (WithAttachments < 0) { LView *p = (m->Ui)?(LView*)m->Ui:(LView*)App; int Result = LgiMsg(p, LLoadString(IDS_ASK_FORWARD_ATTACHMENTS), AppName, MB_YESNOCANCEL); if (Result == IDYES) { WithAttachments = 1; } else if (Result == IDCANCEL) { return false; } } } if (MarkOriginal) { // source email has been forwarded auto MailRef = m->GetMailRef(); if (MailRef) SetFwdMsgId(MailRef); } // this email is ready to send SetFlags(GetFlags() | MAIL_CREATED); SetDirty(); LVariant UserName, EmailAddr, ReplyTo; ScribeAccount *a = App->GetCurrentAccount(); if (a) { UserName = a->Identity.Name(); EmailAddr = a->Identity.Email(); ReplyTo = a->Identity.ReplyTo(); } else LAssert(!"No current account."); ScribeAccount *MatchedAccount = m->GetAccountSentTo(); if (MatchedAccount && MatchedAccount->Identity.Email().Str()) { GetFrom()->SetStr(FIELD_NAME, MatchedAccount->Identity.Name().Str()); GetFrom()->SetStr(FIELD_EMAIL, MatchedAccount->Identity.Email().Str()); ReplyTo = MatchedAccount->Identity.ReplyTo(); if (ValidStr(ReplyTo.Str())) { GetReply()->SetStr(FIELD_NAME, MatchedAccount->Identity.Name().Str()); GetReply()->SetStr(FIELD_EMAIL, ReplyTo.Str()); } } else { GetFrom()->SetStr(FIELD_NAME, UserName.Str()); GetFrom()->SetStr(FIELD_EMAIL, EmailAddr.Str()); if (ValidStr(ReplyTo.Str())) { GetReply()->SetStr(FIELD_NAME, UserName.Str()); GetReply()->SetStr(FIELD_EMAIL, ReplyTo.Str()); } } SetBodyCharset(0); char PostFix[32]; sprintf_s(PostFix, sizeof(PostFix), "%s:", LLoadString(IDS_FORWARD_PREFIX)); auto SrcSubject = (m->GetSubject()) ? m->GetSubject() : ""; if (_strnicmp(SrcSubject, PostFix, strlen(PostFix)) != 0) { size_t Len = strlen(SrcSubject) + strlen(PostFix) + 2; char *s = new char[Len]; if (s) { sprintf_s(s, Len, "%s %s", PostFix, SrcSubject); SetSubject(s); DeleteArray(s); } } else { SetSubject(m->GetSubject()); } // Wrap/Quote/Sig the text... const char *EditMimeType = App->EditCtrlMimeType(); LAutoString Xml = App->GetForwardXml(EditMimeType); if (ValidStr(Xml)) { RemoveReturns(Xml); PreviousMail = m; LString Body = App->ProcessReplyForwardTemplate(m, this, Xml, Cursor, EditMimeType); LVariant EditCtrl; if (App->GetOptions()->GetValue(OPT_EditControl, EditCtrl) && EditCtrl.CastInt32()) { SetHtml(Body); } else { SetBody(Body); SetBodyCharset("utf-8"); } PreviousMail = 0; } else { ProcessTextForResponse(m, App->GetOptions(), MatchedAccount); } // Attachments if (WithAttachments > 0) for (auto From: Attach) AttachFile(new Attachment(App, From)); // Set MsgId GetMessageId(true); // Unless the user edits the message it shouldn't be made dirty. SetDirty(false); return true; } bool Mail::OnBounce(Mail *m, bool MarkOriginal, int WithAttachments) { if (m) { if (MarkOriginal) { // source email has been forwarded auto MsgId = m->GetMessageId(true); if (MsgId) { ScribeFolder *Fld = m->GetFolder(); LString FldPath; if (Fld) FldPath = Fld->GetPath(); if (FldPath) { LUri u; LString a = u.EncodeStr(MsgId, MsgIdEncodeChars); if (a) { LString p; p.Printf("%s/%s", FldPath.Get(), a.Get()); SetBounceMsgId(p); } } else { SetBounceMsgId(MsgId); } } else m->SetFlags(m->GetFlags() | MAIL_BOUNCED); } *this = (Thing&)*m; GetTo()->DeleteObjects(); SetFlags(MAIL_READ | MAIL_BOUNCE); return true; } return false; } void Mail::OnMeasure(LPoint *Info) { LListItem::OnMeasure(Info); if (PreviewLines && !(GetFlags() & MAIL_READ) && (!GetObject() || !GetObject()->GetInt(FIELD_DONT_SHOW_PREVIEW))) { LFont *PreviewFont = App->GetPreviewFont(); Info->y += PreviewFont ? PreviewFont->GetHeight() << 1 : 28; } } void Mail::OnPaintColumn(LItem::ItemPaintCtx &Ctx, int i, LItemColumn *c) { int Field = 0; if (FieldArray.Length()) { Field = i >= 0 && i < (int)FieldArray.Length() ? FieldArray[i] : 0; } else if (i >= 0 && i < CountOf(DefaultMailFields)) { Field = DefaultMailFields[i]; } if (Container && i >= 0 && Field == FIELD_SUBJECT) { LFont *f = Parent->GetFont(); auto Subj = GetSubject(); if (Container) { // Container needs to paint the subject... Container->OnPaint(Ctx.pDC, Ctx, c, Ctx.Fore, Ctx.Back, f?f:LSysFont, Subj); } } else { LListItem::OnPaintColumn(Ctx, i, c); if (i >= 0 && App->GetIconImgList()) { int Icon = -1; switch (Field) { case FIELD_PRIORITY: { switch (GetPriority()) { case MAIL_PRIORITY_HIGH: { Icon = ICON_PRIORITY_HIGH; break; } case MAIL_PRIORITY_LOW: { Icon = ICON_PRIORITY_LOW; break; } default: break; } break; } case FIELD_FLAGS: { if (GetFlags() & MAIL_BOUNCED) { Icon = ICON_FLAGS_BOUNCE; } else if (GetFlags() & MAIL_REPLIED) { Icon = ICON_FLAGS_REPLY; } else if (GetFlags() & MAIL_FORWARDED) { Icon = ICON_FLAGS_FORWARD; } break; } default: break; } if (Icon >= 0) { LRect *b = App->GetIconImgList()->GetBounds(); if (b) { b += Icon; int x = Ctx.x1 + ((Ctx.X()-b->X())/2) - b->x1; int y = Ctx.y1 + ((MIN(Ctx.Y(), 16)-b->Y())/2) - b->y1; LColour Back(Ctx.Back); App->GetIconImgList()->Draw(Ctx.pDC, x, y, Icon, Back); } } } } } void Mail::OnPaint(LItem::ItemPaintCtx &InCtx) { LItem::ItemPaintCtx Ctx = InCtx; int64_t MarkCol = GetMarkColour(); if (MarkCol > 0) { LColour Col((uint32_t)MarkCol, 32); LColour CtxBack(Ctx.Back); LColour Mixed = Col.Mix(CtxBack, MarkColourMix); Ctx.Back = Mixed; } if (Parent) ((ThingList*)Parent)->CurrentMail = this; LListItem::OnPaint(Ctx); if (Parent) ((ThingList*)Parent)->CurrentMail = 0; LFont *PreviewFont = App->GetPreviewFont(); LFont *ColumnFont = Parent && Parent->GetFont() ? Parent->GetFont() : LSysFont; if (Parent && PreviewLines && PreviewFont && ColumnFont && GetObject() && !GetObject()->GetInt(FIELD_DONT_SHOW_PREVIEW) && !(GetFlags() & MAIL_READ)) { int y = ColumnFont->GetHeight() + 2; int Space = (Ctx.Y() - y) >> 1; int Line = MAX(PreviewFont->GetHeight(), Space); PreviewFont->Transparent(true); // Setup if (!PreviewCache[0] || abs(PreviewCacheX-Ctx.X()) > 20) { PreviewCache.DeleteObjects(); PreviewCacheX = Ctx.X(); LVariant v; if (GetValue("BodyAsText[1024]", v) && v.Str()) { char16 *Base = Utf8ToWide(v.Str()); char16 *Txt = Base; int i; for (i=0; Txt[i]; i++) { if (Txt[i] < ' ') { Txt[i] = ' '; } } auto TxtLen = StrlenW(Txt); for (i=0; Txt && *Txt && i<2; i++) { LDisplayString *Temp = new LDisplayString(PreviewFont, Txt, MIN(1024, TxtLen)); if (Temp) { int W = Ctx.X()-18; ssize_t Cx = Temp->CharAt(W); if (Cx > 0 && Cx <= TxtLen) { LDisplayString *d = new LDisplayString(PreviewFont, Txt, Cx); if (d) { PreviewCache.Insert(d); } DeleteObj(Temp); Txt += Cx; // LSeekUtf8 TxtLen -= Cx; } else break; } } DeleteArray(Base); } } // Display LColour PreviewCol(App->GetColour(L_MAIL_PREVIEW)); if (Select()) { int GreyPrev = PreviewCol.GetGray(); int GreyBack = Ctx.Back.GetGray(); int d = GreyPrev - GreyBack; if (d < 0) d = -d; if (d < 128) { // too near PreviewFont->Colour(Ctx.Fore, Ctx.Back); } else { // ok PreviewFont->Colour(PreviewCol, Ctx.Back); } } else { PreviewFont->Colour(PreviewCol, Ctx.Back); } for (auto p: PreviewCache) { p->Draw(Ctx.pDC, Ctx.x1+16, Ctx.y1+y, 0); y += Line; } } } bool Mail::GetFormats(bool Export, LString::Array &MimeTypes) { if (Export) { MimeTypes.Add("text/plain"); MimeTypes.Add(sMimeMbox); } MimeTypes.Add(sMimeMessage); return MimeTypes.Length() > 0; } Thing::IoProgress Mail::Import(IoProgressImplArgs) { if (!mimeType) IoProgressError("No mime type."); if (Stricmp(mimeType, sMimeMessage)) IoProgressNotImpl(); // Single email.. OnAfterReceive(stream); int Flags = GetFlags(); Flags &= ~MAIL_CREATED; Flags |= MAIL_READ; SetFlags(Flags); Update(); IoProgressSuccess(); } #define TIMEOUT_OBJECT_LOAD 20000 Thing::IoProgress Mail::Export(IoProgressImplArgs) { if (!mimeType) IoProgressError("No mimetype."); if (!Stricmp(mimeType, "text/plain")) { LStringPipe Buf; char Str[256]; char Eol[] = EOL_SEQUENCE; // Addressee if (GetFlags() & MAIL_CREATED) { Buf.Push("To:"); for (LDataPropI *a=GetTo()->First(); a; a=GetTo()->Next()) { sprintf_s(Str, sizeof(Str), "\t%s <%s>%s", a->GetStr(FIELD_NAME), a->GetStr(FIELD_EMAIL), Eol); Buf.Push(Str); } } else { sprintf_s(Str, sizeof(Str), "From: %s <%s>%s", GetFrom()->GetStr(FIELD_NAME), GetFrom()->GetStr(FIELD_EMAIL), Eol); Buf.Push(Str); } // Subject sprintf_s(Str, sizeof(Str), "Subject: %s%s", GetSubject(), Eol); Buf.Push(Str); // Sent date if (GetDateSent()->Year()) { int ch = sprintf_s(Str, sizeof(Str), "Sent Date: "); GetDateSent()->Get(Str+ch, sizeof(Str)-ch); } else { int ch = sprintf_s(Str, sizeof(Str), "Date Received: "); GetDateReceived()->Get(Str+ch, sizeof(Str)-ch); } strcat(Str, Eol); Buf.Push(Str); // Body Buf.Push(Eol); Buf.Push(GetBody()); // Write the output auto s = Buf.NewGStr(); if (!s) IoProgressError("No data to output."); stream->Write(s.Get(), s.Length()); IoProgressSuccess(); } else if (!Stricmp(mimeType, sMimeMbox)) { char Temp[256]; // generate from header sprintf_s(Temp, sizeof(Temp), "From %s ", GetFrom()->GetStr(FIELD_EMAIL)); struct tm Ft; ZeroObj(Ft); LDateTime Rec = *GetDateReceived(); if (!Rec.Year()) Rec.SetNow(); Ft.tm_sec = Rec.Seconds(); /* seconds after the minute - [0,59] */ Ft.tm_min = Rec.Minutes(); /* minutes after the hour - [0,59] */ Ft.tm_hour = Rec.Hours(); /* hours since midnight - [0,23] */ Ft.tm_mday = Rec.Day(); /* day of the month - [1,31] */ Ft.tm_mon = Rec.Month() - 1; /* months since January - [0,11] */ Ft.tm_year = Rec.Year() - 1900; /* years since 1900 */ Ft.tm_wday = Rec.DayOfWeek(); strftime(Temp+strlen(Temp), MAX_NAME_SIZE, "%a %b %d %H:%M:%S %Y", &Ft); strcat(Temp, "\r\n"); // write mail stream->Write(Temp, strlen(Temp)); auto Status = Export(stream, sMimeMessage, [cb](auto io, auto stream) { if (io->status == Store3Success) stream->Write((char*)"\r\n.\r\n", 2); else if (io->status == Store3Delayed) LAssert(!"We should never get delayed here... it's the callback!"); if (cb) cb(io, stream); }); return Status; } else if (!Stricmp(mimeType, sMimeMessage)) { // This function can't be asynchronous, it must complete with UI or waiting for a callback. // Because it is used by the drag and drop system. Which won't wait. auto state = GetLoaded(); if (state != Store3Loaded) IoProgressError("Mail not loaded."); if (!GetObject()) IoProgressError("No store object."); auto Data = GetObject()->GetStream(_FL); if (!Data) IoProgressError("Mail for export has no data."); Data->SetPos(0); LCopyStreamer Cp(512<<10); if (Cp.Copy(Data, stream) <= 0) IoProgressError("Mail copy stream failed."); IoProgressSuccess(); } IoProgressNotImpl(); } char *Mail::GetNewText(int Max, const char *AsCp) { LAutoString CharSet = GetCharSet(); char *Txt = 0; // This limits the preview of the text to // the first 64kb, which is reasonable. int Len = Max > 0 ? Strnlen(GetBody(), Max) : -1; // Convert or allocate the text. if (CharSet) { Txt = (char*)LNewConvertCp(AsCp, GetBody(), GetBodyCharset(), Len); } else { Txt = NewStr(GetBody(), Len); } return Txt; } LAutoString Mail::GetCharSet() { LAutoString Status; LAutoString ContentType; if (GetBodyCharset()) { // If the charset has a stray colon... char *Colon = strchr(GetBodyCharset(), ';'); // Kill it. if (Colon) *Colon = 0; // Copy the string.. Status.Reset(NewStr(GetBodyCharset())); } if (!GetBodyCharset()) { ContentType.Reset(InetGetHeaderField(GetInternetHeader(), "Content-Type")); if (ContentType) { char *CharSet = stristr(ContentType, "charset="); if (CharSet) { CharSet += 8; if (*CharSet) { if (strchr("\"\'", *CharSet)) { char Delim = *CharSet++; char *e = CharSet; while (*e && *e != Delim) { e++; } Status.Reset(NewStr(CharSet, e - CharSet)); } else { char *e = CharSet; while (*e && (IsDigit(*e) || IsAlpha(*e) || strchr("-_", *e))) { e++; } Status.Reset(NewStr(CharSet, e - CharSet)); } } } } /* // If it's not a valid charset... if (!LGetCsInfo(Status)) { // Then kill it. Status.Reset(); if (GetBodyCharset()) { SetBodyCharset(0); SetDirty(); } } */ } return Status; } /* Old Body Printing Code int Lines = 0; char *n; for (n=Body.Str(); n && *n; n++) { if (*n == '\n') Lines++; } char *c = Body.Str(); if (c) { c = WrapLines(c, c ? strlen(c) : 0, 76); Lines = 0; for (n=c; *n; n++) { if (*n == '\n') Lines++; } while (c && *c) { if (Cy + Line > r.y2) { pDC->EndPage(); if (Window->GetMaxPages() > 0 && ++Page >= Window->GetMaxPages()) { break; } pDC->StartPage(); Cy = 0; } char *Eol = strchr(c, '\n'); int Len = 0; int Ret = 0; if (Eol) { Len = (int) Eol - (int) c; if (Len > 0 && c[Len-1] == '\r') { Len--; Ret = 1; } } else { Len = strlen(c); } MailFont->Text(pDC, r.x1, Cy, c, Len); Cy += Line; c += Len + Ret; if (*c == '\n') c++; } } */ int Mail::OnNotify(LViewI *Ctrl, LNotification n) { switch (Ctrl->GetId()) { case IDC_TEXT: { LDocView *Doc = dynamic_cast(Ctrl); if (Doc) { if (n.Type == LNotifyShowImagesChanged) { if (Doc->GetLoadImages() ^ TestFlag(GetFlags(), MAIL_SHOW_IMAGES)) { if (Doc->GetLoadImages()) { SetFlags(GetFlags() | MAIL_SHOW_IMAGES); } else { SetFlags(GetFlags() & ~MAIL_SHOW_IMAGES); } } } if (n.Type == LNotifyFixedWidthChanged) { bool DocFixed = Doc->GetFixedWidthFont(); bool MailFixed = TestFlag(GetFlags(), MAIL_FIXED_WIDTH_FONT); if (DocFixed ^ MailFixed) { if (Doc->GetFixedWidthFont()) { SetFlags(GetFlags() | MAIL_FIXED_WIDTH_FONT); } else { SetFlags(GetFlags() & ~MAIL_FIXED_WIDTH_FONT); } } } if (n.Type == LNotifyCharsetChanged && dynamic_cast(Doc)) { char s[256]; sprintf_s(s, sizeof(s), ">%s", Doc->GetCharset()); SetHtmlCharset(s); } } break; } } return 0; } void Mail::OnPrintHeaders(ScribePrintContext &Context) { char Str[256]; // print document LDrawListSurface *Page = Context.Pages.Last(); int Line = Context.AppFont->GetHeight(); Page->SetFont(Context.AppFont); LDisplayString *Ds = Context.Text(LLoadString(IDS_MAIL_MESSAGE)); Context.CurrentY += Line; LDataPropI *Ad = GetTo()->First(); if (Ad) { sprintf_s(Str, sizeof(Str), "%s: ", LLoadString(FIELD_TO)); Ds = Page->Text(Context.MarginPx.x1, Context.CurrentY, Str); int x = Ds->X(); for (; Ad; Ad = GetTo()->Next()) { if (Ad->GetStr(FIELD_EMAIL) && Ad->GetStr(FIELD_NAME)) { sprintf_s(Str, sizeof(Str), "%s <%s>", Ad->GetStr(FIELD_NAME), Ad->GetStr(FIELD_EMAIL)); } else if (Ad->GetStr(FIELD_EMAIL)) { strcpy_s(Str, sizeof(Str), Ad->GetStr(FIELD_EMAIL)); } else if (Ad->GetStr(FIELD_EMAIL)) { strcpy_s(Str, sizeof(Str), Ad->GetStr(FIELD_EMAIL)); } else continue; Ds = Page->Text(Context.MarginPx.x1 + x, Context.CurrentY, Str); Context.CurrentY += Ds->Y(); } } if (GetFrom()->GetStr(FIELD_EMAIL) || GetFrom()->GetStr(FIELD_NAME)) { sprintf_s(Str, sizeof(Str), "%s: ", LLoadString(FIELD_FROM)); Ds = Page->Text(Context.MarginPx.x1, Context.CurrentY, Str); int x = Ds->X(); if (GetFrom()->GetStr(FIELD_EMAIL) && GetFrom()->GetStr(FIELD_NAME)) { sprintf_s(Str, sizeof(Str), "%s <%s>", GetFrom()->GetStr(FIELD_NAME), GetFrom()->GetStr(FIELD_EMAIL)); } else if (GetFrom()->GetStr(FIELD_EMAIL)) { strcpy_s(Str, sizeof(Str), GetFrom()->GetStr(FIELD_EMAIL)); } else if (GetFrom()->GetStr(FIELD_NAME)) { strcpy_s(Str, sizeof(Str), GetFrom()->GetStr(FIELD_NAME)); } else Str[0] = 0; Ds = Page->Text(Context.MarginPx.x1 + x, Context.CurrentY, Str); Context.CurrentY += Ds->Y(); } { // Subject LString s; const char *Subj = GetSubject(); s.Printf("%s: %s", LLoadString(FIELD_SUBJECT), Subj?Subj:""); Context.Text(s); } { // Date LString s; GetDateSent()->Get(Str, sizeof(Str)); s.Printf("%s: %s", LLoadString(IDS_DATE), Str); Context.Text(s); } // attachment list... List Attached; if (GetAttachments(&Attached) && Attached.Length() > 0) { sprintf_s(Str, sizeof(Str), "%s: ", LLoadString(IDS_ATTACHMENTS)); Ds = Page->Text(Context.MarginPx.x1, Context.CurrentY, Str); int x = Ds->X(); for (auto a: Attached) { char Size[32]; LFormatSize(Size, sizeof(Size), a->GetSize()); sprintf_s(Str, sizeof(Str), "%s (%s)", a->GetName(), Size); Ds = Page->Text(Context.MarginPx.x1 + x, Context.CurrentY, Str); Context.CurrentY += Ds->Y(); } } // separator line LRect Sep(Context.MarginPx.x1, Context.CurrentY + (Line * 5 / 10), Context.MarginPx.x2, Context.CurrentY + (Line * 6 / 10)); Page->Rectangle(&Sep); Context.CurrentY += Line; } void Mail::OnPrintText(ScribePrintContext &Context, LPrintPageRanges &Pages) { // Text printing LDrawListSurface *Page = Context.Pages.Last(); LVariant Body; if (!GetVariant("BodyAsText", Body) || !Body.Str()) { LgiTrace("%s:%i - No content to print.\n", _FL); return; } LAutoWString w(Utf8ToWide(Body.Str())); if (!w) { LgiTrace("%s:%i - Utf8ToWide failed.\n", _FL); return; } Page->SetFont(Context.MailFont); for (char16 *s = w; s && *s; ) { // Find end of line.. char16 *n = StrchrW(s, '\n'); if (!n) n = s + StrlenW(s); ssize_t LineLen = n - s; // Find how many characters will fit on the page ssize_t Fit = 0; if (n > s) { LDisplayString a(Context.MailFont, s, MIN(LineLen, 1024)); Fit = a.CharAt(Context.MarginPx.X()); } if (Fit < 0) { break; } char16 *e = s + Fit; if (e < n) { // The whole line doesn't fit on the page... // Find the best breaking opportunity before that... #define ExtraBreak(c) ( ( (c) >= 0x3040 && (c) <= 0x30FF ) || \ ( (c) >= 0x3300 && (c) <= 0x9FAF ) \ ) char16 *StartE = e; while (e > s) { if (e[-1] == ' ' || ExtraBreak(e[-1])) { break; } e--; } if (e == s) { e = StartE; } } // Output the segment of text bool HasRet = e > s ? e[-1] == '\r' : false; LString Str(s, (e - s) - (HasRet ? 1 : 0)); Context.Text(Str); // Update the pointers s = e; if (*s == '\n') s++; } } -void Mail::OnPrintHtml(ScribePrintContext &Context, LPrintPageRanges &Pages, LSurface *RenderedHtml) +/// \returns the number of pages printed +int Mail::OnPrintHtml(ScribePrintContext &Context, LPrintPageRanges &Pages, LSurface *RenderedHtml) { // HTML printing... LDrawListSurface *Page = Context.Pages.Last(); // Work out the scaling from memory bitmap to page.. double MemScale = (double) Context.pDC->X() / (double) RenderedHtml->X(); // Now paint the bitmap onto the existing page - int PageIdx = 0; + int PageIdx = 0, Printed = 0; for (int y = 0; y < RenderedHtml->Y(); PageIdx++) { - // Work out how much bitmap we can paint onto the current page... - int PageRemaining = Context.MarginPx.Y() - Context.CurrentY; - int MemPaint = (int) (PageRemaining / MemScale); - - // This is how much of the memory context we can fit on the page - LRect MemRect(0, y, RenderedHtml->X()-1, y + MemPaint - 1); - LRect Bnds = RenderedHtml->Bounds(); - MemRect.Bound(&Bnds); + if (Pages.InRanges(PageIdx)) + { + // Work out how much bitmap we can paint onto the current page... + int PageRemaining = Context.MarginPx.Y() - Context.CurrentY; + int MemPaint = (int) (PageRemaining / MemScale); + + // This is how much of the memory context we can fit on the page + LRect MemRect(0, y, RenderedHtml->X()-1, y + MemPaint - 1); + LRect Bnds = RenderedHtml->Bounds(); + MemRect.Bound(&Bnds); - // Work out how much page that is take up - int PageHeight = (int) (MemRect.Y() * MemScale); + // Work out how much page that is take up + int PageHeight = (int) (MemRect.Y() * MemScale); - // This is the position on the page we are blting to - LRect PageRect(Context.MarginPx.x1, Context.CurrentY, Context.MarginPx.x2, Context.CurrentY + PageHeight - 1); + // This is the position on the page we are blting to + LRect PageRect(Context.MarginPx.x1, Context.CurrentY, Context.MarginPx.x2, Context.CurrentY + PageHeight - 1); - // Do the blt - Page->StretchBlt(&PageRect, RenderedHtml, &MemRect); + // Do the blt + Page->StretchBlt(&PageRect, RenderedHtml, &MemRect); + Printed++; - // Now move our position down the page.. - Context.CurrentY += PageHeight; - if ((Context.MarginPx.Y() - Context.CurrentY) * 100 / Context.MarginPx.Y() < 5) - { - // Ok we hit the end of the page and need to go to the next page - Context.CurrentY = Context.MarginPx.y1; - Page = Context.NewPage(); - } - - y += MemRect.Y(); - } + // Now move our position down the page.. + Context.CurrentY += PageHeight; + if ((Context.MarginPx.Y() - Context.CurrentY) * 100 / Context.MarginPx.Y() < 5) + { + // Ok we hit the end of the page and need to go to the next page + Context.CurrentY = Context.MarginPx.y1; + Page = Context.NewPage(); + } + + y += MemRect.Y(); + } + } + + return Printed; } ////////////////////////////////////////////////////////////////////////////// size_t Mail::Length() { size_t Status = 0; for (auto a: Attachments) { if (a->GetMimeType() && _stricmp(a->GetMimeType(), sMimeMessage) == 0) { Status++; } } return Status; } ssize_t Mail::IndexOf(Mail *m) { ssize_t Status = -1; ssize_t n = 0; for (auto a: Attachments) { if (a->GetMimeType() && _stricmp(a->GetMimeType(), sMimeMessage) == 0) { if (a->GetMsg() == m) { Status = n; break; } n++; } } return Status; } Mail *Mail::operator [](size_t i) { size_t n = 0; for (auto a: Attachments) { if (a->GetMimeType() && _stricmp(a->GetMimeType(), sMimeMessage) == 0) { if (n == i) return a->GetMsg(); n++; } } return NULL; } bool Mail::LoadFromFile(char *File) { if (File) { LAutoPtr f(new LFile); if (f->Open(File, O_READ)) { return Import(AutoCast(f), sMimeMessage); } } return false; } /////////////////////////////////////////////////////////////////////////////////////////////////////// bool CreateMailAddress(LStream &Out, LDataPropI *Addr, MailProtocol *Protocol) { if (!Addr) return false; auto Name = Addr->GetStr(FIELD_NAME); auto Email = Addr->GetStr(FIELD_EMAIL); if (!Email) return false; Name = EncodeRfc2047(NewStr(Name), 0, &Protocol->CharsetPrefs); if (Name) { if (strchr(Name, '\"')) Out.Print("'%s' ", Name); else Out.Print("\"%s\" ", Name); DeleteArray(Name); } Out.Print("<%s>", Email); return true; } bool CreateMailHeaders(ScribeWnd *App, LStream &Out, LDataI *Mail, MailProtocol *Protocol) { bool Status = true; // Setup char Buffer[1025]; // Construct date LDateTime Dt; int TimeZone = Dt.SystemTimeZone(); Dt.SetNow(); sprintf_s(Buffer, sizeof(Buffer), "Date: %s, %i %s %i %i:%2.2i:%2.2i %s%2.2d%2.2d\r\n", LDateTime::WeekdaysShort[Dt.DayOfWeek()], Dt.Day(), LDateTime::MonthsShort[Dt.Month()-1], Dt.Year(), Dt.Hours(), Dt.Minutes(), Dt.Seconds(), (TimeZone >= 0) ? "+" : "", TimeZone / 60, abs(TimeZone) % 60); Status &= Out.Write(Buffer, strlen(Buffer)) > 0; if (Protocol && Protocol->ProgramName) { // X-Mailer: Status &= Out.Print("X-Mailer: %s\r\n", Protocol->ProgramName.Get()) > 0; } if (Protocol && Protocol->ExtraOutgoingHeaders) { for (char *s=Protocol->ExtraOutgoingHeaders; s && *s; ) { char *e = s; while (*e && *e != '\r' && *e != '\n') e++; ssize_t 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; } } int Priority = (int)Mail->GetInt(FIELD_PRIORITY); if (Priority != MAIL_PRIORITY_NORMAL) { // X-Priority: Status &= Out.Print("X-Priority: %i\r\n", Priority) > 0; } uint32_t MarkColour = (uint32_t)Mail->GetInt(FIELD_COLOUR); if (MarkColour) { // X-Color (HTML Colour Ref for email marking) Status &= Out.Print("X-Color: #%2.2X%2.2X%2.2X\r\n", R24(MarkColour), G24(MarkColour), B24(MarkColour)) > 0; } // Message-ID: auto MessageID = Mail->GetStr(FIELD_MESSAGE_ID); if (MessageID) { for (auto m=MessageID; *m; m++) { if (*m <= ' ') { printf("%s:%i - Bad message ID '%s'\n", _FL, MessageID); return false; } } Status &= Out.Print("Message-ID: %s\r\n", MessageID) > 0; } // References: auto References = Mail->GetStr(FIELD_REFERENCES); if (ValidStr(References)) { auto Dir = strrchr(References, '/'); LString Ref; if (Dir) { LUri u; Ref = u.DecodeStr(Dir + 1); } else Ref = References; if (*Ref == '<') Status &= Out.Print("References: %s\r\n", Ref.Get()) > 0; else Status &= Out.Print("References: <%s>\r\n", Ref.Get()) > 0; } // To: GDataIt Addr = Mail->GetList(FIELD_TO); LArray Objs; LArray To, Cc; ContactGroup *Group; for (unsigned i=0; iLength(); i++) { LDataPropI *a = (*Addr)[i]; EmailAddressType AddrType = (EmailAddressType)a->GetInt(FIELD_CC); LString Addr = a->GetStr(FIELD_EMAIL); if (LIsValidEmail(Addr)) { if (AddrType == MAIL_ADDR_CC) Cc.Add(a); else if (AddrType == MAIL_ADDR_TO) To.Add(a); } else if ((Group = LookupContactGroup(App, Addr))) { Group->UsedTs.SetNow(); LString::Array Addrs = Group->GetAddresses(); for (unsigned n=0; nGetObject()->GetStore(), NULL); if (sa) { sa->Addr = Addrs[n]; Objs.Add(sa); if (AddrType == MAIL_ADDR_CC) Cc.Add(sa); else if (AddrType == MAIL_ADDR_TO) To.Add(sa); } } } } if (To.Length()) { for (unsigned i=0; iGetObj(FIELD_FROM); if (From && From->GetStr(FIELD_EMAIL)) { Out.Print("From: "); if (!CreateMailAddress(Out, From, Protocol)) return false; Out.Print("\r\n"); } else { LgiTrace("%s:%i - No from address.\n", _FL); return false; } // Reply-To: LDataPropI *Reply = Mail->GetObj(FIELD_REPLY); if (Reply && ValidStr(Reply->GetStr(FIELD_EMAIL))) { Out.Print("Reply-To: "); if (!CreateMailAddress(Out, Reply, Protocol)) return false; Out.Print("\r\n"); } // Subject: char *Subj = EncodeRfc2047(NewStr(Mail->GetStr(FIELD_SUBJECT)), 0, &Protocol->CharsetPrefs, 9); sprintf_s(Buffer, sizeof(Buffer), "Subject: %s\r\n", (Subj) ? Subj : ""); Status &= Out.Write(Buffer, strlen(Buffer)) > 0; DeleteArray(Subj); // DispositionNotificationTo uint8_t DispositionNotificationTo = TestFlag(Mail->GetInt(FIELD_FLAGS), MAIL_READ_RECEIPT); if (DispositionNotificationTo && From) { int ch = sprintf_s(Buffer, sizeof(Buffer), "Disposition-Notification-To:"); char *Nme = EncodeRfc2047(NewStr(From->GetStr(FIELD_NAME)), 0, &Protocol->CharsetPrefs); if (Nme) { ch += sprintf_s(Buffer+ch, sizeof(Buffer)-ch, " \"%s\"", Nme); DeleteArray(Nme); } ch += sprintf_s(Buffer+ch, sizeof(Buffer)-ch, " <%s>\r\n", From->GetStr(FIELD_EMAIL)); Status &= Out.Write(Buffer, ch) > 0; } // Content-Type LDataPropI *Root = Mail->GetObj(FIELD_MIME_SEG); if (Root) { auto MimeType = Root->GetStr(FIELD_MIME_TYPE); auto Charset = Root->GetStr(FIELD_CHARSET); if (MimeType) { LString s; s.Printf("Content-Type: %s%s%s\r\n", MimeType, Charset?"; charset=":"", Charset?Charset:""); Status &= Out.Write(s, s.Length()) == s.Length(); } } else LAssert(0); Objs.DeleteObjects(); return Status; } diff --git a/Code/ScribeMain.cpp b/Code/ScribeMain.cpp --- a/Code/ScribeMain.cpp +++ b/Code/ScribeMain.cpp @@ -1,143 +1,143 @@ /* ** FILE: ScribeMain.cpp ** AUTHOR: Matthew Allen ** DATE: 9/6/98 ** DESCRIPTION: Scribe app entry point ** ** Copyright (C) 1998, Matthew Allen ** fret@memecode.com */ #include "Scribe.h" #include "ScribePrivate.h" #include "lgi/common/Css.h" #include "lgi/common/Filter.h" #include "lgi/common/DateTime.h" #include "lgi/common/DateTime.h" #include "lgi/common/EmojiFont.h" ScribeApp::ScribeApp(OsAppArguments &AppArgs, LAppArguments *Opts) : LApp(AppArgs, "Scribe", Opts) { Status = true; } bool ScribeOnIdle(void *Param) { LApp *a = (LApp*)Param; ScribeWnd *w = dynamic_cast(a->AppWnd); return (w) ? w->OnIdle() : false; } #include "lgi/common/TextLog.h" void Test() { const 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 }; LAutoString Name, Addr; for (const char **i = Input; *i; i++) { Name.Reset(); Addr.Reset(); DecodeAddrName(*i, Name, Addr, "name.com"); LgiTrace("N=%-#32s A=%-32s\n", Name.Get(), Addr.Get()); } } int LgiMain(OsAppArguments &AppArgs) { #if 0 && defined(__GTK_H__) && defined(_DEBUG) LArray a; for (int i=0; i f(new LEmojiFont()); if (f && f->Create()) LFontSystem::Inst()->AddFont(f); #endif ScribeWnd *Wnd = NULL; if (App.GetOption("help")) { printf( "\n" "Options:\n" " -m New email to address.\n" " -c Cc recipients.\n" " -s<\"subject\"> Message's subject.\n" " -f Body of message.\n" " -b Attach body as an attachment instead.\n" " -nch [Linux] Don't use KDE crash handler.\n" "\n"); } else if (!(Wnd = new ScribeWnd)) { LgiTrace("Memory alloc failed.\n"); return -2; } - if (Wnd->GetScribeState() == ScribeWnd::ScribeInitializing) + if (Wnd->GetScribeState() != ScribeWnd::ScribeExiting) { if (App.AppWnd->Attach(0)) { auto State = Wnd->GetScribeState(); - if (State == ScribeWnd::ScribeRunning) + if (State != ScribeWnd::ScribeExiting) { #if 0 App.Run(true, ScribeOnIdle, &App); #else App.Run(); #endif } else { LgiTrace("%s:%i - GetScribeState() not running.\n", _FL); } } else { LgiTrace("Couldn't create main window.\n"); LgiMsg(0, "Couldn't create main window."); } } return 0; } diff --git a/Code/ScribePageSetup.cpp b/Code/ScribePageSetup.cpp --- a/Code/ScribePageSetup.cpp +++ b/Code/ScribePageSetup.cpp @@ -1,225 +1,225 @@ #include #include #include "Scribe.h" #include "ScribePageSetup.h" #include "resdefs.h" //////////////////////////////////////////////////////////////// class BorderEdit : public LLayout, public ResObject { friend class ScribePageSetup; double x1, y1, x2, y2; double PageX, PageY; double Scale; public: BorderEdit() : ResObject(Res_Custom) { x1 = y1 = x2 = y2 = 1.0; PageX = PageDefaultX; // A4 is the default PageY = PageDefaultY; Scale = 1.0; // scaling.. Sunken(true); } void SetBorders(double X1, double Y1, double X2, double Y2) { x1 = limit(X1, 0.0, PageX/2); y1 = limit(Y1, 0.0, PageY/2); x2 = limit(X2, 0.0, PageX/2); y2 = limit(Y2, 0.0, PageY/2); Invalidate(); } void OnPaint(LSurface *pDC) { // calculate scaling // double Aspect = PageY / PageX; Scale = ((double)Y()) * 0.8 / PageY; // paint background pDC->Colour(L_LOW); pDC->Rectangle(); // paint doc... int Cx = (int) (X()/2); int Cy = (int) (Y()/2); int x = (int) (Scale * PageX); int y = (int) (Scale * PageY); LRect r(Cx - (x/2), Cy - (y/2), Cx + (x/2), Cy + (y/2)); pDC->Colour(L_BLACK); pDC->Box(&r); r.Inset(1, 1); pDC->Colour(L_WHITE); pDC->Rectangle(&r); // paint borders.. pDC->Colour(L_LTGREY); int n = (int) (r.x1 + (Scale * x1)); // left pDC->Line(n, r.y1, n, r.y2); n = (int) (r.y1 + (Scale * y1)); // top pDC->Line(r.x1, n, r.x2, n); n = (int) (r.x2 - (Scale * x2)); // right pDC->Line(n, r.y1, n, r.y2); n = (int) (r.y2 - (Scale * y2)); // bottom pDC->Line(r.x1, n, r.x2, n); // paint dummy lines r.x1 += (int) (Scale * x1); r.y1 += (int) (Scale * y1); r.x2 -= (int) (Scale * x2); r.y2 -= (int) (Scale * y2); r.Inset(2, 2); for (int i=r.y1; iRectangle(r.x1, i, (i%4==3) ? r.x1 + (r.X()*2/3) : r.x2, i+2); } } bool OnLayout(LViewLayoutInfo &Inf) { if (!Inf.Width.Max) { Inf.Width.Max = 10000; Inf.Width.Min = 100; } else if (!Inf.Height.Max) { Inf.Height.Max = 10000; Inf.Height.Min = 100; } else return false; return true; } }; class BorderEditFactory : public LViewFactory { LView *NewView(const char *Class, LRect *Pos, const char *Text) { if (!_stricmp(Class, "BorderEdit")) return new BorderEdit; return NULL; } public: } BorderEditFact; //////////////////////////////////////////////////////////////// ScribePageSetup::ScribePageSetup(LView *parent, LOptionsFile *options) { Options = options; SetParent(parent); if (LoadFromResource(IDD_PAGE_SETUP)) { MoveToCenter(); SetCtrlEnabled(IDC_FONT, false); Serialize(false); // update the ctrl LViewI *w = FindControl(IDC_LEFT); LNotification note(LNotifyValueChanged); if (w) OnNotify(w, note); } } void ScribePageSetup::Serialize(bool Write) { if (Options) { Font.Serialize(Options, OPT_PrintFont, Write); if (Write) { #define SaveBorder(ctrl, opt) \ { LVariant s; \ s = GetCtrlName(ctrl); \ if (s.Str()) \ Options->SetValue(opt, s); \ } SaveBorder(IDC_LEFT, OPT_MarginX1); SaveBorder(IDC_TOP, OPT_MarginY1); SaveBorder(IDC_RIGHT, OPT_MarginX2); SaveBorder(IDC_BOTTOM, OPT_MarginY2); } else { char s[128]; #define LoadBorder(ctrl, opt) \ { LVariant d; \ if (Options->GetValue(opt, d)) \ { sprintf_s(s, sizeof(s), "%.2f", d.CastDouble()); \ SetCtrlName(ctrl, s); } \ else SetCtrlName(ctrl, "1.00"); } LoadBorder(IDC_LEFT, OPT_MarginX1); LoadBorder(IDC_TOP, OPT_MarginY1); LoadBorder(IDC_RIGHT, OPT_MarginX2); LoadBorder(IDC_BOTTOM, OPT_MarginY2); if (Font.GetDescription(s, sizeof(s))) { SetCtrlName(IDC_FONT, s); } } } } int ScribePageSetup::OnNotify(LViewI *Ctrl, LNotification n) { switch (Ctrl->GetId()) { case IDC_LEFT: case IDC_TOP: case IDC_RIGHT: case IDC_BOTTOM: { BorderEdit *Ctrl; if (GetViewById(IDC_PREVIEW, Ctrl)) { auto x1 = GetCtrlName(IDC_LEFT); auto y1 = GetCtrlName(IDC_TOP); auto x2 = GetCtrlName(IDC_RIGHT); auto y2 = GetCtrlName(IDC_BOTTOM); if (x1 && x2 && y1 && y2) { Ctrl->SetBorders(atof(x1), atof(y1), atof(x2), atof(y2)); } } break; } case IDC_BROWSE_FONT: { - char s[256]; - if (Font.DoUI(this) && - Font.GetDescription(s, sizeof(s))) + Font.DoUI(this, [&](auto fontType) { + char s[256]; + Font.GetDescription(s, sizeof(s)); SetCtrlName(IDC_FONT, s); - } + }); break; } case IDOK: { Serialize(true); // fall thru } case IDCANCEL: { EndModal(Ctrl->GetId()); break; } } return 0; } diff --git a/Code/ScribePrivate.h b/Code/ScribePrivate.h --- a/Code/ScribePrivate.h +++ b/Code/ScribePrivate.h @@ -1,780 +1,780 @@ /*hdr ** FILE: ScribePrivate.h ** AUTHOR: Matthew Allen ** DATE: 22/10/97 ** DESCRIPTION: Scribe email application ** ** Copyright (C) 1998-2002 Matthew Allen ** fret@memecode.com */ // Includes #ifndef __SCRIBE_PRIVATE__ #define __SCRIBE_PRIVATE__ #include "lgi/common/XmlTreeUi.h" #include "lgi/common/Html.h" #include "lgi/common/CheckBox.h" #include "lgi/common/TabView.h" #include "lgi/common/RadioGroup.h" // Defines #define SCRIBE_TOOLBAR_BORDER_SPACING_PX 3 // Externs extern const char *AppName; extern char HelpFile[]; extern const char *DefaultFolderNames[]; extern const char *DefaultRfXml; extern int DefaultFilterFields[]; extern char MailToStr[]; extern char SubjectStr[]; extern char ContentTypeDefault[]; extern bool OptionSizeInKiB; extern bool ShowRelativeDates; // Mime types extern char sMimeVCard[]; extern char sMimeVCalendar[]; extern char sMimeICalendar[]; extern char sMimeMbox[]; extern char sMimeLgiResource[]; extern char sMimeMessage[]; extern char sMimeXml[]; // Default templates extern char DefaultTextReplyTemplate[]; extern char DefaultHtmlReplyTemplate[]; //////////////////////////////////////////////////////////////////////////////////////////// // Functions // Misc extern int SizeOfFile(char *FileName); extern char *OsName(); extern LString GetFullAppName(bool Platform = true); extern void LogMsg(char *str, ...); // Dnd and clipboard format for an array of "Thing*" extern char ScribeThingList[]; #define ScribeThingMagic "thng" #define ScribeFolderMagic "fldr" class ScribeClipboardFmt { char Magic[4]; OsProcessId ProcessId; uint32_t Len; union { Thing *Things[1]; // 'Len' long... ScribeFolder *Folders[1]; }; static bool Is(const char *Type, void *Ptr, size_t Len); public: static ScribeClipboardFmt *Alloc(bool ForFolders, size_t Size); static ScribeClipboardFmt *Alloc(List &Lst); static ScribeClipboardFmt *Alloc(LArray &Arr); static bool IsThing(void *Ptr, size_t Bytes) { return Is(ScribeThingMagic, Ptr, Bytes); } static bool IsFolder(void *Ptr, size_t Bytes) { return Is(ScribeFolderMagic, Ptr, Bytes); } size_t Sizeof(); uint32_t Length() { return Len; } Thing *ThingAt(size_t Idx, Thing *Set = NULL); ScribeFolder *FolderAt(size_t Idx, ScribeFolder *Set = NULL); }; extern LToolBar *LoadToolbar(LView *Parent, const char *File); extern LString ScribeGetFileMimeType(const char *File); // extern void UpgradeRfOption(ObjProperties *Opts, const char *New, const char *Old, const char *Default); extern ScribeFolder *CastFolder(LDataI *f); extern int MakeOpenFlags(ScribeAccount *a, bool Send); extern bool HasEmoji(char *Txt); extern bool HasEmoji(uint32_t *Txt); extern LAutoWString TextToEmoji(uint32_t *Txt, bool IsHtml); extern int FilterCompare(Filter *a, Filter *b, NativeInt Data); extern bool ExtractHtmlContent( LString &OutHtml, LString &Charset, LString &Styles, const char *InHtml); // Scripting extern bool OnFilterScript(Filter *f, Mail *m, const char *script); extern bool OnToolScript(ScribeWnd *App, const char *File); //////////////////////////////////////////////////////////////////////////////////////////// // Classes class MailTree; class ScribeWnd; class Mail; class Contact; class MailUi; class ScribeFolder; class ContactUi; class FolderPropertiesDlg; class ScribeAccount; class Filter; class Attachment; class Calendar; class CalendarSource; class ScribeApp : public LApp { public: int Status; ScribeApp(OsAppArguments &AppArgs, LAppArguments *Opts); }; //////////////////////////////////////////////////////////////////////// extern ItemFieldDef MailFieldDefs[]; extern ItemFieldDef ContactFieldDefs[]; extern ItemFieldDef CalendarFields[]; extern ItemFieldDef FilterFieldDefs[]; extern ItemFieldDef *GetFieldDefByName(char *Name); extern ItemFieldDef *GetFieldDefById(int Id); //////////////////////////////////////////////////////////////////////// // extern void StorageCount(StorageItem *Store, Counter &c); class ContactUi : public ThingUi, public LResourceLoad { protected: Contact *Item; LList *Lst; class LContactImage *ImgView; bool InitField(int Id, const char *Name); bool SaveField(int Id, const char *Name); public: ContactUi(Contact *item); ~ContactUi(); void OnLoad(); void OnSave(); void OnDestroy(); int OnNotify(LViewI *Col, LNotification n); LMessage::Result OnEvent(LMessage *Msg); void OnPosChange(); void OnPulse(); }; ///////////////////////////////////////////////////////////// #define DefaultMarkColour (MarkColours32[5]) #define IDM_MARK_MAX 8 #define IDM_UNMARK 30000 #define IDM_MARK_ALL 30001 #define IDM_SELECT_NONE 30002 #define IDM_SELECT_ALL 30003 #define IDM_MARK_BASE 30100 #define IDM_MARK_SELECT_BASE 30200 #define IDM_IDENTITY_BASE 30300 enum MarkedState { MS_None, MS_One, MS_Multiple }; extern uint32_t MarkColours32[IDM_MARK_MAX]; extern LSubMenu *BuildMarkMenu( LSubMenu *MarkMenu, MarkedState MarkState, uint32_t SelectedMark, bool None = false, bool All = false, bool Select = false); class AddressList : public LList, public LDragDropTarget { ScribeWnd *App; public: AddressList(ScribeWnd *app, int id, int x, int y, int cx, int cy, const char *name = "List"); const char *GetClass() { return "AddressList"; } void OnCreate(); bool OnKey(LKey &k); void OnInit(GDataIt l); void OnSave(LDataStoreI *store, GDataIt l); void OnItemClick(LListItem *Item, LMouse &m); void Copy(); void Paste(); // D'n'd support int WillAccept(LDragFormats &Formats, LPoint Pt, int KeyState); int OnDrop(LArray &Data, LPoint Pt, int KeyState); }; struct BrowseItem { int Score; LString First; LString Last; LString Email; BrowseItem(const char *first, const char *last, const char *email, int score) { Score = score; First = first; Last = last; Email = email; } }; class MailUi : public ThingUi, public MailContainerIter, public MailViewOwner { friend class AddressList; friend class Mail; friend class ScribeTextPipe; LViewI *WorkingDlg; protected: int AddMode; int Sx, Sy; int CmdAfterResize; bool MetaFieldsDirty; bool HtmlCtrlDirty; bool TextCtrlDirty; bool IgnoreShowImgNotify; int CurrentEditCtrl; MissingCapsBar *MissingCaps; LCapabilityTarget::CapsHash Caps; // Commands LScriptUi Commands; LToolButton *BtnPrev; LToolButton *BtnNext; LToolButton *BtnSend; LToolButton *BtnSave; LToolButton *BtnSaveClose; LToolButton *BtnAttach; LToolButton *BtnReply; LToolButton *BtnReplyAll; LToolButton *BtnForward; LToolButton *BtnBounce; // Gpg class MailUiGpg *GpgUi; // To: LPanel *ToPanel; LEdit *Entry; class AddressBrowse *Browse; LCombo *SetTo; AddressList *To; LCombo *Remove; // From LPanel *FromPanel; AddressList *FromList; LCombo *FromCbo; LArray FromAccountId; // GDropDown *Drop; LPanel *ReplyToPanel; LCheckBox *ReplyToChk; LCombo *ReplyToCbo; // Subject LPanel *SubjectPanel; LEdit *Subject; // Calendar panel LPanel *CalendarPanel; LView *CalPanelStatus; // Tabs LTabView *Tab; LTabPage *TabText; bool TextLoaded; LDocView *TextView; LTabPage *TabHtml; bool HtmlLoaded; LDocView *HtmlView; LTabPage *TabAttachments; class AttachmentList *Attachments; LTabPage *TabHeader; LDocView *Header; // Methods bool SeekMsg(int Delta); bool NeedsCapability(const char *Name, const char *Param = NULL); LDocView *GetDoc(const char *MimeType); bool SetDoc(LDocView *v, const char *MimeType); void OnInstall(LCapabilityTarget::CapsHash *Caps, bool Status); void OnCloseInstaller(); void SetCmdAfterResize(int Cmd); void OnChildrenChanged(LViewI *Wnd, bool Attaching); public: MailUi(Mail *item, MailContainer *Container = 0); ~MailUi(); const char *GetClass() { return "MailUi"; } Mail *GetItem(); void SetItem(Mail *m); bool SetDirty(bool d, bool ui = true); void OnLoad(); AttachmentList *GetAttachments() { return Attachments; } void SerializeText(bool FromCtrl); bool AddRecipient(Contact *c); bool AddRecipient(const char *Email, const char *Name); bool AddRecipient(AddressDescriptor *Addr); bool AddCalendarEvent(bool AddPopupReminder); int OnNotify(LViewI *Col, LNotification n); void OnSysKey(int a, int b); void OnDirty(bool Dirty); void OnDataEntered(); void OnSave(); void OnPaint(LSurface *pDC); void OnPulse(); LMessage::Result OnEvent(LMessage *Msg); void OnPosChange(); int OnCommand(int Cmd, int Event, OsView Window); void OnReceiveFiles(LArray &Files); bool OnViewKey(LView *v, LKey &k); void OnAttachmentsChange(); void OnChange(); bool IsWorking(int Set = -1); bool OnRequestClose(bool OsClose); bool CallMethod(const char *Name, LVariant *Dst, LArray &Arg); }; class AttachmentList : public LList { MailUi *Ui; public: AttachmentList(int id, int x, int y, int cx, int cy, MailUi *ui); ~AttachmentList(); void OnItemClick(LListItem *Item, LMouse &m); bool OnKey(LKey &k); }; // this is the list pane that displays the // contents of a ScribeFolder class ThingList : public LList { friend class Mail; ScribeFolder *Container; Mail *CurrentMail; int BoldUnread; ScribeWnd *App; public: ThingList(ScribeWnd *wnd); ~ThingList(); const char *GetClass() { return "ThingList"; } LRect &GetClient(bool ClientSpace = true); int GetSortCol() { return (Container) ? Container->GetSortCol() : 0; } int GetSortField() { return (Container) ? Container->GetSortField() : 0; } bool GetSortAscending() { return (Container) ? Container->GetSortAscend() != 0 : 0; } void SetSort(int Col, int Ascend); void ReSort(); ScribeFolder *GetContainer() { return Container; } void SetContainer(ScribeFolder *c) { Container = c; } LFont *GetFont(); void OnPaint(LSurface *pDC); void OnColumnClick(int Col, LMouse &m); void OnItemClick(LListItem *Item, LMouse &m); void OnItemSelect(LArray &Item); bool OnKey(LKey &k); void OnColumnDrag(int Col, LMouse &m); bool OnColumnReindex(LItemColumn *Col, int OldIndex, int NewIndex); List PlaceHolders; void DeletePlaceHolders(); }; // this is the tree view on the left hand side // it contains all the ThingContainers class MailTree : public LTree, public LDragDropTarget { protected: ScribeWnd *App; LTreeItem *LastHit; int8 LastWasRoot; ThingList *Things() { return App->GetMailList(); } void OnCreate(); int WillAccept(LDragFormats &Formats, LPoint Pt, int KeyState); int OnDrop(LArray &Data, LPoint Pt, int KeyState); void OnDragEnter() { LTree::OnDragEnter(); } void OnDragExit() { LTree::OnDragExit(); } public: MailTree(ScribeWnd *app); ~MailTree(); const char *GetClass() { return "MailTree"; } ssize_t Sizeof(); bool Serialize(LFile &f, bool Write); void OnCreateSubDirectory(ScribeFolder *Item); void OnDelete(ScribeFolder *Item, bool Force); void OnProperties(ScribeFolder *Item); void OnItemClick(LTreeItem *Item, LMouse &m); void OnItemSelect(LTreeItem *Item); LMessage::Result OnEvent(LMessage *Msg); }; ////////////////////////////////////////////////////////////// class FilterUi : public ThingUi { protected: struct FilterUiPriv *d; Filter *Item; LDocView *Script; LScriptUi Commands; LTabView *Tab; class LFilterView *Conditions; LList *Actions; void OnLoad(); void OnSave(); bool OnViewKey(LView *v, LKey &k); public: FilterUi(Filter *item); ~FilterUi(); // Data access Filter *GetItem() { return Item; } // Events int OnNotify(LViewI *Col, LNotification n); LMessage::Result OnEvent(LMessage *Msg); int OnCommand(int Cmd, int Event, OsView Window); }; /////////////////////////////////////////////////////////////////////////////// extern void LogStrToFile(char *File, char *Str, ...); class ILog { protected: LVariant LogFileName; int LogFmt; LStreamI *Log; bool DeleteLog; Progress *Info; char *EnumFileName(char *File); void WriteLog(const char *s, ssize_t l); public: ILog(LStreamI *log); ~ILog(); void LogRead(char *Data, ssize_t Len); void LogWrite(const char *Data, ssize_t Len); void LogError(int ErrorCode, const char *ErrorDescription); void LogInfomation(const char *Str); bool LogSetVariant(const char *Name, LVariant &v, const char *Array = NULL); }; class ILogConnection : public ILog, public LSocket { public: ILogConnection(LStreamI *log) : ILog(log) { } ~ILogConnection() { } void OnRead(char *Data, ssize_t Len) override { LogRead(Data, Len); } void OnWrite(const char *Data, ssize_t Len) override { LogWrite(Data, Len); } void OnError(int ErrorCode, const char *ErrorDescription) override { LogError(ErrorCode, ErrorDescription); } void OnInformation(const char *Str) override { LogInfomation(Str); } bool SetVariant(const char *Name, LVariant &v, const char *Array = NULL) override { return LogSetVariant(Name, v, Array); } }; class ILogSocks5Connection : public ILog, public LSocks5Socket { public: ILogSocks5Connection( char *proxy, int port, char *username, char *password, LStreamI *log) : ILog(log) { SetProxy(proxy, port, username, password); } void OnRead(char *Data, ssize_t Len) override { if (Socks5Connected) LogRead(Data, Len); } void OnWrite(const char *Data, ssize_t Len) override { if (Socks5Connected) LogWrite(Data, Len); } void OnError(int ErrorCode, const char *ErrorDescription) override { LogError(ErrorCode, ErrorDescription); } void OnInformation(const char *Str) override { LogInfomation(Str); } bool SetVariant(const char *Name, LVariant &v, const char *Array = NULL) override { return LogSetVariant(Name, v, Array); } }; /////////////////////////////////////////////////////////////////////////////// #include "ScribeFolderSelect.h" #include "NewMailDlg.h" #include "SearchView.h" ////////////////////////////////////////////////////////////////////// class ScribePanel : public LPanel { protected: ScribeWnd *App; public: ScribePanel(ScribeWnd *app, const char *name, int size, bool open = true); bool Pour(LRegion &r); }; ////////////////////////////////////////////////////////////////////// // UI class CreateSubFolderDlg : public LDialog { LEdit *FolderName; LRadioGroup *FolderType; public: int SubType; char *SubName; CreateSubFolderDlg(LView *parent, int defaulttype = 0, bool *enable = 0, char *default_name = 0); ~CreateSubFolderDlg(); int OnNotify(LViewI *Ctrl, LNotification n); }; class LanguageDlg : public LDialog { class LanguageDlgPrivate *d; public: bool Ok; LAutoString Lang; LanguageDlg(ScribeWnd *app); ~LanguageDlg(); int OnNotify(LViewI *c, LNotification n); }; class FolderNameDlg : public LDialog { LEdit *Ed; public: char *Name; FolderNameDlg(LView *parent, const char *Old = ""); ~FolderNameDlg(); void OnCreate(); int OnNotify(LViewI *Ctrl, LNotification n); }; //////////////////////////////////////////////////////////////////////////////////////////////// class ScribeAccountItem; class OptionsDlg : public TabDialog, public LXmlTreeUi { friend class ScribeAccountItem; friend class AccountItem; class OptionsDlgPrivate *d; ScribeWnd *App; LFontType EditorFont; LFontType HtmlFont; List Langs; LCombo *UiLang; int SinkHnd; ScribeAccountItem *LastRecord; void UpdateDefaultSendAccounts(); void UpdateFontDescription(); bool PasswordCtrlValue(int CtrlId, char *Option, bool ToWindow); void WriteNativeText(LFile &f, char *t); public: OptionsDlg(ScribeWnd *window); ~OptionsDlg(); void OnCreate(); void OnAccountEnable(ScribeAccount *Acc, bool Enable); int OnNotify(LViewI *Ctrl, LNotification n); LMessage::Result OnEvent(LMessage *m); }; class ScribeThread : public LThread { ScribeWnd *App; int Index; public: ScribeThread(ScribeWnd *app, int index); int Main(); }; // Security Dialog class SecurityDlg : public LDialog, public LXmlTreeUi { class SecurityDlgPrivate *d; public: SecurityDlg(ScribeWnd *app); ~SecurityDlg(); int OnNotify(LViewI *c, LNotification n); }; // Bayesian filtering setup class BayesDlg : public LDialog, public LXmlTreeUi { class BayesDlgPrivate *d; public: BayesDlg(ScribeWnd *app); ~BayesDlg(); int OnNotify(LViewI *c, LNotification n); }; //////////////////////////////////////////////////////////////////////////////////// class DynamicHtml : public Html1::LHtml, public LDefaultDocumentEnv { class DynamicHtmlPrivate *d; public: DynamicHtml(ScribeWnd *app, const char *file); ~DynamicHtml(); char *OnDynamicContent(LDocView *Parent, const char *Code); bool OnNavigate(LDocView *Parent, const char *Uri); }; //////////////////////////////////////////////////////////////////////////////////// class ScribeBehaviour { public: static ScribeBehaviour *New(ScribeWnd *app); virtual ~ScribeBehaviour() {} // Events/Actions virtual bool DoWizard() { return false; } virtual bool SerializeOptions ( LOptionsFile *Opts, char *OptsFile, bool Write ) { return false; } }; //////////////////////////////////////////////////////////////////////////////////// struct SystemFolderInfo { int Id; const char *PathOption; const char *HasOption; }; extern SystemFolderInfo SystemFolders[]; //////////////////////////////////////////////////////////////////////////////////// // Shared memory record format, used to pass argument data to the right running // instance of Scribe. Mul also uses this to broker new instances of Scribe. #include "ScribeSharedMem.h" //////////////////////////////////////////////////////////////////////////////////// // ScribeFunc void TraceTime(char *s); extern void LoadCalendarStringTable(); // Misc extern void WrapAndQuote(LStream &Out, const char *Quote, int Wrap, const char *Text, const char *Cp = NULL, const char *MimeType = NULL); extern void RemoveReturns(char *s); extern bool DecodeUuencodedAttachment(LDataStoreI *Store, LArray &Files, LStreamI *Out, const char *In); // extern LDocView *CreateIeControl(int Id); // Casters... extern Mail *IsMail(LListItem *Item); extern Contact *IsContact(LListItem *Item); extern Thing *CastThing(LDataI *t); // Folder extern void Scribe_Repair(ScribeWnd *Parent); // Scribe windows extern bool OpenPopView(ScribeWnd *Parent, LArray &Lst); extern bool OpenFolderProperties(ScribeFolder *Parent, int Tab); extern LView *OpenFinder(ScribeWnd *App, ScribeFolder *Folder); // Import #include "Imp_Outlook.h" #include "Imp_Beos.h" extern void Import_NetscapeContacts(ScribeWnd *Parent); extern void Import_UnixMBox(ScribeWnd *Parent); extern void Import_OutlookExpress(ScribeWnd *Parent, bool v5 = true); -extern bool Import_EudoraAddressBook(ScribeWnd *App); -extern bool Import_MozillaAddressBook(ScribeWnd *App); -extern bool Import_MozillaMail(ScribeWnd *App); +extern void Import_EudoraAddressBook(ScribeWnd *App); +extern void Import_MozillaAddressBook(ScribeWnd *App); +extern void Import_MozillaMail(ScribeWnd *App); extern void Import_OutlookContacts(ScribeWnd *Parent); extern MailSource *NewOutlookMailSource(ScribeWnd *Parent, ScribeAccount *Account); // Export extern void Export_UnixMBox(ScribeWnd *Parent); extern void ImportCsv(ScribeWnd *App); extern void ExportCsv(ScribeWnd *App); extern void ImportEml(ScribeWnd *App); // DOM stuff extern void InitStrToDom(); #endif diff --git a/Code/ScribeRepair.cpp b/Code/ScribeRepair.cpp --- a/Code/ScribeRepair.cpp +++ b/Code/ScribeRepair.cpp @@ -1,742 +1,740 @@ // Scribe Folder Repair code #include #include #include #include #include "Scribe.h" #include "lgi/common/FileSelect.h" #if 0 // FIXME char ModuleName[] = "Storage Repair"; class RepairFile { ScribeWnd *Parent; List PossibleLocs; List Items; StorageKit *StoreKit; ScribeFolder *MailThing; StorageItem *MailBox; char FolderName[256]; char *GetFolderName(int i); public: RepairFile(ScribeWnd *parent, char *FileName); void CreateFolder(char *Name, int Type, StorageItem *&Item); bool PatchChild(StorageItem *Item, StorageItem *Child = 0); bool PatchParent(StorageItem *Item, StorageItem *Parent = 0); bool PatchNext(StorageItem *Item, StorageItem *Next = 0); bool PatchPrev(StorageItem *Item, StorageItem *Prev = 0); }; #define I_FOLDER 0 #define I_MAIL 1 #define I_CONTACT 2 RepairFile::RepairFile(ScribeWnd *parent, char *FileName) { int ItemCount[3] = {0, 0, 0}; // Init Parent = parent; MailThing = 0; #if 0 // v1 storage // first lets do a general search for possible items in the file LFile F; if (F.Open(FileName, O_READ)) { int Len = F.GetSize(); uchar *Buf = new uchar[Len]; if (Buf) { if (F.Read(Buf, Len) == Len) { for (int i=64; iSetObject(MailThing); MailThing->Window = Parent; MailThing->Store = MailBox; // Detects and fixes broken link from the mail to child PatchChild(MailBox); // Ok now lets get down to the real business of locating items // and if necessary repatching them back into the tree // Now lets check all these possibles for actuals for (int *n = PossibleLocs.First(); n; n = PossibleLocs.Next()) { StorageItem *Item = (*n > 64) ? Kit.LoadLocation(*n) : 0; if (Item) { StorageItem *Insert = 0; if (Item->StoreType == MAGIC_FOLDER) { ScribeFolder *t = new ScribeFolder(NULL, 0); t->SetObject(Item); LFile *f = Item->GotoObject(); if (f) { if (t->Serialize(*f, FALSE)) { ItemCount[I_FOLDER]++; Insert = Item; } } } else { Thing *t = Parent->LoadItem(Item); if (t) { switch (Item->StoreType) { case MAGIC_MAIL: { ItemCount[I_MAIL]++; break; } case MAGIC_CONTACT: { ItemCount[I_CONTACT]++; break; } } Insert = Item; } } if (Insert) { // None of the objects have been loaded correctly // So null em out. We use this later when we're // patching everything back together again. If a pointer // is non-null then we know we set it and it's ok. Insert->Prev = 0; Insert->Next = 0; Insert->Parent = 0; Insert->Child = 0; // It's an item of some sort Items.Insert(Insert); } } } // Lets loop through the items and see if any base // folders are there StorageItem *i; for (i = Items.First(); i; i = Items.Next()) { if (i->StoreType == MAGIC_FOLDER) { ScribeFolder *c = dynamic_cast(Parent->LoadItem(i)); if (c) { char *Text = c->LTreeItem::GetText(); if (Text) { #define CheckName(n, v) if (stricmp(Text, n) == 0) v = i; CheckName(GetFolderName(FOLDER_INBOX), Inbox); CheckName(GetFolderName(FOLDER_OUTBOX), Outbox); CheckName(GetFolderName(FOLDER_SENT), Sent); CheckName(GetFolderName(FOLDER_TRASH), Trash); CheckName(GetFolderName(FOLDER_CONTACTS), Contacts); } } } } // if the folders don't exist then create them CreateFolder(GetFolderName(FOLDER_INBOX), MAGIC_MAIL, Inbox); CreateFolder(GetFolderName(FOLDER_OUTBOX), MAGIC_MAIL, Outbox); CreateFolder(GetFolderName(FOLDER_SENT), MAGIC_MAIL, Sent); CreateFolder(GetFolderName(FOLDER_TRASH), MAGIC_ANY, Trash); CreateFolder(GetFolderName(FOLDER_CONTACTS), MAGIC_CONTACT, Contacts); // follow links back to root // patching them as necessary... doesn't really matter where // as long as they show up. Iterator ItemList(&Items); for (i = ItemList.First(); i; i = ItemList.Next()) { int Index = Items.IndexOf(i); StorageItem *Find = i; while ( Find AND Find->StoreLoc != MailBox->StoreLoc) { // Walk prev list while (Find->StorePrev) { if (PatchPrev(Find)) { // Keep walking... prev Find = Find->Prev; // Fall thru and walk parent } else { // It's dead // End of the line buddy, terminate here Find->StorePrev = 0; Kit.SerializeItem(Find, true, STORAGE_ITEM_NOSIZE); } } // Ok check the parent pointer if (Find->StoreParent) { if (PatchParent(Find)) { // Keep walking... up Find = Find->Parent; // Go walking the next list again continue; } else { // It's dead // End of the line buddy, terminate here Find->StoreParent = 0; Kit.SerializeItem(Find, true, STORAGE_ITEM_NOSIZE); } } // Ok no parent... *sigh* if (NOT Find->StoreParent) { // this peice of the tree has been orphaned // so we have to attach it somewhere // // If it's a mail then dump it in the inbox // If it's a contact, the contacts folder // If it's a folder then straight off the mailbox itself // Otherwise we just have to chuck it. StorageItem *Link = 0; List Attachments; switch (Find->StoreType) { case MAGIC_MAIL: { // Just to make it worse // Attachments were marked with the MAGIC_MAIL type // in versions before 1.25 // So we need to make sure it's actually a real mail // Attachments will always have a parent of type // MAGIC_MAIL, whereas a real email will have a folder // for a parent... and because we don't know what the // parent is here we're stuffed. // // However the load function shouldn't of let any // attachments get into the item list in the fist // place because it'll try loading the object using // an email object, which of course will [probably] // fail... Link = Inbox; /* This is broken??? if (PatchChild(Find)) { StorageItem *a = Find->Child; while (a) { if (a->StoreType == MAGIC_ATTACHMENT OR a->StoreType == MAGIC_MAIL) { Attachments.Insert(a); } if (PatchNext(a)) { a = a->Next; } else { a = 0; } } } */ break; } case MAGIC_CONTACT: { Link = Contacts; break; } case MAGIC_FOLDER: { Link = MailBox; break; } case MAGIC_ATTACHMENT: { // ??? bugger // Create an email just to stick the attachment in it? // Maybe write it out as a file... at least it's not lost break; } default: case MAGIC_NONE: case MAGIC_ANY: { // Ahhh, dunno what to do with this // throw it away (by the default action // of not attaching it). break; } } if (Link) { // this chain, with it's top defined as Find // as a child of the parent store, which we // know is valid. // Ok check the child pointer if (Link->StoreChild AND PatchChild(Link)) { // Keep walking... child Link = Link->Child; } else { // No child eh? // Link em here Link->Child = Find; Link->StoreChild = Find->StoreLoc; Kit.SerializeItem(Link, true, STORAGE_ITEM_NOSIZE); Find->Parent = Link; Find->StoreParent = Link->StoreLoc; Kit.SerializeItem(Find, true, STORAGE_ITEM_NOSIZE); // exit out of this loop, our job here is done Find = 0; } if (Find) { // Walk next list while (Link->StoreNext) { if (PatchNext(Link)) { // Keep walking... next Link = Link->Next; } else break; } // Link em here Link->Next = Find; Link->StoreNext = Find->StoreLoc; Kit.SerializeItem(Link, true, STORAGE_ITEM_NOSIZE); Find->Prev = Link; Find->StorePrev = Link->StoreLoc; Kit.SerializeItem(Find, true, STORAGE_ITEM_NOSIZE); // exit out of this loop, our job here is done Find = 0; } } } if (Find) { Find = Find->Parent; } } // while (Find) } // for (All Items) char Str[512]; sprintf(Str, "Repair complete\n" "\n" "Object Id's: %i\n" "Valid Items: %i\n" "\n" "Folders: %i\n" "Email: %i\n" "Contacts: %i\n", PossibleLocs.GetItems(), Items.GetItems(), ItemCount[I_FOLDER], ItemCount[I_MAIL], ItemCount[I_CONTACT]); LgiMsg(Parent, Str, "Repair", MB_OK); } // if (MailThing) } else { LgiMsg(Parent, "Couldn't create the root item.", ModuleName, MB_OK); } #endif } void RepairFile::CreateFolder(char *Name, int Type, StorageItem *&Item) { #if 0 if (MailThing) { if (Item) { // MailBox will always have valid 1st level children // Ok item exists but is it patched to it parent? StorageItem *i = MailBox->Child; while (i) { if (i->StoreLoc == Item->StoreLoc) { // already in the list... // we are cool return; } else if (NOT i->Next) { // we are at the end of the list and we havn't // found "Item" yet.. so patch it in. PatchNext(i, Item); // Patch any furthur items i = Item; while (PatchNext(i)) { i = i->Next; } return; } i = i->Next; } } else { // No item exists... create one // Mailbox should be safe by now to create children from ScribeFolder *t = MailThing->CreateSubDirectory(Name, Type); if (t) { Item = t->Store; } } } #endif } bool RepairFile::PatchChild(StorageItem *Item, StorageItem *Link) { #if 0 #define ItemLinkPtr Child #define LinkItemPtr Parent #define ItemLinkLoc StoreChild #define LinkItemLoc StoreParent if (Item AND NOT Item->ItemLinkPtr) { if (NOT Link) { for (StorageItem *p = Items.First(); p; p = Items.Next()) { if (p->StoreLoc == Item->ItemLinkLoc) { Link = p; break; } } } if (Link) { Link->LinkItemPtr = Item; Link->LinkItemLoc = Item->StoreLoc; StoreKit->SerializeItem(Link, true, STORAGE_ITEM_NOSIZE); } Item->ItemLinkPtr = Link; Item->ItemLinkLoc = (Link) ? Link->StoreLoc : 0; StoreKit->SerializeItem(Item, true, STORAGE_ITEM_NOSIZE); } return (Item) ? (Item->ItemLinkPtr != 0) : (false); #undef ItemLinkPtr #undef LinkItemPtr #undef ItemLinkLoc #undef LinkItemLoc #else return 0; #endif } bool RepairFile::PatchParent(StorageItem *Item, StorageItem *Link) { #if 0 #define ItemLinkPtr Parent #define LinkItemPtr Child #define ItemLinkLoc StoreParent #define LinkItemLoc StoreChild if (Item AND NOT Item->ItemLinkPtr) { if (NOT Link) { for (StorageItem *p = Items.First(); p; p = Items.Next()) { if (p->StoreLoc == Item->ItemLinkLoc) { Link = p; break; } } } if (Link) { Link->LinkItemPtr = Item; Link->LinkItemLoc = Item->StoreLoc; StoreKit->SerializeItem(Link, true, STORAGE_ITEM_NOSIZE); } Item->ItemLinkPtr = Link; Item->ItemLinkLoc = (Link) ? Link->StoreLoc : 0; StoreKit->SerializeItem(Item, true, STORAGE_ITEM_NOSIZE); } return (Item) ? (Item->ItemLinkPtr != 0) : (false); #undef ItemLinkPtr #undef LinkItemPtr #undef ItemLinkLoc #undef LinkItemLoc #else return 0; #endif } bool RepairFile::PatchNext(StorageItem *Item, StorageItem *Link) { #if 0 #define ItemLinkPtr Next #define LinkItemPtr Prev #define ItemLinkLoc StoreNext #define LinkItemLoc StorePrev if (Item AND NOT Item->ItemLinkPtr) { if (NOT Link) { // Search for item for (StorageItem *p = Items.First(); p; p = Items.Next()) { if (p->StoreLoc == Item->ItemLinkLoc) { Link = p; break; } } /* // check the validity of that item StorageItem *i = Item->Prev; while (i AND p) { while (i->Prev AND p) { if (i->StoreLoc == p->StoreLoc) { p = 0; } i = i->Prev; } if (i->Parent AND p) { if (i->StoreLoc == p->StoreLoc) { p = 0; } i = i->Parent; } } */ } if (Link) { Link->LinkItemPtr = Item; Link->LinkItemLoc = Item->StoreLoc; StoreKit->SerializeItem(Link, true, STORAGE_ITEM_NOSIZE); } Item->ItemLinkPtr = Link; Item->ItemLinkLoc = (Link) ? Link->StoreLoc : 0; StoreKit->SerializeItem(Item, true, STORAGE_ITEM_NOSIZE); } return (Item) ? (Item->ItemLinkPtr != 0) : (false); #undef ItemLinkPtr #undef LinkItemPtr #undef ItemLinkLoc #undef LinkItemLoc #else return 0; #endif } bool RepairFile::PatchPrev(StorageItem *Item, StorageItem *Link) { #if 0 #define ItemLinkPtr Prev #define LinkItemPtr Next #define ItemLinkLoc StorePrev #define LinkItemLoc StoreNext if (Item AND NOT Item->ItemLinkPtr) { if (NOT Link) { // Search for possible item for (StorageItem *p = Items.First(); p; p = Items.Next()) { if (p->StoreLoc == Item->ItemLinkLoc) { Link = p; break; } } } if (Link) { Link->LinkItemPtr = Item; Link->LinkItemLoc = Item->StoreLoc; StoreKit->SerializeItem(Link, true, STORAGE_ITEM_NOSIZE); } Item->ItemLinkPtr = Link; Item->ItemLinkLoc = (Link) ? Link->StoreLoc : 0; StoreKit->SerializeItem(Item, true, STORAGE_ITEM_NOSIZE); } return (Item) ? (Item->ItemLinkPtr != 0) : (false); #undef ItemLinkPtr #undef LinkItemPtr #undef ItemLinkLoc #undef LinkItemLoc #else return 0; #endif } char *RepairFile::GetFolderName(int i) { FolderName[0] = 0; if (Parent) { LVariant v; char Str[32], *s; sprintf(Str, "Folder-%i", i); if (Parent->GetOptions()->GetValue(Str, v) AND (s = v.Str()) != 0) { char *n = 0; for (n = s+strlen(s)-1; n>=s; n--) { if (*n == '/') { strcpy(FolderName, n+1); break; } } if (nType("Mail folders", "*.mail"); + Select->Open([&](auto dlg, auto status) { // FIXME RepairFile Worker(Parent, Select.Name()); - } + delete dlg; + }); } diff --git a/Code/ScribeSendReceive.cpp b/Code/ScribeSendReceive.cpp --- a/Code/ScribeSendReceive.cpp +++ b/Code/ScribeSendReceive.cpp @@ -1,2873 +1,2847 @@ /* ** FILE: ScribeAccount.cpp ** AUTHOR: Matthew Allen ** DATE: 19/10/99 ** DESCRIPTION: Scribe account ** ** Copyright (C) 2002, Matthew Allen ** fret@memecode.com */ // Includes #include #include #include #include #include "Scribe.h" #include "lgi/common/OpenSSLSocket.h" #include "ScribePrivate.h" #include "lgi/common/NetTools.h" #include "resdefs.h" #include "lgi/common/LgiRes.h" #include "ScribeAccountPreview.h" #include "ScribeUtils.h" #include "lgi/common/TextConvert.h" #define MAIL_POSTED_TO_GUI 0x0001 #define MAIL_EXPLICIT 0x0002 #define SECONDS(i) ((i)*1000) #define TRANSFER_WAIT_TIMEOUT SECONDS(30) #define DEFAULT_SOCKET_TIMEOUT SECONDS(15) LColour SocketMsgTypeToColour(LSocketI::SocketMsgType flags) { LColour col; switch (flags) { case LSocketI::SocketMsgInfo: case LSocketI::SocketMsgNone: col.Set(MAIL_INFO_COLOUR, 24); break; case LSocketI::SocketMsgSend: col.Set(MAIL_SEND_COLOUR, 24); break; case LSocketI::SocketMsgReceive: col.Set(MAIL_RECEIVE_COLOUR, 24); break; case LSocketI::SocketMsgError: col.Set(MAIL_ERROR_COLOUR, 24); break; case LSocketI::SocketMsgWarning: col.Set(MAIL_WARNING_COLOUR, 24); break; default: LAssert(0); break; } return col; } int MakeOpenFlags(ScribeAccount *a, bool Send) { int OpenFlags = 0; Accountlet *l = Send ? (Accountlet*)&a->Send : (Accountlet*)&a->Receive; auto Nam = a->Identity.Name(); auto Em = a->Identity.Email(); // auto SslSmtp = a->Send.UseSSL(); ScribeSslMode SslMode = (ScribeSslMode)l->UseSSL(); switch (SslMode) { case SSL_STARTTLS: OpenFlags |= MAIL_USE_STARTTLS; break; case SSL_DIRECT: OpenFlags |= MAIL_SSL; break; default: break; } if (!Send && a->Receive.SecureAuth()) { OpenFlags |= MAIL_SECURE_AUTH; } if (Send) { if (a->Send.RequireAuthentication()) { OpenFlags |= MAIL_USE_AUTH; } switch (a->Send.AuthType()) { case 1: OpenFlags |= MAIL_USE_PLAIN; break; case 2: OpenFlags |= MAIL_USE_LOGIN; break; case 3: OpenFlags |= MAIL_USE_CRAM_MD5; break; case 4: OpenFlags |= MAIL_USE_OAUTH2; break; } } else { switch (a->Receive.AuthType()) { case 1: OpenFlags |= MAIL_USE_PLAIN; break; case 2: OpenFlags |= MAIL_USE_LOGIN; break; case 3: OpenFlags |= MAIL_USE_NTLM; break; case 4: OpenFlags |= MAIL_USE_OAUTH2; break; } } return OpenFlags; } /* class LProtocolLogger : public LStreamI { LViewI *Wnd; int Msg; List *Log; public: LProtocolLogger(LViewI *wnd, int msg, List *log) { Wnd = wnd; Msg = msg; Log = log; } ssize_t Read(void *Buffer, ssize_t Size, int Flags = 0) { return -1; } ssize_t Write(const void *b, ssize_t len, int f = 0) { LogEntry *l = new LogEntry(SocketMsgTypeToColour((GSocketI::SocketMsgType)f)); if (l) { l->Add((const char*)b, len); Wnd->PostEvent(Msg, (LMessage::Param)Log, (LMessage::Param)l); } return len; } }; */ void MakeTempPath(char *Path, int PathSize) { // Create unique temporary filename char r[32]; do { sprintf_s(r, sizeof(r), "%X.eml", LRand()); LMakePath(Path, PathSize, ScribeTempPath(), r); } while (LFileExists(Path)); } const char *AccountThreadStateName(AccountThreadState i) { switch (i) { case ThreadIdle: return "ThreadIdle"; case ThreadSetup: return "ThreadSetup"; case ThreadConnecting: return "ThreadConnecting"; case ThreadTransfer: return "ThreadTransfer"; case ThreadWaiting: return "ThreadWaiting"; case ThreadDeleting: return "ThreadDeleting"; case ThreadDone: return "ThreadDone"; case ThreadCancel: return "ThreadCancel"; case ThreadError: return "ThreadError"; } return "(none)"; } const char *ReceiveActionName(ReceiveAction i) { switch (i) { case MailNoop: return "MailNoop"; case MailDelete: return "MailDelete"; case MailDownloadAndDelete: return "MailDownloadAndDelete"; case MailDownload: return "MailDownload"; case MailUpload: return "MailUpload"; case MailHeaders: return "MailHeaders"; default: break; } return "(error)"; } const char *ReceiveStatusName(ReceiveStatus i) { switch (i) { case MailReceivedNone: return "ReceiveNone"; case MailReceivedWaiting: return "ReceiveWaiting"; case MailReceivedOk: return "ReceiveOk"; case MailReceivedError: return "ReceiveError"; default: break; } return "(none)"; } bool Accountlet::WaitForTransfers(List &Files) { LArray Counts; auto StartTs = LCurrentTime(); do { Counts.Length(0); for (auto e: Files) Counts[e->Status]++; LSleep(100); if (LCurrentTime() - StartTs > TRANSFER_WAIT_TIMEOUT) { LgiTrace("%s:%i - WaitForTransfers timed out.\n", _FL); for (ReceiveStatus rs = MailReceivedNone; rs < MailReceivedMax; rs = (ReceiveStatus)(((int)rs)+1)) LgiTrace("...%s=%i\n", ReceiveStatusName(rs), Counts[rs]); return false; } else if (Thread && Thread->IsCancelled()) { LgiTrace("%s:%i - WaitForTransfers exiting because thread is cancelled.\n", _FL); return false; } } while (Counts[MailReceivedWaiting] > 0); return true; } //////////////////////////////////////////////////////////////////////////// char *NewStrCat(char *a, char *b) { size_t Len = ((a)?strlen(a):0) + ((b)?strlen(b):0); char *s = new char[Len+1]; if (s) { s[0] = 0; if (a) strcat(s, a); if (b) strcat(s, b); } DeleteArray(a); return s; } class AccountletThread : public AccountThread { friend class SendAccountlet; friend class ReceiveAccountlet; friend bool AfterReceived(MailTransaction *Trans, void *Data); List Files; void MakeTempPath(char *Buf); void SetState(AccountThreadState s); AccountThreadState GetState(); public: // Object - Api AccountletThread(Accountlet *acc, List *Input); ~AccountletThread(); // Methods void Delete(int Index); void Complete(); // Called by the app when the all the files have been processed // void Cancel(); // Called by the app to signal the user is canceling the transfer // Thread int Main(); }; //////////////////////////////////////////////////////////////////////////// AccountletThread::AccountletThread(Accountlet *acc, List *Input) : AccountThread(acc) { } AccountletThread::~AccountletThread() { Files.DeleteObjects(); } void AccountletThread::SetState(AccountThreadState s) { Acc->State = s; } AccountThreadState AccountletThread::GetState() { return Acc->GetState(); } void AccountletThread::Delete(int Index) { MailTransferEvent *e = Files[Index]; if (e) { e->Action = MailDelete; } } void AccountletThread::Complete() { for (auto f: Files) { f->Rfc822Msg.Reset(); } Acc->State = ThreadDeleting; } int AccountletThread::Main() { // We're running... SetState(ThreadSetup); // Do all the connect, transfer and clean up Acc->Main(this); // This tells the app we're all done SetState(ThreadIdle); return 0; } //////////////////////////////////////////////////////////////////////////// LList *MailTransferEvent::GetList() { if (!Account) { LAssert(!"No account."); return NULL; } // As the list is set in the GUI thread, we should only ever use it in the // GUI thread... so check for that here. Otherwise we need to lock it while it's // in use. Which is not currently done. #ifdef _DEBUG ScribeWnd *App = Account->GetApp(); #endif LAssert(App->InThread()); ReceiveAccountlet *ra = dynamic_cast(Account); return ra ? ra->GetItems() : NULL; } //////////////////////////////////////////////////////////////////////////// Accountlet::Accountlet(ScribeAccount *a) : PrivLock("Accountlet") { DataStore = NULL; MailStore = NULL; Root = 0; Account = a; ConnectionStatus = true; OptPassword = 0; Client = 0; - TempPsw = 0; LastOnline = 0; Parent = 0; State = ThreadIdle; // Update sig to new format... char Buf[256]; LVariant Old; if (GetApp()->GetOptions()->GetValue(OptionName("AccSig", Buf, sizeof(Buf)), Old)) { if (LFileExists(Old.Str())) { char *Xml = LReadTextFile(Old.Str()); if (Xml) { GetAccount()->Identity.TextSig(Xml); DeleteArray(Xml); } } GetApp()->GetOptions()->DeleteValue(Buf); } } Accountlet::~Accountlet() { I Lck = Lock(_FL); if (Lck) { Lck->d->Log.DeleteObjects(); Lck.Reset(); } - DeleteArray(TempPsw); DeleteObj(Root); DeleteObj(DataStore); } void Accountlet::OnBeforeDelete() { // This has to be before ~ScribeAccount is finished, so that the // account's index is still valid, if we are going to save the "Expanded" // setting. LString p; if (DataStore && Root) { p = Root->GetPath(); bool e = Root->Expanded(); Expanded(e); } } bool Accountlet::GetVariant(const char *Name, LVariant &Value, const char *Array) { char k[128]; bool mapped = Account->IsMapped(OptionName(Name, k, sizeof(k))); auto Opts = GetApp()->GetOptions(); Value.Empty(); if (mapped) Opts->GetValue(k, Value); return true; } bool Accountlet::SetVariant(const char *Name, LVariant &Value, const char *Array) { char k[128]; bool mapped = Account->IsMapped(OptionName(Name, k, sizeof(k))); auto Opts = GetApp()->GetOptions(); if (mapped) Opts->SetValue(k, Value); return true; } void Accountlet::StrOption(const char *Opt, LVariant &v, const char *Set) { char Key[128]; OptionName(Opt, Key, sizeof(Key)); if (Set) GetApp()->GetOptions()->SetValue(Key, v = Set); else GetApp()->GetOptions()->GetValue(Key, v); } void Accountlet::IntOption(const char *Opt, LVariant &v, int Set) { char Key[128]; OptionName(Opt, Key, sizeof(Key)); if (Set >= 0) GetApp()->GetOptions()->SetValue(Key, v = Set); else GetApp()->GetOptions()->GetValue(Key, v); } const char *Accountlet::GetStateName() { switch (State) { case ThreadIdle: return LLoadString(IDS_ACCSTAT_IDLE); case ThreadSetup: return LLoadString(IDS_ACCSTAT_SETUP); case ThreadConnecting: return LLoadString(IDS_ACCSTAT_CONNECT); case ThreadTransfer: return LLoadString(IDS_ACCSTAT_TRANS); case ThreadWaiting: return LLoadString(IDS_ACCSTAT_WAIT); case ThreadDeleting: return LLoadString(IDS_ACCSTAT_DELETING); case ThreadDone: return LLoadString(IDS_ACCSTAT_FINISHED); case ThreadCancel: return LLoadString(IDS_ACCSTAT_CANCELLED); case ThreadError: return LLoadString(IDS_ERROR); } return "(none)"; } void Accountlet::OnThreadDone() { Thread.Reset(); if (DataStore && Root) Expanded(Root->Expanded()); DeleteObj(Root); } ScribeWnd *Accountlet::GetApp() { LAssert(Account->Parent != NULL); return Account->Parent; } LSocketI *Accountlet::CreateSocket(bool Sending, LCapabilityClient *Caps, bool RawLFCheck) { LSocketI *Socket = 0; LVariant File; LVariant Socks5Proxy; LVariant UseSocks; LVariant Format = (int)NET_LOG_NONE; GetApp()->GetOptions()->GetValue(OPT_LogFile, File); GetApp()->GetOptions()->GetValue(OPT_LogFormat, Format); int SslMode = UseSSL(); if (SslMode > 0) { Socket = new SslSocket(this, Caps, SslMode == SSL_DIRECT, RawLFCheck); } else if ( GetApp()->GetOptions()->GetValue(OPT_UseSocks, UseSocks) && UseSocks.CastInt32() && GetApp()->GetOptions()->GetValue(OPT_Socks5Server, Socks5Proxy) && ValidStr(Socks5Proxy.Str())) { LVariant Socks5UserName; LVariant Socks5Password; GetApp()->GetOptions()->GetValue(OPT_Socks5UserName, Socks5UserName); GetApp()->GetOptions()->GetValue(OPT_Socks5Password, Socks5Password); LUri Server(Socks5Proxy.Str()); Socket = new ILogSocks5Connection( Server.sHost, Server.Port?Server.Port:1080, Socks5UserName.Str(), Socks5Password.Str(), this); } else { Socket = new ILogConnection(this); } if (Socket) { Socket->SetTimeout(DEFAULT_SOCKET_TIMEOUT); if (File.Str()) { Socket->SetValue(OPT_LogFile, File); Socket->SetValue(OPT_LogFormat, Format); } Socket->SetCancel(Thread); } return Socket; } void Accountlet::Disconnect() { if (Thread) { Thread->Disconnect(); } else { if (MailStore) Account->Parent->OnMailStore(&MailStore, false); if (Root) { if (DataStore && Root) Expanded(Root->Expanded()); Root->Remove(); DeleteObj(Root); } DeleteObj(DataStore); } } void Accountlet::Kill() { if (Thread) { Thread->Kill(); } } bool Accountlet::Lock() { return Account->Parent->Lock(_FL); } void Accountlet::Unlock() { Account->Parent->Unlock(); } ssize_t Accountlet::Write(const void *buf, ssize_t size, int flags) { if (!Account->GetApp()) return 0; LColour col = SocketMsgTypeToColour((LSocketI::SocketMsgType)flags); I Lck = Lock(_FL); if (Lck) { bool Match = false; LArray &Log = Lck->d->Log; if (Log.Length() > 0) Match = Log.Last()->GetColour() == col; if (Match) { Log.Last()->Add((const char*)buf, size); } else { LogEntry *le = new LogEntry(col); if (le) { le->Add((const char*)buf, size); Log.Add(le); } } } return size; } void Accountlet::OnOnlineChange(bool Online) { if (DataStore && Root && Expanded()) { // uint64 s = LCurrentTime(); Root->Expanded(true); // uint64 e = LCurrentTime(); // LgiTrace("Expanded took: %i\n", (int)(e-s)); } } bool Accountlet::Connect(LView *p, bool quiet) { bool Status = false; I Lck = Lock(_FL); if (Lck) { Lck->d->Log.DeleteObjects(); Lck.Reset(); } if (Lock()) { Parent = p; Quiet = quiet; if (!Thread) { ReceiveAccountlet *Receive = NULL; if (IsReceive() && (Receive = &GetAccount()->Receive) && Receive->IsPersistant()) { LVariant Proto = GetAccount()->Receive.Protocol(); ScribeWnd *Wnd = GetAccount()->Parent; ScribeProtocol Protocol = ProtocolStrToEnum(Proto.Str()); #ifdef ImapSupport LString HostName = Server().Str(); if (!DataStore && Protocol == ProtocolImapFull && ValidStr(HostName)) { char Password[256] = ""; GPassword p; if (GetPassword(&p)) { p.Get(Password); } int OpenFlags = MakeOpenFlags(Account, false); MailProtocolProgress *Prog[2] = { &Group, &Item }; LAutoPtr Store; char StorePath[128]; if (OptionName(NULL, StorePath, sizeof(StorePath))) { Store.Reset(new ProtocolSettingStore(Wnd->GetOptions(), StorePath)); } DataStore = OpenImap(HostName, Receive->Port(), UserName().Str(), Password, OpenFlags, Account->GetApp(), GetAccount(), Prog, this, Id(), Store); if (DataStore) { DeleteObj(Root); if ((Root = new ScribeFolder)) { Root->App = Account->GetApp(); Root->SetLoadOnDemand(); LDataFolderI *r = DataStore->GetRoot(); if (r) Root->SetObject(r, false, _FL); Wnd->Tree->Insert(Root); } } } #endif #ifdef WIN32 if (!DataStore && Protocol == ProtocolMapi && ValidStr(Server().Str())) { char Password[256] = ""; GPassword p; if (GetPassword(&p)) p.Get(Password); DataStore = OpenMapiStore( Server().Str(), UserName().Str(), Password, Account->Receive.Id(), Account->GetApp()); if (DataStore) { LDataFolderI *r = DataStore->GetRoot(); if (r) { DeleteObj(Root); if ((Root = new ScribeFolder)) { Root->App = Account->GetApp(); Root->SetLoadOnDemand(); Root->SetObject(r, false,_FL); Wnd->Tree->Insert(Root); } } else LgiTrace("%s:%i - Failed to get data store root.\n", _FL); } } #endif } else { // Setup Enabled(false); I Lck = Lock(_FL); if (Lck) { Lck->d->Log.DeleteObjects(); Lck.Reset(); } - - // Start thread - if (Thread.Reset(new AccountletThread(this, 0))) + + auto StartThread = [&]() { - Status = true; - Thread->Run(); - Account->Parent->OnBeforeConnect(Account, IsReceive()); + if (Thread.Reset(new AccountletThread(this, 0))) + { + Status = true; + Thread->Run(); + Account->Parent->OnBeforeConnect(Account, IsReceive()); + } + }; + + // Do we need the password? + LString Password; + GPassword Psw; + GetPassword(&Psw); + Password = Psw.Get(); + auto User = UserName(); + + if (User.Str() && !ValidStr(Password)) + { + auto RemoteHost = Server(); + + LString Msg; + Msg.Printf(LLoadString(IDS_ASK_ACCOUNT_PASSWORD), User.Str(), RemoteHost.Str()); + GetApp()->GetUserInput( Parent ? Parent : GetApp(), + Msg, + true, + [&](auto Psw) + { + this->TempPsw = Psw; + StartThread(); + }); } + else StartThread(); } } Unlock(); } return Status; } void Accountlet::IsCancelled(bool b) { if (Thread) Thread->Cancel(b); } bool Accountlet::IsCancelled() { return Thread ? Thread->IsCancelled() : false; } char *Accountlet::OptionName(const char *Opt, char *Dest, int DestLen) { int Idx = Account->GetIndex(); LAssert(Idx >= 0); if (Opt) { if (sprintf_s(Dest, DestLen, "Accounts.Account-%i.%s", Idx, Opt) < 0) return NULL; } else { if (sprintf_s(Dest, DestLen, "Accounts.Account-%i", Idx) < 0) return NULL; } return Dest; } bool Accountlet::GetPassword(GPassword *p) { bool Status = false; if (p && OptPassword && Lock()) { char Name[128]; OptionName(OptPassword, Name, sizeof(Name)); Status = p->Serialize(Account->GetApp()->GetOptions(), Name, false); Unlock(); } return Status; } void Accountlet::SetPassword(GPassword *p) { if (OptPassword && Lock()) { char Name[128]; OptionName(OptPassword, Name, sizeof(Name)); if (p) { p->Serialize(Account->GetApp()->GetOptions(), Name, true); } else { Account->GetApp()->GetOptions()->DeleteValue(Name); } Unlock(); } } bool Accountlet::IsCheckDialup() { LVariant CheckDialUp; char Name[128]; Account->GetApp()->GetOptions()->GetValue(OptionName(OPT_CheckForDialUp, Name, sizeof(Name)), CheckDialUp); return CheckDialUp.CastInt32() != 0; } //////////////////////////////////////////////////////////////////////////// AccountIdentity::AccountIdentity(ScribeAccount *a) : Accountlet(a) { } bool AccountIdentity::IsValid() { return ValidStr(Email().Str()) && ValidStr(Name().Str()); } void AccountIdentity::CreateMaps() { char Name[128]; Account->Map(OptionName(OPT_AccIdentName, Name, sizeof(Name)), IDC_NAME, GV_STRING); Account->Map(OptionName(OPT_AccIdentEmail, Name, sizeof(Name)), IDC_EMAIL, GV_STRING); Account->Map(OptionName(OPT_AccIdentReply, Name, sizeof(Name)), IDC_REPLY_TO, GV_STRING); Account->Map(OptionName(OPT_AccIdentTextSig, Name, sizeof(Name)), IDC_SIG, GV_STRING); Account->Map(OptionName(OPT_AccIdentHtmlSig, Name, sizeof(Name)), IDC_SIG_HTML, GV_STRING); } bool AccountIdentity::GetVariant(const char *Name, LVariant &Value, const char *Array) { /* case SdName: // Type: String case SdEmail: // Type: String case SdReply: // Type: String case SdSig: // Type: String case SdHtmlSig: // Type: String */ char n[128]; sprintf_s(n, sizeof(n), "Identity.%s", Name); return Accountlet::GetVariant(n, Value, Array); } bool AccountIdentity::SetVariant(const char *Name, LVariant &Value, const char *Array) { char n[128]; sprintf_s(n, sizeof(n), "Identity.%s", Name); return Accountlet::SetVariant(n, Value, Array); } //////////////////////////////////////////////////////////////////////////// SendAccountlet::SendAccountlet(ScribeAccount *a) : Accountlet(a) { SendItem = 0; // Options OptPassword = OPT_EncryptedSmtpPassword; } SendAccountlet::~SendAccountlet() { } bool SendAccountlet::GetVariant(const char *Name, LVariant &Value, const char *Array) { /* case SdServer: // Type: String case SdPort: // Type: Integer case SdDomain: // Type: String case SdName: // Type: String case SdAuth: // Type: Bool case SdAuthType: // Type: Integer case SdPrefCharset1: // Type: String case SdPrefCharset2: // Type: String case SdOnlySendThis: // Type: Bool case SdSSL: // Type: Integer */ char n[128]; sprintf_s(n, sizeof(n), "Send.%s", Name); return Accountlet::GetVariant(n, Value, Array); } bool SendAccountlet::SetVariant(const char *Name, LVariant &Value, const char *Array) { char n[128]; sprintf_s(n, sizeof(n), "Send.%s", Name); return Accountlet::SetVariant(n, Value, Array); } void SendAccountlet::CreateMaps() { // Fields char Name[256] = ""; Account->Map(OptionName(OPT_SmtpServer, Name, sizeof(Name)), IDC_SMTP_SERVER, GV_STRING); Account->Map(OptionName(OPT_SmtpPort, Name, sizeof(Name)), IDC_SMTP_PORT, GV_INT32); Account->Map(OptionName(OPT_SmtpDomain, Name, sizeof(Name)), IDC_SMTP_DOMAIN, GV_STRING); Account->Map(OptionName(OPT_SmtpName, Name, sizeof(Name)), IDC_SMTP_NAME, GV_STRING); Account->Map(OptionName(OPT_SmtpAuth, Name, sizeof(Name)), IDC_SMTP_AUTH, GV_BOOL); Account->Map(OptionName(OPT_SmtpAuthType, Name, sizeof(Name)), IDC_SEND_AUTH_TYPE, GV_INT32); Account->Map(OptionName(OPT_SendCharset1, Name, sizeof(Name)), IDC_SEND_CHARSET1, GV_STRING); Account->Map(OptionName(OPT_SendCharset2, Name, sizeof(Name)), IDC_SEND_CHARSET2, GV_STRING); Account->Map(OptionName(OPT_OnlySendThroughThis, Name, sizeof(Name)), IDC_ONLY_SEND_THIS, GV_BOOL); Account->Map(OptionName(OPT_SmtpSSL, Name, sizeof(Name)), IDC_SEND_SSL, GV_INT32); } ScribeAccountletStatusIcon SendAccountlet::GetStatusIcon() { return IsOnline() ? STATUS_ONLINE : STATUS_OFFLINE; } bool SendAccountlet::InitMenus() { // Menus LVariant n = Name(); LVariant s = Server(); LVariant u = UserName(); if (s.Str()) { char Name[256]; // Base name of menu if (n.Str()) { strcpy_s(Name, sizeof(Name), n.Str()); } else if (u.Str()) { sprintf_s(Name, sizeof(Name), "%s@%s", u.Str(), s.Str()); } else { strcpy_s(Name, sizeof(Name), s.Str()); } // Add send menuitem if (GetApp()->SendMenu) { LVariant Default; GetApp()->GetOptions()->GetValue(OPT_DefaultSendAccount, Default); if (Default.CastInt32() == Account->GetIndex()) { strcat(Name+strlen(Name), "\tCtrl-S"); } SendItem = GetApp()->SendMenu->AppendItem(Name, IDM_SEND_FROM+Account->GetIndex(), true); } return true; } return false; } void SendAccountlet::Enabled(bool b) { if (Account->GetIndex() == 0) { GetApp()->CmdSend.Enabled(b); } else if (SendItem) { SendItem->Enabled(b); } } void SendAccountlet::Main(AccountletThread *Thread) { bool Status = false; bool MissingConfig = false; LStringPipe Err; if (GetApp()) { LVariant v; GetApp()->GetOptions()->GetValue(OPT_DebugTrace, v); int DebugTrace = v.CastInt32(); LastOnline = LCurrentTime(); { MailSink *Sink; // LProtocolLogger Logger(GetApp(), M_SCRIBE_LOG_MSG, &Log); LVariant SendHotFolder = HotFolder(); if (SendHotFolder.Str() && LDirExists(SendHotFolder.Str())) { Client = Sink = new MailSendFolder(SendHotFolder.Str()); } else { Client = Sink = new MailSmtp; } if (Client) { LVariant s; if (GetApp()->GetOptions()->GetValue(OPT_ExtraHeaders, s)) { Client->ExtraOutgoingHeaders = s.Str(); } s = PrefCharset1(); if (s.Str()) { Sink->CharsetPrefs.Insert(NewStr(s.Str())); } s = PrefCharset2(); if (s.Str()) { Sink->CharsetPrefs.Insert(NewStr(s.Str())); } } if (Outbox.Length()) { int n = 0; for (unsigned Idx=0; IdxAction = MailUpload; t->Index = n++; t->Send = m; Thread->Files.Insert(t); } } if (DebugTrace) LgiTrace("Send(%i) got %i mail to send\n", Account->GetIndex(), Thread->Files.Length()); if (Sink && Thread->Files.Length()) { Sink->Logger = this; Sink->Transfer = &Item; if (Client) { LVariant _HotFld = HotFolder(); LVariant _Server = Server(); LVariant _Domain = Domain(); LVariant _UserName = UserName(); LString _Password; int Sent = 0; // int Total = Outbox.Length(); LVariant HideId = false; auto Opts = GetApp()->GetOptions(); Opts->GetValue(OPT_HideId, HideId); if (!HideId.CastInt32()) { Opts->GetValue(OPT_HideId, HideId); Client->ProgramName = GetFullAppName(!HideId.CastInt32()); } Client->SetSettingStore(Opts); if (ValidStr(_HotFld.Str()) || ValidStr(_Server.Str())) { bool Error = false; if (RequireAuthentication()) { GPassword p; if (GetPassword(&p)) _Password = p.Get(); } if (DebugTrace) LgiTrace("Send(%i) connecting to SMTP server\n", Account->GetIndex()); Thread->SetState(ThreadConnecting); int OpenFlags = MakeOpenFlags(Account, true); auto Params = GetOAuth2Params(_Server.Str(), MAGIC_MAIL); if (Params.IsValid()) { Sink->SetOAuthParams(Params); } if (!Sink->Open(CreateSocket(true, GetAccount(), true), _Server.Str(), _Domain.Str(), _UserName.Str(), _Password, Port(), OpenFlags)) { if (DebugTrace) LgiTrace("Send(%i) %s:%i\n", Account->GetIndex(), _FL); Err.Push(LLoadString(IDS_NO_CONNECTION_TO_SERVER)); Err.Push("\n"); } else { if (DebugTrace) LgiTrace("Send(%i) connected\n", Account->GetIndex()); Group.Value = 0; Group.Range = Thread->Files.Length(); Thread->SetState(ThreadTransfer); MailTransferEvent *e = NULL; for (auto FileIt = Thread->Files.begin(); FileIt != Thread->Files.end() && (e = dynamic_cast(*FileIt)) && !Thread->IsCancelled(); FileIt++, Group.Value++) { if (DebugTrace) LgiTrace("Send(%i) Item(%i) starting send\n", Account->GetIndex(), Group.Value); AddressDescriptor From; From.sAddr = e->Send->From; List ToLst; for (unsigned n=0; nSend->To.Length(); n++) { AddressDescriptor *ad = new AddressDescriptor; ad->sAddr = e->Send->To[n]; ToLst.Insert(ad); } MailProtocolError SendErr; LStringPipe *Out = Sink->SendStart(ToLst, &From, &SendErr); List *To = &ToLst; bool BadAddress = false; for (auto a: *To) { if (!a->Status) { if (a->sAddr.Find(",") >= 0) { auto t = a->sAddr.SplitDelimit(","); for (unsigned i=0; iPrint().Get()); } BadAddress = true; } } if (BadAddress) { LString m; m.Printf(LLoadString(Sink->ErrMsgId, Sink->ErrMsgFmt), Sink->ErrMsgParam.Get()); Err.Push(m.Get()); } if (Out) { LStringPipe InetHeaders; ssize_t Len = e->Send->Rfc822.Length(); ssize_t Written = 0; Item.Range = Len; Item.Start = LCurrentTime(); for (ssize_t Pos = 0; Pos < Len; ) { ssize_t Remaining = Len - Pos; ssize_t Block = MIN(Remaining, 4 << 10); ssize_t Wr = Out->Write(e->Send->Rfc822.Get() + Pos, Block); if (Wr <= 0) break; Pos += Wr; Written += Wr; Item.Value = Pos; } Item.Start = 0; if (Written == Len) { if (Sink->SendEnd(Out)) { if (DebugTrace) LgiTrace("Send(%i) Item(%i) success\n", Account->GetIndex(), Group.Value); Status = true; Sent++; e->OutgoingHeaders = InetHeaders.NewStr(); if (e->Send->References) { GetApp()->PostEvent(M_SCRIBE_SET_MSG_FLAG, (LMessage::Param)NewStr(e->Send->References), MAIL_REPLIED); } if (e->Send->FwdMsgId) { GetApp()->PostEvent(M_SCRIBE_SET_MSG_FLAG, (LMessage::Param)NewStr(e->Send->FwdMsgId), MAIL_FORWARDED); } if (e->Send->BounceMsgId) { GetApp()->PostEvent(M_SCRIBE_SET_MSG_FLAG, (LMessage::Param)NewStr(e->Send->BounceMsgId), MAIL_BOUNCED); } e->Account = this; e->Status = MailReceivedWaiting; GetApp()->OnMailTransferEvent(e); } else { Write("SendEnd failed.\n", -1, LSocketI::SocketMsgError); Error = true; break; } } else { Write("Encoding failed.\n", -1, LSocketI::SocketMsgError); Error = true; break; } } else { LString s; s.Printf("SendStart failed: %i, %s\n", SendErr.Code, SendErr.ErrMsg.Get()); Write(s, -1, LSocketI::SocketMsgError); Error = true; break; } } if (DebugTrace) LgiTrace("Send(%i) closing, Error=%i\n", Account->GetIndex(), Error); if (Error) { // we can't call Client->Close() on error because it // sends a quit command and expects a reply. Under // error conditions that can fail because the server // might not be in a state to handle that request. // So just close the socket and call it a day. if (DebugTrace) LgiTrace("Send(%i) %s:%i\n", Account->GetIndex(), _FL); Sink->CloseSocket(); } else { // Normal quit if (DebugTrace) LgiTrace("Send(%i) %s:%i\n", Account->GetIndex(), _FL); Sink->Close(); } if (DebugTrace) LgiTrace("Send(%i) %s:%i\n", Account->GetIndex(), _FL); Group.Value = 0; Group.Range = 0; Item.Value = 0; Item.Range = 0; Status &= WaitForTransfers(Thread->Files); } if (DebugTrace) LgiTrace("Send(%i) %s:%i, status=%i\n", Account->GetIndex(), _FL, Status); if (!Status) { if (DebugTrace) LgiTrace("Send(%i) %s:%i, Client=%p\n", Account->GetIndex(), _FL, Client); if (ValidStr(Client->ErrMsgFmt)) { LString m; m.Printf(LLoadString(Sink->ErrMsgId, Sink->ErrMsgFmt), Sink->ErrMsgParam.Get()); Err.Push("\n"); Err.Push(m.Get()); Err.Push("\n"); } } } else { MissingConfig = true; Err.Push(LLoadString(IDS_NO_SMTP_SERVER_SETTINGS)); Err.Push("\n"); } } if (DebugTrace) LgiTrace("Send(%i) %s:%i, Thread=%p\n", Account->GetIndex(), _FL, Thread); if (DebugTrace) LgiTrace("Send(%i) %s:%i\n", Account->GetIndex(), _FL); } Outbox.DeleteObjects(); } DeleteObj(Sink); } if (DebugTrace) LgiTrace("Send(%i) exit\n", Account->GetIndex()); } else { Thread->SetState(ThreadError); } char *e = 0; if (MissingConfig) { Err.Push(LLoadString(IDS_ASK_ABOUT_OPTIONS)); Err.Push("\n"); e = Err.NewStr(); GetApp()->PostEvent(M_SCRIBE_MSG, (LMessage::Param)e, 1); } else if ((e = Err.NewStr())) { GetApp()->PostEvent(M_SCRIBE_MSG, (LMessage::Param)e); } } //////////////////////////////////////////////////////////////////////////// ReceiveAccountlet::ReceiveAccountlet(ScribeAccount *a) : Accountlet(a) { // Init SecondsTillOnline = -1; LVariant Ct = CheckTimeout(); if (Ct.Str()) { SecondsTillOnline = GetCheckTimeout(); } ReceiveItem = 0; PreviewItem = 0; Items = 0; IdTemp = 0; char Key[128]; OptionName("Messages", Key, sizeof(Key)); Msgs.Reset(new MsgList(a->GetApp()->GetOptions(), Key)); OptionName("Spam", Key, sizeof(Key)); Spam.Reset(new MsgList(a->GetApp()->GetOptions(), Key)); // Upgrade props LVariant OldType = -1; char Buf[128]; LOptionsFile *Options = GetApp()->GetOptions(); if (Options->GetValue(OptionName(OPT_Pop3Type, Buf, sizeof(Buf)), OldType)) { // Old Account types: #define MAIL_SOURCE_POP3 0 #define MAIL_SOURCE_IMAP4 1 #define MAIL_SOURCE_MAPI 2 #define MAIL_SOURCE_SCP 3 #define MAIL_SOURCE_HTTP 4 switch (OldType.CastInt32()) { case MAIL_SOURCE_POP3: Protocol(PROTOCOL_POP3); break; case MAIL_SOURCE_IMAP4: Protocol(PROTOCOL_IMAP4_FETCH); break; case MAIL_SOURCE_SCP: Protocol(PROTOCOL_CALENDAR); break; case MAIL_SOURCE_HTTP: Protocol(PROTOCOL_POP_OVER_HTTP); break; case MAIL_SOURCE_MAPI: Protocol(PROTOCOL_MAPI); break; } Options->DeleteValue(Buf); } char Name[256] = ""; LVariant Auto; if (!Options->GetValue(OptionName(OPT_Pop3AutoReceive, Name, sizeof(Name)), Auto)) { LVariant s; Options->GetValue(OptionName(OPT_Pop3CheckEvery, Name, sizeof(Name)), s); if (s.Str()) { int Timeout = atoi(s.Str()); Options->SetValue(OptionName(OPT_Pop3AutoReceive, Name, sizeof(Name)), s = (Timeout > 0)); } } // Options OptPassword = OPT_EncryptedPop3Password; } ReceiveAccountlet::~ReceiveAccountlet() { } bool ReceiveAccountlet::GetVariant(const char *Name, LVariant &Value, const char *Array) { char n[128]; sprintf_s(n, sizeof(n), "Receive.%s", Name); return Accountlet::GetVariant(n, Value, Array); } bool ReceiveAccountlet::SetVariant(const char *Name, LVariant &Value, const char *Array) { char n[128]; sprintf_s(n, sizeof(n), "Receive.%s", Name); return Accountlet::SetVariant(n, Value, Array); } ScribeAccountletStatusIcon ReceiveAccountlet::GetStatusIcon() { if (DataStore) { int64 s = DataStore->GetInt(FIELD_STORE_STATUS); if (s >= STATUS_OFFLINE && s < STATUS_MAX) return (ScribeAccountletStatusIcon)s; } if (IsOnline()) return STATUS_ONLINE; if (!GetStatus()) return STATUS_ERROR; if (AutoReceive()) return STATUS_WAIT; return STATUS_OFFLINE; } void ReceiveAccountlet::CreateMaps() { char Name[256] = ""; // Fields Account->Map(OptionName(OPT_Pop3Protocol, Name, sizeof(Name)), IDC_REC_TYPE, GV_STRING); Account->Map(OptionName(OPT_Pop3Server, Name, sizeof(Name)), IDC_REC_SERVER, GV_STRING); Account->Map(OptionName(OPT_Pop3Port, Name, sizeof(Name)), IDC_REC_PORT, GV_INT32); Account->Map(OptionName(OPT_Pop3Name, Name, sizeof(Name)), IDC_REC_NAME, GV_STRING); Account->Map(OptionName(OPT_Pop3AutoReceive, Name, sizeof(Name)), IDC_CHECK_EVERY, GV_BOOL); Account->Map(OptionName(OPT_Pop3CheckEvery, Name, sizeof(Name)), IDC_REC_CHECK, GV_STRING); Account->Map(OptionName(OPT_Pop3LeaveOnServer, Name, sizeof(Name)), IDC_POP3_LEAVE, GV_BOOL); Account->Map(OptionName(OPT_DeleteAfter, Name, sizeof(Name)), IDC_DELETE_AFTER, GV_BOOL); Account->Map(OptionName(OPT_DeleteDays, Name, sizeof(Name)), IDC_DELETE_DAYS, GV_INT32); Account->Map(OptionName(OPT_DeleteIfLarger, Name, sizeof(Name)), IDC_DELETE_LARGER, GV_BOOL); Account->Map(OptionName(OPT_DeleteIfLargerSize, Name, sizeof(Name)), IDC_DELETE_SIZE, GV_INT32); Account->Map(OptionName(OPT_Pop3Folder, Name, sizeof(Name)), IDC_FOLDER, GV_STRING); Account->Map(OptionName(OPT_MaxEmailSize, Name, sizeof(Name)), IDC_MAX_SIZE, GV_INT32); Account->Map(OptionName(OPT_Receive8BitCs, Name, sizeof(Name)), IDC_REC_8BIT_CS, GV_STRING); Account->Map(OptionName(OPT_ReceiveAsciiCs, Name, sizeof(Name)), IDC_REC_ASCII_CP, GV_STRING); Account->Map(OptionName(OPT_ReceiveAuthType, Name, sizeof(Name)), IDC_RECEIVE_AUTH_TYPE, GV_INT32); Account->Map(OptionName(OPT_Pop3SSL, Name, sizeof(Name)), IDC_RECEIVE_SSL, GV_INT32); Account->Map(OptionName(OPT_ReceiveSecAuth, Name, sizeof(Name)), IDC_SEC_AUTH, GV_INT32); } int ReceiveAccountlet::GetCheckTimeout() { int Sec = -1; LVariant Timeout = CheckTimeout(); if (Timeout.Str()) { LToken t(Timeout.Str(), ":"); if (t.Length() == 2) { Sec = (atoi(t[0]) * 60) + atoi(t[1]); } else { Sec = atoi(Timeout.Str()) * 60; } } return Sec; } bool ReceiveAccountlet::InitMenus() { // Menus LVariant n = Name(); LVariant s = Server(); LVariant u = UserName(); if (IsConfigured()) { char Name[256]; // Base name of menu if (n.Str()) strcpy_s(Name, sizeof(Name), n.Str()); else if (u.Str() && s.Str()) sprintf_s(Name, sizeof(Name), "%s@%s", u.Str(), s.Str()); else if (u.Str()) strcpy_s(Name, sizeof(Name), u.Str()); else if (s.Str()) strcpy_s(Name, sizeof(Name), s.Str()); else strcpy_s(Name, sizeof(Name), "(untitled)"); // Add preview menuitem if (GetApp()->PreviewMenu) { PreviewItem = GetApp()->PreviewMenu->AppendItem(Name, IDM_PREVIEW_FROM+Account->GetIndex(), !Disabled()); } // Add shortcut if (Account->GetIndex() < 9) { size_t Len = strlen(Name); sprintf_s(Name+Len, sizeof(Name)-Len, "\tCtrl+%i", Account->GetIndex()+1); } // Add receive menuitem if (GetApp()->ReceiveMenu) { ReceiveItem = GetApp()->ReceiveMenu->AppendItem(Name, IDM_RECEIVE_FROM+Account->GetIndex(), !Disabled()); } return true; } return false; } bool ReceiveAccountlet::RemoveFromSpamIds(const char *Id) { if (!Id || !Spam) return false; if (!Lock()) return false; Spam->Delete(Id); Spam->Dirty = true; Unlock(); return true; } void ReceiveAccountlet::DeleteAsSpam(const char *Id) { if (HasMsg(Id)) { RemoveMsg(Id); if (Lock()) { if (Spam) { if (!Spam->Find(Id)) { Spam->Add(Id); Spam->Dirty = true; } } Unlock(); } } } bool ReceiveAccountlet::IsSpamId(const char *Id, bool Delete) { bool Status = false; if (ValidStr(Id)) { if (Lock()) { if (Spam) { Status = Spam->Find(Id); } Unlock(); } } return Status; } bool ReceiveAccountlet::SetActions(LArray *a) { bool Status = false; if (Lock()) { if (!IsOnline()) { if (a) Actions = *a; else Actions.Length(0); Status = true; } else LAssert(!"Trying to set actions when online is forbidden."); Unlock(); } return Status; } bool ReceiveAccountlet::SetItems(LList *l) { bool Status = false; if (Lock()) { Items = l; Status = true; Unlock(); } return Status; } void ReceiveAccountlet::Enabled(bool b) { if (Account->GetIndex() == 0) { GetApp()->CmdReceive.Enabled(b); GetApp()->CmdPreview.Enabled(b); } if (ReceiveItem) { ReceiveItem->Enabled(b); } if (PreviewItem) { PreviewItem->Enabled(b); } } int FilterCompare(Filter *a, Filter *b, NativeInt Data) { return a->GetIndex() - b->GetIndex(); } bool ReceiveAccountlet::IsPersistant() { LVariant Proto = Protocol(); if (Proto.Str()) { #ifdef ImapSupport if (_stricmp(Proto.Str(), PROTOCOL_IMAP4) == 0) return true; #endif #ifdef WIN32 if (!_stricmp(Proto.Str(), PROTOCOL_MAPI)) return true; #endif } return false; } int64 ConvertRelativeSize(char *Size, int64 Total) { int64 Status = -1; if (Size) { double f = atof(Size); if (strchr(Size, '%')) { Status = (int64)(Total * f) / 100; } else if (stristr(Size, "KB")) { Status = (int64)(f * 1024); } else if (stristr(Size, "MB")) { Status = (int64)(f * 1024 * 1024); } else if (stristr(Size, "GB")) { Status = (int64)(f * 1024 * 1024 * 1024); } else { Status = (int64)f; } } return Status; } struct MailReceiveParams { ScribeWnd *App; AccountletThread *Thread; uint64 MaxSize; uint64 NoAttachLimit; uint64 NoDownloadLimit; char *KitFileName; int DownloadLines; MailReceiveParams() { App = 0; Thread = 0; MaxSize = 0; NoAttachLimit = 0; NoDownloadLimit = 0; KitFileName = 0; DownloadLines = 0; } }; bool AfterReceived(MailTransaction *Trans, void *Data) { if (Trans->Oversize) { } else if (Trans->Status) { MailReceiveParams *p = (MailReceiveParams*) Data; if (!p) return false; MailTransferEvent *t = p->Thread->Files[Trans->Index]; if (!t) return false; Trans->Flags |= MAIL_POSTED_TO_GUI; if (t->Rfc822Msg) { // t->Data->Parse(); } // Set the event status t->Status = MailReceivedWaiting; t->Account = p->Thread->GetAccountlet(); // Ask the gui thread to load the mail in p->App->OnMailTransferEvent(t); } return true; } MailSrcStatus ReceiveCallback(MailTransaction *Trans, uint64 Size, int *TopLines, void *Data) { MailReceiveParams *Params = (MailReceiveParams*) Data; if (Params->KitFileName) { uint64 Free; if (LGetDriveInfo(Params->KitFileName, &Free)) { if (Free <= Params->NoDownloadLimit) { return DownloadNone; } else if (Free <= Params->NoAttachLimit) { if (TopLines && Params->DownloadLines > 0) { *TopLines = Params->DownloadLines; } return DownloadTop; } } } if (Params->MaxSize) { if (Size > Params->MaxSize) { if (!TestFlag(Trans->Flags, MAIL_EXPLICIT)) { return DownloadNone; } } } return DownloadAll; } bool ReceiveAccountlet::OnIdle() { return DataStore ? DataStore->OnIdle() : false; } void ReceiveAccountlet::Main(AccountletThread *Thread) { bool Status = false; LVariant v; LastOnline = LCurrentTime(); #define TimeDelta() ((int) (LCurrentTime() - LastOnline)) SecondsTillOnline = -1; int DebugTrace = false; GetApp()->GetOptions()->GetValue(OPT_DebugTrace, v); DebugTrace = v.CastInt32(); if (DebugTrace) LgiTrace("Receive(%i) starting, %i\n", Account->GetIndex(), TimeDelta()); - LVariant RemoteHost = Server(); - LVariant RemotePort = Port(); - LVariant User = UserName(); + auto RemoteHost = Server(); + auto RemotePort = Port(); + auto User = UserName(); // int LeaveCopy = LeaveOnServer(); int DeleteIfLargerThan = DeleteLarger() ? DeleteSize() << 10 : 0; MailReceiveParams Params; Params.App = GetApp(); Params.Thread = Thread; Params.MaxSize = DownloadLimit() << 10; auto MailSourceType = ProtocolStrToEnum(Protocol().Str()); - /* - LXmlTag *Limits = LAppInst->GetConfig("Scribe-Limits"); - if (Limits) - { - LMailStore *Ms = GetApp()->GetDefaultMailStore(); - if (Ms) - { - #ifdef _MSC_VER - // #pragma message(__LOC__"no advanced receive parameters support.") - #endif - #if 0 // fixme - Params.KitFileName = Kit->GetFileName(); - - uint64 Size; - if (LGetDriveInfo(Kit->GetFileName(), 0, &Size)) - { - char *s; - if (s = Limits->GetAttr("NoAttach")) - { - Params.NoAttachLimit = ConvertRelitiveSize(s, Size); - } - if (s = Limits->GetAttr("NoDownload")) - { - Params.NoDownloadLimit = ConvertRelitiveSize(s, Size); - } - if (s = Limits->GetAttr("Lines")) - { - Params.DownloadLines = atoi(s); - } - if (Params.NoAttachLimit < Params.NoDownloadLimit) - { - Params.NoAttachLimit = Params.NoDownloadLimit; - } - } - #endif - } - } - */ - - char Password[256] = ""; + auto Ms = GetApp()->GetDefaultMailStore(); + + LString Password; GPassword Psw; GetPassword(&Psw); - Psw.Get(Password); + Password = Psw.Get(); MailSource *Source = 0; LVariant RecHotFolder = HotFolder(); if (RecHotFolder.Str() && LDirExists(RecHotFolder.Str())) { Client = Source = new MailReceiveFolder(RecHotFolder.Str()); } else { switch (MailSourceType) { case ProtocolPop3: { Client = Source = new MailPop3; break; } case ProtocolImapFetch: { Client = Source = new MailIMap; break; } case ProtocolPopOverHttp: { Client = Source = new MailPhp; break; } default: { LAssert(!"Unsupported protocol."); return; } } } if (DebugTrace) LgiTrace("Receive(%i) protocol=%i client=%p, time=%i\n", Account->GetIndex(), MailSourceType, Client, TimeDelta()); if (Source) { - LAutoString HttpProxy = GetApp()->GetHttpProxy(); + auto HttpProxy = GetApp()->GetHttpProxy(); if (HttpProxy) { LUri Host(HttpProxy); if (Host.sHost) Source->SetProxy(Host.sHost, Host.Port?Host.Port:80); } // Setup logging - // LProtocolLogger Logger(GetApp(), M_SCRIBE_LOG_MSG, &Log); Source->Logger = this; Source->Items = &Group; Source->Transfer = &Item; - int OpenFlags = MakeOpenFlags(Account, false); - bool IsTempPsw = !ValidStr(Password) && ValidStr(User.Str()); - if (IsTempPsw) + auto OpenFlags = MakeOpenFlags(Account, false); + auto NeedsPassword = !ValidStr(Password) && ValidStr(User.Str()); + if (NeedsPassword) { if (TempPsw) - { - strcpy_s(Password, sizeof(Password), TempPsw); - } + Password = TempPsw; else if (!SecureAuth()) - { - LString Str; - Str.Printf(LLoadString(IDS_ASK_ACCOUNT_PASSWORD), User.Str(), RemoteHost.Str()); - auto Psw = GetApp()->GetUserInput(Parent ? Parent : GetApp(), Str, true); - if (Psw) - strcpy_s(Password, sizeof(Password), Psw.Get()); - } + LAssert(!"Need to ask user for password BEFORE we're in the worker thread."); } if (DebugTrace) LgiTrace("Receive(%i) opening connection..., time=%i\n", Account->GetIndex(), TimeDelta()); Thread->SetState(ThreadConnecting); LHashTbl,bool> Uids; ReceiveAccountlet *Receive = dynamic_cast(Thread->Acc); if (!RecHotFolder.Str() && !SecureAuth() && !ValidStr(Password)) { Status = true; } else if (!Source->Open( CreateSocket(false, GetAccount(), false), RemoteHost.Str(), - RemotePort.CastInt32(), + RemotePort, User.Str(), Password, SettingStore, OpenFlags)) { Thread->SetState(ThreadError); } else { if (DebugTrace) LgiTrace("Receive(%i) connected, time=%i\n", Account->GetIndex(), TimeDelta()); - if (!TempPsw && IsTempPsw) - { - TempPsw = NewStr(Password); - } SecondsTillOnline = -1; // Get all the messages.. Thread->SetState(ThreadTransfer); auto Msgs = Source->GetMessages(); if (Msgs) { bool LeaveOnServer = Receive->LeaveOnServer() != 0; bool DeleteAfter = Receive->DeleteAfter() != 0; int DeleteDays = Receive->DeleteDays(); bool GetUids = LeaveOnServer; bool HasGetHeaders = false; if (DeleteDays < 1) { Receive->DeleteDays(DeleteDays = 1); } for (int i=0; iRfc822Msg.Reset(new LTempStream(ScribeTempPath())); t->Index = i; if (Actions.Length()) { t->Action = (unsigned)i < Actions.Length() ? Actions[i] : MailNoop; t->Explicit = true; } else if (Receive->Items) { t->Action = MailHeaders; GetUids = true; } else { t->Action = LeaveOnServer ? MailDownload : MailDownloadAndDelete; } switch (t->Action) { default: break; case MailHeaders: { HasGetHeaders = true; // fall thru } case MailDownloadAndDelete: case MailDownload: case MailUpload: { Group.Range++; break; } } Thread->Files.Insert(t); } } if (DebugTrace) LgiTrace("Receive(%i) got %i actions, time=%i\n", Account->GetIndex(), Thread->Files.Length(), TimeDelta()); if (HasGetHeaders || DeleteIfLargerThan) { LArray Sizes; if (Source->GetSizes(Sizes)) { unsigned i = 0; for (auto t: Thread->Files) { if (i >= Sizes.Length()) break; t->Size = Sizes[i]; if (t->Action == MailHeaders) { t->Msg = new AccountMessage(Account); if (t->Msg) { t->Msg->Size = t->Size; } } i++; } } } // Getting the UID's of the messages on the server if (GetUids || Receive->Msgs->Length() > 0) { if (DebugTrace) LgiTrace("Receive(%i) getting UID's, time=%i\n", Account->GetIndex(), TimeDelta()); LString::Array UidLst; Source->GetUidList(UidLst); for (auto u: UidLst) Uids.Add(u, true); // Assign all the ID strings to the transfer events LDateTime Now; Now.SetNow(); for (auto t: Thread->Files) { auto k = UidLst[0]; if (k) { UidLst.DeleteAt(0, true); t->Uid = k; if (DebugTrace) LgiTrace("\tUid[%i]='%s' (time=%i)\n", t->Index, t->Uid.Get(), TimeDelta()); } else break; if (!t->Explicit && DeleteAfter) { // Check how long the message has been on the server. LDateTime MsgDate; if (Receive->Msgs->GetDate(t->Uid, &MsgDate)) { LDateTime Days = Now - MsgDate; if (Days.Day() > DeleteDays) { if (t->Action == MailDownload) t->Action = MailDownloadAndDelete; } } } } } if (DebugTrace) LgiTrace("Receive(%i) starting main action loop, time=%i\n", Account->GetIndex(), TimeDelta()); Group.Start = LCurrentTime(); bool Error = false; LArray Trans; char NotLoaded[256]; sprintf_s( NotLoaded, sizeof(NotLoaded), "%s: %s", LLoadString(FIELD_SUBJECT), LLoadString(IDS_NOT_LOADED)); for (auto it = Thread->Files.rbegin(); it != Thread->Files.end() && !Thread->IsCancelled(); it--) { MailTransferEvent *t = *it; if (!t) continue; bool Ok = false; switch (t->Action) { case MailDownload: case MailDownloadAndDelete: { if (!t->Explicit && t->Uid && Receive->Msgs->Find(t->Uid)) { // Already got it... // if (DebugTrace) LgiTrace("Receive(%i) Item(%i) Already got msg\n", Account->GetIndex(), t->Index); Status = true; Group.Value++; if (DeleteIfLargerThan > 0 && t->Size > 0 && t->Size > DeleteIfLargerThan) { t->Action = MailDelete; } continue; } else { if (t->Uid && Receive->IsSpamId(t->Uid)) { // Delete the spam t->Action = MailDelete; Status = true; Group.Value++; continue; } else { // Download it MailTransaction *Get = new MailTransaction; if (Get) { if (t->Explicit) Get->Flags |= MAIL_EXPLICIT; Get->Index = t->Index; Get->Stream = t->Rfc822Msg; Trans.Add(Get); continue; } } Group.Value++; } break; } case MailHeaders: { if (DebugTrace) LgiTrace("Receive(%i) Item(%i) Getting headers..., time=%i\n", Account->GetIndex(), t->Index, TimeDelta()); auto Headers = Source->GetHeaders(t->Index); if (DebugTrace) LgiTrace("Receive(%i) Item(%i) headers=%p, time=%i\n", Account->GetIndex(), t->Index, Headers.Get(), TimeDelta()); if (!Headers) Headers = NotLoaded; if (Headers) { if (!t->Msg) t->Msg = new AccountMessage(Account); if (t->Msg) { bool Attachments = false; LAutoString ContentType(InetGetHeaderField(Headers, "Content-Type")); if (ContentType) { Attachments = stristr(ContentType, "multipart/mixed") != NULL; } t->Msg->Index = t->Index; t->Msg->From = DecodeRfc2047(InetGetHeaderField(Headers, "From")); t->Msg->Subject = DecodeRfc2047(InetGetHeaderField(Headers, "Subject")); t->Msg->ServerUid = NewStr(t->Uid); LAutoString d(InetGetHeaderField(Headers, "Date")); if (d) t->Msg->Date.Decode(d); t->Msg->Attachments = Attachments; if (IsSpamId(t->Uid)) { t->Msg->Download->Value(false); t->Msg->Delete->Value(true); t->Msg->New = false; } else { t->Msg->New = !HasMsg(t->Uid); } Ok = true; } } Group.Value++; break; } default: { continue; } } if (Ok) { Status = true; t->Account = this; t->Status = MailReceivedWaiting; GetApp()->OnMailTransferEvent(t); } else { if (DebugTrace) LgiTrace("Receive(%i) Item(%i) Error, time=%i\n", Account->GetIndex(), t->Index, TimeDelta()); Error = true; break; } } if (Trans.Length() > 0) { MailCallbacks Callbacks; ZeroObj(Callbacks); Callbacks.CallbackData = &Params; Callbacks.OnSrc = ReceiveCallback; Callbacks.OnReceive = AfterReceived; Error = !Source->Receive(Trans, &Callbacks); for (unsigned i=0; iFiles[Tran->Index]; if (t) { LgiTrace("%s:%i - Trans[%i]: No 't' ptr for idx=%i files.len=%i.\n", _FL, i, Tran->Index, (int)Thread->Files.Length()); } else { if (Tran->Oversize) { // Ignore t->Action = MailNoop; t->Status = MailReceivedOk; Status = true; } else if (Tran->Status) { if (t->Status == MailReceivedNone) { if (DebugTrace) LgiTrace("Receive(%i) Item(%i) Posting WM_SCRIBE_THREAD_ITEM, time=%i\n", Account->GetIndex(), t->Index, TimeDelta()); Status = true; if (!TestFlag(Tran->Flags, MAIL_POSTED_TO_GUI)) { // Ask the gui thread to load the mail in t->Status = MailReceivedWaiting; GetApp()->OnMailTransferEvent(t); } } else { Status = true; } } else { LgiTrace("%s:%i - Error: Bad Status on download %i of %i.\n", _FL, i, Trans.Length()); // Don't delete a mail that failed to download t->Action = MailNoop; } } } Trans.DeleteObjects(); } // Done... wait for main thread to finish processing if (DebugTrace) LgiTrace("Receive(%i) Waiting for main thread, time=%i\n", Account->GetIndex(), TimeDelta()); Thread->SetState(ThreadWaiting); // Wait for the main thread to finish with all the items we sent over if (!WaitForTransfers(Thread->Files)) Error = true; if (Error) { LgiTrace("%s:%i - Error receiving mail.\n", _FL); } else { // Do delete's if (DebugTrace) LgiTrace("Receive(%i) Delete phase, time=%i\n", Account->GetIndex(), TimeDelta()); Group.Empty(); { for (auto d: Thread->Files) { if (d->Action == MailDelete || d->Action == MailDownloadAndDelete) { Group.Range++; } } } Group.Start = LCurrentTime(); Thread->SetState(ThreadDeleting); for (auto It = Thread->Files.rbegin(); It != Thread->Files.end() && Thread->GetState() == ThreadDeleting; It--) { MailTransferEvent *d = *It; if (d->Action == MailDelete || d->Action == MailDownloadAndDelete) { if (DebugTrace) LgiTrace("Receive(%i) Delete(%i) Deleting, time=%i\n", Account->GetIndex(), d->Index, TimeDelta()); if (Source->Delete(d->Index)) { Status = true; if (d->Uid) { Uids.Delete(d->Uid); } } Group.Value++; } } } Thread->Files.DeleteObjects(); Group.Empty(); } else { Receive->RemoveAllMsgs(); Status = true; } // Clear out the UID's if (Uids.Length() && Receive->Msgs) { // ssize_t UidsLen = Uids.Length(); // ssize_t AllMsgIdsLen = Receive->Msgs->Length(); auto MsgKeys = Receive->Msgs->CopyKeys(); for (auto &k : MsgKeys) { if (!Uids.Find(k)) RemoveMsg(k); } if (Spam) { auto SpamKeys = Spam->CopyKeys(); for (auto &s : SpamKeys) { if (!Uids.Find(s)) Spam->Delete(s); } } } if (DebugTrace) LgiTrace("Receive(%i) Closing the connection, time=%i\n", Account->GetIndex(), TimeDelta()); // Close the connection. Source->Close(); } DeleteObj(Source); } else { Thread->SetState(ThreadError); } // Clean up, notify the app ConnectionStatus = Status; Actions.Length(0); if (DebugTrace) LgiTrace("Receive(%i) Exit, time=%i\n", Account->GetIndex(), TimeDelta()); } void ReceiveAccountlet::OnPulse(char *s, int s_len) { // this is called every second if (Lock()) { LVariant Offline; bool On = IsOnline(); LString Serv = Server().Str(); if (On) { strcat(s, "Online"); SecondsTillOnline = -1; if (Thread && Thread->GetState() == LThread::THREAD_EXITED) { #ifdef _DEBUG LgiTrace("Caught exited thread state, Accountlet[%i]=%p, Thread=%p, &Thread=%p\n", GetAccount()->GetIndex(), this, Thread.Get(), &Thread); #endif Thread.Reset(); } } else if ((GetApp()->GetOptions()->GetValue(OPT_WorkOffline, Offline) && Offline.CastInt32()) || Disabled() > 0) { strcat(s, "Offline"); SecondsTillOnline = -1; } else { if (AutoReceive()) { if (SecondsTillOnline < 0) { int To = GetCheckTimeout(); SecondsTillOnline = To > 0 ? To : 10 * 60; } } else { SecondsTillOnline = -1; } if (SecondsTillOnline == 0) { SecondsTillOnline = -1; if ((!IsCheckDialup() || HaveNetConnection()) && !Offline.CastInt32()) { Connect(0, false); } } else if (SecondsTillOnline > 0) { SecondsTillOnline--; if (CheckTimeout().Str()) { sprintf_s(s, s_len, "%i:%2.2i", SecondsTillOnline/60, SecondsTillOnline%60); } } } Unlock(); } } bool ReceiveAccountlet::HasMsg(const char *Id) { if (!Id || !Msgs || !Lock()) return false; auto Status = Msgs->Find(Id); Unlock(); return Status; } void ReceiveAccountlet::AddMsg(const char *Id) { if (!Msgs || !Lock()) return; if (!Msgs->Find(Id)) Msgs->Add(Id); Unlock(); } void ReceiveAccountlet::RemoveMsg(const char *Id) { if (!Id || !Msgs || !Lock()) return; Msgs->Delete(Id); Unlock(); } void ReceiveAccountlet::RemoveAllMsgs() { if (!Msgs || !Lock()) return; Msgs->Empty(); Unlock(); } int ReceiveAccountlet::GetMsgs() { int Status = 0; if (Lock()) { if (Msgs) { Status = Msgs->Length(); } Unlock(); } return Status; } ////////////////////////////////////////////////////////////////////////////////// #define OPT_MsgDate "Date" MsgList::MsgList(LOptionsFile *opts, char *tag) { Opts = opts; Tag = tag; Loaded = false; Dirty = false; } MsgList::~MsgList() { } bool MsgList::Load() { if (Opts && Tag && !Loaded) { LXmlTag *Msg = Opts->LockTag(Tag, _FL); if (!Msg) { Opts->CreateTag(Tag); Msg = Opts->LockTag(Tag, _FL); } if (Msg) { for (auto t: Msg->Children) { if (!t->GetAttr(OPT_MsgDate)) { LDateTime Now; char n[64]; Now.SetNow(); Now.Get(n, sizeof(n)); t->SetAttr(OPT_MsgDate, n); } Parent::Add(t->GetContent(), t); } Opts->Unlock(); } Loaded = true; } return Loaded; } LXmlTag *MsgList::LockId(const char *id, const char *file, int line) { if (Load()) { LXmlTag *r = Opts->LockTag(Tag, file, line); if (r) { LXmlTag *m = (LXmlTag*) Parent::Find(id); if (m) { // deliberately leave the options locked. return m; } Opts->Unlock(); } } return 0; } void MsgList::Unlock() { Opts->Unlock(); } bool MsgList::Add(const char *id) { bool Status = false; LXmlTag *r = Opts->LockTag(Tag, _FL); if (r) { if (!Parent::Find(id)) { LXmlTag *t = new LXmlTag("Message"); if (t) { LDateTime Now; char n[64]; Now.SetNow(); Now.Get(n, sizeof(n)); t->SetContent(id); t->SetAttr(OPT_MsgDate, n); r->InsertTag(t); Status = Parent::Add(id, t); } } Unlock(); } return Status; } bool MsgList::Delete(const char *id) { bool Status = false; LXmlTag *r = Opts->LockTag(Tag, _FL); if (r) { LXmlTag *t = Parent::Find(id); if (t) { // LgiTrace("DelMsg '%s' = %p\n", id, t); Parent::Delete(id); t->RemoveTag(); DeleteObj(t); Status = true; } Opts->Unlock(); } return Status; } int MsgList::Length() { if (Load()) { return (int)Parent::Length(); } return 0; } bool MsgList::Find(const char *id) { if (Load()) { return Parent::Find(id) != 0; } return 0; } LString::Array MsgList::CopyKeys() { LString::Array a(Length()); for (auto i : *this) { a.Add(i.key); } return a; } void MsgList::Empty() { if (!Load()) return; LXmlTag *Msg = Opts->LockTag(Tag, _FL); if (!Msg) return; LXmlTag *t; while ( Msg->Children.Length() && (t = Msg->Children[0])) { DeleteObj(t); } Opts->Unlock(); Parent::Empty(); } bool MsgList::SetDate(char *id, LDateTime *dt) { bool Status = false; if (id && dt) { LXmlTag *t = LockId(id, _FL); if (t) { char s[64]; LAssert(dt->IsValid()); // Force the date to save in a guessable format. // yyyy/m/d is always easy to sniff. dt->SetFormat(GDTF_YEAR_MONTH_DAY); dt->Get(s, sizeof(s)); t->SetAttr(OPT_MsgDate, s); Status = true; Unlock(); } } return Status; } bool MsgList::GetDate(char *id, LDateTime *dt) { if (!id || !dt) return false; LXmlTag *t = LockId(id, _FL); if (!t) return false; char *s = t->GetAttr(OPT_MsgDate); if (s) { // This is where I fucked up... previous versions of the software // saved the date in whatever the currently selected date format. // Which could've been anything... and the user can change that. // So if they do change it, the saved dates are now in the wrong // format to load back in (or mixed). Setting the format to '0' // here puts the LDateTime object into "guess" mode based on the // data. Which is going to be the closest we get to recovering // from the ugly situation of not saving the date in a specific // format. dt->SetFormat(0); dt->Set(s); LAssert(dt->IsValid()); } Unlock(); return s != NULL; } /////////////////////////////////////////////////////////////////////////////// LMimeStream::LMimeStream() : LTempStream(ScribeTempPath(), 4 << 20) { } #ifdef _DEBUG #define DEBUG_MIME_STREAM 1 #else #define DEBUG_MIME_STREAM 0 #endif bool LMimeStream::Parse() { if (Tmp) Tmp->SetPos(0); #if DEBUG_MIME_STREAM LMemStream *Src = dynamic_cast(s); #endif bool Status = Text.Decode.Pull(s) != 0; if (!Status) { #if DEBUG_MIME_STREAM LFile f; if (Src && f.Open("c:\\temp\\mime_error.txt", O_WRITE)) { Src->SetSize(0); Src->SetPos(0); Src->Write(&f, (int)Src->GetSize()); f.Close(); } s->SetPos(0); #endif LAssert(!"Mime Parsing Failed."); } LTempStream::Empty(); return Status; } diff --git a/Code/ScribeStatusPanel.cpp b/Code/ScribeStatusPanel.cpp --- a/Code/ScribeStatusPanel.cpp +++ b/Code/ScribeStatusPanel.cpp @@ -1,880 +1,881 @@ #include "Scribe.h" #include "ScribePrivate.h" #include "ScribeStatusPanel.h" #include "resdefs.h" #include "lgi/common/ClipBoard.h" #include "lgi/common/TableLayout.h" #include "lgi/common/LgiRes.h" #include "lgi/common/TextView4.h" #include "lgi/common/TextLog.h" #include "lgi/common/TabView.h" //////////////////////////////////////////////////////////////////////////// #define STATUS_BASE 22 #define UP_ARROW 4 #define DOWN_ARROW 5 #define BLACK_ARROW 6 #define FTP_SIZE 22 #define IDM_SEND 100 #define IDM_RECEIVE 101 #define IDM_PREVIEW 102 #define IDM_CONFIG 103 const char *AccountStatusTxt[] = { "Idle", "Connected", "Waiting", "Error" }; //////////////////////////////////////////////////////////////////////////// typedef LThreadSafeTextView LAccountLogParent; class LAccountLog : public LAccountLogParent { LArray Rgb; LString sEmpty; public: LogEntry *Prev; LAccountLog() : LAccountLogParent(-1) { Prev = NULL; SetPourLargest(true); SetWrapType(TEXTED_WRAP_NONE); sEmpty.Printf("(%s)", LLoadString(IDS_EMPTY)); LFont *f = new LFont; if (f) { *f = *LSysFont; f->PointSize(f->PointSize() - 1); SetFont(f, true); } } void Empty() { Name(NULL); Rgb.Length(0); } void AddText(char16 *Txt, size_t Chars, LColour &c) { for (size_t i=0; i= Rgb.Length()) break; tl->c.c32(Rgb[i]); i++; } } void OnPaint(LSurface *pDC) { if (LAccountLogParent::Length() == 0) { LColour f(192,192,192), b(L_WORKSPACE); auto Fnt = GetFont(); LDisplayString ds(Fnt, sEmpty); auto c = GetClient(); Fnt->Transparent(false); Fnt->Colour(f, b); ds.Draw(pDC, 4, 4, &c); } else LAccountLogParent::OnPaint(pDC); } }; class LAccountLogFactory : public LViewFactory { LView *NewView ( /// The name of the class to create const char *Class, /// The initial position of the view LRect *Pos, /// The initial text of the view const char *Text ) { if (!_stricmp(Class, "LAccountLog")) return new LAccountLog(); return NULL; } } AccountLogFactory; //////////////////////////////////////////////////////////////////////////// AccountStatusItem::AccountStatusItem(AccountStatusPanel *panel, ScribeAccount *account, LImageList *imglst) { Buf[0] = 0; Panel = panel; ImgLst = imglst; Account = account; Account->Views.Add(this); State = STATUS_OFFLINE; SetImage(ICON_UNSENT_MAIL); } AccountStatusItem::~AccountStatusItem() { LAssert(Account->Views.HasItem(this)); Account->Views.Delete(this); } const char *AccountStatusItem::GetText(int Col) { char *Status = 0; if (Account) { if (Col == 2) { LVariant v = Account->Receive.Name(); if (!v.Str()) { v = Account->Receive.Server(); } if (!v.Str()) { v = Account->Send.Server(); } static char Buf[64]; if (v.Str()) { strcpy_s(Buf, sizeof(Buf), v.Str()); Status = Buf; } } else if (Col == 3) { return Buf; } } return Status; } int AccountStatusItem::Compare(LListItem *To, ssize_t Field) { int ASort = Account->Identity.Sort(); AccountStatusItem *b = dynamic_cast(To); if (!b) return 0; int BSort = b->Account->Identity.Sort(); return ASort - BSort; } void AccountStatusItem::OnPaintColumn(LItem::ItemPaintCtx &Ctx, int i, LItemColumn *c) { LListItem::OnPaintColumn(Ctx, i, c); if (ImgLst && Account) { LRect *Bounds = ImgLst->GetBounds(); if (Bounds) { int Icon = -1; if (i == 0) { // Send Status if (Account->Send.IsConfigured()) Icon = STATUS_BASE + (Account->Send.IsOnline() ? STATUS_ONLINE : STATUS_OFFLINE); } else if (i == 1) { // Receive Status if (Account->Receive.IsConfigured()) Icon = STATUS_BASE + Panel->AccountStatus(&Account->Receive); } if (Icon >= 0) { Bounds += Icon; int x = Ctx.x1 + ((Ctx.X()-Bounds->X())/2) - Bounds->x1; int y = Ctx.y1 + ((Ctx.Y()-Bounds->Y())/2) - Bounds->y1; LColour Back(Ctx.Back); ImgLst->Draw(Ctx.pDC, x, y, Icon, Back); } } } } void AccountStatusItem::OnPulse() { if (Account) { Buf[0] = 0; Account->OnPulse(Buf, sizeof(Buf)); Update(); } } void AccountStatusItem::OnMouseClick(LMouse &m) { if (!Account) return; if (m.IsContextMenu()) { LSubMenu RClick; LArray Acc; List Sel; int RecAcc = 0; int SendAcc = 0; if (GetList()->GetSelection(Sel)) { for (auto i: Sel) { auto *si = dynamic_cast(i); Acc.Add(si->Account); if (!si->Account->Receive.Disabled()) { RecAcc += si->Account->Receive.Server().Str() ? 1 : 0; SendAcc += si->Account->Send.Server().Str() ? 1 : 0; } } } RClick.AppendItem(LString(LLoadString(IDS_SEND)) + " " + Account->Send.Server().Str(), IDM_SEND, SendAcc != 0); RClick.AppendItem(LString(LLoadString(IDS_RECEIVE)) + " " + Account->Receive.Server().Str(), IDM_RECEIVE, RecAcc != 0); RClick.AppendItem(LLoadString(IDS_PREVIEW), IDM_PREVIEW, RecAcc != 0); RClick.AppendSeparator(); RClick.AppendItem(LLoadString(IDS_CONFIGURE), IDM_CONFIG, Acc.Length() == 1); switch (RClick.Float(GetList(), m)) { case IDM_SEND: { if (!Account->Send.IsOnline()) { auto Idx = Account->GetIndex(); Panel->App->Send(Idx); } break; } case IDM_RECEIVE: { if (!Account->Receive.IsOnline()) { Account->Receive.Connect(0, false); } break; } case IDM_PREVIEW: { OpenPopView(Panel->App, Acc); break; } case IDM_CONFIG: { - if (Account->GetApp()->GetAccountSettingsAccess(GetList(), ScribeReadAccess)) + Account->GetApp()->GetAccountSettingsAccess(GetList(), ScribeReadAccess, [&](auto status) { - Account->InitUI(Parent); - } + if (status) + Account->InitUI(Parent, 0, NULL); + }); break; } } } else if (m.Left() && m.Double()) { // receive if (Account->Receive.IsConfigured()) { Panel->App->Receive(Account->GetIndex()); } else if (Account->Send.IsConfigured()) { Panel->App->Send(Account->GetIndex()); } } } //////////////////////////////////////////////////////////////////////////// #define OPT_StatusOpen "ScribeUI.StatusOpen" AccountStatusPanel::AccountStatusPanel(ScribeWnd *app, LImageList *imglst) : ScribePanel(app, LLoadString(IDS_STATUS), 20, false) { App = app; Accounts = (App) ? App->GetAccounts() : 0; ImgLst = imglst; PrevAccounts = 0; Current = 0; Lst = 0; Total = 0; Sub = 0; AccountTbl = 0; ProgressTbl = 0; CurStatusItem = NULL; Alignment(GV_EDGE_BOTTOM); LVariant Op = false; App->GetOptions()->GetValue(OPT_StatusOpen, Op); if (Op.CastInt32()) { Open(Op.CastInt32() != 0); } LRect *Bounds = ImgLst ? ImgLst->GetBounds() : 0; LAutoString n; LRect p; if (Bounds && LoadFromResource(IDD_STATUS, this, &p, &n)) { int MaxY = 0; for (auto c: Children) { MaxY = MAX(MaxY, c->GetPos().y2); } SetOpenSize(MaxY + 10); GetViewById(IDC_ACCOUNT_TBL, AccountTbl); GetViewById(IDC_PROGRESS_TBL, ProgressTbl); GetViewById(IDC_OP_PROG, Total); GetViewById(IDC_EMAIL_PROG, Sub); if (GetViewById(IDC_ACC_LOG, Log)) { Log->SetPourChildren(true); } if (GetViewById(IDC_ACC_LIST, Lst)) { Lst->SetImageList(ImgLst, false); int x = MAX(Bounds[STATUS_BASE + UP_ARROW].X(), Bounds[STATUS_BASE + STATUS_OFFLINE].X()); LItemColumn *c = Lst->AddColumn("Send", x + 6); if (c) { c->Image(STATUS_BASE + UP_ARROW); c->Resizable(false); } x = MAX(Bounds[STATUS_BASE + DOWN_ARROW].X(), Bounds[STATUS_BASE + STATUS_OFFLINE].X()); c = Lst->AddColumn("Receive", x + 6); if (c) { c->Image(STATUS_BASE + DOWN_ARROW); c->Resizable(false); } c = Lst->AddColumn(LLoadString(IDS_SERVER), 120); c = Lst->AddColumn(LLoadString(IDS_TIME), 50); } } }; AccountStatusPanel::~AccountStatusPanel() { LVariant v; auto Opts = App->GetOptions(); Opts->SetValue(OPT_StatusOpen, v = Open()); } void AccountStatusPanel::OnPosChange() { LLayoutRect c(this); if (Open() && c.Valid()) { c.x1 += 20; int FontHt = LSysFont->GetHeight(); c.Left(AccountTbl, 170 + FontHt * 6); c.Left(ProgressTbl, 200 + FontHt * 12); c.Remaining(Log); } } bool AccountStatusPanel::_Lock() { return App->Lock(_FL); } void AccountStatusPanel::_Unlock() { App->Unlock(); } int AccountStatusPanel::OnNotify(LViewI *Ctrl, LNotification n) { switch (Ctrl->GetId()) { case IDC_STOP: { // stop this client now static bool Stopping = false; if (!Stopping) { Stopping = true; if (_Lock()) { Accountlet *AccLet = 0; if (Current) { if (Current->Receive.IsOnline()) { AccLet = &Current->Receive; } else if (Current->Send.IsOnline()) { AccLet = &Current->Send; } } _Unlock(); if (AccLet) { AccLet->Disconnect(); } } Stopping = false; } else { printf("Recursion lockout.\n"); } break; } } return 0; } void AccountStatusPanel::OnAccountSelect(AccountStatusItem *Item) { Current = (Item) ? Item->Account : 0; if (Total && Sub && _Lock()) { Accountlet *AccLet = 0; if (Current) { bool IsSending = Current->Send.IsOnline(); bool IsReceiving = Current->Receive.IsOnline(); if ((IsSending ^ IsReceiving) == 0) { // Accounts are either both online or both offline if (Current->Send.GetLastOnline() > Current->Receive.GetLastOnline()) { AccLet = &Current->Send; } else { AccLet = &Current->Receive; } } else { // Only one is online if (IsSending) { AccLet = &Current->Send; } else if (IsReceiving) { AccLet = &Current->Receive; } } } bool IsCur = AccLet ? AccLet->IsOnline() : false; Total->Enabled(IsCur); Sub->Enabled(IsCur); SetCtrlEnabled(IDC_STATUS_TXT, IsCur); if (Accounts && Item) { int Status = AccountStatus(AccLet); // update the list item if needed if (Item->State != Status) { Item->Update(); Item->State = Status; } // get ptrs to the progress objects const char *OpDesc = AccLet ? AccLet->GetStateName() : 0; SetCtrlName(IDC_STATUS_TXT, OpDesc); } else { SetCtrlName(IDC_STATUS_TXT, LLoadString(IDS_SELECT_ACCOUNT)); } if (AccLet) { // Connection char Str[256]; if (AccLet->Group.Range) { Total->SetRange(AccLet->Group.Range); Total->Value(AccLet->Group.Value); int Ch = sprintf_s(Str, sizeof(Str), LLoadString(IDS_EMAIL_PROGRESS), AccLet->Group.Value, AccLet->Group.Range); if (AccLet->Group.Start) { double Sec = (double)(LCurrentTime() - AccLet->Group.Start)/1000.0; if (Sec > 0.0) { double Rate = (double)AccLet->Group.Value / Sec; sprintf_s(Str+Ch, sizeof(Str)-Ch, " (%.1f/s)", Rate); } } SetCtrlName(IDC_OP_TXT, Str); } else { SetCtrlName(IDC_OP_TXT, 0); Total->Value(0); } // Mail if (AccLet->Item.Range) { Sub->SetRange(AccLet->Item.Range); Sub->Value(AccLet->Item.Value); } else { Sub->Value(0); } if (AccLet->Item.Start) { double Rate = 0.0; uint64 Period = LCurrentTime() - AccLet->Item.Start; double Sec = (double)Period / 1000.0; if (Period > 0) Rate = (double)AccLet->Item.Value / Sec; if (AccLet->Item.Range) { char sVal[32], sRange[32], sRate[32]; LFormatSize(sVal, sizeof(sVal), AccLet->Item.Value); LFormatSize(sRange, sizeof(sRange), AccLet->Item.Range); LFormatSize(sRate, sizeof(sRate), (uint64)Rate); sprintf_s(Str, sizeof(Str), "%s of %s (%s/second)", sVal, sRange, sRate); } else { char sRate[32]; LFormatSize(sRate, sizeof(sRate), (uint64)Rate); sprintf_s(Str, sizeof(Str), "%s/second", sRate); } SetCtrlName(IDC_EMAIL_TXT, Str); } else { SetCtrlName(IDC_EMAIL_TXT, 0); } } else { SetCtrlName(IDC_OP_TXT, 0); SetCtrlValue(IDC_OP_PROG, 0); SetCtrlName(IDC_EMAIL_TXT, 0); SetCtrlValue(IDC_EMAIL_PROG, 0); Total->Value(0); Sub->Value(0); } if (ProgressTbl) ProgressTbl->InvalidateLayout(); SetCtrlEnabled(IDC_STOP, AccLet && AccLet->IsOnline()); // do log list if (Log) { int Ctrls[2] = {IDC_SEND_LOG, IDC_RECEIVE_LOG}; if (CurStatusItem != Item) { CurStatusItem = Item; for (int i=0; iGetViewById(Ctrls[i], al)) al->Empty(); } } if (Current) { Accountlet *Lets[2] = { &Current->Send, &Current->Receive }; for (int i=0; iGetViewById(Ctrls[i], al)) { auto Priv = Lets[i]->Lock(_FL); if (Priv) { auto &l = Priv->d->Log; if (l.Length()) { if (!al->Prev || al->Prev != l[0]) { // Changed content al->Empty(); al->Prev = l[0]; } auto StartTs = LCurrentTime(); size_t pos = 0; for (unsigned i=0; iTxt.Length(); size_t ch = al->LAccountLogParent::Length(); if (end > ch) { ssize_t offset = 0; size_t len = e->Txt.Length(); if (al->LAccountLogParent::Length() > pos) { offset = al->LAccountLogParent::Length() - pos; len -= offset; } LColour c = e->GetColour(); al->AddText(e->Txt.AddressOf(offset), len, c); } pos += e->Txt.Length(); auto CurTs = LCurrentTime(); if (CurTs - StartTs >= 1000) break; } auto Taken = LCurrentTime() - StartTs; if (Taken > 200) { al->Empty(); // Delete the first 1/3 of the log... LRange r(0, (ssize_t)(l.Length() * 0.33)); if (r.Len == 0) r.Len++; LgiTrace("%s:%i - Log processing blocking (" LPrintfInt64 "ms), removing " LPrintfSizeT " oldest items.\n", _FL, Taken, r.Len); l.DeleteRange(r); al->Prev = l[0]; } } else if (!al->Length()) { al->Empty(); } } } } } } _Unlock(); } } int AccountStatusPanel::AccountStatus(Accountlet *a) { return a ? a->GetStatusIcon() : STATUS_ERROR; } int AccountStatusPanel::CalcWidth() { int BaseX = LPanel::CalcWidth(); LRect *Bounds = ImgLst ? ImgLst->GetBounds() : 0; if (Bounds) { ScribeAccount *Send = App->GetSendAccount(); int UpArrow = STATUS_BASE + UP_ARROW; int SendIcon = STATUS_BASE + (Send ? AccountStatus(&Send->Send) : 0); int DownArrow = STATUS_BASE + DOWN_ARROW; BaseX += Bounds[UpArrow].X() + 2; BaseX += Bounds[SendIcon].X() + 2; BaseX += 8; BaseX += Bounds[DownArrow].X() + 2; for (size_t i=0; iLength(); i++) { ScribeAccount *Acc = Accounts->ItemAt(i); if (Acc && Acc->Receive.IsConfigured()) { int ReceiveIcon = STATUS_BASE + AccountStatus(&Acc->Receive); BaseX += Bounds[ReceiveIcon].X() + 2; } } } return BaseX; } void AccountStatusPanel::OnPaint(LSurface *pDC) { #ifdef __GTK_H__ LDoubleBuffer Buf(pDC); #endif LPanel::OnPaint(pDC); if (!IsOpen && ImgLst && Accounts) { int x = LPanel::CalcWidth() - 8, y = 3; auto Accs = Accounts->Length(); ScribeAccount *Send = App->GetSendAccount(); LColour Background(L_MED); LRect *Bounds = ImgLst->GetBounds(); if (Bounds) { // SMTP int UpArrow = STATUS_BASE + UP_ARROW; ImgLst->Draw(pDC, x, y, UpArrow, Background); // arrow x += Bounds[UpArrow].X() + 2; if (Send) { int SendIcon = STATUS_BASE + AccountStatus(&Send->Send); ImgLst->Draw(pDC, x, y, SendIcon, Background); x += Bounds[SendIcon].X() + 2; } // break x += 8; // Receive accounts int DownArrow = STATUS_BASE + DOWN_ARROW; ImgLst->Draw(pDC, x, y, DownArrow, Background); // arrow x += Bounds[DownArrow].X() + 2; for (size_t i=0; iItemAt(i); if (Acc && Acc->Receive.IsConfigured()) { int ReceiveIcon = STATUS_BASE + AccountStatus(&Acc->Receive); ImgLst->Draw(pDC, x, y, ReceiveIcon, Background); x += Bounds[ReceiveIcon].X() + 2; } } } } } void AccountStatusPanel::OnPulse() { if (Accounts && PrevAccounts != Accounts->Length()) { OnAccountListChange(); } if (!IsOpen) { // Invalidate(); } else if (Lst) { OnAccountSelect(dynamic_cast(Lst->GetSelected())); } if (Lst) { List All; Lst->GetAll(All); for (auto a: All) { a->OnPulse(); } } } void AccountStatusPanel::Empty() { OnAccountSelect(0); if (Lst) { Lst->Empty(); } } int AccountItemCmp(LListItem *a, LListItem *b, NativeInt Data) { return a->Compare(b); } void AccountStatusPanel::OnAccountListChange() { if (Lst) { Lst->Empty(); if (Accounts) { PrevAccounts = Accounts->Length(); RePour(); for (auto a: *Accounts) Lst->Insert(new AccountStatusItem(this, a, ImgLst)); Lst->Sort(AccountItemCmp); auto All = Lst->begin(); if (*All && !Lst->GetSelected()) { (*All)->Select(true); } } } Invalidate(); } LXmlTag *AccountStatusPanel::GetOptions() { return 0; } void AccountStatusPanel::SetDataRate(int Percent) { } diff --git a/Code/ScribeThing.cpp b/Code/ScribeThing.cpp --- a/Code/ScribeThing.cpp +++ b/Code/ScribeThing.cpp @@ -1,865 +1,879 @@ #include "lgi/common/Lgi.h" #include "Scribe.h" #include "../Resources/resdefs.h" #include "Store3Imap/ScribeImap.h" #include "lgi/common/LgiRes.h" #include "lgi/common/FileSelect.h" ///////////////////////////////////////////////////////////////////////////////// ThingType::ThingType() { } ThingType::~ThingType() { } void ThingType::WhenLoaded(const char *file, int line, std::function Callback, int index) { if (!Callback) { LAssert(!"No callback."); return; } if (!Loaded && GetObject()) { // Lets just check the state of the object first... auto i = GetObject()->GetInt(FIELD_LOADED); if (i == Store3Loaded) { // This is the default for mail3 for instance... Loaded = true; } } if (Loaded) { Callback(); } else { ThingEventInfo *cb = new ThingEventInfo; cb->File = file; cb->Line = line; cb->Callback = Callback; if (index < 0) OnLoadCallbacks.Add(cb); else OnLoadCallbacks.AddAt(index, cb); } } bool ThingType::IsLoaded(int Set) { if (Set >= 0) { if (!Loaded && Set > 0) { Loaded = true; for (auto cb: OnLoadCallbacks) { // LgiTrace("OnLoadCallbacks %s:%i\n", cb.File, cb.Line); cb->Callback(); } OnLoadCallbacks.DeleteObjects(); } Loaded = Set > 0; } return Loaded; } bool ThingType::SetDirty(bool b) { bool Status = false; if (WillDirty) { Thing *t = dynamic_cast(this); if (t && t->GetObject() && t->GetObject()->GetInt(FIELD_STORE_TYPE) == Store3Imap) { // IMAP email need to be explicitly saved when the message // is fully constructed. return true; } if (Dirty == b) { if (Dirty) LAssert(Thing::DirtyThings.HasItem(this)); else LAssert(!Thing::DirtyThings.HasItem(this)); } else { if (b) { Dirty = true; if (!Thing::DirtyThings.HasItem(this)) Thing::DirtyThings.Add(this); } else { Dirty = false; Thing::DirtyThings.Delete(this); } } } return Status; } ///////////////////////////////////////////////////////////////////////////////// LArray ThingType::DirtyThings; Thing::Thing(ScribeWnd *app, LDataI *object) { _UserPtr = this; App = app; IncRef(); // Someone always starts with owning this object. SetObject(object, false, _FL); } Thing::~Thing() { if (GetUI()) { LAssert(!"Really, should we still be linked to a UI here?"); } DirtyThings.Delete(this); DeleteObj(Data); SetParentFolder(NULL); auto o = GetObject(); if (o) { SetObject(NULL, true, _FL); DeleteObj(o); } } LDataI *Thing::DefaultObject(LDataI *arg) { LAssert(App != NULL); if (arg) { SetObject(arg, false, _FL); } else if (!GetObject() && App && App->GetDefaultMailStore()) { LMailStore *Ms = App->GetDefaultMailStore(); if (Ms) SetObject(Ms->Store->Create(Type()), false, _FL); } return GetObject(); } bool Thing::OnKey(LKey &k) { #ifndef WINDOWS // This is being done by the VK_APPS key on windows... if (k.IsContextMenu()) { if (k.Down()) { LMouse m; m.x = 5; m.y = 5; m.ViewCoords = true; m.Target = GetList(); DoContextMenu(m); } return true; } #endif return false; } void Thing::SetParentFolder(ScribeFolder *f) { if (GetFolder() == f) return; if (_ParentFolder) { LAssert(_ParentFolder->Items.HasItem(this)); _ParentFolder->Items.Delete(this); } _ParentFolder = f; if (_ParentFolder) { LAssert(!_ParentFolder->Items.HasItem(this)); _ParentFolder->Items.Insert(this); } } Store3Status Thing::SetFolder(ScribeFolder *New, int Param) { Store3Status Moved = Store3Error; if (New) { ScribeFolder *Old = GetFolder(); if (Old) { if (Old->GetObject() && New->GetObject() && Old->GetObject()->GetStore() != 0 && Old->GetObject()->GetStore() == New->GetObject()->GetStore()) { // Both source and dest are local folders... // This is really an optimization to reduce the overhead of moving objects // between folders, a function which is provided by the storage sub-system // does all the work for us. LArray Mv; Mv.Add(GetObject()); Moved = Old->GetObject()->GetStore()->Move(New->GetFldObj(), Mv); if (Moved == Store3Success) { LAssert(!Old->Items.HasItem(this)); LAssert(GetFolder() == New); LAssert(New->Items.HasItem(this)); } } else { if (IsPlaceHolder()) { } else if (New->GetObject() && GetObject() && New->GetObject()->GetStore()) { // Source OR Dest are remote... LDataI *NewObject = New->GetObject()->GetStore()->Create(Type()); if (NewObject) { LDataI *OldObject = GetObject(); // Copy the current data into the new object NewObject->CopyProps(*GetObject()); SetObject(NewObject, false, _FL); // Try writing it to the store... // bool InOld = Old->Items.HasItem(this); Store3Status WrStatus = New->WriteThing(this); switch (WrStatus) { default: case Store3Error: { // It failed, delete the new object... SetObject(OldObject, false, _FL); DeleteObj(NewObject); break; } case Store3Success: { // Ok, immediate save, set new object // delete old object Moved = OldObject->Delete(false); if (Moved == Store3Error) { SetObject(OldObject, false, _FL); DeleteObj(NewObject); } else if (Moved == Store3Success) { // Remove the Thing from the old folder. Old->Items.Delete(this); LAssert(New->Items.HasItem(this)); if (GetList()) GetList()->Remove(this); } else // Delayed { // Because the list item is the Mail object itself we can't leave a // place holder in the LList until the delayed delete happens. The // mail object is need to appear in the destination folder, as it's // now associated with 'NewObject'. Old->Items.Delete(this); if (GetList()) GetList()->Remove(this); } break; } case Store3Delayed: { // We have to wait for the object to be written. // There will be a ScribeWnd::OnNew(...) call back // when that happens. // // If is succeeds: // - we need to swap the objects over... complete // the updating of the UI. // // If it fails: // - do nothing... // // In the meantime change the object back to the old // one. But leave the UserData pointing to us. This // is so the OnNew handler can finish the move for // us later, and still know whats going on. LAssert(Old->Items.HasItem(this)); // The old folder needs to have // a pointer to us until "OnNew". SetObject(OldObject, false, _FL); // Setup a new Thing for the new Object... Thing *t = App->CreateThingOfType(Type(), NewObject); if (t) { // Add it to the new folder... New->Items.Add(t); // Setup a delete operation to be executed when the object arrives // back at the app with an OnNew events. t->DeleteOnAdd.Path = Old->GetPath(); t->DeleteOnAdd.Obj = this; } Moved = WrStatus; break; } } } } } } else { Moved = New->WriteThing(this); } } return Moved; } void Thing::OnCreate() { } bool Thing::OnDelete() { if (!App) return false; if (IsPlaceHolder()) { DecRef(); return true; } Mail *m = IsMail(); if (m) { List Lst; Lst.Insert(m); App->OnNewMail(&Lst, false); } if (GetObject() && GetObject()->GetStore()) { LArray Del; Del.Add(GetObject()); if (GetObject()->GetStore()->Delete(Del, true)) { return true; } } ScribeFolder *Trash = App->GetFolder(FOLDER_TRASH); if (!Trash) return false; LArray Items; Items.Add(this); return Trash->MoveTo(Items); } void Thing::OnMove() { /* if (LListItem::Parent) { int MyIndex = LListItem::Parent->IndexOf(this); LListItem::Parent->Remove(this); if (Parent && Parent->Length() < 1) { Window->OnSelect(); } else if (LListItem::Parent && MyIndex >= 0) { LListItem::Parent->Value(MyIndex); } } */ } bool Thing::OnBeginDrag(LMouse &m) { int Ico = -1; switch (Type()) { case MAGIC_MAIL: Ico = ICON_UNREAD_MAIL; break; case MAGIC_CONTACT: Ico = ICON_CONTACT; break; case MAGIC_FILTER: Ico = ICON_FILTER; break; case MAGIC_CALENDAR: Ico = ICON_CALENDAR; default: break; } if (Ico >= 0) { LImageList *s = App->GetIconImgList(); if (s) { LRect r; r.ZOff(s->TileX()-1, s->TileY()-1); r.Offset(s->TileX() * Ico, 0); SetIcon(s, &r); } } Drag(App, m.Event, DROPEFFECT_MOVE | DROPEFFECT_COPY); SetIcon(NULL); return true; } bool Thing::GetFormats(LDragFormats &Formats) { Formats.Supports(ScribeThingList); #if !defined(LGI_COCOA) Formats.Supports(LGI_FileDropFormat); #else Formats.Supports(LGI_StreamDropFormat); #endif return Formats.Length() > 0; } -bool Thing::ExportAll(LViewI *Parent, const char *ExportMimeType) +void Thing::ExportAll( LViewI *Parent, + const char *ExportMimeType, + std::function Callback) { List Sel; if (GetList()) GetList()->GetSelection(Sel); else Sel.Insert(this); - LFileSelect Select; - if (Sel.Length() == 1) - Select.Name(LGetLeaf(GetDropFileName())); - Select.Parent(Parent); - Select.Type("Email", "*.eml"); - - if - ( - Sel.Length() > 1 - ? - !Select.OpenFolder() - : - !Select.Save() - ) - return false; - - int Exported = 0; - int Errors = 0; - for (auto m: Sel) + auto Process = [&](LFileSelect *Select) { - const char *Out; - char Buf[MAX_PATH_LEN]; - if (Sel.Length() == 1) + int Exported = 0; + int Errors = 0; + for (auto m: Sel) { - Out = Select.Name(); - } - else - { - char *Leaf = LGetLeaf(m->GetDropFileName()); - if (!Leaf) + const char *Out; + char Buf[MAX_PATH_LEN]; + if (Sel.Length() == 1) { - Errors++; - continue; + Out = Select->Name(); } - - // Make a unique name... - for (int Index = 1; Index < 1000; Index++) + else { - LString Nm = Leaf; - if (Index > 1) - { - LString::Array a = Nm.RSplit(".", 1); - if (a.Length() == 2) - Nm.Printf("%s %i.%s", a[0].Get(), Index, a[1].Get()); - else - Nm.Printf("%s %i", a[0].Get(), Index); - } - if (!LMakePath(Buf, sizeof(Buf), Select.Name(), Nm)) + char *Leaf = LGetLeaf(m->GetDropFileName()); + if (!Leaf) { Errors++; - break; + continue; + } + + // Make a unique name... + for (int Index = 1; Index < 1000; Index++) + { + LString Nm = Leaf; + if (Index > 1) + { + LString::Array a = Nm.RSplit(".", 1); + if (a.Length() == 2) + Nm.Printf("%s %i.%s", a[0].Get(), Index, a[1].Get()); + else + Nm.Printf("%s %i", a[0].Get(), Index); + } + if (!LMakePath(Buf, sizeof(Buf), Select->Name(), Nm)) + { + Errors++; + break; + } + + if (!LFileExists(Buf)) + break; } - if (!LFileExists(Buf)) - break; + Out = Buf; + } + + LAutoPtr f(new LFile); + if (!f->Open(Out, O_WRITE)) + { + LgiTrace("%s:%i - Couldn't open '%s' for writing.", _FL, Select->Name()); + Errors++; } - - Out = Buf; - } - - LAutoPtr f(new LFile); - if (!f->Open(Out, O_WRITE)) - { - LgiTrace("%s:%i - Couldn't open '%s' for writing.", _FL, Select.Name()); - Errors++; + else + { + f->SetSize(0); + if (m->Export(m->AutoCast(f), ExportMimeType)) + Exported++; + else + Errors++; + } } - else + + if (Errors > 0) + LgiMsg(Parent, "Export failed: %i exported, %i errors.", AppName, MB_OK, Exported, Errors); + + if (Callback) + Callback(Errors == 0); + }; + + auto Select = new LFileSelect(Parent); + if (Sel.Length() == 1) + Select->Name(LGetLeaf(GetDropFileName())); + Select->Type("Email", "*.eml"); + + if (Sel.Length() > 1) + { + Select->OpenFolder([&](auto dlg, auto status) { - f->SetSize(0); - if (m->Export(m->AutoCast(f), ExportMimeType)) - Exported++; - else - Errors++; - } + if (status) + Process(dlg); + delete dlg; + }); } - - if (Errors > 0) - LgiMsg(Parent, "Export failed: %i exported, %i errors.", AppName, MB_OK, Exported, Errors); - - return Errors == 0; + else + { + Select->Save([&](auto dlg, auto status) + { + if (status) + Process(dlg); + delete dlg; + }); + } } bool Thing::CallMethod(const char *MethodName, LVariant *ReturnValue, LArray &Args) { ScribeDomType Fld = StrToDom(MethodName); switch (Fld) { case SdImport: // Type: (String FileName, String MimeType) { *ReturnValue = false; if (Args.Length() != 2) LgiTrace("%s:%i - Error: expecting 2 arguments to 'Import'.\n", _FL); else { auto FileName = Args[0]->Str(); LAutoPtr f(new LFile); if (f->Open(FileName, O_READ)) { auto status = Import(AutoCast(f), Args[1]->Str()); *ReturnValue = status.status; } else LgiTrace("%s:%i - Error: Can't open '%s' for reading.\n", _FL, FileName); } break; } case SdExport: // Type: (String FileName, String MimeType) { *ReturnValue = false; if (Args.Length() != 2) LgiTrace("%s:%i - Error: expecting 2 arguments to 'Export'.\n", _FL); else { auto FileName = Args[0]->Str(); LAutoPtr f(new LFile); if (f->Open(FileName, O_WRITE)) { auto status = Export(AutoCast(f), Args[1]->Str()); *ReturnValue = status.status; } else LgiTrace("%s:%i - Error: Can't open '%s' for writing.\n", _FL, FileName); } break; } default: return false; } return true; } bool Thing::GetData(LArray &Data) { bool Status = false; LArray Objs; LList *ParentList = LListItem::Parent; if (ParentList) ParentList->GetSelection(Objs); for (unsigned idx = 0; idx < Data.Length(); idx++) { LDragData &dd = Data[idx]; if (!dd.Format) continue; if (dd.IsFormat(LGI_FileDropFormat)) { LMouse m; App->GetMouse(m, true); LString::Array Files; for (auto t: Objs) Status |= t->GetDropFiles(Files); if (Status && CreateFileDrop(&dd, m, Files)) Status = true; } else if (dd.IsFormat(LGI_StreamDropFormat)) { for (auto t: Objs) { if (t->GetObject()) { LAutoStreamI s = t->GetObject()->GetStream(_FL); if (s) { s->SetPos(0); auto Fn = t->GetDropFileName(); auto MimeType = Store3ItemTypeToMime(t->Type()); dd.AddFileStream(LGetLeaf(Fn), MimeType, s); } } } Status = dd.Data.Length() > 0; } else if (dd.IsFormat(ScribeThingList)) { ScribeClipboardFmt *Fmt = ScribeClipboardFmt::Alloc(Objs); if (Fmt) { Status |= dd.Data[0].SetBinary(Fmt->Sizeof(), Fmt); free(Fmt); } } } return Status; } //////////////////////////////////////// bool Thing::SetField(int Field, int n) { return GetObject() ? GetObject()->SetInt(Field, n) != 0 : false; } bool Thing::SetField(int Field, double n) { LAssert(!"Not implemented"); return false; } bool Thing::SetField(int Field, char *n) { return GetObject() ? GetObject()->SetStr(Field, n) != 0 : false; } bool Thing::SetField(int Field, LDateTime &n) { return GetObject() ? GetObject()->SetDate(Field, &n) != 0 : false; } bool Thing::GetField(int Field, int &n) { n = GetObject() ? (int)GetObject()->GetInt(Field) : 0; return true; } bool Thing::GetField(int Field, double &n) { LAssert(!"Not implemented"); return false; } bool Thing::GetField(int Field, const char *&n) { n = GetObject() ? GetObject()->GetStr(Field) : 0; return n != 0; } bool Thing::GetField(int Field, LDateTime &n) { const LDateTime *t = GetObject() ? GetObject()->GetDate(Field) : 0; if (t) { n = *t; return n.IsValid(); } return false; } bool Thing::DeleteField(int Field) { LAssert(!"Not implemented"); return false; } ////////////////////////////////////////////////////////////////////////////// LArray ThingUi::All; ThingUi::ThingUi(Thing *item, const char *name) { _Dirty = false; _Running = false; _Name = NewStr(name); _Item = item; App = item ? item->App : NULL; LAssert(App != NULL); SetQuitOnClose(false); SetSnapToEdge(true); Name(_Name); All.Add(this); } ThingUi::~ThingUi() { LAssert(InThread()); LAssert(All.HasItem(this)); All.Delete(this); _Running = false; _Dirty = false; DeleteArray(_Name); } bool ThingUi::OnViewKey(LView *v, LKey &k) { bool IsPopup = false; #ifdef __GTK_H__ OsView Hnd = GtkCast(GetWindow()->WindowHandle(), gtk_widget, GtkWidget); #else OsView Hnd = Handle(); #endif for (LViewI *p = v; p; p = p->GetParent()) { if (dynamic_cast(p)) { IsPopup = true; break; } } bool Status = LWindow::OnViewKey(v, k); // The 'this' pointer may not be valid from here on. if (IsPopup) { return Status; } if (!Status && k.Down() && k.vkey == LK_ESCAPE) { LPostEvent(Hnd, M_CLOSE); return false; } return Status; } bool ThingUi::SetDirty(bool d, bool ui) { bool Status = true; if (d ^ _Dirty) { if (d) { if (_Item && dynamic_cast(_Item->GetObject())) { LAssert(!"Should imap mail become dirty?"); } _Dirty = true; OnDirty(_Dirty); } else { int Result = ui ? LgiMsg(this, LLoadString(IDS_SAVE_ITEM), AppName, MB_YESNOCANCEL) : IDYES; if (Result == IDYES) { OnSave(); _Dirty = false; OnDirty(_Dirty); } else if (Result == IDCANCEL) { Status = false; } else { // Result == IDNO _Dirty = false; OnDirty(_Dirty); } } if (Status) { char s[256]; if (_Dirty) { sprintf_s(s, sizeof(s), "%s (%s)", _Name, LLoadString(IDS_CHANGED)); } else { strcpy_s(s, sizeof(s), _Name); } Name(s); } } return Status; } bool ThingUi::OnRequestClose(bool OsShuttingDown) { static bool Processing = false; bool Status = false; if (!Processing) { Processing = true; bool Cleaned = SetDirty(false); if (Cleaned) { Status = LWindow::OnRequestClose(OsShuttingDown); } Processing = false; } return Status; } diff --git a/Code/ScribeUi.cpp b/Code/ScribeUi.cpp --- a/Code/ScribeUi.cpp +++ b/Code/ScribeUi.cpp @@ -1,160 +1,159 @@ /* ** FILE: ScribeUi.cpp ** AUTHOR: Matthew Allen ** DATE: 30/10/98 ** DESCRIPTION: Scribe email application ** ** Copyright (C) 1998, Matthew Allen ** fret@memecode.com */ #include #include #include #include "Scribe.h" #include "lgi/common/Edit.h" #include "lgi/common/RadioGroup.h" #include "lgi/common/Combo.h" #include "lgi/common/Button.h" #include "lgi/common/TextView3.h" #include "lgi/common/TextLabel.h" #include "resdefs.h" #include "lgi/common/ControlTree.h" #include "lgi/common/TabView.h" #include "ScribeSpellCheck.h" #include "lgi/common/TableLayout.h" // static char AutoInBrackets[] = "(auto)"; ////////////////////////////////////////////////////////////////////////////// CreateSubFolderDlg::CreateSubFolderDlg(LView *parent, int defaulttype, bool *Enable, char *default_name) { SubType = -1; SubName = 0; SetParent(parent); LRect r(0, 0, 380, 195); SetPos(r); Name("Create Sub Folder"); if (LoadFromResource(IDD_NEWFOLDER_F)) { if (GetViewById(IDC_NAME, FolderName)) { FolderName->Name(default_name); FolderName->Focus(true); } GetViewById(IDC_TYPE, FolderType); SetCtrlValue(IDC_TYPE, defaulttype); int Ctrls[] = { IDC_MAIL, IDC_CONTACTS, IDC_FILTERS, IDC_CALENDER, IDC_GROUP }; for (int c=0; cGetId()) { case IDOK: { if (FolderName) { auto n = FolderName->NameW(); if (ValidStrW(n)) { SubName = WideToUtf8(n); // n belongs to the control } } if (FolderType) { SubType = (int)FolderType->Value(); } EndModal(1); break; } case IDCANCEL: { EndModal(0); break; } } return 0; } ///////////////////////////////////////////////////////////////////////////////////////////// FolderNameDlg::FolderNameDlg(LView *parent, const char *Old) { Name = NewStr(Old); SetParent(parent); if (LoadFromResource(IDD_FOLDER_NAME)) { MoveToMouse(); GetViewById(IDC_NAME, Ed); } } FolderNameDlg::~FolderNameDlg() { DeleteArray(Name); } void FolderNameDlg::OnCreate() { if (Ed && Name) { char16 *Txt = Utf8ToWide(Name); if (Txt) { Ed->NameW(Txt); DeleteArray(Txt); } } } int FolderNameDlg::OnNotify(LViewI *Ctrl, LNotification n) { switch (Ctrl->GetId()) { case IDOK: { if (Ed) { auto Txt = Ed->NameW(); if (Txt) { DeleteArray(Name); Name = WideToUtf8(Txt); } } EndModal(1); break; } case IDCANCEL: { EndModal(0); break; } } return 0; } diff --git a/Code/ScribeUtils.cpp b/Code/ScribeUtils.cpp --- a/Code/ScribeUtils.cpp +++ b/Code/ScribeUtils.cpp @@ -1,2085 +1,2123 @@ #include "Scribe.h" #include "lgi/common/Http.h" #include "lgi/common/DocView.h" #include "lgi/common/Store3.h" #include "lgi/common/Button.h" #include "lgi/common/TableLayout.h" #include "lgi/common/TabView.h" #include "lgi/common/OpenSSLSocket.h" #include "lgi/common/LgiRes.h" #include "ScribeUtils.h" #include "ScribeDefs.h" #include "ScribeListAddr.h" #include "resdefs.h" #include "../src/common/Coding/ScriptingPriv.h" #define COMP_FUNCTIONS 1 #include "lgi/common/ZlibWrapper.h" static char Ws[] = " \t\r\n"; #include "chardet.h" LString DetectCharset(LString s) { DetectObj *obj = detect_obj_init (); if (!obj) return NULL; LString cs; if (detect_r(s.Get(), s.Length(), &obj) == CHARDET_SUCCESS) cs = obj->encoding; // obj->encoding, obj->confidence, obj->bom detect_obj_free (&obj); return cs; } const char *ScribeResourcePath() { static char Res[MAX_PATH_LEN] = {0}; if (!Res[0]) { #if defined(LINUX) // Check for AppImage location LFile::Path app(LSP_APP_INSTALL); app += "../../usr/share/applications"; if (app.Exists()) { strcpy_s(Res, sizeof(Res), app.GetFull()); LgiTrace("%s:%i - Res: %s.\n", _FL, Res); return Res; } // else LgiTrace("%s:%i - Warning: app image resource '%s' doesn't exist.\n", _FL, app.GetFull().Get()); // else fall through to portable mode #elif defined(MAC) // Find resource folder in app bundle LMakePath(Res, sizeof(Res), LGetExeFile(), "Contents/Resources"); return Res; #endif #if !defined(MAC) const char *Paths[] = { "./Resources", "../Resources", "../../Resources", }; bool Found = false; for (unsigned i=0; iGet(d, sizeof(d)); p.Push(d); break; } } } void PushArrayContent(LStringPipe &p, char *&s, LDom *Source) { // Process inside of array index while (s && *s) { // Skin ws while (*s && strchr(Ws, *s)) s++; // Is end of array brackets? if (*s == ']') { break; } else if (*s == '\'' || *s == '\"') { // String const char Delim = *s++; char *e = strchr(s, Delim); if (e) { p.Push(s, e-s); s = e + 1; } else { s += strlen(s); break; } } else { // Variable LStringPipe Var; char *e = s; int Depth = 0; while (*e && !strchr(Ws, *e)) { if (*e == '[') { e++; Var.Push(s, e-s); PushArrayContent(Var, e, Source); s = e; Depth++; } else if (*e == ']') { if (Depth > 0 || e[1] == '.') { // Continue the variable if (Depth) Depth--; e++; } else { // End the var break; } } else { e++; } } Var.Push(s, e-s); char *Tok = Var.NewStr(); if (Tok) { LVariant v; if (Source->GetValue(Tok, v)) { PushVariant(p, v); } else { p.Push(Tok); } DeleteArray(Tok); } s = e; } } } char *ScribeInsertFields(const char *Template, LDom *Source) { if (Template && Source) { LStringPipe p; char *n; for (const char *s=Template; s && *s; s = n) { n = strstr((char*)s, "') || strchr(Ws, *e) ) { break; } e++; } if (*e == '[') { e++; Var.Push(s, e-s); // Process inside of array index PushArrayContent(Var, e, Source); } else if (strchr(Ws, *e)) { // Skip whitespace Var.Push(s, e-s); while (*e && strchr(Ws, *e)) { e++; } } else if (e[0] == '?' && e[1] == '>') { // End of var Var.Push(s, e-s); GotEnd = true; n = e; break; } else { // error n = e;; break; } s = e; } if (GotEnd) { char *Name = Var.NewStr(); if (Name) { char *i = Name, *o = Name; while (*i) { if (*i == '=') { i++; char h[] = {i[0], i[1], 0}; *o++ = htoi(h); i += 2; } else { *o++ = *i++; } } *o++ = 0; LVariant v; if (Source->GetValue(Name, v)) { switch (v.Type) { default: break; case GV_STRING: { p.Push(v.Str()); break; } case GV_INT32: { char i[32]; sprintf_s(i, sizeof(i), "%i", v.Value.Int); p.Push(i); break; } case GV_DOUBLE: { char d[32]; sprintf_s(d, sizeof(d), "%f", v.Value.Dbl); p.Push(d); break; } case GV_DATETIME: { char d[64]; v.Value.Date->Get(d, sizeof(d)); p.Push(d); break; } } } DeleteArray(Name); } n += 2; } } else { p.Push(s); break; } } return p.NewStr(); } return 0; } ////////////////////////////////////////////////////////////////////////////////////// HttpImageThread::HttpImageThread(ScribeWnd *app, const char *proxy, LThreadTarget *First) : LThreadWorker(First, "HtmlImageLoader") { App = app; Proxy = proxy; Cache = ScribeTempPath(); if (!LDirExists(Cache)) FileDev->CreateFolder(Cache); } HttpImageThread::~HttpImageThread() { } void HttpImageThread::DoJob(LThreadJob *j) { LDocumentEnv::LoadJob *Job = dynamic_cast(j); if (!Job) return; char *d = strrchr(Job->Uri, '/'); if (!d) { Job->Status = LDocumentEnv::LoadJob::JobErr_Uri; Job->Error.Printf("No '/' in uri '%s'", Job->Uri.Get()); return; } LString CachedFile = UriMap.Find(Job->Uri); if (!CachedFile) { char *Ext = LGetExtension(++d); auto Qm = Ext ? strchr(Ext, '?') : NULL; auto Len = Qm ? Qm - Ext : Strlen(Ext); char p[MAX_PATH_LEN]; for (int i=0; i<1000; i++) { char Hash[256]; sprintf_s(Hash, sizeof(Hash), "%x_%i.%.*s", LHash((uchar*)d + 1, -1, true), i++, (int)Len, Ext); if (!LMakePath(p, sizeof(p), Cache, Hash)) { Job->Status = LDocumentEnv::LoadJob::JobErr_Path; Job->Error.Printf("MakePath failed: '%s' + '%s'", Cache.Get(), Hash); return; } if (!LFileExists(p)) break; } UriMap.Add(Job->Uri, CachedFile = p); } if (!LFileExists(CachedFile)) { const char *InHeaders = "User-Agent: Memecode Scribe\r\n" "Accept: text/html,application/xhtml+xml,application/xml,image/png,image/*;q=0.9,*/*;q=0.8\r\n" "Accept-Encoding: gzip, deflate\r\n"; LFile f; if (f.Open(CachedFile, O_READWRITE)) { LUri Prox(Proxy); bool r = LgiGetUri(this, &f, &Job->Error, Job->Uri, InHeaders, Proxy ? &Prox : NULL); f.Close(); if (!r) { Job->Status = LDocumentEnv::LoadJob::JobErr_GetUri; FileDev->Delete(CachedFile, false); } } else { Job->Status = LDocumentEnv::LoadJob::JobErr_FileOpen; } } if (LFileExists(CachedFile)) { LString::Array Mime = LGetFileMimeType(CachedFile).Split("/"); if (Mime[0].Equals("image")) { int Promote = GdcD->SetOption(GDC_PROMOTE_ON_LOAD, 0); Job->pDC.Reset(GdcD->Load(CachedFile)); GdcD->SetOption(GDC_PROMOTE_ON_LOAD, Promote); if (Job->pDC) { Job->Status = LDocumentEnv::LoadJob::JobOk; } else { char *d = strrchr(CachedFile, DIR_CHAR); Job->Error.Printf("%s:%i - LoadDC(%s) failed [%s].", _FL, d?d+1:CachedFile.Get(), Job->Uri.Get()); FileDev->Delete(CachedFile, false); Job->Status = LDocumentEnv::LoadJob::JobErr_ImageFilter; } } else { // Css??? LFile *f = new LFile; if (f) { if (f->Open(CachedFile, O_READ)) { Job->Stream.Reset(f); Job->Status = LDocumentEnv::LoadJob::JobOk; } else { Job->Status = LDocumentEnv::LoadJob::JobErr_FileOpen; Job->Error.Printf("%s:%i - Cant read from '%s' (err=%i).", _FL, CachedFile.Get(), f->GetError()); delete f; } } else Job->Status = LDocumentEnv::LoadJob::JobErr_NoMem; } } else if (!Job->Error) { Job->Status = LDocumentEnv::LoadJob::JobErr_NoCachedFile; Job->Error = "No file in cache"; } if (Job->Error) { LgiTrace("Image load failed: %s\n", Job->Error.Get()); } } char *ScribeTempPath() { static char Tmp[MAX_PATH_LEN] = ""; if (Tmp[0] == 0) { if (LGetSystemPath(LSP_TEMP, Tmp, sizeof(Tmp))) { LMakePath(Tmp, sizeof(Tmp), Tmp, "Scribe"); } else { LgiTrace("%s:%i - LgiGetSystemPath(LSP_TEMP) failed.\n", _FL); return NULL; } } if (!LDirExists(Tmp)) { LError Err; if (!FileDev->CreateFolder(Tmp, true, &Err)) { LgiTrace("%s:%i - CreateFolder(%s) failed with %i\n", _FL, Tmp, Err.GetCode()); return NULL; } } return Tmp; } void ClearTempPath() { char *Tmp = ScribeTempPath(); if (Tmp) { if (!LDirExists(Tmp)) FileDev->CreateFolder(Tmp); LDirectory d; for (int b = d.First(Tmp); b; b = d.Next()) { if (!d.IsDir()) { char p[256]; d.Path(p, sizeof(p)); FileDev->Delete(p, false); } } } } //////////////////////////////////////////////////////////////////////////////////////////////// #define BufferLen_64ToBin(l) ( ((l)*3)/4 ) #define BufferLen_BinTo64(l) ( ((((l)+2)/3)*4) ) int DecodeUuencodedChar(const char *&s) { int Status = -1; if (*s == 0x60) { Status = 0; s++; } else if (*s >= (' ' + 64) || *s < ' ') { printf("%s:%i - Invalid uuencode char: %c (%i)\n", _FL, *s, (uchar)*s); } else { Status = *s - ' '; s++; } return Status; } bool DecodeUuencodedLine(LStreamI *Out, const char *Text, ssize_t Len) { bool Status = false; if (Text && Len > 1) { uchar *Buf = new uchar[Len]; if (Buf) { const char *End = Text + Len; const char *c = Text; uchar *d = Buf; int Count = DecodeUuencodedChar(c); int Processed = 0; if (Count < 0) return false; while (c < End && *c) { int t[4]; // De-text t[0] = DecodeUuencodedChar(c); if (t[0] < 0) break; t[1] = DecodeUuencodedChar(c); if (t[1] < 0) break; t[2] = DecodeUuencodedChar(c); if (t[2] < 0) break; t[3] = DecodeUuencodedChar(c); if (t[3] < 0) break; // Convert to binary uchar b[3] = { (uchar) ((t[0] << 2) | ((t[1] & 0x30) >> 4)), (uchar) (((t[1] & 0xF) << 4) | ((t[2] & 0x3C) >> 2)), (uchar) (((t[2] & 0x3) << 6) | (t[3])) }; // Push onto the output stream switch (Count - Processed) { case 1: { *d++ = b[0]; Processed++; break; } case 2: { *d++ = b[0]; *d++ = b[1]; Processed += 2; break; } default: { if (Count - Processed >= 3) { *d++ = b[0]; *d++ = b[1]; *d++ = b[2]; Processed += 3; } break; } } } if (Processed != Count) { printf("%s:%i - uuencode line error, processed %i of %i\n", _FL, Processed, Count); } Status = Out->Write(Buf, d-Buf) > 0; DeleteArray(Buf); } } return Status; } bool DecodeUuencodedAttachment(LDataStoreI *Store, LArray &Files, LStreamI *Out, const char *In) { if (Store && In) { // const char Ws[] = " \t\r\n"; LStringPipe FileName; LAutoPtr FileData; const char *e; const char *Last = In; int Line = 1; for (const char *s = In; s && *s; s = *e?e+1:e) { // Find the end of the line... e = s; while (*e && *e != '\n') e++; if (FileData) { if (_strnicmp(s, "end", 3) == 0) { // Write attachment LDataI *Attachment = Store->Create(MAGIC_ATTACHMENT); if (Attachment) { LAutoString Name(FileName.NewStr()); if (Name) { char *e = Name + strlen(Name); while (e > Name.Get() && strchr(" \t\r\n", e[-1])) *--e = 0; Attachment->SetStr(FIELD_NAME, Name); } LAutoStreamI fd(FileData.Release()); Attachment->SetStream(fd); Files.Add(Attachment); } FileData.Reset(); } else if (!DecodeUuencodedLine(FileData, s, e - s)) { /* printf("%s:%i - DecodeUuencodedLine failed on line %i:\n\t%s\n", _FL, Line, s); */ } } // Is it the start of a file else if (_strnicmp(s, "begin ", 6) == 0) { if (Last) { Out->Write(Last, s - Last); Last = 0; } LToken Header(s, " ", true, e - s); if (Header.Length() >= 3) { LMemQueue File; for (int n=2; Header[n]; n++) { FileName.Print("%s%s", n==2?"":" ", Header[n]); } } FileData.Reset(new LStringPipe(256)); } else if (!Last) { Last = s; } Line++; } if (Files.Length() && Last) { Out->Write(Last, strlen(Last)); } } return Files.Length() > 0; } char *MakeFileName(const char *ContentUtf, const char *Ext) { if (!ContentUtf) { LAssert(!"Invalid parameter."); return 0; } char *Content = 0; if (LIsUtf8(ContentUtf)) { // Valid UTF-8 Content = NewStr(ContentUtf); } else { // Garbage, so just ignore the input data. char n[256]; sprintf_s(n, sizeof(n), "_%i", LRand(1000000)); Content = NewStr(n); } if (!Content) { LAssert(!"No content to make filename from."); return 0; } char File[MAX_PATH_LEN]; char *e = Content; for (int i=0; i<64 && *e; i++) { char *before = e; e = LSeekUtf8(e, 1); if (e == before) { LAssert(!"LSeekUtf8 failed to more pointer forward."); break; } } *e = 0; if (strlen(Content) > 0) { if (Ext) sprintf_s(File, sizeof(File), "%s.%s", Content, Ext); else sprintf_s(File, sizeof(File), "%s", Content); } else { LAssert(!"No content for file name?"); strcpy_s(File, sizeof(File), "file"); } // Strip out invalid characters... char *Out = File; for (char *In = File; *In; In++) { if (!strchr("\\/?*:\"<>|\r\n", *In)) { *Out++ = *In; } } *Out++ = 0; LAssert(strlen(File) > 0); char Temp[MAX_PATH_LEN]; LMakePath(Temp, sizeof(Temp), ScribeTempPath(), File); if (LFileExists(Temp)) { char *Dot = strrchr(Temp, '.'); for (int i=2; LFileExists(Temp); i++) { ssize_t Len = Dot - Temp; sprintf_s(Dot, sizeof(Temp)-Len, "%i.%s", i, Ext); } } DeleteArray(Content); return NewStr(Temp); } /////////////////////////////////////////////////////////////////////////////////////////// Store3Progress::Store3Progress(LView *parent, bool interact) : LProgressDlg(parent) { Interact = interact; NewFormat = -1; } const char *Store3Progress::GetStr(int id) { switch (id) { case Store3UiError: return Err; case Store3UiStatus: return (Cache = ItemAt(0)->GetDescription()); } LAssert(0); return 0; } Store3Status Store3Progress::SetStr(int id, const char *str) { switch (id) { case Store3UiError: Err = str; // Fall through case Store3UiStatus: ItemAt(0)->SetDescription(str); return Store3Success; } LAssert(0); return Store3Error; } int64 Store3Progress::GetInt(int id) { switch (id) { case Store3UiCurrentPos: return ItemAt(0)->Value(); case Store3UiInteractive: return Interact; case Store3UiCancel: return IsCancelled(); case Store3UiNewFormat: return NewFormat; } LAssert(0); return -1; } Store3Status Store3Progress::SetInt(int id, int64 i) { switch (id) { case Store3UiCancel: return Store3Error; case Store3UiCurrentPos: ItemAt(0)->Value(i); break; case Store3UiMaxPos: ItemAt(0)->SetRange(i); break; case Store3UiNewFormat: NewFormat = (int)i; break; default: LAssert(0); return Store3Error; } return Store3Success; } /////////////////////////////////////////////////////////////////////// class BufferedTrace { List Traces; public: ~BufferedTrace() { for (auto s: Traces) { LgiTrace(s); DeleteArray(s); } } void Trace(char *s) { if (s) { Traces.Insert(NewStr(s)); } } } ; static BufferedTrace Bt; void TraceTime(char *s) { static int64 Last = 0; if (s) { int64 Now = LCurrentTime(); int64 Diff = 0; if (Last) { Diff = Now - Last; } else { Diff = 0; } Last = Now; char m[256]; sprintf_s(m, sizeof(m), "%s (+%i)", s, (int)Diff); Bt.Trace(m); } else { Last = 0; } } ///////////////////////////////////////////////////////////////////////////// Counter::~Counter() { for (auto c: *this) { DeleteObj(c); } } CountItem *Counter::FindType(int Type) { for (auto c: *this) { if (Type == c->Type) { return c; } } CountItem *c = new CountItem; if (c) { c->Type = Type; Insert(c); } return c; } void Counter::Inc(int Type) { CountItem *c = FindType(Type); if (c) { c->Count++; } } void Counter::Dec(int Type) { CountItem *c = FindType(Type); if (c) { c->Count--; } } void Counter::Add(int Type, int64 n) { CountItem *c = FindType(Type); if (c) { c->Count += n; } } void Counter::Sub(int Type, int64 n) { CountItem *c = FindType(Type); if (c) { c->Count -= n; } } int64 Counter::GetTypeCount(int Type) { CountItem *c = FindType(Type); if (c) { return c->Count; } return 0; } ////////////////////////////////////////////////////////// ItemFieldDef *ScribeGetFieldDefs(int Type) { switch ((uint32_t)Type) { case MAGIC_MAIL: { return MailFieldDefs; } case MAGIC_CONTACT: { return ContactFieldDefs; } case MAGIC_CALENDAR: { return CalendarFields; } } return 0; } Contact *IsContact(LListItem *Item) { return dynamic_cast(Item); } Mail *IsMail(LListItem *Item) { return dynamic_cast(Item); } ////////////////////////////////////////////////////////////////////////////// char sMimeVCard[] = "text/x-vcard"; char sMimeVCalendar[] = "text/calendar"; char sMimeICalendar[] = "application/ics"; char sMimeMbox[] = "text/mbox"; char sMimeLgiResource[] = "application/x-lgi-resource"; char sMimeMessage[] = "message/rfc822"; char sMimeXml[] = "text/xml"; LString ScribeGetFileMimeType(const char *File) { LString Ret; if (File) { char *Ext = LGetExtension((char*)File); if (Ext) { if (_stricmp(Ext, "lr8") == 0) { Ret = sMimeLgiResource; } else if (_stricmp(Ext, "ici") == 0) { Ret = "application/x-ici"; } else if (_stricmp(Ext, "vcf") == 0) { Ret = sMimeVCard; } else if (_stricmp(Ext, "vcs") == 0 || _stricmp(Ext, "ics") == 0) { Ret = sMimeVCalendar; } else if (_stricmp(Ext, "eml") == 0) { Ret = sMimeMessage; } #if defined WIN32 // Hard code extensions (because windows doesn't get it right) else if (_stricmp(Ext, "mbx") == 0 || _stricmp(Ext, "mbox") == 0) { Ret = sMimeMbox; } #endif } if (!Ret) { // Do normal lookup Ret = LGetFileMimeType(File); } } return Ret; } ///////////////////////////////////////////////////////////////////// Mailto::Mailto(ScribeWnd *app, const char *s) { App = app; Subject = NULL; Body = NULL; if (!s) return; // Do some detection of what type of string this is... // // Could be in various formats: // 1. user@isp.com // 2. user@isp.com, user2@isp.com, user3@isp.com // 3. "First Last" // 4. "First Last" , "First2 Last2" // 5. mailto:user@isp.com // 6. mailto:user@isp.com?subject=xxxxxx&body=xxxxxxxx // Skip whitespace while (*s && strchr(" \t\r\n", *s)) s++; // Check for mailto prefix if (_strnicmp(s, "mailto:", 7) == 0) { // Parse mailto URI char *e = NewStr(s + 7); char *In, *Out = e; for (In = e; *In; ) { if (In[0] == '%' && In[1] && In[2]) { char h[3] = { In[1], In[2], 0 }; *Out++ = htoi(h); In += 3; } else { *Out++ = *In++; } } *Out++ = 0; // Process mailto syntax char *Question = strchr(e, '?'); if (Question) { *Question++ = 0; // Split all the headers up LToken Headers(Question, "&"); for (unsigned h=0; hsAddr = e; To.Insert(la); } } DeleteArray(e); } else { // Not a mailto, apply normal email recipient parsing char White[] = " \t\r\n"; #define SkipWhite(s) while (*s && strchr(White, *s)) s++; const char *Addr = s; for (const char *c = s; true;) { SkipWhite(c); if (*c == '\'' || *c == '\"') { char Delim = *c++; char *e = strchr((char*)c, Delim); if (e) c = e + 1; else c += strlen(c); } else if (*c == '<') { char *e = strchr((char*)c, '>'); if (e) c = e + 1; else c++; } else if (*c == ',' || *c == 0) { char *a = NewStr(Addr, c - Addr); if (a) { LAutoString Name, Addr; DecodeAddrName(a, Name, Addr, 0); if (Name || Addr) { ListAddr *la = new ListAddr(App); if (la) { if (Name && Addr) { la->sName = Name.Get(); la->sAddr = Addr.Get(); } else { la->sAddr = Name ? Name.Get() : Addr.Get(); } To.Insert(la); } } DeleteArray(a); } if (!*c) break; else { c++; SkipWhite(c); Addr = c; } } else c++; } } } Mailto::~Mailto() { To.DeleteObjects(); DeleteArray(Subject); DeleteArray(Body); } void Mailto::Apply(Mail *m) { if (m) { bool Dirty = false; if (Subject) { m->SetSubject(Subject); Dirty = true; } if (Body) { LVariant HtmlEdit; m->App->GetOptions()->GetValue(OPT_EditControl, HtmlEdit); auto Email = m->GetFromStr(FIELD_EMAIL); ScribeAccount *Acc = m->App->GetAccountByEmail(Email); if (!Acc) Acc = m->App->GetCurrentAccount(); LVariant Sig; LString Content; if (Acc) { if (HtmlEdit.CastInt32()) { Sig = Acc->Identity.HtmlSig(); if (Sig.Str()) { char *s = Sig.Str(); char *e = stristr(s, ""); if (e) Content.Printf("%.*s\n%s\n%s", e - s, s, Body, e + 6); else Content.Printf("%s\n%s", Body, s); } else Content = Body; } else { Sig = Acc->Identity.TextSig(); Content.Printf("%s\n%s", Body, Sig.Str()); } } if (HtmlEdit.CastInt32()) m->SetHtml(Content); else m->SetBody(Content); Dirty = true; } for (auto t: To) { GDataIt To = m->GetObject()->GetList(FIELD_TO); if (To) { LDataPropI *Addr = To->Create(m->GetObject()->GetStore()); if (Addr) { LDataPropI *p = dynamic_cast(t); if (p) { Addr->CopyProps(*p); To->Insert(Addr); } else { LAssert(!"Not the right object."); DeleteObj(Addr); } } if (m->GetUI()) { m->GetUI()->AddRecipient(new ListAddr(App, t)); } Dirty = true; } } if (Dirty) m->SetDirty(); } } ScribeDom::ScribeDom(ScribeWnd *a) { App = a; Email = NULL; Con = NULL; Cal = NULL; Fil = NULL; Grp = NULL; } bool ScribeDom::GetVariant(const char *Name, LVariant &Value, const char *Array) { ScribeDomType Fld = StrToDom(Name); switch (Fld) { case SdScribe: // Type: ScribeWnd { Value = (LDom*)App; break; } case SdMail: // Type: Mail { Value = Email; break; } case SdContact: // Type: Contact { Value = Con; break; } case SdContactGroup: // Type: ContactGroup { Value = Grp; break; } case SdCalendar: // Type: Calendar { Value = Cal; break; } case SdFilter: // Type: Filter { Value = Fil; break; } case SdNow: // Type: String { char n[256]; LDateTime Now; Now.SetNow(); Now.Get(n, sizeof(n)); Value = n; break; } default: { return false; } } return true; } ////////////////////////////////////////////////////////////////////////////////////// #include "lgi/common/Html.h" #include "lgi/common/Button.h" class HtmlMsg : public LDialog, public LDefaultDocumentEnv { LTableLayout *Tbl; Html1::LHtml *Html2; public: HtmlMsg(LViewI *Parent, const char *Html, const char *Title, int Type) { LPoint Size(200, 200); Tbl = NULL; SetParent(Parent); Name(Title?Title:"Message"); AddView(Tbl = new LTableLayout(2222)); auto c = Tbl->GetCell(0, 0); if (c->Add(Html2 = new Html1::LHtml(100, 0, 0, (int)(GdcD->X() * 0.5), (int)(GdcD->Y() * 0.75), this))) { Html2->SetCharset("utf-8"); Html2->Name(Html); Size = Html2->Layout(); LRect r(0, 0, Size.x, Size.y); Html2->SetPos(r); } LArray Btns; switch (Type & 0xf) { case MB_OK: Btns.Add(new LButton(IDOK, 0, 0, -1, -1, "Ok")); break; case MB_OKCANCEL: Btns.Add(new LButton(IDOK, 0, 0, -1, -1, "Ok")); Btns.Add(new LButton(IDCANCEL, 0, 0, -1, -1, "Cancel")); break; case MB_YESNO: Btns.Add(new LButton(IDYES, 0, 0, -1, -1, "Yes")); Btns.Add(new LButton(IDNO, 0, 0, -1, -1, "No")); break; case MB_YESNOCANCEL: Btns.Add(new LButton(IDYES, 0, 0, -1, -1, "Yes")); Btns.Add(new LButton(IDNO, 0, 0, -1, -1, "No")); Btns.Add(new LButton(IDCANCEL, 0, 0, -1, -1, "Cancel")); break; } c = Tbl->GetCell(0, 1); c->TextAlign(LCss::AlignCenter); for (auto b: Btns) c->Add(b); LRect r(0, 0, Size.x + 20 + LAppInst->GetMetric(LGI_MET_DECOR_X), Size.y + 20 + LSysFont->GetHeight() + LAppInst->GetMetric(LGI_MET_DECOR_CAPTION) + LAppInst->GetMetric(LGI_MET_DECOR_Y)); SetPos(r); MoveSameScreen(Parent); } int OnNotify(LViewI *Ctrl, LNotification n) { switch (Ctrl->GetId()) { case IDOK: case IDCANCEL: case IDYES: case IDNO: EndModal(Ctrl->GetId()); break; } return LDialog::OnNotify(Ctrl, n); } }; -int LHtmlMsg(LViewI *Parent, const char *Html, const char *Title, int Type, ...) +void LHtmlMsg(std::function Callback, LViewI *Parent, const char *Html, const char *Title, int Type, ...) { va_list Arg; va_start(Arg, Type); #undef vsnprintf int length = vsnprintf(NULL, 0, Html, Arg); LAutoString Msg(new char[++length]); vsprintf_s(Msg, length, Html, Arg); va_end(Arg); - HtmlMsg Dlg(Parent, Msg, Title, Type); - return Dlg.DoModal(); + auto Dlg = new HtmlMsg(Parent, Msg, Title, Type); + Dlg->DoModal([Callback](auto dlg, auto id) + { + if (Callback) + Callback(id); + delete dlg; + }); } ///////////////////////////////////////////////////////////////////////////// void TabDialog::OnCreate() { LTabView *Tab; if (GetViewById(TabCtrlId, Tab)) { Tab->SetPourChildren(true); LRect r(0, 0, 100, 100); Tab->SetPos(r); } OnPosChange(); } void TabDialog::IdealSize(LButton *b) { LViewLayoutInfo Inf; if (b->OnLayout(Inf)) { b->OnLayout(Inf); } else if (b->GetWindow()) { auto s = b->GetWindow()->GetDpiScale(); LDisplayString ds(b->GetFont(), b->Name()); Inf.Width.Max = (int32)(ds.X() + (s.x * LButton::Overhead.x)); Inf.Height.Max = (int32)(ds.Y() + (s.y * LButton::Overhead.y)); } else { LAssert(!"No way to set ideal size."); return; } LRect p = b->GetPos(); p.SetSize(Inf.Width.Max, Inf.Height.Max); b->SetPos(p); } void TabDialog::OnPosChange() { LButton *Ok = 0, *Cancel = 0, *Help = 0; LViewI *Tab = 0; if (GetViewById(TabCtrlId, Tab) && GetViewById(IDOK, Ok) && GetViewById(IDCANCEL, Cancel)) { GetViewById(HelpBtnId, Help); LRect r = GetClient(); r.Inset(LTableLayout::CellSpacing, LTableLayout::CellSpacing); IdealSize(Ok); IdealSize(Cancel); LRect t = r; t.y2 -= LTableLayout::CellSpacing + Ok->Y(); Tab->SetPos(t); if (Help) { IdealSize(Help); LRect h = Help->GetPos(); h.Offset(r.x1 - h.x1, r.y2 - h.Y() + 1 - h.y1); Help->SetPos(h); } LRect c = Cancel->GetPos(); c.Offset(r.x2 - c.X() + 1 - c.x1, r.y2 - c.Y() + 1 - c.y1); Cancel->SetPos(c); LRect o = Ok->GetPos(); o.Offset(c.x1 - LTableLayout::CellSpacing - o.X() + 1 - o.x1, r.y2 - o.Y() + 1 - o.y1); Ok->SetPos(o); } } LAutoString ConvertThreadIndex(char *ThreadIndex, int TruncateChars) { LAutoString a; if (ThreadIndex) { uchar InBuf[256]; ssize_t In = ConvertBase64ToBinary(InBuf, sizeof(InBuf), ThreadIndex, strlen(ThreadIndex)); LAssert(In >= 22); LStringPipe OutBuf(256); for (int i=0; i, ScribeDomType> Scribe_StrToDom(0, SdNone); static LHashTbl, const char *> Scribe_DomToStr; void InitStrToDom() { if (Scribe_StrToDom.Length() == 0) { #undef _ #define _(name) Scribe_StrToDom.Add(#name, Sd##name); \ LAssert(Scribe_StrToDom.Find(#name) == Sd##name); \ Scribe_DomToStr.Add(Sd##name, #name); #include "DomTypeValues.h" #undef _ } } ScribeDomType StrToDom(const char *s) { ScribeDomType d = Scribe_StrToDom.Find(s); return d; } const char *DomToStr(ScribeDomType d) { const char *s = Scribe_DomToStr.Find(d); return s; } void PatternBox(LSurface *pDC, const LRect &r) { int All = r.X() + r.Y() - 1; int MinEdge = MIN(r.X(), r.Y()); bool Wider = r.X() > r.Y(); for (int i=0; i> 2) % 2; /* if (i <= 4) LgiTrace("pt=%i,%i draw=%i\n", pt.x, pt.y, Draw); */ if (!Draw) continue; if (i < MinEdge) { pDC->Line(r.x1, r.y1 + i, r.x1 + i, r.y1); } else if (Wider) { if (i < r.X()) { int yy = r.Y() - 1; pDC->Line(pt.x, pt.y, pt.x - yy, pt.y + yy); } else { int yy = r.Y() - (i - r.X() + 1) - 1; pDC->Line(r.x2, r.y2-yy, r.x2-yy, r.y2); } } else // Tall { if (i < r.Y()) { int xx = r.X() - 1; pDC->Line(r.x1, r.y1 + i, r.x1 + xx, r.y1 + i - xx); } else { int xx = r.X() - (i - r.Y() + 1) - 1; pDC->Line(r.x2-xx, r.y2, r.x2, r.y2-xx); } } } } /////////////////////////////////////////////////////////////////////////////////////// ContactGroup *LookupContactGroup(ScribeWnd *App, const char *Name) { auto Srcs = App->GetThingSources(MAGIC_GROUP); if (!Srcs.Length() || !Name) return NULL; for (auto s: Srcs) { s->LoadThings(); for (auto t: s->Items) { ContactGroup *g = t->IsGroup(); if (!g) continue; LVariant Nm; if (g->GetVariant("Name", Nm) && Nm.Str() && _stricmp(Nm.Str(), Name) == 0) { return g; } } } return NULL; } ////////////////////////////////////////////////////////////////////////////////////////////////// LOAuth2::Params GetOAuth2Params(const char *Host, Store3ItemTypes Context) { LOAuth2::Params p; // FYI: None of this works due to issues at the providers end. It did sometime in the // past. And is only here in case someone wants to try and get it working again. if (stristr(Host, "google.") || stristr(Host, "gmail.")) { if (Context == MAGIC_MAIL) { p.AuthUri = "https://accounts.google.com/o/oauth2/auth"; p.ApiUri = "https://www.googleapis.com/oauth2/v3/token"; #if 1 // Old scope: p.Scope = "https://mail.google.com/"; #else // New scope: (doesn't work) p.Scope = "https://www.googleapis.com/auth/gmail.modify"; #endif // p.RevokeUri = "https://accounts.google.com/o/oauth2/revoke"; } /* else if (Context == MAGIC_CALENDAR) { p.AuthUri = "https://accounts.google.com/o/oauth2/v2/auth"; p.ApiUri = "https://apidata.googleusercontent.com/caldav/v2/%s/user"; p.Scope = "https://www.googleapis.com/auth/calendar"; } */ else return p; p.Provider = LOAuth2::Params::OAuthGoogle; p.ClientID = ""; p.ClientSecret = ""; p.RedirURIs = "urn:ietf:wg:oauth:2.0:oob\nhttp://localhost"; } else if (stristr(Host, "outlook.") && !stristr(Host, "office365.")) { if (Context == MAGIC_MAIL) { p.RedirURIs = "urn:ietf:wg:oauth:2.0:oob\nhttp://localhost"; p.AuthUri = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize"; p.ApiUri = "https://login.microsoftonline.com/common/oauth2/v2.0/token"; } else return p; p.Provider = LOAuth2::Params::OAuthMicrosoft; p.ClientID = ""; p.ClientSecret = ""; p.Scope = "https://outlook.office.com/mail.readwrite%20https://outlook.office.com/mail.send"; } return p; } ////////////////////////////////////////////////////////////////////////////////////////////////// class ScribeHtmLParser : public LHtmlParser { LScriptEngine Eng; public: struct HtmlElem : public LHtmlElement { LHashTbl, LString> Attr; HtmlElem(LHtmlElement *e) : LHtmlElement(e) { } bool GetVariant(const char *Name, LVariant &Value, const char *Array = NULL) { if (!Stricmp(Name, "element")) { Value = Tag.Get(); return true; } else if (!Stricmp(Name, "content")) { Value.OwnStr(WideToUtf8(Txt.Get())); return true; } else if (!Stricmp(Name, "attr")) { if (Array) { char *s = Attr.Find(Array); if (s) { Value = s; return true; } } } return false; } bool Get(const char *attr, const char *&val) { auto s = Attr.Find(attr); if (!s) return false; val = s.Get(); return true; } void Set(const char *attr, const char *val) { Attr.Add(attr, val); } }; ScribeHtmLParser() : LHtmlParser(NULL), Eng(NULL, NULL, NULL) { } LHtmlElement *CreateElement(LHtmlElement *Parent) { return new HtmlElem(Parent); } void Evaluate(LArray &Out, LString Search, HtmlElem *Elem) { LVariant Result; if (Eng.EvaluateExpression(&Result, Elem, Search)) { if (Result.CastInt32()) Out.Add(Elem); } for (auto e: Elem->Children) Evaluate(Out, Search, dynamic_cast(e)); } }; bool SearchHtml(LVariant *ReturnValue, const char *Html, const char *SearchExp, const char *ResultExp) { ScribeHtmLParser Parser; ScribeHtmLParser::HtmlElem Root(NULL); if (!Parser.Parse(&Root, Html)) { LgiTrace("%s:%i - HTML parsing failed.\n", _FL); *ReturnValue = false; return false; } LArray Matches; Parser.Evaluate(Matches, SearchExp, &Root); if (!ReturnValue->SetList()) return false; LScriptEngine Eng(NULL, NULL, NULL); for (auto e: Matches) { LVariant *Result = new LVariant; Eng.EvaluateExpression(Result, e, ResultExp); ReturnValue->Add(Result); } return true; } ////////////////////////////////////////////////////////////////////////////////////////////////////////// ScriptDownloadContentThread::ScriptDownloadContentThread(ScribeWnd *app, LString uri, LString callbackName, LVariant *userData) : LThread("ScriptDownloadContentThread", (App = app)->AddDispatch()) { Uri = uri; CallbackName = callbackName; if (userData) UserData = *userData; DeleteOnExit = true; Run(); } int ScriptDownloadContentThread::Main() { Result = LgiGetUri(this, &Out, &Err, Uri); return false; } void ScriptDownloadContentThread::OnComplete() { auto Cb = App->GetCallback(CallbackName); if (!Cb.Func) return; LVirtualMachine Vm; LScriptArguments Args(&Vm); LVariant vApp((LDom*)App); Args.Add(&vApp); LVariant vUri = Uri.Get(); Args.Add(&vUri); LVariant vResult = Result; Args.Add(&vResult); LVariant vData; if (Result) vData.OwnStr(Out.NewStr()); else vData = Err.Get(); Args.Add(&vData); Args.Add(&UserData); App->ExecuteScriptCallback(Cb, Args); } +//////////////////////////////////////////////////////////////////////////////////////////////////////// +// This converts an async call to sync, because the GetVariant / CallMethod API +// can't be changed to include a callback. It's a hack until such time as there +// is proper support for callbacks in the DOM api. +void WaitForVariant(LVariant &var) +{ + auto StartTs = LCurrentTime(); + while (var.Type == GV_NULL) + { + LSleep(10); + LYield(); + if (LCurrentTime() - StartTs > 20000) + { + LgiTrace("%s:%i - WaitForVariant waiting for: %is", _FL, (int)(LCurrentTime()-StartTs)); + StartTs = LCurrentTime(); + } + } +} + +void WaitForString(LString &var) +{ + auto StartTs = LCurrentTime(); + while (var.Get() == NULL) + { + LSleep(10); + LYield(); + if (LCurrentTime() - StartTs > 20000) + { + LgiTrace("%s:%i - WaitForString waiting for: %is", _FL, (int)(LCurrentTime()-StartTs)); + StartTs = LCurrentTime(); + } + } +} diff --git a/Code/ScribeUtils.h b/Code/ScribeUtils.h --- a/Code/ScribeUtils.h +++ b/Code/ScribeUtils.h @@ -1,271 +1,275 @@ #ifndef _SCRIBE_UTILS_H_ #define _SCRIBE_UTILS_H_ #include "lgi/common/DateTime.h" #include "lgi/common/Variant.h" #include "lgi/common/DocView.h" #include "lgi/common/ProgressDlg.h" #include "lgi/common/Store3.h" #include "lgi/common/OAuth2.h" #include "ScribeInc.h" ScribeFunc const char *MimeToUti(const char *Mime); ScribeFunc char *ScribeTempPath(); ScribeFunc char *ScribeInsertFields(const char *Template, LDom *Source); ScribeFunc char *MakeFileName(const char *ContentUtf, const char *Ext); ScribeClass LColour SocketMsgTypeToColour(LSocketI::SocketMsgType flags); ScribeFunc class ContactGroup *LookupContactGroup(class ScribeWnd *App, const char *Name); ScribeExtern LString AskOverwriteMsg(const char *FileName); ScribeFunc void PatternBox(LSurface *pDC, const LRect &r); ScribeExtern LOAuth2::Params GetOAuth2Params(const char *Host, Store3ItemTypes Context); ScribeFunc const char *ScribeResourcePath(); ScribeExtern LString::Array ScribeThemePaths(); ScribeExtern LString DetectCharset(LString s); +ScribeExtern void WaitForVariant(LVariant &var); +ScribeExtern void WaitForString(LString &var); extern LAutoString ConvertThreadIndex(char *ThreadIndex, int TruncateChars = 0); /// Parses HTML into a tree and then evaluates 'SearchExp' on each node. /// On the list of matching nodes it evaluates 'ResultExp' using the scripting /// engine. The resulting variables are stored in 'ReturnValue' as a list. extern bool SearchHtml ( /// List of values created from evaluating 'ResultExp' on the matching elements. LVariant *ReturnValue, /// The input HTML to parse. const char *Html, /// A LGI scripting expression. The element being inspected has the following /// variables: /// 'element' - the name of the element. e.g. 'div', 'body' etc. /// 'content' - any textual content after the element in the document. /// 'attr[name]' - the value of an attribute called 'name'. /// If the expression evaluates to non-zero the element is stored in a result /// array. const char *SearchExp, /// The result array is then evaluated into values that can be passed back to /// scripts via this expression. It uses the same available fields as the /// 'SearchExp'. const char *ResultExp ); -extern int LHtmlMsg +extern void LHtmlMsg ( + /// The callback to receive the status + std::function Callback, /// The parent view or NULL if none available LViewI *Parent, /// The message's text. This is a printf format string that you can pass arguments to const char *Html, /// The title of the message box window const char *Title = 0, /// The type of buttons below the message. Can be one of: /// #MB_OK, #MB_OKCANCEL, #MB_YESNO or #MB_YESNOCANCEL. int Type = MB_OK, ... ); extern void ClearTempPath(); extern char *RemoveAmp(const char *s); extern LString AddAmp(const char *menu, int shortcut); class HttpImageThread : public LThreadWorker, public LCancel { class ScribeWnd *App; LString Proxy, Cache; LHashTbl,LString> UriMap; LAutoPtr z; public: HttpImageThread(ScribeWnd *app, const char *proxy, LThreadTarget *First); ~HttpImageThread(); void DoJob(LThreadJob *j); }; class Store3Progress : public LProgressDlg, public LDataPropI { bool Interact; int NewFormat; LString Err, Cache; public: Store3Progress(LView *parent, bool interact); const char *GetStr(int id); Store3Status SetStr(int id, const char *str); int64 GetInt(int id); Store3Status SetInt(int id, int64 i); }; ScribeFunc void TraceTime(char *s); class CountItem { public: int Type; int64 Count; CountItem() { Type = 0; Count = 0; } }; class Counter : public List { CountItem *FindType(int Type); public: Counter() {} ~Counter(); void Inc(int Type); void Dec(int Type); void Add(int Type, int64 n); void Sub(int Type, int64 n); int64 GetTypeCount(int Type); }; class Mailto { public: ScribeWnd *App; List To; char *Subject; char *Body; Mailto(ScribeWnd *app, const char *s); ~Mailto(); void Apply(class Mail *m); }; class LStringStream : public LStringPipe { LStream *s; ssize_t Read(void *Ptr, ssize_t Size, int Flags = 0) { return s->Read(Ptr, Size, Flags); } ssize_t Write(const void *Ptr, ssize_t Size, int Flags = 0) { return s->Write(Ptr, Size, Flags); } public: LStringStream(LStream *str) { s = str; s->SetPos(0); } bool IsOpen() { return s->IsOpen(); } int Close() { return s->Close(); } bool IsEmpty() { return s->GetSize() == 0; } void Empty() { s->SetSize(0); } int64 GetSize() { return s->GetSize(); } void *New(int AddBytes = 0) { char *Buf = 0; int Len = (int)s->GetSize(); if (Len > 0) { Buf = new char[Len + AddBytes]; if (Buf) { s->Read(Buf, Len); memset(Buf+Len, 0, AddBytes); } } else { LAssert(0); } return Buf; } int64 Peek(uchar *Ptr, int Size) { LAssert(0); return 0; } int64 Peek(LStreamI *Ptr, int Size) { LAssert(0); return 0; } }; class TabDialog : public LDialog { int TabCtrlId; int HelpBtnId; void IdealSize(LButton *b); public: TabDialog(int tabCtrlId, int helpBtnId) { TabCtrlId = tabCtrlId; HelpBtnId = helpBtnId; } void OnCreate(); void OnPosChange(); }; class ProtocolSettingStore : public LDom { LOptionsFile *Opts; LString AccountTag; public: ProtocolSettingStore(LOptionsFile *opts, const char *accountTag) { Opts = opts; AccountTag = accountTag; } LOptionsFile *GetOptions() { return Opts; } bool GetVariant(const char *Name, LVariant &Value, const char *Array = NULL) override { char s[256]; sprintf_s(s, sizeof(s), "%s.%s", AccountTag.Get(), Name); LAssert(Array == NULL); // Shouldn't need this return Opts->GetValue(s, Value); } bool SetVariant(const char *Name, LVariant &Value, const char *Array = NULL) override { char s[256]; sprintf_s(s, sizeof(s), "%s.%s", AccountTag.Get(), Name); LAssert(Array == NULL); // Shouldn't need this return Opts->SetValue(s, Value); } }; class ScriptDownloadContentThread : public LThread, public LCancel { ScribeWnd *App; LString Uri; LString CallbackName; LStringPipe Out; LString Err; LVariant UserData; bool Result = false; public: ScriptDownloadContentThread(ScribeWnd *App, LString Uri, LString CallbackName, LVariant *userData = NULL); int Main(); void OnComplete(); }; #endif diff --git a/Code/Scripting.cpp b/Code/Scripting.cpp --- a/Code/Scripting.cpp +++ b/Code/Scripting.cpp @@ -1,1102 +1,1130 @@ #include "Scribe.h" #include "ScribePrivate.h" #include "lgi/common/Scripting.h" #include "lgi/common/TextView3.h" -#include "resdefs.h" #include "lgi/common/ThreadEvent.h" #include "lgi/common/LgiRes.h" +#include "resdefs.h" +#include "../src/common/Coding/ScriptingPriv.h" + extern GHostFunc Methods[]; #ifdef _MSC_VER #define __func__ __FUNCTION__ #endif #define ARG_CHECK(op, num) \ if (Args.Length() op num) \ { \ Args.Throw(_FL, "%s: Wrong number of args: %i (expecting %i)\n", __func__, Args.Length(), num); \ *Args.GetReturn() = false; \ return true; \ } LScribeScript *LScribeScript::Inst = 0; -LView *CastGView(LVariant *v) +LView *CastLView(LVariant *v) { if (v) { if (v->Type == GV_DOM) return dynamic_cast(v->Value.Dom); else if (v->Type == GV_GVIEW) return v->Value.View; } return 0; } ScribeFolder *CastFolder(LVariant *v) { if (v && v->Type == GV_DOM) return dynamic_cast(v->Value.Dom); return NULL; } void LScribeScriptPriv_ConsoleClosingCallback(LScriptConsole *Console, void *UserData); struct LScribeScriptPriv : public LStream, public LThread { ScribeWnd *App; bool Loop; LThreadEvent Event; // Incoming buffer LMutex InputLock; // Covers Used and Buffer ssize_t Used; LArray Buffer; // Outputs LMutex OutputLock; // Covers LogFile, LogMem and Console LAutoString LogFile; LArray LogMem; ssize_t LogMemUsed; LScriptConsole *Console; LScribeScriptPriv(ScribeWnd *app) : LThread("LScribeScriptPriv.Thread"), App(app), InputLock("LScribeScriptPriv.Input"), OutputLock("LScribeScriptPriv.Output") { Loop = true; Console = NULL; LogMem.Length(64 << 10); LogMemUsed = 0; // Get the log path char p[MAX_PATH_LEN]; if (!LgiTraceGetFilePath(p, sizeof(p))) { LAssert(0); } else if (!LogFile.Reset(NewStr(p))) { LAssert(0); } // Setup our buffer first Used = 0; Buffer.Length(64 << 10); // Now register to get trace messages... LgiTraceSetStream(this); // Start thread Run(); } ~LScribeScriptPriv() { Loop = false; LgiTraceSetStream(NULL); DeleteObj(Console); Event.Signal(); while (!IsExited()) LSleep(true); } /// This method can't block at all, and must accept data from any /// thread. ssize_t Write(const void *Data, ssize_t Size, int Flags = 0) { ssize_t Status = 0; LAssert(Size < (16 << 10)); if (Data && InputLock.LockWithTimeout(500, _FL)) { // Put all data into a buffer as quickly as possible ssize_t Remaining = (ssize_t)Buffer.Length() - Used; ssize_t Common = MIN(Remaining, Size); char *Ptr = &Buffer[Used]; if (Ptr) { memcpy(Ptr, Data, Common); Status = Common; Used += Common; } InputLock.Unlock(); if (Status > 0) Event.Signal(); } return Status; } void ShowScriptingWindow(bool Show) { LMutex::Auto Lock(&OutputLock, _FL); if (Show) { if (!Console) { Console = new LScriptConsole(App, LScribeScriptPriv_ConsoleClosingCallback, this); if (Console) Console->Write(&LogMem[0], LogMemUsed); } } else { DeleteObj(Console); } } int Main() { while (Loop) { Event.Wait(); if (!Loop) // Shutting down? break; // Check for input data LArray Input; if (InputLock.LockWithTimeout(500, _FL)) { if (Used > 0) { if (Input.Length(Used)) memcpy(&Input[0], &Buffer[0], Used); Used = 0; } InputLock.Unlock(); } if (Input.Length()) { // We have some input... send to outputs if (OutputLock.LockWithTimeout(500, _FL)) { // Write to our log file... LFile f; if (LogFile && f.Open(LogFile, O_WRITE)) { f.SetPos(f.GetSize()); f.Write(&Input[0], Input.Length()); f.Close(); } if (LogMemUsed + Input.Length() > LogMem.Length()) { // Move data down to make space... by deleting a 1/4 of the data // and shifting down the rest. auto i = LogMemUsed >> 2; // Seek to the end of a line for (; i < LogMemUsed; i++) { if (LogMem[i] == '\n') { i++; break; } } // Shift the memory down, and update the used variable memmove(&LogMem[0], &LogMem[i], LogMemUsed-i); LogMemUsed -= i; // Check there is enough space now...? if (LogMemUsed + Input.Length() > LogMem.Length()) // Nope... make it bigger then LogMem.Length(LogMemUsed + Input.Length()); } if (LogMemUsed + Input.Length() < LogMem.Length()) { memcpy(&LogMem[LogMemUsed], &Input[0], Input.Length()); LogMemUsed += Input.Length(); } if (Console) { Console->Write((const char*)&Input[0], Input.Length()); } OutputLock.Unlock(); // Tell the UI about the new messages... #if LGI_VIEW_HANDLE if (App->Handle()) #endif App->PostEvent(M_NEW_CONSOLE_MSG); } } } return 0; } }; void LScribeScriptPriv_ConsoleClosingCallback(LScriptConsole *Console, void *UserData) { ((LScribeScriptPriv*)UserData)->Console = NULL; } LScribeScript::LScribeScript(ScribeWnd *app) { d = new LScribeScriptPriv(app); App = app; Eng = 0; } LScribeScript::~LScribeScript() { DeleteObj(d); } void LScribeScript::ShowScriptingWindow(bool show) { d->ShowScriptingWindow(show); } LStream *LScribeScript::GetLog() { return d; } LAutoString LScribeScript::GetDataFolder() { return App->GetDataFolder(); } char *LScribeScript::GetIncludeFile(char *FileName) { LString Path; const char *Search[] = { "./Scripts", "../Scripts", "../../Scripts", NULL }; LFile::Path p(ScribeResourcePath()); p += FileName; for (int i=0; Search[i] && !p.Exists(); i++) { p = ScribeResourcePath(); p += Search[i]; p += FileName; } if (p.Exists()) Path = p.GetFull(); else Path = LFindFile(FileName); if (Path) return LReadTextFile(Path); else LgiTrace("%s:%i - GetIncludeFile(%s) failed.\n", _FL, FileName); return NULL; } GHostFunc *LScribeScript::GetCommands() { return Methods; } void LScribeScript::SetEngine(LScriptEngine *eng) { } bool LScribeScript::GetFolder(LScriptArguments &Args) { ARG_CHECK(!=, 1); Args.GetReturn()->Empty(); if (Args[0]->Type == GV_INT32) { ScribeFolder *f = App->GetFolder(Args[0]->Value.Int); if (f) { *Args.GetReturn() = (LDom*)f; return true; } } char *f = Args[0]->Str(); if (f) { *Args.GetReturn() = (LDom*)App->GetFolder(f); } return true; } bool LScribeScript::GetSourceFolders(LScriptArguments &Args) { ARG_CHECK(!=, 1); LArray Folders; auto FolderType = Args[0]->CastInt32(); Store3ItemTypes ItemType = MAGIC_NONE; switch (FolderType) { case FOLDER_INBOX: case FOLDER_OUTBOX: case FOLDER_SENT: case FOLDER_TRASH: case FOLDER_TEMPLATES: case FOLDER_SPAM: Folders.Add(App->GetFolder(FolderType)); break; case FOLDER_CONTACTS: ItemType = MAGIC_CONTACT; break; case FOLDER_FILTERS: ItemType = MAGIC_FILTER; break; case FOLDER_CALENDAR: ItemType = MAGIC_CALENDAR; break; case FOLDER_GROUPS: ItemType = MAGIC_GROUP; break; } if (ItemType != MAGIC_NONE) Folders = App->GetThingSources(ItemType); auto r = Args.GetReturn(); if (!r->SetList()) return false; auto Lst = r->Value.Lst; for (auto f: Folders) Lst->Add(new LVariant((LDom*)f)); return true; } bool LScribeScript::BrowseFolder(LScriptArguments &Args) { - ARG_CHECK(!=, 3); + ARG_CHECK(<, 4); + + LView *Parent = CastLView(Args[0]); + LString MessageTxt = Args[1]->Str(); + LString DefaultFolderName = Args[2]->Str(); + LString CallbackName = Args[3]->Str(); + + if (!CallbackName) + { + *Args.GetReturn() = false; + return true; + } + + *Args.GetReturn() = true; - LView *Parent = CastGView(Args[0]); - FolderDlg d(Parent, - App, - MAGIC_ANY, // limit to - 0, // root - 0, // init sel - true, - Args[2]->Str(), - Args[1]->Str()); - if (d.DoModal()) + auto d = new FolderDlg( Parent, + App, + MAGIC_ANY, // limit to + 0, // root + 0, // init sel + true, + DefaultFolderName, + MessageTxt); + d->DoModal([this, d, CallbackName](auto dlg, auto id) { - *Args.GetReturn() = d.Get(); - } + if (id) + { + auto FolderPath = d->Get(); + auto cb = App->GetCallback(CallbackName); + if (cb.Func) + { + LVirtualMachine Vm; + LScriptArguments Args(&Vm); + LVariant vFolderPath = FolderPath; + Args.Add(&vFolderPath); + App->ExecuteScriptCallback(cb, Args); + } + else LgiTrace("%s:%i - No callback called '%s'\n", _FL, CallbackName.Get()); + } + delete dlg; + }); return true; } bool LScribeScript::CreateSubFolder(LScriptArguments &Args) { ARG_CHECK(!=, 3); *Args.GetReturn() = false; ScribeFolder *Parent = CastFolder(Args[0]); if (!Parent) return true; char *ChildName = Args[1]->Str(); if (!ChildName) return true; for (ScribeFolder *c = Parent->GetChildFolder(); c; c = c->GetNextFolder()) { auto n = c->GetName(true); if (n.Equals(ChildName)) { *Args.GetReturn() = c; return true; } } char *TypeStr = Args[2]->Str(); if (!TypeStr) return true; int Type = MAGIC_NONE; if (_stricmp(TypeStr, "Mail") == 0) Type = MAGIC_MAIL; else if (_stricmp(TypeStr, "Contact") == 0) Type = MAGIC_CONTACT; else if (_stricmp(TypeStr, "Filter") == 0) Type = MAGIC_FILTER; else if (_stricmp(TypeStr, "Calendar") == 0) Type = MAGIC_CALENDAR; else if (_stricmp(TypeStr, "Group") == 0) Type = MAGIC_GROUP; if (Type >= MAGIC_BASE) { ScribeFolder *Child = Parent->CreateSubDirectory(ChildName, Type); if (Child) { *Args.GetReturn() = Child; return true; } } return true; } bool LScribeScript::SaveThing(LScriptArguments &Args) { ARG_CHECK(!=, 2); ScribeFolder *Folder = dynamic_cast(Args[0]->CastDom()); Thing *T = dynamic_cast(Args[1]->CastDom()); *Args.GetReturn() = Folder && T ? T->Save(Folder) : false; return true; } bool LScribeScript::CreateThing(LScriptArguments &Args) { ARG_CHECK(<, 1); Thing *t; uint32_t Type = Args[0]->CastInt32(); ScribeFolder *Folder = Args.Length() > 1 ? dynamic_cast(Args[1]->CastDom()) : NULL; t = App->CreateItem(Type, Folder, false); *Args.GetReturn() = dynamic_cast(t); return true; } bool LScribeScript::MoveThing(LScriptArguments &Args) { ScribeFolder *To = NULL; Thing *t = NULL; LArray Items; ARG_CHECK(!=, 2); // Resolve the thing... t = dynamic_cast(Args[1]->CastDom()); if (!t) { LgiTrace("%s:%i - MoveThing error: Arg 1 not a DOM object.\n", _FL); goto MoveThingErr; } // Resolve the folder... if (Args[0]->Type == GV_DOM) { To = dynamic_cast(Args[0]->CastDom()); if (!To) LgiTrace("%s:%i - MoveThing error: Arg 0 (object ptr) not a Folder.\n", _FL); } else if (Args[0]->Type == GV_STRING) { char *Path = Args[0]->CastString(); To = t->App->GetFolder(Path); if (!To) LgiTrace("%s:%i - MoveThing error: Arg 0 ('%s') not a valid path to a folder.\n", _FL, Path); } if (!To) goto MoveThingErr; // Move the thing to the folder... Items.Add(t); *Args.GetReturn() = To->MoveTo(Items); return true; MoveThingErr: *Args.GetReturn() = false; return true; } bool LScribeScript::LoadFolder(LScriptArguments &Args) { ARG_CHECK(!=, 1); *Args.GetReturn() = false; if (Args[0]->Type == GV_DOM) { ScribeFolder *Folder = dynamic_cast(Args[0]->Value.Dom); if (Folder) *Args.GetReturn() = Folder->LoadThings(); } return true; } bool LScribeScript::LookupContact(LScriptArguments &Args) { ARG_CHECK(!=, 1); auto Email = Args[0]->CastString(); *Args.GetReturn() = (LDom*) Contact::LookupEmail(Email); return true; } bool LScribeScript::MsgBox(LScriptArguments &Args) { - LViewI *Parent = CastGView(Args[0]); + LViewI *Parent = CastLView(Args[0]); LgiMsg(Parent, "The 'MsgBox' function is deprecated. Please use 'MessageDlg' (it has the same arguments).", AppName); *Args.GetReturn() = 0; return true; } bool LScribeScript::GetSystemPath(LScriptArguments &Args) { ARG_CHECK(!=, 1); Args.GetReturn()->Empty(); LSystemPath sp = LSP_TEMP; const char *Name; if (Args[0]->IsInt()) sp = (LSystemPath) Args[0]->CastInt32(); else if ((Name = Args[0]->CastString())) { #undef _ #define _(path) if (!_stricmp(Name, #path)) sp = path; _(LSP_ROOT) _(LSP_OS) _(LSP_OS_LIB) _(LSP_TEMP) _(LSP_COMMON_APP_DATA) _(LSP_USER_APP_DATA) _(LSP_LOCAL_APP_DATA) _(LSP_DESKTOP) _(LSP_HOME) _(LSP_USER_APPS) _(LSP_EXE) _(LSP_TRASH) _(LSP_APP_INSTALL) _(LSP_APP_ROOT) _(LSP_USER_DOCUMENTS) _(LSP_USER_MUSIC) _(LSP_USER_VIDEO) _(LSP_USER_DOWNLOADS) _(LSP_USER_LINKS) } else return true; LFile::Path p(sp); *Args.GetReturn() = p.GetFull(); return true; } bool LScribeScript::GetScribeTempPath(LScriptArguments &Args) { *Args.GetReturn() = ScribeTempPath(); return true; } bool LScribeScript::JoinPath(LScriptArguments &Args) { char p[MAX_PATH_LEN] = ""; if (Args.Length() > 0) { bool First = true; for (unsigned i=0; iCastString(); if (s) { if (First) strcpy_s(p, sizeof(p), s); else LMakePath(p, sizeof(p), p, s); First = false; } } } *Args.GetReturn() = p; return true; } bool LScribeScript::DeleteThing(LScriptArguments &Args) { *Args.GetReturn() = false; LArray a; int Ok = 0, Error = 0; LDom *dom; bool ToTrash = false; for (auto Arg: Args) { if (Arg->Type == GV_DOM) { if (!(dom = Arg->CastDom())) continue; Attachment *attachment = dynamic_cast(dom); if (attachment) { Mail *m = attachment->GetOwner(); if (m) { if (m->DeleteAttachment(attachment)) Ok++; else Error++; } else LAssert(!"No owner?"); } else { auto t = dynamic_cast(dom); if (t) a.Add(t); } } else if (Arg->Type == GV_LIST) { auto l = Arg->Value.Lst; if (!l) continue; for (auto i: *l) { if (!(dom = i->CastDom())) continue; auto t = dynamic_cast(dom); if (t) a.Add(t); } } else if (Arg->Type == GV_STRING) { if (Stristr(Arg->Str(), "trash")) ToTrash = true; } } while (a.Length() > 0) { auto t = a[0]; auto Store = t->GetObject()->GetStore(); LArray Del; for (auto it = a.begin(); it != a.end(); ) { t = *it; if (t->GetObject()->GetStore() == Store) { Del.Add(t->GetObject()); a.Delete(it); } else it++; } if (Store->Delete(Del, ToTrash) > Store3Error) Ok++; else Error++; } *Args.GetReturn() = Error == 0; return true; } bool LScribeScript::ShowThingWindow(LScriptArguments &Args) { ARG_CHECK(!=, 1); *Args.GetReturn() = false; if (Args[0]->Type == GV_DOM) { Thing *t = dynamic_cast(Args[0]->CastDom()); if (t) *Args.GetReturn() = t->DoUI(); } return true; } bool LScribeScript::AddToolsMenuItem(LScriptArguments &Args) { ARG_CHECK(!=, 2); *Args.GetReturn() = App->RegisterCallback(LToolsMenu, Args); return true; } bool LScribeScript::AddCallback(LScriptArguments &Args) { ARG_CHECK(<, 2); auto Str = Args[0]->CastString(); ScribeDomType Type = StrToDom(Str); switch (Type) { #define HandleCallbackType(DomField, CbType) \ case DomField: \ *Args.GetReturn() = App->RegisterCallback(CbType, Args); \ break HandleCallbackType(SdOnBeforeMailSend, LMailOnBeforeSend); HandleCallbackType(SdOnAfterMailReceive, LMailOnAfterReceive); HandleCallbackType(SdOnThingContextMenu, LThingContextMenu); HandleCallbackType(SdOnFolderContextMenu, LFolderContextMenu); HandleCallbackType(SdOnThingToolbar, LThingUiToolbar); HandleCallbackType(SdOnApplicationToolbar, LApplicationToolbar); HandleCallbackType(SdOnBeforeInstallBar,LBeforeInstallBar); HandleCallbackType(SdOnInstallComponent, LInstallComponent); HandleCallbackType(SdOnTimer, LOnTimer); HandleCallbackType(SdOnRenderMail, LRenderMail); HandleCallbackType(SdOnLoad, LOnLoad); default: Args.Throw(_FL, "No callback named '%s'", Str); *Args.GetReturn() = false; break; } return true; } // MenuAddItem(SubMenu, IconIndex/File, LabelText, Position, CallbackMethod, CallbackId) bool LScribeScript::MenuAddItem(LScriptArguments &Args) { ARG_CHECK(!=, 6); *Args.GetReturn() = false; LDom *Dom = Args[0]->CastDom(); LScriptUi *Menu = dynamic_cast(Dom); char *IconFile = 0; int IconIdx = -1; if (Args[1]->Type == GV_INT32) IconIdx = Args[1]->CastInt32(); else if (Args[1]->Type == GV_STRING) IconFile = Args[1]->Str(); char *LabelTxt = Args[2]->Str(); int Position = Args[3]->CastInt32(); char *CallbackMethodName = Args[4]->Str(); int CallbackId = Args[5]->CastInt32(); if (!Menu || !Menu->Sub) return Args.Throw(NULL, -1, "No menu to append to."); if (CallbackId < 0) { Menu->Sub->AppendSeparator(Position); *Args.GetReturn() = true; return true; } if (!LabelTxt) return Args.Throw(NULL, -1, "No label text."); if (!CallbackMethodName) return Args.Throw(NULL, -1, "No label callback method name."); LScriptCallback Cb = App->GetCallback(CallbackMethodName); if (!Cb.Func) { Args.Throw(NULL, -1, "Callback not defined."); return true; } auto Top = Menu->Sub; while (Top->GetParent() && Top->GetParent()->GetParent()) Top = Top->GetParent()->GetParent(); auto Existing = Top->FindItem(CallbackId); if (Existing) return Args.Throw(NULL, -1, "Item ID '%i' already exists.", CallbackId); Cb.Param = CallbackId; Menu->Sub->AppendItem(LabelTxt, CallbackId, true, Position); // The callbacks are always in a flat list in the top most LScriptCallback, // where the caller can easily see them. LScriptUi *Root = Menu; while (Root->Parent) Root = Root->Parent; Root->Callbacks.New() = Cb; *Args.GetReturn() = true; return true; } bool LScribeScript::MenuAddSubmenu(LScriptArguments &Args) { ARG_CHECK(!=, 3); Args.GetReturn()->Empty(); LScriptUi *Menu = dynamic_cast(Args[0]->CastDom()); char *SubmenuTxt = Args[1]->Str(); int Position = Args[2]->CastInt32(); if (Menu && Menu->Sub && SubmenuTxt) { LScriptUi *s = new LScriptUi(Menu->Sub->AppendSub(SubmenuTxt, Position)); if (s) { s->Parent = Menu; Menu->Subs.Add(s); *Args.GetReturn() = s; } } return true; } bool LScribeScript::ToolbarAddItem(LScriptArguments &Args) { ARG_CHECK(<, 6); LScriptUi *Tb = dynamic_cast(Args[0]->CastDom()); int Icon = Args[1]->CastInt32(); char *Label = Args[2]->Str(); // int Pos = Args[3]->CastInt32(); char *CallbackMethodName = Args[4]->Str(); int CallbackId = Args[5]->CastInt32(); if (Tb && Tb->Toolbar && CallbackMethodName) { if (CallbackId < 0) { Tb->Toolbar->AppendSeparator(); *Args.GetReturn() = true; } else { LScriptCallback Cb = App->GetCallback(CallbackMethodName); if (Cb.Func) { Cb.Param = CallbackId; Tb->Toolbar->AppendButton(Label, CallbackId, TBT_PUSH, true, Icon); LScriptUi *Root = Tb; while (Root->Parent) Root = Root->Parent; Root->Callbacks.New() = Cb; *Args.GetReturn() = true; } else *Args.GetReturn() = false; } } else *Args.GetReturn() = false; return true; } bool LScribeScript::FilterDoActions(LScriptArguments &Args) { ARG_CHECK(!=, 2); Filter *f = dynamic_cast(Args[0]->CastDom()); Mail *m = dynamic_cast(Args[1]->CastDom()); if (f && m) { bool Stop = false; f->DoActions(m, Stop); *Args.GetReturn() = true; } else *Args.GetReturn() = false; return true; } #define DefFn(Name) \ GHostFunc(#Name, 0, (ScriptCmd)&LScribeScript::Name) GHostFunc Methods[] = { DefFn(MsgBox), DefFn(GetSystemPath), DefFn(GetScribeTempPath), DefFn(JoinPath), DefFn(GetFolder), DefFn(GetSourceFolders), DefFn(LoadFolder), DefFn(CreateSubFolder), DefFn(BrowseFolder), DefFn(CreateThing), DefFn(MoveThing), DefFn(SaveThing), DefFn(DeleteThing), DefFn(ShowThingWindow), DefFn(FilterDoActions), DefFn(LookupContact), DefFn(AddToolsMenuItem), DefFn(AddCallback), DefFn(MenuAddItem), DefFn(MenuAddSubmenu), DefFn(ToolbarAddItem), GHostFunc(0, 0, 0), }; LScriptConsole::LScriptConsole(ScribeWnd *app, ConsoleClosingCallback callback, void *callback_data) { Txt = NULL; Callback = callback; CallbackData = callback_data; App = app; LRect r(0, 0, 600, 500); SetPos(r); if (App) MoveSameScreen(App); else MoveToCenter(); Name(LLoadString(IDS_CONSOLE, "Console")); if (Attach(0)) { Children.Insert(Txt = new LTextLog(100)); if (Txt) { Txt->SetPourLargest(true); } AttachChildren(); Visible(true); RegisterHook(this, LKeyEvents); } } LScriptConsole::~LScriptConsole() { printf("%p::~LScriptConsole() %p\n", this, Callback); if (Callback) Callback(this, CallbackData); } bool LScriptConsole::OnViewKey(LView *v, LKey &k) { if (k.CtrlCmd() && ToLower(k.c16) == 'w') { if (k.Down()) Quit(); return true; } return false; } bool LScriptConsole::OnRequestClose(bool OsShuttingDown) { LVariant v; App->GetOptions()->SetValue(OPT_ShowScriptConsole, v = 0); auto Item = App->GetMenu()->FindItem(IDM_SCRIPTING_CONSOLE); if (Item) Item->Checked(false); if (Callback) { Callback(this, CallbackData); Callback = NULL; } return true; } void LScriptConsole::Write(const char *s, int64 Len) { if (Txt) Txt->Write(s, (int)Len); } bool OnFilterScript(Filter *f, Mail *m, const char *Script) { if (!Script || !f || !m || !LScribeScript::Inst) return false; LScriptEngine *e = m->App->GetScriptEngine(); LAutoPtr obj(new LCompiledCode); if (!obj) { return false; } // Setup the various pre-defined variables LVariant v; obj->Set("App", v = dynamic_cast(f->App)); obj->Set("Mail", v = dynamic_cast(m)); obj->Set("Filter", v = dynamic_cast(f)); // Compile... LString ScriptName; ScriptName.Printf("%s.filter", f->GetName()); if (!e->Compile(obj, NULL, Script, ScriptName)) { f->App->OnScriptCompileError(NULL, f); return false; } // And run... LExecutionStatus Result = e->Run(obj, NULL, ScribeTempPath()); return Result != ScriptError; } bool OnToolScript(ScribeWnd *App, const char *File) { bool Status = false; if (App && File && LScribeScript::Inst) { LAutoString Script(LReadTextFile(File)); if (Script) { LScriptEngine *e = App->GetScriptEngine(); if (e) { LAutoPtr Obj(new LCompiledCode); if (e->Compile(Obj, NULL, Script, File)) { LScriptArguments Args(NULL); Args.Add(new LVariant((LDom*)App)); Status = e->CallMethod(Obj, "Main", Args); Args.DeleteObjects(); } else { App->OnScriptCompileError(File, NULL); } } } } return Status; } /////////////////////////////////////////////////////////////////////////// bool LScriptUi::SetupCallbacks(ScribeWnd *App, ThingUi *Parent, Thing *t, LScriptCallbackType Type) { if (!App) return false; LArray Callbacks; if (!App->GetScriptCallbacks(Type, Callbacks)) return false; LScriptArguments Args(NULL); int a = 0; Args[a++] = new LVariant(App); Args[a++] = new LVariant(this); Args[a++] = new LVariant(t); for (unsigned i=0; iExecuteScriptCallback(*Callbacks[i], Args); return true; } bool LScriptUi::ExecuteCallbacks(ScribeWnd *App, ThingUi *Parent, Thing *t, int Cmd) { if (!App) return false; LScriptArguments Args(NULL); Args[0] = new LVariant(App); // App Args[1] = new LVariant(Parent); // Window Args[2] = new LVariant(t); // Thing Args[3] = new LVariant(Cmd); // CallbackId for (auto &c: Callbacks) { if (c.Param == Cmd) App->ExecuteScriptCallback(c, Args); } return false; } diff --git a/Code/Store3Mail3/Mail3Store.cpp b/Code/Store3Mail3/Mail3Store.cpp --- a/Code/Store3Mail3/Mail3Store.cpp +++ b/Code/Store3Mail3/Mail3Store.cpp @@ -1,2178 +1,2181 @@ #include "Mail3.h" #include "resdefs.h" #include "lgi/common/SubProcess.h" #include #include "lgi/common/LgiRes.h" #include "lgi/common/Thread.h" #include "lgi/common/Store3MimeTree.h" #define SanityCheck() if (!IsOk()) return NULL; /////////////////////////////////////////////////////////////////////////////////// LMail3Thing::~LMail3Thing() { if (Parent) Parent->Items.a.Delete(this); } Store3Status LMail3Thing::Delete(bool ToTrash) { LArray Del; Del.Add(this); return Store->Delete(Del, ToTrash); } Store3Status LMail3Thing::Save(LDataI *Folder) { LMail3Folder *Fld = dynamic_cast(Folder); if (Folder && !Fld) { LAssert(!"Not the right folder type"); LgiTrace("%s:%i - Not the right folder type.\n", _FL); return Store3Error; } if (Fld && Fld->Store != Store) { SetStore(Fld->Store); } const char *Table = GetTable(); bool Create = false; bool IsNewMail = Type() == MAGIC_MAIL && TestFlag(GetInt(FIELD_FLAGS), MAIL_NEW); LArray NewItems; if (Id < 0) { if (Fld) { // Make sure the folder is loaded... Fld->Children(); Parent = Fld; ParentId = Fld->Id; LAssert(Parent->Items.IndexOf(this, true) < 0); // LgiTrace("%s:%i - Saving %i to %s (%i items)\n", _FL, (int)Id, Parent->GetStr(FIELD_FOLDER_NAME), Parent->Items.Length()); Parent->Items.Insert(this, -1, true); Create = true; NewItems.Add(this); if (IsNewMail) SetInt(FIELD_FLAGS, GetInt(FIELD_FLAGS) & ~MAIL_NEW); } else { LAssert(!"No parent to save to."); LgiTrace("%s:%i - No parent to save to.\n", _FL); return Store3Error; } } else if (Fld) { // Make sure the folder is loaded... Fld->Children(); ParentId = Fld->Id; if (Parent != Fld) { LDataFolderI *Old = Parent; if (Parent) { // Remove this item from the current parent folder... LAssert(Parent->Items.IndexOf(this) >= 0); Parent->Items.Delete(this); } // Add it to the new one... Parent = Fld; LAssert(Parent->Items.IndexOf(this, true) < 0); // LgiTrace("%s:%i - Saving %i to %s (%i items)\n", _FL, (int)Id, Parent->GetStr(FIELD_FOLDER_NAME), Parent->Items.Length()); Parent->Items.Insert(this, -1, true); LArray a; a.Add(this); if (Old) Store->OnMove(_FL, Parent, Old, a); else NewItems.Add(this); if (IsNewMail) SetInt(FIELD_FLAGS, GetInt(FIELD_FLAGS) & ~MAIL_NEW); } } bool Status = Write(Table, Create); if (Status) { OnSave(); if (NewItems.Length()) Store->OnNew(_FL, Parent, NewItems, -1, IsNewMail); } else { LgiTrace("%s:%i - Write failed.\n", _FL); } return Status ? Store3Success : Store3Error; } /////////////////////////////////////////////////////////////////////////////////// GMail3Idx Mail3Indexes[] = { {"MailFolderIdx", MAIL3_TBL_MAIL, "ParentId"}, {"MailSegMailIdx", MAIL3_TBL_MAILSEGS, "MailId"}, {"MailSegParentIdx", MAIL3_TBL_MAILSEGS, "ParentId"}, }; LMail3Store::LMail3Store(const char *Mail3Folder, LDataEventsI *callback, bool Create) : TableStatus(0, Store3Error) { Format = Mail3v1; Folder = Mail3Folder; Callback = callback; Db = 0; Transaction = 0; Root = 0; OpenStatus = Store3Success; Fields.Add(MAIL3_TBL_FOLDER, TblFolder); Fields.Add(MAIL3_TBL_FOLDER_FLDS, TblFolderFlds); Fields.Add(MAIL3_TBL_MAIL, TblMail); Fields.Add(MAIL3_TBL_MAILSEGS, TblMailSegs); Fields.Add(MAIL3_TBL_CONTACT, TblContact); Fields.Add(MAIL3_TBL_GROUP, TblGroup); Fields.Add(MAIL3_TBL_FILTER, TblFilter); Fields.Add(MAIL3_TBL_CALENDAR, TblCalendar); bool Exist = LDirExists(Mail3Folder); if (!Create && !Exist) { ErrorMsg.Printf(LLoadString(IDS_ERROR_FILE_DOESNT_EXIST), Mail3Folder); } else if (!Exist && !FileDev->CreateFolder(Mail3Folder)) { ErrorMsg.Printf(LLoadString(IDS_ERROR_CANT_CREATE_FOLDER), Mail3Folder); } else { OpenDb(); } #if _DEBUG LMail3Mail m(this); Store3MimeTree Tree(&m, m.Seg); if (!Tree.UnitTests(this, &m)) { LgiTrace("%s:%i - Error: Store3MimeTree unit tests failed\n", _FL); LAssert(!"Store3MimeTree unit tests failed.\n"); } #endif } LMail3Store::~LMail3Store() { CloseDb(); DeleteObj(Root); } LMail3Folder *LMail3Store::GetSystemFolder(int Type) { if (!Callback || !Root) return NULL; LVariant Path; if (Callback->GetSystemPath(FOLDER_INBOX, Path)) { auto p = LString(Path.Str()).Split("/"); for (auto f: Root->Sub.a) { auto Nm = f->GetStr(FIELD_FOLDER_NAME); if (p.Last().Equals(Nm)) { return f; } } } return NULL; } LDataPropI *LMail3Store::GetObj(int id) { switch (id) { case FIELD_INBOX: return GetSystemFolder(FOLDER_INBOX); case FIELD_OUTBOX: return GetSystemFolder(FOLDER_OUTBOX); case FIELD_SENT: return GetSystemFolder(FOLDER_SENT); case FIELD_TRASH: return GetSystemFolder(FOLDER_TRASH); } return NULL; } bool LMail3Store::OpenDb() { char f[MAX_PATH_LEN]; StatusMsg.Empty(); LMakePath(f, sizeof(f), Folder, MAIL3_DB_FILE); DbFile = f; if (!Check(sqlite3_open(f, &Db), 0)) { ErrorMsg.Printf(LLoadString(IDS_ERROR_CANT_CREATE_DB), f); return false; } else { // const char *Tbl; // for (GMail3Def *Flds=Fields.First(&Tbl); Flds; Flds=Fields.Next(&Tbl)) for (auto it : Fields) { Store3Status s = CheckTable(it.key, it.value); if (s == Store3Missing && UpdateTable(it.key, it.value, s)) { s = Store3Success; } TableStatus.Add(it.key, s); if (s == Store3Error || (s == Store3UpgradeRequired && OpenStatus != Store3Error)) OpenStatus = s; } LArray DelNames; { LStatement Tables( this, "SELECT name FROM sqlite_master " "WHERE type='table' " "ORDER BY name;"); while (Tables.Row()) { char *Name = Tables.GetStr(0); if (Name && strchr(Name, '_') && !stristr(Name, "sqlite")) DelNames.New().Reset(NewStr(Name)); } } for (unsigned i=0; i &new_items, int pos, bool is_new) { if (!Callback) return; Callback->SetContext(File, Line); Callback->OnNew(parent, new_items, pos, is_new); } bool LMail3Store::OnChange(const char *File, int Line, LArray &items, int FieldHint) { if (!Callback) return false; Callback->SetContext(File, Line); return Callback->OnChange(items, FieldHint); } bool LMail3Store::OnMove(const char *File, int Line, LDataFolderI *new_parent, LDataFolderI *old_parent, LArray &items) { if (!Callback) return false; Callback->SetContext(File, Line); return Callback->OnMove(new_parent, old_parent, items); } bool LMail3Store::OnDelete(const char *File, int Line, LDataFolderI *parent, LArray &items) { if (!Callback) return false; Callback->SetContext(File, Line); return Callback->OnDelete(parent, items); } const char *LMail3Store::GetStr(int id) { switch (id) { case FIELD_ERROR: return ErrorMsg; case FIELD_STATUS: return StatusMsg; case FIELD_TEMP_PATH: return TempPath; } return NULL; } Store3Status LMail3Store::SetStr(int id, const char *s) { switch (id) { case FIELD_ERROR: ErrorMsg = s; break; case FIELD_STATUS: StatusMsg = s; break; case FIELD_TEMP_PATH: TempPath = s; break; default: LAssert(0); return Store3Error; } return Store3Success; } int64 LMail3Store::GetInt(int id) { switch (id) { case FIELD_STATUS: { if (OpenStatus != Store3Success) return OpenStatus; return IsOk() ? Store3Success : Store3Error; } case FIELD_READONLY: return false; case FIELD_VERSION: return 3; case FIELD_FORMAT: return Format; case FIELD_STORE_TYPE: return Store3Sqlite; } return -1; } Store3Status LMail3Store::SetInt(int id, int64 i) { switch (id) { case FIELD_FORMAT: break; } return Store3Error; } bool LMail3Store::Check(int Code, const char *Sql) { if (Code == SQLITE_OK || Code == SQLITE_DONE) return true; const char *Err = sqlite3_errmsg(Db); LgiTrace("%s:%i - Sqlite error %i: %s\n%s", _FL, Code, Err, Sql?Sql:(char*)"", Sql?"\n":""); LAssert(!"Db Error"); #if MAIL3_TRACK_OBJS for (int i=0; iItems.State = Store3Loaded; Root->Serialize(s, false); } } else { // Create new root folder... if ((Root = new LMail3Folder(this))) { Root->Items.State = Store3Loaded; Root->Write(MAIL3_TBL_FOLDER, true); } } } } return Root; } #define PROFILE_MOVE 0 #if PROFILE_MOVE #define PROF_MOVE(...) prof.Add(__VA_ARGS__) #else #define PROF_MOVE(...) #endif Store3Status LMail3Store::Move(LDataFolderI *NewFolder, LArray &Items) { Store3Status Status = Store3Error; LMail3Folder *To = dynamic_cast(NewFolder); if (!To) return Status; if (Items.Length() == 0) return Store3Success; #if PROFILE_MOVE LProfile prof("LMail3Store::Move"); #endif LDataFolderI *OldParent = NULL; LArray Moved; StoreTrans Tr = StartTransaction(); for (unsigned n=0; n(Items[n]); if (Fld) { PROF_MOVE("0"); if (Fld->ParentId == To->Id) Status = Store3Success; else { char s[256]; sprintf_s(s, sizeof(s), "update " MAIL3_TBL_FOLDER " set ParentId=" LPrintfInt64 " where Id=" LPrintfInt64, To->Id, Fld->Id); PROF_MOVE("1"); LStatement Stmt(this, s); if (Stmt.Exec()) { PROF_MOVE("2"); Status = Store3Success; LAssert(Fld->Parent->Sub.IndexOf(Fld) >= 0); Fld->Parent->Sub.Delete(Fld); LDataFolderI *From = Fld->Parent; Fld->Parent = To; Fld->ParentId = Fld->Parent->Id; To->Sub.Insert(Fld); if (!OldParent) OldParent = From; if (Callback && OldParent && Moved.Length() && OldParent != From) { PROF_MOVE("3"); Callback->OnMove(To, OldParent, Moved); PROF_MOVE("4"); Moved.Length(0); OldParent = From; } Moved.Add(Fld); } } } else if ((Thing = dynamic_cast(Items[n]))) { PROF_MOVE("5"); if (Thing->ParentId == To->Id) Status = Store3Success; else if (To->ItemType != MAGIC_ANY && Thing->Type() != To->ItemType) { LgiTrace("%s:%i - Can't move item (type=%x) to folder containing type %x.\n", _FL, Thing->Type(), To->ItemType); Status = Store3Error; } else { // This needs to be before the SQL update so that duplicate Mail // objects don't get created when moving to an unloaded folder. if (To->Items.GetState() != Store3Loaded) To->Children(); PROF_MOVE("6"); char s[256]; sprintf_s(s, sizeof(s), "update %s set ParentId=" LPrintfInt64 " where Id=" LPrintfInt64, Thing->GetTable(), To->Id, Thing->Id); LStatement Stmt(this, s); if (Stmt.Exec()) { PROF_MOVE("6"); Status = Store3Success; LDataFolderI *From = Thing->Parent; if (Thing->Parent) { LAssert(Thing->Parent->Items.IndexOf(Thing) >= 0); Thing->Parent->Items.Delete(Thing); } Thing->Parent = To; Thing->ParentId = To->Id; LAssert(To->Items.IndexOf(Thing) < 0); #ifdef _DEBUG if (To->System != Store3SystemTrash) { // Check there is no duplicate ID.. for (unsigned k=0; kItems.a.Length(); k++) { if (To->Items.a[k]->Id == Thing->Id) { LAssert(!"Can't have duplicate IDs in the same folder."); } } } #endif // LgiTrace("%s:%i - Saving %p:%i to %s (%i items)\n", _FL, Thing, (int)Thing->Id, To->GetStr(FIELD_FOLDER_NAME), To->Items.Length()); To->Items.Insert(Thing); if (!OldParent) OldParent = From; if (Callback && OldParent && Moved.Length() && OldParent != From) { PROF_MOVE("7"); Callback->OnMove(To, OldParent, Moved); PROF_MOVE("8"); Moved.Length(0); OldParent = From; } Moved.Add(Thing); } } } } if (Callback && Moved.Length()) Callback->OnMove(To, OldParent, Moved); return Status; } Store3Status LMail3Store::Delete(LArray &Items, bool ToTrash) { if (Items.Length() == 0) return Store3Error; LVariant FolderName; LMail3Folder *Trash = 0; if (ToTrash && Callback->GetSystemPath(FOLDER_TRASH, FolderName)) { auto t = LString(FolderName.Str()).SplitDelimit("/"); if (t.Length() == 1) Trash = Root->FindSub(t[0]); } if (Trash) { LArray MoveItems; for (unsigned i=0; i(di); if (t && t->Parent != Trash) { // Move the item to the trash instead... MoveItems.Add(di); Items.DeleteAt(i--, true); } else { LMail3Folder *f = dynamic_cast(di); if (f && f->Parent != Trash) { // Move the folder to the trash instead... MoveItems.Add(di); Items.DeleteAt(i--, true); } } } if (MoveItems.Length() > 0) { Store3Status s = Move(Trash, MoveItems); if (s != Store3Success) return s; } if (Items.Length() == 0) return Store3Success; } Store3Status s = Store3Success; LDataI *Item = Items[0]; switch ((uint32_t)Item->Type()) { default: { LMail3Thing *t = dynamic_cast(Item); if (!t) { LAssert(!"What are you trying to do?"); s = Store3Error; } else if (Callback && !Callback->OnDelete(t->Parent, Items)) { s = Store3Error; } else { for (unsigned i=0; i(Items[i]))) { if (t->DbDelete()) { if (t->Parent) t->Parent->Items.Delete(t); else LAssert(0); DeleteObj(t); } else s = Store3Error; } } } break; } case MAGIC_ATTACHMENT: { for (auto i: Items) { LMail3Attachment *a = dynamic_cast(i); if (a) { a->Delete(ToTrash); a->Detach(); delete a; } } break; } case MAGIC_FOLDER: { LMail3Folder *f = dynamic_cast(Item); if (!f) s = Store3Error; else if (Callback && !Callback->OnDelete(f->Parent, Items)) s = Store3Error; else { for (unsigned i=0; i(Items[i]))) { if (f->DbDelete()) { if (f->Parent) f->Parent->Sub.Delete(f); else LAssert(0); DeleteObj(f); } else s = Store3Error; } } } break; } } return s; } Store3Status LMail3Store::Change(LArray &Items, int PropId, LVariant &Value, LOperator Operator) { if (Items.Length() == 0) return Store3Success; if (PropId != FIELD_FLAGS) { LAssert(!"Not impl."); return Store3NotImpl; } int32 Val = Value.CastInt32(); if (!Val) { LAssert(!"One flag must be set"); return Store3Error; } if (Operator != OpPlusEquals && Operator != OpMinusEquals) { LAssert(!"Operator not supported."); return Store3Error; } StoreTrans Tr = StartTransaction(); // Set/unset flag for (unsigned i=0; i Chunk; for (unsigned n=0; n<100 && i(Items[i]); if (!m) { LAssert(!"Invalid object."); continue; } if ( (Operator == OpMinusEquals && (m->Flags & Val)) || (Operator == OpPlusEquals && !(m->Flags & Val)) ) { p.Print("%sId=" LPrintfInt64, Chunk.Length()?" or ":"", m->Id); Chunk.Add(m); } } if (Chunk.Length()) { auto Sql = p.NewGStr(); LStatement s(this, Sql); if (!s.Exec()) return Store3Error; for (unsigned n=0; n(Chunk[n]); if (m) { if (Operator == OpMinusEquals) m->Flags &= ~Val; else m->Flags |= Val; } } OnChange(_FL, Chunk, PropId); } } return Store3Success; } bool LMail3Store::SetFormat(LViewI *Parent, LDataPropI *Props) { if (!Parent || !Props) return false; Mail3SubFormat NewFormat = (Mail3SubFormat)Props->GetInt(Store3UiNewFormat); if (NewFormat == Format) return true; bool Error = false; LStatement Folders(this, "select * from " MAIL3_TBL_FOLDER); char Sql[256]; LStatement Count(this, "select Count(*) as c from " MAIL3_TBL_FOLDER); if (Count.Row()) { char *c = Count.GetStr(0); if (c) Props->SetInt(Store3UiMaxPos, atoi(c)); } if (NewFormat == Mail3v1) { // Change format to single table for all email // Create the mail table Store3Status s = CheckTable(MAIL3_TBL_MAIL, TblMail); if (!UpdateTable(MAIL3_TBL_MAIL, TblMail, s)) Error = true; else { // Copy all the sub-folder mail into that one table... while (Folders.Row()) { int64 Id = Folders.GetInt64(0); char TblName[128]; sprintf_s(TblName, sizeof(TblName), "Mail_" LPrintfInt64, Id); // Set the parent ID on all mail rows in the sub-folder table (just to be sure) sprintf_s(Sql, sizeof(Sql), "update %s set ParentId=" LPrintfInt64, TblName, Id); LStatement SetId(this, Sql); if (!SetId.Exec()) { Error = true; break; } // Now copy all the rows into the main Mail table sprintf_s(Sql, sizeof(Sql), "insert into " MAIL3_TBL_MAIL " select * from %s", TblName); LStatement Copy(this, Sql); if (!Copy.Exec()) { Error = true; break; } // And drop the sub-folder table.. sprintf_s(Sql, sizeof(Sql), "drop table if exists %s", TblName); LStatement Del(this, Sql); if (!Del.Exec()) { Error = true; break; } } } } else if (NewFormat == Mail3v2) { // Change format to a table per folder of email { LTransaction Trans(this); int Pos = 0; while (Folders.Row()) { int64 Id = Folders.GetInt64(0); char TblName[128]; sprintf_s(TblName, sizeof(TblName), "Mail_" LPrintfInt64, Id); sprintf_s(Sql, sizeof(Sql), "create table if not exists %s as select * from Mail where ParentId=" LPrintfInt64, TblName, Id); LStatement Convert(this, Sql); if (!Convert.Exec()) { Error = true; Trans.RollBack(); break; } Props->SetInt(Store3UiCurrentPos, ++Pos); } } sprintf_s(Sql, sizeof(Sql), "drop table if exists " MAIL3_TBL_FOLDER); LStatement Del(this, Sql); if (!Del.Exec()) Error = true; } if (!Error) Format = NewFormat; return !Error; } void LMail3Store::Upgrade(LViewI *Parent, LDataPropI *Props, std::function OnStatus) { bool Status = true; LStringPipe p; for (auto it : Fields) { Store3Status s = TableStatus.Find(it.key); if (s == Store3UpgradeRequired) { if (!UpdateTable(it.key, it.value, s)) { p.Print("Failed to upgrade %s\n", it.key); Status = false; } } } if (Props && !Status) { LAutoString a(p.NewStr()); Props->SetStr(Store3UiError, a); } if (OnStatus) OnStatus(Status); } class SqliteRepairThread : public LThread { LString Exe; LString Db; LString RepairSql; LString OldDb; LViewI *Parent; LDataPropI *Props; LAutoPtr Shell; ssize_t Ch; char Line[512]; public: bool Status; SqliteRepairThread(LString exe, LString db, LViewI *parent, LDataPropI *props) : LThread("SqliteRepairThread") { Status = false; Ch = 0; Exe = exe; Db = db; Parent = parent; Props = props; char p[MAX_PATH_LEN]; LMakePath(p, sizeof(p), Db, "..\\Repair.sql"); for (char *c = p; *c; c++) { if (*c == '\\') *c = '/'; } RepairSql = p; LString s; LDateTime dt; dt.SetNow(); char sNow[64]; dt.Get(sNow, sizeof(sNow)); for (char *c = sNow; *c; c++) { if (*c == ':' || *c == '/') *c = '-'; } s.Printf("..\\Database %s.sqlite", sNow); LMakePath(p, sizeof(p), Db, s); OldDb = p; LgiTrace("Sqlite Repair paths:\n" "Exe: '%s'\n" "Db: '%s'\n" "Old: '%s'\n" "Sql: '%s'\n", Exe.Get(), Db.Get(), OldDb.Get(), RepairSql.Get()); Run(); } char *Read() { Ch = Shell->Read(Line, sizeof(Line)-1); if (Ch < 0) return NULL; Line[Ch] = 0; return Line; } bool GetPrompt() { char *l; while ((l = Read())) { if (stristr(l, "sqlite>")) return true; } if (Props) Props->SetStr(Store3UiError, "Failed to get prompt..."); return false; } void StatusMsg(const char *msg) { if (Props) { int64 p = Props->GetInt(Store3UiCurrentPos); Props->SetInt(Store3UiCurrentPos, p + 1); Props->SetStr(Store3UiStatus, msg); } } int Main() { if (Props) { Props->SetInt(Store3UiMaxPos, 7); Props->SetStr(Store3UiStatus, "Starting sqlite..."); } // Open the shell and start the export process LString Args; Args.Printf("-interactive %s", Db.Get()); if (!Shell.Reset(new LSubProcess(Exe, Args))) return -1; if (!Shell->Start(true, true)) { if (Props) Props->SetStr(Store3UiError, "Couldn't execute sqlite3 shell."); return false; } // http://froebe.net/blog/2015/05/27/error-sqlite-database-is-malformed-solved/ // // Run through the commands: // pragma integrity_check; // .mode insert // .output mydb_export.sql // .dump // .exit if (!GetPrompt()) return -2; StatusMsg("Setting mode..."); LString Cmd; Cmd.Printf(".mode insert\r\n"); Shell->Write(Cmd, Cmd.Length()); if (!GetPrompt()) return -3; StatusMsg("Setting output file..."); Cmd.Printf(".output \"%s\"\r\n", RepairSql.Get()); Shell->Write(Cmd, Cmd.Length()); if (!GetPrompt()) return -4; StatusMsg("Dumping SQL..."); Cmd.Printf(".dump\r\n"); Shell->Write(Cmd, Cmd.Length()); if (!GetPrompt()) return -5; Cmd.Printf(".exit\r\n"); Shell->Write(Cmd, Cmd.Length()); Shell->Wait(); StatusMsg("Renaming database..."); if (!FileDev->Move(Db, OldDb)) { LString s; s.Printf("Can't move '%s' to '%s'", Db.Get(), OldDb.Get()); Props->SetStr(Store3UiError, s); return -6; } StatusMsg("Importing SQL..."); char *r = RepairSql; for (char *c = r; *c; c++) { if (*c == '/' || *c == '\\') *c = DIR_CHAR; } #ifdef WINDOWS wchar_t wd[MAX_PATH_LEN]; _wgetcwd(wd, MAX_PATH_LEN); #else char wd[MAX_PATH_LEN]; #ifdef __GTK_H__ // Gtk:: #endif getcwd(wd, MAX_PATH_LEN); #endif char base[MAX_PATH_LEN]; LMakePath(base, sizeof(base), Db, ".."); #ifdef WINDOWS LAutoWString baseW(Utf8ToWide(base)); _wchdir(baseW); #else #ifdef __GTK_H__ // Gtk:: #endif chdir(base); #endif char *exe_leaf = strrchr(Exe, DIR_CHAR); Args.Printf("%s \"%s\" < \"%s\"", exe_leaf ? exe_leaf + 1 : Exe.Get(), Db.Get(), RepairSql.Get()); int Result = system(Args); LgiTrace("Import result = %i\n", Result); #ifdef WINDOWS _wchdir(wd); #else #ifdef __GTK_H__ // Gtk:: #endif chdir(wd); #endif StatusMsg("Deleting temporary files..."); FileDev->Delete(RepairSql, false); Status = true; return 0; } }; bool CheckPathForFile(const char *File, char *Exe, int ExeSize) { LString Path; #ifdef WINDOWS char *buffer = NULL; size_t sz = 0; errno_t err = _dupenv_s(&buffer, &sz, "PATH"); if (err) { LgiTrace("%s:%i - _dupenv_s failed with %i.\n", _FL, err); return false; } if (sz == 0) // PATH not found return false; Path = buffer; free(buffer); #else Path = getenv("PATH"); if (!Path) return false; #endif LString::Array p = Path.Split(LGI_PATH_SEPARATOR); for (unsigned i=0; i OnStatus) { // Is the sqlite3 shell binary available? char base[MAX_PATH_LEN]; char exe[MAX_PATH_LEN] = ""; LMakePath(base, sizeof(base), DbFile, ".."); #ifdef WINDOWS - const char *SqliteBin = "sqlite3.exe"; - LMakePath(exe, sizeof(exe), base, SqliteBin); + const char *SqliteBin = "sqlite3.exe"; + LMakePath(exe, sizeof(exe), base, SqliteBin); #else - const char *SqliteBin = "sqlite3"; - CheckPathForFile(SqliteBin, exe, sizeof(exe)); + const char *SqliteBin = "sqlite3"; + CheckPathForFile(SqliteBin, exe, sizeof(exe)); #endif if (!LFileExists(exe)) { LString DownloadUrl = "https://www.sqlite.org/download.html"; LViewI *p = dynamic_cast(Props); LString Msg; Msg.Printf("Error: sqlite shell binary missing (%s). " #ifdef WINDOWS "Download from:\n" "\n" " %s\n" #else "Install using your package manager:\n" "\n" " e.g. sudo apt-get install sqlite3\n" #endif "\n" "Download the shell binary and extract to this folder:\n" "\n" " %s\n" "\n" "Then try the Repair command again.\n", SqliteBin, DownloadUrl.Get(), base); - LAlert Dlg( p?p:Parent, - "LMail3Store::Repair", - Msg, - "Browse Download Site & Local Folder", - "Cancel"); - int Btn = Dlg.DoModal(); - if (Btn == 1) + auto Dlg = new LAlert(p?p:Parent, + "LMail3Store::Repair", + Msg, + "Browse Download Site & Local Folder", + "Cancel"); + Dlg->DoModal([this, Dlg, DownloadUrl, base](auto dlg, auto ctrlId) { - LExecute(DownloadUrl); - LExecute(base); - } + if (ctrlId == 1) + { + LExecute(DownloadUrl); + LExecute(base); + } + delete dlg; + }); if (OnStatus) OnStatus(true); return; } // Close our database... CloseDb(); // Do the repair in a thread SqliteRepairThread Worker(exe, DbFile, Parent, Props); while (!Worker.IsExited()) { LSleep(20); LYield(); } OpenDb(); if (OnStatus) OnStatus(Worker.Status); return; } int64 LMail3Store::GetFolderId(char *Path) { auto Parts = LString(Path).SplitDelimit("/"); LMail3Folder *Root = dynamic_cast(GetRoot()); if (!Root) return 0; uint64 Id = Root->Id; for (unsigned i=0; iSetStr(Store3UiError, s); } void SetStatus(const char *s) { Props->SetStr(Store3UiStatus, s); } int Main() { // Clean up orphaned mail segments. { SetStatus("Cleaning up orphaned Mail segments..."); LMail3Store::LStatement s(Store, "delete from " MAIL3_TBL_MAILSEGS " where MailId not in (select Id from " MAIL3_TBL_MAIL ")"); if (!s.Exec()) { SetError("Failed to delete orphaned segments."); OnStatus(false); return -1; } } // Clean up orphaned mail. if (!IsCancelled()) { SetStatus("Cleaning up orphaned Mail..."); LMail3Store::LStatement count(Store, "select COUNT(*) from " MAIL3_TBL_MAIL " where ParentId not in (select Id from " MAIL3_TBL_FOLDER ")"); if (count.Row()) { int64 Rows = count.GetInt64(0); Max = Rows; } LDataStoreI::StoreTrans Trn = Store->StartTransaction(); LMail3Store::LStatement s(Store, "select Id from " MAIL3_TBL_MAIL " where ParentId not in (select Id from " MAIL3_TBL_FOLDER ")"); int64 RowPos = 0; while (!IsCancelled() && s.Row()) { int64 Id = s.GetInt64(0); Store->DeleteMailById(Id); Value = ++RowPos; } Max = 1; Value = 0; } // Vacuum if (!IsCancelled()) { SetStatus("Vacuum unused space..."); LMail3Store::LStatement s(Store, "vacuum;"); if (!s.Exec()) { SetError("Reclaiming space failed."); OnStatus(false); return -1; } } OnStatus(true); return 0; } }; void LMail3Store::Compact(LViewI *Parent, LDataPropI *Props, std::function OnStatus) { if (!Props || !Callback) return; bool Status = false; LVariant InboxPath; if (!Callback->GetSystemPath(FOLDER_INBOX, InboxPath)) Props->SetStr(Store3UiError, "Couldn't get path to Inbox."); else { int64 InboxId = GetFolderId(InboxPath.Str()); if (!InboxId) Props->SetStr(Store3UiError, "Couldn't get Inbox ID."); { new CompactThread(this, InboxId, Props, OnStatus); return; // without calling OnStatus, the CompactThread will do it. } } OnStatus(Status); } void LMail3Store::OnEvent(void *Param) { } bool LMail3Store::IsOk() { return #ifndef __llvm__ this != 0 && #endif Db != 0; } bool LMail3Store::ParseTableFormat(const char *Name, TableDefn &Defs) { char Sql[256]; sprintf_s(Sql, sizeof(Sql), "SELECT * FROM sqlite_master WHERE type='table' and name='%s'", Name); LStatement st(this, Sql); if (!st.IsOk()) return false; if (!st.Row()) return false; char *Fmt = st.GetStr(4); char *s = Fmt ? strchr(Fmt, '(') + 1 : 0; char *e = Fmt ? strrchr(Fmt, ')') : 0; if (!s || !e) return false; LString f(s, e - s); Defs.t = f.SplitDelimit(","); for (unsigned i=0; i= Defs.Length()) { LString Msg; Msg.Printf("\nTable '%s' is missing the schema field: '%s %s'", Name, Flds[i].Type, Flds[i].Name); StatusMsg += Msg; break; } GMail3Def &Def = Defs[i]; if (i >= FieldCount) // Schema field count is less than existing table { LString Msg; Msg.Printf("\nTable '%s' has an extra field: '%s %s'\n", Name, Def.Type, Def.Name); StatusMsg += Msg; break; } if (!Flds[i].Name || !Flds[i].Type || !Def.Name || !Def.Type) { Status = Store3Error; break; } if (_stricmp(Flds[i].Name, Def.Name) != 0 || _stricmp(Flds[i].Type, Def.Type) != 0) { LString Msg; Msg.Printf("\nTable '%s' has a field '%s %s' which is to the schema: '%s %s'\n", Name, Def.Type, Def.Name, Flds[i].Type, Flds[i].Name); StatusMsg += Msg; Status = Store3UpgradeRequired; break; } } if (Defs.Length() != FieldCount) { LString Msg; Msg.Printf("\nTable '%s' has %i fields (should have %i)\n", Name, Defs.Length(), FieldCount); StatusMsg += Msg; Status = Store3UpgradeRequired; } } else Status = Store3Missing; return Status; } bool LMail3Store::UpdateTable(const char *Name, GMail3Def *Flds, Store3Status Check) { LStatement st(this); LAutoString TempTable; TableDefn Defs; if (Check == Store3UpgradeRequired) { // Get the old table format so we can copy over field by field later... if (!ParseTableFormat(Name, Defs)) { return false; } char Tmp[256]; sprintf_s(Tmp, sizeof(Tmp), "%s_tmp", Name); TempTable.Reset(NewStr(Tmp)); char AlterSql[256]; sprintf_s(AlterSql, sizeof(AlterSql), "alter table %s rename to %s", Name, Tmp); LStatement Alter(this, AlterSql); if (!Alter.Exec()) { LAssert(!"Can't rename table."); return false; } } // Create table? LStringPipe p; LHashTbl, bool> AllFlds; p.Print("create table %s (", Name); for (int i=0; Flds[i].Name; i++) { AllFlds.Add(Flds[i].Name, true); if (i) p.Print(", "); p.Print("%s %s", Flds[i].Name, Flds[i].Type); } p.Print(")"); LAutoString s(p.NewStr()); if (s) { if (st.Prepare(s)) st.Exec(); else { LgiTrace("Sql='%s'\n", s.Get()); return false; } } // Copy over data from the old table if (TempTable) { LStringPipe p; p.Print("insert into %s (", Name); int Count = 0; for (unsigned i=0; iGetFields(Tbl); if (f) { LVariant v; LStringPipe p; p.Print("insert into '%s' values (", Tbl); for (int i=0; f[i].Name; i++) { if (i) p.Print(","); p.Print("?"); } p.Print(")"); v.OwnStr(p.NewStr()); Prepare(v.Str()); } } LMail3Store::LUpdate::LUpdate(LMail3Store *store, const char *Tbl, int64 rowid, char *ExcludeField) : LStatement(store) { RowId = rowid; Store = store; Table = Tbl; LAssert(Store != 0 && Tbl != 0 && RowId > 0); GMail3Def *f = Store->GetFields(Tbl); if (f) { LVariant v; LStringPipe p; p.Print("update %s set ", Tbl); for (int i=1; f[i].Name; i++) { if (ExcludeField && !_stricmp(f[i].Name, ExcludeField)) continue; if (i>1) p.Print(", "); p.Print("%s=?%i", f[i].Name, i + 1); } p.Print(" where %s=?1", f[0].Name); v.OwnStr(p.NewStr()); Prepare(v.Str()); } } //////////////////////////////////////////////////////////////////////// bool LMail3Obj::Check(int r, char *sql) { return Store->Check(r, sql); } #define DEBUG_MAIL3_WRITE 0 bool LMail3Obj::Write(const char *Table, bool Insert) { #if DEBUG_MAIL3_WRITE LProfile Prof("LMail3Obj::Write"); #endif LAutoPtr s; #if DEBUG_MAIL3_WRITE Prof.Add(_FL); #endif if (Insert) s.Reset(new LMail3Store::LInsert(Store, Table)); else s.Reset(new LMail3Store::LUpdate(Store, Table, Id)); #if DEBUG_MAIL3_WRITE Prof.Add(_FL); #endif if (s) { Serialize(*s, true); #if DEBUG_MAIL3_WRITE Prof.Add(_FL); #endif if (s->Exec()) { #if DEBUG_MAIL3_WRITE Prof.Add(_FL); #endif if (Insert) { Id = s->LastInsertId(); if (Id < 0) { LAssert(!"No ID returned."); LgiTrace("%s:%i - No ID from statement.\n", _FL); } } } else { LgiTrace("%s:%i - Exec failed.\n", _FL); } } else LAssert(!"No statement"); return Id >= 0; } ////////////////////////////////////////////////////////////////////////// LMail3Store::LStatement::LStatement(LMail3Store *store, const char *sql) { Store = store; s = 0; #if MAIL3_TRACK_OBJS LMail3Store::SqliteObjs &_d = Store->All.New(); _d.Stat = this; #endif if (sql) Prepare(sql); } LMail3Store::LStatement::~LStatement() { Finalize(); #if MAIL3_TRACK_OBJS Store->RemoveFromAll(this); #endif } int64 LMail3Store::LStatement::LastInsertId() { return sqlite3_last_insert_rowid(Store->GetDb()); } bool LMail3Store::LStatement::Prepare(const char *Sql) { Finalize(); TempSql.Reset(NewStr(Sql)); Store->Check(sqlite3_prepare_v2(Store->GetDb(), Sql, -1, &s, 0), Sql); return s != 0; } bool LMail3Store::LStatement::Finalize() { if (!s) return false; bool Status = Store->Check(sqlite3_finalize(s), 0); s = 0; return Status; } bool LMail3Store::LStatement::Row() { if (!IsOk()) return false; int r = sqlite3_step(s); return r == SQLITE_ROW; } bool LMail3Store::LStatement::Exec() { if (!IsOk()) return false; if (!Store->Check(sqlite3_step(s), TempSql)) return false; bool Status = true; if (Post.Length()) { int64 Id = GetRowId(); LAssert(Id > 0); for (unsigned i=0; iCheck(sqlite3_blob_open( Store->GetDb(), 0, Table.Str(), b.ColName.Str(), Id, true, &Blob), 0)) { LArray Buf; if (Buf.Length(32<<10)) { ssize_t r, Pos = 0; b.Data->SetPos(0); while ((r = b.Data->Read(&Buf[0], Buf.Length())) > 0) { if (!Store->Check(sqlite3_blob_write(Blob, &Buf[0], (int)r, (int)Pos), 0)) { Status = false; break; } Pos += r; } } else Status = false; Store->Check(sqlite3_blob_close(Blob), 0); } } Post.Length(0); } return Status; } bool LMail3Store::LStatement::Reset() { if (!IsOk()) return false; return Store->Check(sqlite3_reset(s), 0) && Store->Check(sqlite3_clear_bindings(s), 0); } /* All the getter functions use 'Col' as the first column is '0' */ int LMail3Store::LStatement::GetSize(int Col) { return IsOk() ? sqlite3_column_bytes(s, Col) : 0; } bool LMail3Store::LStatement::GetBool(int Col) { if (!IsOk() || sqlite3_column_type(s, Col) == SQLITE_NULL) return false; return sqlite3_column_int(s, Col) != 0; } int LMail3Store::LStatement::GetInt(int Col) { if (!IsOk() || sqlite3_column_type(s, Col) == SQLITE_NULL) return -1; return sqlite3_column_int(s, Col); } int64 LMail3Store::LStatement::GetInt64(int Col) { if (!IsOk() || sqlite3_column_type(s, Col) == SQLITE_NULL) return -1; return sqlite3_column_int64(s, Col); } bool LMail3Store::LStatement::SetInt64(int Col, int64 n) { if (!IsOk()) return false; if (n == -1) return Store->Check(sqlite3_bind_null(s, Col+1), 0); else return Store->Check(sqlite3_bind_int64(s, Col+1, n), 0); } char *LMail3Store::LStatement::GetStr(int Col) { return IsOk() ? (char*)sqlite3_column_text(s, Col) : 0; } bool LMail3Store::LStatement::GetBinary(int Col, LVariant *v) { if (!v) return false; const void *Ptr = sqlite3_column_blob(s, Col); if (!Ptr) return false; int Bytes = sqlite3_column_bytes(s, Col); return v->SetBinary(Bytes, (void*)Ptr); } /* All the setter functions use 'Col+1' as the first column is '1' */ bool LMail3Store::LStatement::SetInt(int Col, int n) { if (!IsOk()) return false; if (n == -1) return Store->Check(sqlite3_bind_null(s, Col+1), 0); else return Store->Check(sqlite3_bind_int(s, Col+1, n), 0); } bool LMail3Store::LStatement::SetStr(int Col, char *Str) { if (!IsOk()) return false; if (Str) return Store->Check(sqlite3_bind_text(s, Col+1, Str, -1, SQLITE_STATIC), 0); else return Store->Check(sqlite3_bind_null(s, Col+1), 0); } bool LMail3Store::LStatement::SetDate(int Col, LDateTime &Var) { if (!Var.Year()) return Store->Check(sqlite3_bind_null(s, Col+1), 0); char c[64]; sprintf_s(c, sizeof(c), "%4.4i-%2.2i-%2.2i %2.2i:%2.2i:%2.2i", Var.Year(), Var.Month(), Var.Day(), Var.Hours(), Var.Minutes(), Var.Seconds()); return Store->Check(sqlite3_bind_text(s, Col+1, (const char*)c, -1, SQLITE_TRANSIENT), 0); }; bool LMail3Store::LStatement::SetStream(int Col, const char *ColName, LStreamI *Data) { if (!IsOk() || !Data) return false; PostBlob &p = Post.New(); p.ColName = ColName; p.Data = Data; p.Size = Data->GetSize(); LAssert((p.Size & 0xffffffff00000000) == 0); return Store->Check(sqlite3_bind_zeroblob(s, Col+1, (int)p.Size), 0); } bool LMail3Store::LStatement::SetBinary(int Col, const char *ColName, LVariant *v) { if (!v || v->Type != GV_BINARY) { LAssert(!"Invalid binary type."); return false; } if (!Store->Check(sqlite3_bind_blob(s, Col+1, v->Value.Binary.Data, (int)v->Value.Binary.Length, SQLITE_TRANSIENT), NULL)) return false; return true; } ////////////////////////////////////////////////////////////////////////// LMail3Store::LTransaction::LTransaction(LMail3Store *store) { Store = store; const char *Sql = "begin;"; Open = Store ? Store->Check(sqlite3_exec(Store->GetDb(), Sql, 0, 0, 0), Sql) : false; } LMail3Store::LTransaction::~LTransaction() { if (Open) { const char *Sql = "end;"; Store->Check(sqlite3_exec(Store->GetDb(), Sql, 0, 0, 0), Sql); } } bool LMail3Store::LTransaction::RollBack() { bool Status = false; if (Open) { const char *Sql = "rollback;"; Status = Store->Check(sqlite3_exec(Store->GetDb(), Sql, 0, 0, 0), Sql); Open = false; } return Status; } ///////////////////////////////////////////////////////////////////////// LDataStoreI *OpenMail3(const char *Mail3Folder, LDataEventsI *Callback, bool Create) { return new LMail3Store(Mail3Folder, Callback, Create); } diff --git a/Help/help.css b/Help/help.css --- a/Help/help.css +++ b/Help/help.css @@ -1,210 +1,211 @@ body { font-size: 13pt; font-family: Sans-Serif; color: #101040; margin: 0px; padding: 0px; height: 100%; } table { font-family: Sans-Serif; } td { vertical-align: top; padding-right: 0.8em; } th { font-weight: bold; font-size: larger; background: #e0e0e8; } iframe { display:block; width:100%; height:100%; border:none; } ul { padding-top: 0px; } pre { color: #1010c0; margin: 1em; padding: 0.4em; border-radius: 0.5em; background: #f0f3f6; border-bottom: 1px solid #a0b0a0; border-right: 1px solid #a0b0a0; + font-size: smaller; } a:link, a:visited { color: #800000; } a:hover { color: #800000; background-color: #f0e0f0; } ul { margin-top: 3px; margin-bottom: 3px; } .menu { background-color: #f0f0ff; } .content { margin-left: 190px; padding: 8px; background-color: #fafaff; border-style: dotted; border-left-width: 2px; border-right-width: 0px; border-top-width: 0px; border-bottom-width: 2px; border-color: #d0d0ff; } .alt-line { background-color: #f2f2ff; } .helpheading { background-color: #444499; color: white; font-weight: bold; text-align: center; padding: 0.3em; margin-top: 0.8em; margin-bottom: 0.3em; } .inscribe { color: #009; background: #eef; margin: 0.5em 0px; border-radius: 9px; padding: 9px; } .action { font-style: italic; color: #66c; } kbd { display: inline-block; margin: 0 .1em; padding: 0px 3px; color: #242729; text-shadow: 0 1px 0 #FFF; background-color: #E6E7E9; border: 1px solid #adb3b9; border-radius: 3px; box-shadow: 0 1px 0 rgba(12,13,14,0.2),0 0 0 1px #FFF inset; white-space: nowrap; } fld { background: #e0e0ff; } .desc { margin: 0px 0px 0.5em 2em; color: #999; } method { display:inline; color: #008; font-weight: bold; } arg { display:inline; color: #c01010; } .desc method { color: #889; font-weight: bold; } .desc arg { color: #b99; } .win_specific { padding: 0.5em; border: 1px solid #e0e0ff; background-color: #f0f0ff; margin-top: 0.5em; margin-bottom: 0.5em; } .bordered td, .bordered th { border-left: 1px solid #ddd; border-top: 1px solid #ddd; } table.bordered { border-right: 1px solid #ddd; border-bottom: 1px solid #ddd; } table.options { margin: 1em; background: #888; } table.options td, table.options th { padding: 0.5em; background: white; } table.options th { color: #444; background: #ddd; } table.options td.fld { background: #aaa; color: white; font-weight: bold; } table.options td.fld-inscribe { background: #aaf; color: white; font-weight: bold; } table.options td.inscribe { color: #009; background: #eef; margin: 0.5em 0px; border-radius: 0px; } diff --git a/Help/scripting.html b/Help/scripting.html --- a/Help/scripting.html +++ b/Help/scripting.html @@ -1,420 +1,445 @@ Scribe Help

Scripting Scribe

Overview
Scribe can execute scripts to extend it's functionality in the following locations:
  • As a filter that is executed on email being sent or received.
  • From an item in the tools menu.
  • From an item in the context menu of an object like email, contacts etc.
  • Before an email is saved and sent.
  • On start up.
Scripts are written in a C like language, the exact syntax is described here. Scripts are stored in 2 places, either in the Script tab of a filter or as a .script file in the Scripts subfolder where the executable is located. Files with the extension ".script" in that folder are loaded and executed when Scribe starts, and they for the most part install a call back that is called when some event happens later. Scripts can print debugging information to the scripting console via the Print(...) method. That console can be openned via the Debug -> Show Scripting Console menu.

For information about what properties various objects support please consult the "Dom.txt" in the Scripts sub-folder. That file is generated from the application's source code as part of the release process so is always fully up to date for the currently installed build.

Scribe Specific Methods
These methods are defined for all scripts run within Scribe, in addition to the various scripts defined by the system library (which are available in any script enabled Lgi application).

List of system folder identifiers:
FOLDER_INBOX 0
FOLDER_OUTBOX 1
FOLDER_SENT 2
FOLDER_TRASH 3
FOLDER_CONTACTS 4
FOLDER_TEMPLATES 5
FOLDER_FILTERS 6
FOLDER_CALENDAR 7
FOLDER_GROUPS 8
FOLDER_SPAM 9
List of item types:
MAGIC_MAIL 0xAAFF0001
MAGIC_CONTACT 0xAAFF0002
MAGIC_ATTACHMENT 0xAAFF0005
MAGIC_FILTER 0xAAFF0007
MAGIC_CALENDAR 0xAAFF000b
MAGIC_GROUP 0xAAFF000d
However it's best to just include the ScribeScripts.h header and use the symbols in that:
#include "ScribeScripts.h"
 
 if (Thing.Type == ScribeItemMail)
 {
 	// Do something with en email...
 }
List of system functions:
  • Print(...)
    Prints string representations of any arguments, converting \r, \t, \n etc to the actual whitespace characters. It doesn't assume there is a new line at the end, that has to be part of the arguments.
  • GetFolder(NameOrIndex)
    Returns a pointer to a folder. If NameOrIndex is a string then it should be the full path of the folder in the form:
    /{MailStoreName}/{LocalPath}
    To get the path of a folder in the main tree, right click and "Copy Path".

    Otherwise the argument can be a system folder identifier (integer) like SystemFolderInbox or SystemFolderContacts etc (See 'ScribeScripts.h').
  • GetSourceFolders(SystemFolderId)
    Returns a list of folders. SystemFolderId is a system folder identifier (integer). Use this to get all the folders for a particular item type. For instance to get all the contacts folder (across all mail stores) you would call:
    Flds = GetSourceFolders(4);
    See 'ScribeScripts.h' for folder symbols.
  • CreateSubFolder(ParentFolderPtr, NameTxt, Type)
    Creates a subfolder under ParentFolderPtr, with the given name and type. Type can be: "Mail", "Contact", "Filter", "Calendar" or "Group".
  • -
  • BrowseFolder(ParentWnd[, MessageTxt[, DefaultFolderName]]) +
  • BrowseFolder(ParentWnd, MessageTxt, DefaultFolderName, CallbackFunctionName)
    - Browses for a folder location, returns NULL on Cancel, and the folder's path on Ok. + Browses for a folder location and calls "CallbackFunctionName" on completion. +
      +
    • ParentWnd: The parent window view or handle, or NULL if not known.
    • +
    • MessageTxt: Message to user or NULL.
    • +
    • DefaultFolderName: Optional path for default selected folder or NULL.
    • +
    • CallbackFunctionName: Name of callback method with the form:
    • +
      + function CallbackFunction(FolderPath); +
      + The FolderPath will be NULL on a cancelled dialog. +
    + There is no useful return value from the function itself. Example: +
    function TestCallback(FolderPath)
    +{
    +    Print("TestCallback called: " + FolderPath + "\n");	
    +}
    +
    +function TestLoad(App)
    +{
    +    BrowseFolder(App, "User prompt:", NULL, "TestCallback");
    +}
    +
    +function Main(App)
    +{
    +    return AddCallback("OnLoad", "TestLoad");
    +}
  • CreateThing(Type[, Folder=None])
    Creates a new thing (email, contact etc). Type is an integer (e.g. MAGIC_MAIL). Optionally you can supply a folder to create the object for. This is especially important if you want to create an IMAP mail. By default CreateThing creates a mail3 based object.
  • MoveThing(Folder, Item)
    Moves 'Item' into 'Folder'.
    'Folder' can either by a direct pointer to a folder object -or- a string specifying the path to the folder.
  • SaveThing(DestinationFolder, Item)
    Saves 'Item' into 'DestinationFolder', more of a copy. Usually used with new objects that are't already saved somewhere.
  • DeleteThing(Item)
    Deletes object(s) from memory and disk (or moves it to the trash). You can supply multiple arguments if needed, including List objects. To specify deleting to trash or deleting without trash use a string parameter. The default is not to delete to the trash.
    • DeleteThing(MyObj, "trash");
  • ShowThingWindow(Item)
    Opens the user interface for 'Item'.
  • LookupContact(Email)
    Searches for the contact with a given email address.
  • AddToolsMenuItem(Name, CallbackMethodName)
    Puts a menu item with the text 'Name' for a script function 'CallbackMethodName' in the tools menu. Where 'CallbackMethodName' is the name of the function as a string. Callbacks have the following form:
    ToolMenuCallback(Application, CmdId)
    The return value of the script is not used for anything.
  • AddCallback(HandlerName, CallbackMethodName[, Parameter])
    Adds a callback to a specific handler. Multiple callbacks can be installed for the same handler. Handlers call the callbacks in the order they were installed.

    Available callback handlers:
    • OnThingContextMenu(Application, Thing, Menu)
      The callback script function is called after the context menu for a Thing has been constructed but before it's shown to the user. The callback can install commands or submenus that call other callbacks.
    • OnFolderContextMenu(Application, Folder, Menu)
      The callback script function is called after the context menu for a Folder has been constructed but before it's shown to the user. The callback can install commands or submenus that call other callbacks.
    • OnThingToolbar(Application, Toolbar, Thing)
      The callback script function is called after the toolbar for a Thing has been constructed, but before the UI is shown to the user. The callback can install extra button(s) that call other callbacks.
    • OnApplicationToolbar(Application, Toolbar)
      The callback script function is called after the main application toolbar has been constructed, but before the UI is shown to the user. The callback can install extra button(s) that call other callbacks.
    • OnBeforeMailSend(Application, EmailMsg)
      The callback script function is called before the email window submits an email for sending. If the script returns zero, the email window does nothing, if the script returns non-zero then sending proceeds as normal.
    • OnAfterMailReceive(Application, EmailMsg)
      These callbacks get called after a new email is received. You can use them to run filtering scripts that need to handle asyncronous callbacks, like App.GetUri, which don't work in a regular filter.
    • OnBeforeInstallBar(Application, UserMsg, ActionList)
      The callback script function is called before the install capability bar is shown. The user msg and action list can be adjusted by the script function. If FALSE is returned the capability bar is not shown, if TRUE is returned normal behaviour follows, with any modifications the script makes.
    • OnInstallComponent(Application, ActionName)
      The callback script function is called to install a component after the capability bar has got user permission. If the function returns TRUE the system installers proceed with their default behaviour. If the function returns FALSE then the installation exits without doing anything further. If the script actually performs the install itself, it should return FALSE. ActionName is the text on the button that the user clicked. It is translated into the current language. The system string IDs used are:
      IDS_OK Just hides the capability bar without doing anything.
      IDS_DONT_SHOW_AGAIN Turn off default client warnings.
      IDS_INSTALL Install library from memecode website (openssl, libjpg etc).
      IDS_SHOW_CONSOLE Shows the console.
      IDS_OPEN_SOURCE Opens the script source (on say compile error).
      IDS_SHOW_REMOTE_CONTENT Shows remote content for a HTML email.
      IDS_DOWNLOAD Downloads a spell check dictionary.
      You can use those #defines and the API call LoadString to get the translated name of the install action. Which you'll need if you are comparing 'ActionName' with any of the system actions.
    • OnTimer(Application)
      Calls the function periodically. Set the period via AddCallback's "Parameter". Values for that are in the form [number][unit], where Unit can be:
      • s/sec/second
      • m/min/minute
      • h/hr/hour
      • d/day
      e.g.
      • AddCallback("OnTimer", "MyFunction", "5min");
      • AddCallback("OnTimer", "DoEveryHour", "1hr");
    • OnLoad(Application)
      Calls the method after the application loads. Most of the folders will exist, except IMAP. If you need to access them maybe OnTimer is better?
    • (if you want something added here, ask the author)
  • MenuAddItem(Menu, Icon, LabelTxt, Position, CallbackMethodName, CallbackId)
    Adds an item to 'Menu' as 'Position' with the specified text. If the menu item is clicked, then the scripting function 'CallbackMethodName' is called using the following arguments:
    OnMenuItem(Application, Object, CallbackId)
    Where 'Object' is the either a 'Thing' or the 'Folder' the menu was used on.
    And 'CallbackId' is the integer supplied to MenuAddItem.
    CallbackId should be an integer > 2000 and < 30000, unless you use -1 in which case a separator is placed in the menu. Position is the index into the menu... or -1 to append at the end.

    This will also append a separator:
    Menu.AppendSeparator();
  • MenuAddSubmenu(Menu, SubmenuTxt, Position)
    Returns a pointer to the submenu or NULL on error.
  • ToolbarAddItem(Toolbar, Icon, LabelTxt, Position, CallbackMethodName, CallbackId)
    Adds a toolbar button to 'Toolbar'. When the button is clicked, 'CallbackMethodName' is called. The callback is called with the following parameters:
    OnToolbarButton(Application, Window, Thing, CallbackId)
    Where 'Window' is the window the toolbar is attached to. CallbackId is the integer passed to ToolbarAddItem.
  • Deprecated: FilterDoActions(Filter, Mail)
    Executes the actions attached to 'Filter' on 'Mail'.
    Use Filter.DoActions(Mail) instead.
  • Deprecated: LoadFolder(FolderPtr)
    Loads the items in a folder if not already.
    Use Folder.Load() instead.
File Based Scripts
When you create a file based script in the ./Scripts subfolder it has to have the extension .script. It will only be loaded at startup, changes to the script after Scribe loads will not be loaded until next start up. Scripts should be utf-8 text with \r\n or \n end of line characters. They should have a method called "Main" and optionally one or more callback methods. The Main function should register the callback(s) via AddToolsMenuItem, AddThingMenuCallback, AddThingUiButton or AddCallback.

See the existing scripts in that folder for some examples. Filter based scripts exist in the Script tab of a filter object. When the script tab has some content any existing filter conditions are ignored and the script is executed. There doesn't need to be a method defined in the script. Several pre-defined variables are available:
App The main application object.
Filter The filter being evaluated.
Mail The mail being filtered.

In your script you can call being into the filter object to evaluate the conditions in the filter against the mail object being filtered by using the syntax:

Filter.TestConditions
Which returns a boolean true of the mail matches the filter's conditions or false otherwise. Also to execute the actions attached to the filter you can call:
Filter.DoActions(Mail)
The default behaviour of a filter with no script is:
if (Filter.TestConditions)
 {
 	Filter.DoActions(Mail);
 }
You can use that as a template to build up to the functionality you want. Alternatively you can replace Filter.DoActions(...) with a set of custom calls to manipulate objects directly from the script.

To stop further filtering of the current email use the StopFiltering method:
Filter.StopFiltering();
Specific Notes on Fields
The Mail.Flags object is a group of flags typically expressed as an Integer. However if you want to get or set a specific flag you can use a string in the array reference to specify which. The flags are as follows (case sensitive):
SENTEmail has been sent via SMTP
RECEIVEDEmail was received by POP or IMAP
CREATEDUser created email
FORWARDEDEmail has been forwarded
REPLIEDEmail has been replied to
ATTACHMENTSEmail has attachments (can be wrong if email hasn't been parsed yet, more a display hint for user)
READUser requested monospace font
READY_TO_SENDEmail is marked for sending in the next SMTP connection
READ_RECEIPTUser requested a receipt
IGNOREUser requested thread to be ignored
FIXED_WIDTH_FONTUser requested monospace font
BOUNCEDMessaged was bounced to somewhere
BOUNCEMessage IS a bounce (copy that is sent onwards)
SHOW_IMAGESUser requested external resources
BAYES_HAMCurrently classified as 'Ham' by the spam filter
BAYES_SPAMCurrently classified as 'Spam'
NEWEmail has been newly received
STORED_FLATEncrpytion implementation specific (don't use)
For example:
if (MailObj.Flags["NEW"])
 	Print("New mail!\n");

The ScribeWnd.Folders field takes an array parameter to look up system folders. You can use an integer:
FOLDER_INBOX 0The mail3 Inbox.
FOLDER_OUTBOX 1The mail3 Outbox.
FOLDER_SENT 2The mail3 Sent folder.
FOLDER_TRASH 3The trash.
FOLDER_CONTACTS 4The mail3 Contacts folder.
FOLDER_TEMPLATES5The templates folder.
FOLDER_FILTERS 6The filters.
FOLDER_CALENDAR 7The primary calendar folder.
FOLDER_GROUPS 8The contact groups.
FOLDER_SPAM 9All the spam.
Or specify a path as a string. When specifying a path, it should be in the form:
/{MailStoreName}/{LocalPath}
© Matthew Allen
diff --git a/Linux/Makefile.linux b/Linux/Makefile.linux --- a/Linux/Makefile.linux +++ b/Linux/Makefile.linux @@ -1,747 +1,745 @@ #!/usr/bin/make # # This makefile generated by LgiIde # http://www.memecode.com/lgi.php # .SILENT : CC = gcc CPP = g++ Target = ./scribe ifndef Build Build = Debug endif BuildDir = $(Build) Flags = -fPIC -w -fno-inline -fpermissive ifeq ($(Build),Debug) Flags += -g -std=c++14 Tag = d Defs = -D_DEBUG -DLINUX -D_REENTRANT -D_FILE_OFFSET_BITS=64 -DPOSIX -DSCRIBE_APP -DLIBPNG_SHARED=1 -DLIBJPEG_SHARED=1 -DLIBPNG_VERSION=\"1.5\" -DPOSIX Libs = \ -L../../libs/build-x64/libchardet \ -lchardet \ -static-libgcc \ -lpthread \ -ldl \ -lpng \ -ljpeg \ -lz \ `pkg-config --libs gtk+-3.0` \ -laspell-dist-060$(Tag) \ -L../../libs/aspell-0.60.6.1/linux/$(BuildDir) \ -lbzip2$(Tag) \ -L../../libs/bzip2-1.0.6/linux/$(BuildDir) \ - -llgi-gtk3$(Tag) \ + -llgi-dev$(Tag) \ -L../../../lgi/trunk/$(BuildDir) Inc = \ `pkg-config --cflags gtk+-3.0` \ -I../Utils/Tables \ -I../Resources \ -I../Code/Store3Webdav \ -I../Code/Sqlite \ -I../Code \ -I../../libs/libchardet/src \ -I../../libs/libchardet/include \ -I../../libs/aspell-0.60.6.1/interfaces/cc \ -I../../../lgi/trunk/private/common \ -I../../../lgi/trunk/include/lgi/linux/Gtk \ -I../../../lgi/trunk/include/lgi/linux \ -I../../../lgi/trunk/include \ -I../../../../codelib/openssl/include else Flags += -s -Os -std=c++14 Defs = -DLINUX -D_REENTRANT -D_FILE_OFFSET_BITS=64 -DPOSIX -DSCRIBE_APP -DLIBPNG_SHARED=1 -DLIBJPEG_SHARED=1 -DLIBPNG_VERSION=\"1.5\" -DPOSIX Libs = \ -L../../libs/build-x64/libchardet \ -lchardet \ -static-libgcc \ -lpthread \ -ldl \ -lpng \ -ljpeg \ -lz \ `pkg-config --libs gtk+-3.0` \ -laspell-dist-060$(Tag) \ -L../../libs/aspell-0.60.6.1/linux/$(BuildDir) \ -lbzip2$(Tag) \ -L../../libs/bzip2-1.0.6/linux/$(BuildDir) \ - -llgi-gtk3$(Tag) \ + -llgi-dev$(Tag) \ -L../../../lgi/trunk/$(BuildDir) Inc = \ `pkg-config --cflags gtk+-3.0` \ -I../Utils/Tables \ -I../Resources \ -I../Code/Store3Webdav \ -I../Code/Sqlite \ -I../Code \ -I../../libs/libchardet/src \ -I../../libs/libchardet/include \ -I../../libs/aspell-0.60.6.1/interfaces/cc \ -I../../../lgi/trunk/private/common \ -I../../../lgi/trunk/include/lgi/linux/Gtk \ -I../../../lgi/trunk/include/lgi/linux \ -I../../../lgi/trunk/include \ -I../../../../codelib/openssl/include endif # Dependencies Source = ../Utils/Tables/MailFlags.c \ ../Code/TitlePage.cpp \ ../Code/Store3Webdav/WebdavThread.cpp \ ../Code/Store3Webdav/WebdavStore.cpp \ ../Code/Store3Webdav/WebdavFolder.cpp \ ../Code/Store3Webdav/WebdavContact.cpp \ ../Code/Store3Webdav/WebdavCalendar.cpp \ ../Code/Store3Mail3/Mail3Store.cpp \ ../Code/Store3Mail3/Mail3Mail.cpp \ ../Code/Store3Mail3/Mail3Group.cpp \ ../Code/Store3Mail3/Mail3Folder.cpp \ ../Code/Store3Mail3/Mail3Filter.cpp \ ../Code/Store3Mail3/Mail3Contact.cpp \ ../Code/Store3Mail3/Mail3Calendar.cpp \ ../Code/Store3Mail3/Mail3Attachment.cpp \ ../Code/Store3Imap/ScribeImap_Thread.cpp \ ../Code/Store3Imap/ScribeImap_Store.cpp \ ../Code/Store3Imap/ScribeImap_Mail.cpp \ ../Code/Store3Imap/ScribeImap_Folder.cpp \ ../Code/Store3Imap/ScribeImap_Attachment.cpp \ ../Code/Store3Common.cpp \ ../Code/Sqlite/v3.6.14/sqlite3.c \ ../Code/SecurityDlg.cpp \ ../Code/SearchView.cpp \ ../Code/Scripting.cpp \ ../Code/ScribeUtils.cpp \ ../Code/ScribeUi.cpp \ ../Code/ScribeThing.cpp \ ../Code/ScribeStatusPanel.cpp \ ../Code/ScribeSockets.cpp \ ../Code/ScribeSendReceive.cpp \ ../Code/ScribeRepair.cpp \ ../Code/ScribePassword.cpp \ ../Code/ScribePageSetup.cpp \ ../Code/ScribeMain.cpp \ ../Code/ScribeMail.cpp \ ../Code/ScribeListAddr.cpp \ ../Code/ScribeLangDlg.cpp \ ../Code/ScribeItemList.cpp \ ../Code/ScribeGroup.cpp \ ../Code/ScribeFolderTree.cpp \ ../Code/ScribeFolderSelect.cpp \ ../Code/ScribeFolderProp.cpp \ ../Code/ScribeFolderDlg.cpp \ ../Code/ScribeFolder.cpp \ ../Code/ScribeFinder.cpp \ ../Code/ScribeFilter.cpp \ ../Code/ScribeContact.cpp \ ../Code/ScribeAttachment.cpp \ ../Code/ScribeApp.cpp \ ../Code/ScribeAccountUI.cpp \ ../Code/ScribeAccountPreview.cpp \ ../Code/ScribeAccount.cpp \ ../Code/ScribeAbout.cpp \ ../Code/ReplicateDlg.cpp \ ../Code/RemoteCalendarSource.cpp \ ../Code/PrintPreview.cpp \ ../Code/PrintContext.cpp \ ../Code/PreviewPanel.cpp \ ../Code/OptionsDlg.cpp \ ../Code/ObjectInspector.cpp \ ../Code/MContainer.cpp \ ../Code/ManageMailStores.cpp \ ../Code/ImpExp_Mbox.cpp \ ../Code/Imp_OutlookExpress.cpp \ ../Code/Imp_NetscapeContacts.cpp \ ../Code/Imp_Mozilla.cpp \ ../Code/Imp_Eudora.cpp \ ../Code/Imp_Eml.cpp \ ../Code/Imp_CsvContacts.cpp \ ../Code/HtmlToText.cpp \ ../Code/GNewMailDlg.cpp \ ../Code/GBTreeWordStore.cpp \ ../Code/FolderCalendarSource.cpp \ ../Code/Exp_Scribe.cpp \ ../Code/Encryption/GnuPG.cpp \ ../Code/Components.cpp \ ../Code/CalendarView.cpp \ ../Code/Calendar.cpp \ ../Code/BayesianFilter.cpp \ ../Code/BayesDlg.cpp \ ../Code/AddressSelect.cpp \ ../../libs/Btree/offsetlist.c \ ../../libs/Btree/hashtable.c \ ../../libs/Btree/gbtree.cpp \ ../../libs/Btree/db_utils.c \ ../../libs/Btree/db_lock.c \ ../../libs/Btree/db_header.c \ ../../libs/Btree/db_cache.c \ ../../libs/Btree/db_blocks.c \ ../../libs/Btree/db_blocklist.c \ ../../libs/Btree/db.c \ ../../libs/Btree/btree_traverse.c \ ../../libs/Btree/btree_search.c \ ../../libs/Btree/btree_node.c \ ../../libs/Btree/btree_lock.c \ ../../libs/Btree/btree_insert.c \ ../../libs/Btree/btree_header.c \ ../../libs/Btree/btree_delete.c \ ../../libs/Btree/btree.c \ ../../../lgi/trunk/src/common/Widgets/ZoomView.cpp \ ../../../lgi/trunk/src/common/Widgets/YearView.cpp \ ../../../lgi/trunk/src/common/Widgets/TimePopup.cpp \ ../../../lgi/trunk/src/common/Widgets/MonthView.cpp \ ../../../lgi/trunk/src/common/Widgets/FilterUi.cpp \ ../../../lgi/trunk/src/common/Widgets/Editor/TextBlock.cpp \ ../../../lgi/trunk/src/common/Widgets/Editor/RichTextEditPriv.cpp \ ../../../lgi/trunk/src/common/Widgets/Editor/RichTextEdit.cpp \ ../../../lgi/trunk/src/common/Widgets/Editor/ImageBlock.cpp \ ../../../lgi/trunk/src/common/Widgets/Editor/HorzRuleBlock.cpp \ ../../../lgi/trunk/src/common/Widgets/Editor/BlockCursor.cpp \ ../../../lgi/trunk/src/common/Widgets/DatePopup.cpp \ ../../../lgi/trunk/src/common/Widgets/ControlTree.cpp \ ../../../lgi/trunk/src/common/Widgets/ColourSelect.cpp \ ../../../lgi/trunk/src/common/Text/XmlTreeUi.cpp \ ../../../lgi/trunk/src/common/Text/vCard-vCal.cpp \ ../../../lgi/trunk/src/common/Text/TextView4.cpp \ ../../../lgi/trunk/src/common/Text/TextConvert.cpp \ ../../../lgi/trunk/src/common/Text/SpellCheckAspell.cpp \ ../../../lgi/trunk/src/common/Text/HtmlParser.cpp \ ../../../lgi/trunk/src/common/Text/HtmlCommon.cpp \ ../../../lgi/trunk/src/common/Text/Html.cpp \ ../../../lgi/trunk/src/common/Text/Homoglyphs/HomoglyphsTable.cpp \ ../../../lgi/trunk/src/common/Text/Homoglyphs/Homoglyphs.cpp \ ../../../lgi/trunk/src/common/Text/Emoji/EmojiTools.cpp \ ../../../lgi/trunk/src/common/Text/Emoji/EmojiMap.cpp \ ../../../lgi/trunk/src/common/Net/WebDav.cpp \ ../../../lgi/trunk/src/common/Net/OpenSSLSocket.cpp \ ../../../lgi/trunk/src/common/Net/OAuth2.cpp \ ../../../lgi/trunk/src/common/Net/Mime.cpp \ ../../../lgi/trunk/src/common/Net/MailImap.cpp \ ../../../lgi/trunk/src/common/Net/MailHttp.cpp \ ../../../lgi/trunk/src/common/Net/Mail.cpp \ ../../../lgi/trunk/src/common/Net/HttpTools.cpp \ ../../../lgi/trunk/src/common/Net/Http.cpp \ ../../../lgi/trunk/src/common/Net/Ftp.cpp \ ../../../lgi/trunk/src/common/Lgi/LgiMain.cpp \ ../../../lgi/trunk/src/common/Lgi/Browser.cpp \ ../../../lgi/trunk/src/common/General/Tnef.cpp \ ../../../lgi/trunk/src/common/General/TimeZone.cpp \ ../../../lgi/trunk/src/common/General/SoftwareUpdate.cpp \ ../../../lgi/trunk/src/common/General/SharedMemory.cpp \ ../../../lgi/trunk/src/common/General/SegmentTree.cpp \ ../../../lgi/trunk/src/common/General/Growl.cpp \ ../../../lgi/trunk/src/common/Gdc2/Font/EmojiFont.cpp \ ../../../lgi/trunk/src/common/Gdc2/Filters/Png.cpp \ ../../../lgi/trunk/src/common/Gdc2/Filters/Lzw.cpp \ ../../../lgi/trunk/src/common/Gdc2/Filters/Jpeg.cpp \ ../../../lgi/trunk/src/common/Gdc2/Filters/Gif.cpp \ ../../../lgi/trunk/src/common/Gdc2/DrawListSurface.cpp \ ../../../lgi/trunk/src/common/Db/Db-Csv.cpp \ ../../../lgi/trunk/src/common/Coding/ScriptVM.cpp \ ../../../lgi/trunk/src/common/Coding/ScriptLibrary.cpp \ ../../../lgi/trunk/src/common/Coding/ScriptCompiler.cpp \ ../../../lgi/trunk/src/common/Coding/LexCpp.cpp SourceLst := $(patsubst %.c,%.o,$(patsubst %.cpp,%.o,$(Source))) Objects := $(addprefix $(BuildDir)/,$(SourceLst)) # Target # Executable target -$(Target) : ../../libs/aspell-0.60.6.1/linux/$(BuildDir)/libaspell-dist-060$(Tag).so ../../libs/bzip2-1.0.6/linux/$(BuildDir)/libbzip2$(Tag).a ../../../lgi/trunk/$(BuildDir)/liblgi-gtk3$(Tag).so $(Objects) +$(Target) : ../../libs/aspell-0.60.6.1/linux/$(BuildDir)/libaspell-dist-060$(Tag).so ../../libs/bzip2-1.0.6/linux/$(BuildDir)/libbzip2$(Tag).a ../../../lgi/trunk/$(BuildDir)/liblgi-dev$(Tag).so $(Objects) mkdir -p $(BuildDir) @echo Linking $(Target) [$(Build)]... $(CPP) -Wl,-export-dynamic,-R. -o \ $(Target) $(Objects) $(Libs) @echo Done. ../../libs/aspell-0.60.6.1/linux/$(BuildDir)/libaspell-dist-060$(Tag).so : ../../libs/aspell-0.60.6.1/common/asc_ctype.hpp \ ../../libs/aspell-0.60.6.1/common/basic_list.hpp \ ../../libs/aspell-0.60.6.1/common/block_slist-t.hpp \ ../../libs/aspell-0.60.6.1/common/block_slist.hpp \ ../../libs/aspell-0.60.6.1/common/cache-t.hpp \ ../../libs/aspell-0.60.6.1/common/cache.cpp \ ../../libs/aspell-0.60.6.1/common/cache.hpp \ ../../libs/aspell-0.60.6.1/common/can_have_error.cpp \ ../../libs/aspell-0.60.6.1/common/can_have_error.hpp \ ../../libs/aspell-0.60.6.1/common/char_vector.hpp \ ../../libs/aspell-0.60.6.1/common/clone_ptr-t.hpp \ ../../libs/aspell-0.60.6.1/common/clone_ptr.hpp \ ../../libs/aspell-0.60.6.1/common/config.cpp \ ../../libs/aspell-0.60.6.1/common/config.hpp \ ../../libs/aspell-0.60.6.1/common/convert.cpp \ ../../libs/aspell-0.60.6.1/common/convert.hpp \ ../../libs/aspell-0.60.6.1/common/copy_ptr.hpp \ ../../libs/aspell-0.60.6.1/common/document_checker.cpp \ ../../libs/aspell-0.60.6.1/common/document_checker.hpp \ ../../libs/aspell-0.60.6.1/common/enumeration.hpp \ ../../libs/aspell-0.60.6.1/common/error.cpp \ ../../libs/aspell-0.60.6.1/common/error.hpp \ ../../libs/aspell-0.60.6.1/common/errors.cpp \ ../../libs/aspell-0.60.6.1/common/errors.hpp \ ../../libs/aspell-0.60.6.1/common/file_data_util.cpp \ ../../libs/aspell-0.60.6.1/common/file_data_util.hpp \ ../../libs/aspell-0.60.6.1/common/file_util.cpp \ ../../libs/aspell-0.60.6.1/common/file_util.hpp \ ../../libs/aspell-0.60.6.1/common/filter.cpp \ ../../libs/aspell-0.60.6.1/common/filter.hpp \ ../../libs/aspell-0.60.6.1/common/filter_char.hpp \ ../../libs/aspell-0.60.6.1/common/filter_char_vector.hpp \ ../../libs/aspell-0.60.6.1/common/fstream.cpp \ ../../libs/aspell-0.60.6.1/common/fstream.hpp \ ../../libs/aspell-0.60.6.1/common/generic_copy_ptr-t.hpp \ ../../libs/aspell-0.60.6.1/common/generic_copy_ptr.hpp \ ../../libs/aspell-0.60.6.1/common/getdata.cpp \ ../../libs/aspell-0.60.6.1/common/getdata.hpp \ ../../libs/aspell-0.60.6.1/common/gettext.h \ ../../libs/aspell-0.60.6.1/common/gettext_init.cpp \ ../../libs/aspell-0.60.6.1/common/hash-t.hpp \ ../../libs/aspell-0.60.6.1/common/hash.hpp \ ../../libs/aspell-0.60.6.1/common/hash_fun.hpp \ ../../libs/aspell-0.60.6.1/common/indiv_filter.hpp \ ../../libs/aspell-0.60.6.1/common/info.cpp \ ../../libs/aspell-0.60.6.1/common/info.hpp \ ../../libs/aspell-0.60.6.1/common/iostream.cpp \ ../../libs/aspell-0.60.6.1/common/iostream.hpp \ ../../libs/aspell-0.60.6.1/common/istream.hpp \ ../../libs/aspell-0.60.6.1/common/itemize.cpp \ ../../libs/aspell-0.60.6.1/common/itemize.hpp \ ../../libs/aspell-0.60.6.1/common/key_info.hpp \ ../../libs/aspell-0.60.6.1/common/lock.hpp \ ../../libs/aspell-0.60.6.1/common/lsort.hpp \ ../../libs/aspell-0.60.6.1/common/mutable_container.hpp \ ../../libs/aspell-0.60.6.1/common/mutable_string.hpp \ ../../libs/aspell-0.60.6.1/common/ndebug.hpp \ ../../libs/aspell-0.60.6.1/common/objstack.cpp \ ../../libs/aspell-0.60.6.1/common/objstack.hpp \ ../../libs/aspell-0.60.6.1/common/ostream.hpp \ ../../libs/aspell-0.60.6.1/common/parm_string.hpp \ ../../libs/aspell-0.60.6.1/common/posib_err.cpp \ ../../libs/aspell-0.60.6.1/common/posib_err.hpp \ ../../libs/aspell-0.60.6.1/common/simple_string.hpp \ ../../libs/aspell-0.60.6.1/common/speller.cpp \ ../../libs/aspell-0.60.6.1/common/speller.hpp \ ../../libs/aspell-0.60.6.1/common/stack_ptr.hpp \ ../../libs/aspell-0.60.6.1/common/string.cpp \ ../../libs/aspell-0.60.6.1/common/string.hpp \ ../../libs/aspell-0.60.6.1/common/string_enumeration.hpp \ ../../libs/aspell-0.60.6.1/common/string_list.cpp \ ../../libs/aspell-0.60.6.1/common/string_list.hpp \ ../../libs/aspell-0.60.6.1/common/string_map.cpp \ ../../libs/aspell-0.60.6.1/common/string_map.hpp \ ../../libs/aspell-0.60.6.1/common/string_pair.hpp \ ../../libs/aspell-0.60.6.1/common/string_pair_enumeration.hpp \ ../../libs/aspell-0.60.6.1/common/strtonum.cpp \ ../../libs/aspell-0.60.6.1/common/strtonum.hpp \ ../../libs/aspell-0.60.6.1/common/tokenizer.cpp \ ../../libs/aspell-0.60.6.1/common/tokenizer.hpp \ ../../libs/aspell-0.60.6.1/common/type_id.hpp \ ../../libs/aspell-0.60.6.1/common/vararray.hpp \ ../../libs/aspell-0.60.6.1/common/vector.hpp \ ../../libs/aspell-0.60.6.1/common/word_list.hpp \ ../../libs/aspell-0.60.6.1/gen/static_filters.src.cpp \ ../../libs/aspell-0.60.6.1/interfaces/cc/aspell.h \ ../../libs/aspell-0.60.6.1/interfaces/cc/pspell.h \ ../../libs/aspell-0.60.6.1/lib/can_have_error-c.cpp \ ../../libs/aspell-0.60.6.1/lib/config-c.cpp \ ../../libs/aspell-0.60.6.1/lib/find_speller.cpp \ ../../libs/aspell-0.60.6.1/lib/info-c.cpp \ ../../libs/aspell-0.60.6.1/lib/new_checker.cpp \ ../../libs/aspell-0.60.6.1/lib/new_config.cpp \ ../../libs/aspell-0.60.6.1/lib/new_filter.cpp \ ../../libs/aspell-0.60.6.1/lib/new_fmode.cpp \ ../../libs/aspell-0.60.6.1/lib/speller-c.cpp \ ../../libs/aspell-0.60.6.1/lib/string_enumeration-c.cpp \ ../../libs/aspell-0.60.6.1/lib/word_list-c.cpp \ ../../libs/aspell-0.60.6.1/linux/dirs.h \ ../../libs/aspell-0.60.6.1/linux/Makefile.linux \ ../../libs/aspell-0.60.6.1/linux/settings.h \ ../../libs/aspell-0.60.6.1/modules/filter/context.cpp \ ../../libs/aspell-0.60.6.1/modules/filter/email.cpp \ ../../libs/aspell-0.60.6.1/modules/filter/nroff.cpp \ ../../libs/aspell-0.60.6.1/modules/filter/sgml.cpp \ ../../libs/aspell-0.60.6.1/modules/filter/tex.cpp \ ../../libs/aspell-0.60.6.1/modules/filter/texinfo.cpp \ ../../libs/aspell-0.60.6.1/modules/filter/url.cpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/affix.cpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/affix.hpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/asuggest.hpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/block_vector.hpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/check_list.hpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/data.cpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/data.hpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/data_id.hpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/data_util.hpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/editdist.cpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/editdist.hpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/editdist2.hpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/language.cpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/language.hpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/leditdist.cpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/leditdist.hpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/Makefile.in \ ../../libs/aspell-0.60.6.1/modules/speller/default/matrix.hpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/multi_ws.cpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/phonet.cpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/phonet.hpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/phonetic.cpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/phonetic.hpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/primes.cpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/primes.hpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/readonly_ws.cpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/speller_impl.cpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/speller_impl.hpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/suggest.cpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/suggest.hpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/typo_editdist.cpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/typo_editdist.hpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/vector_hash-t.hpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/vector_hash.hpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/weights.hpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/wordinfo.hpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/writable.cpp \ ../../libs/aspell-0.60.6.1/modules/tokenizer/basic.cpp \ ../../libs/aspell-0.60.6.1/prog/prezip.c \ ../../libs/aspell-0.60.6.1/win32/api-glue.cpp \ ../../libs/aspell-0.60.6.1/win32/settings.h export Build=$(Build); \ $(MAKE) -C ../../libs/aspell-0.60.6.1/linux -f Makefile.linux ../../libs/bzip2-1.0.6/linux/$(BuildDir)/libbzip2$(Tag).a : ../../libs/bzip2-1.0.6/blocksort.c \ ../../libs/bzip2-1.0.6/bzlib.c \ ../../libs/bzip2-1.0.6/bzlib.h \ ../../libs/bzip2-1.0.6/bzlib_private.h \ ../../libs/bzip2-1.0.6/compress.c \ ../../libs/bzip2-1.0.6/crctable.c \ ../../libs/bzip2-1.0.6/decompress.c \ ../../libs/bzip2-1.0.6/huffman.c \ ../../libs/bzip2-1.0.6/randtable.c export Build=$(Build); \ $(MAKE) -C ../../libs/bzip2-1.0.6/linux -f Makefile.linux -../../../lgi/trunk/$(BuildDir)/liblgi-gtk3$(Tag).so : ../../../lgi/trunk/include/lgi/common/App.h \ +../../../lgi/trunk/$(BuildDir)/liblgi-dev$(Tag).so : ../../../lgi/trunk/include/lgi/common/App.h \ ../../../lgi/trunk/include/lgi/common/Array.h \ ../../../lgi/trunk/include/lgi/common/AutoPtr.h \ ../../../lgi/trunk/include/lgi/common/Base64.h \ ../../../lgi/trunk/include/lgi/common/Bitmap.h \ ../../../lgi/trunk/include/lgi/common/Box.h \ ../../../lgi/trunk/include/lgi/common/Button.h \ ../../../lgi/trunk/include/lgi/common/CairoSurface.h \ ../../../lgi/trunk/include/lgi/common/Cancel.h \ ../../../lgi/trunk/include/lgi/common/Capabilities.h \ ../../../lgi/trunk/include/lgi/common/Charset.h \ ../../../lgi/trunk/include/lgi/common/CheckBox.h \ ../../../lgi/trunk/include/lgi/common/ClipBoard.h \ ../../../lgi/trunk/include/lgi/common/Colour.h \ ../../../lgi/trunk/include/lgi/common/ColourSpace.h \ ../../../lgi/trunk/include/lgi/common/Com.h \ ../../../lgi/trunk/include/lgi/common/Combo.h \ ../../../lgi/trunk/include/lgi/common/Containers.h \ ../../../lgi/trunk/include/lgi/common/Core.h \ ../../../lgi/trunk/include/lgi/common/Css.h \ ../../../lgi/trunk/include/lgi/common/CssTools.h \ ../../../lgi/trunk/include/lgi/common/CurrentTime.h \ ../../../lgi/trunk/include/lgi/common/DataDlg.h \ ../../../lgi/trunk/include/lgi/common/DateTime.h \ ../../../lgi/trunk/include/lgi/common/Dialog.h \ ../../../lgi/trunk/include/lgi/common/DisplayString.h \ ../../../lgi/trunk/include/lgi/common/DocView.h \ ../../../lgi/trunk/include/lgi/common/Dom.h \ ../../../lgi/trunk/include/lgi/common/DomFields.h \ ../../../lgi/trunk/include/lgi/common/DragAndDrop.h \ ../../../lgi/trunk/include/lgi/common/DropFiles.h \ ../../../lgi/trunk/include/lgi/common/Edit.h \ ../../../lgi/trunk/include/lgi/common/Error.h \ ../../../lgi/trunk/include/lgi/common/EventTargetThread.h \ ../../../lgi/trunk/include/lgi/common/File.h \ ../../../lgi/trunk/include/lgi/common/FileSelect.h \ ../../../lgi/trunk/include/lgi/common/Filter.h \ ../../../lgi/trunk/include/lgi/common/FindReplaceDlg.h \ ../../../lgi/trunk/include/lgi/common/Font.h \ ../../../lgi/trunk/include/lgi/common/FontCache.h \ ../../../lgi/trunk/include/lgi/common/FontSelect.h \ ../../../lgi/trunk/include/lgi/common/Gdc2.h \ ../../../lgi/trunk/include/lgi/common/GdcTools.h \ ../../../lgi/trunk/include/lgi/common/GdiLeak.h \ ../../../lgi/trunk/include/lgi/common/HashTable.h \ ../../../lgi/trunk/include/lgi/common/ImageList.h \ ../../../lgi/trunk/include/lgi/common/Input.h \ ../../../lgi/trunk/include/lgi/common/ItemContainer.h \ ../../../lgi/trunk/include/lgi/common/Json.h \ ../../../lgi/trunk/include/lgi/common/Layout.h \ ../../../lgi/trunk/include/lgi/common/Lgi.h \ ../../../lgi/trunk/include/lgi/common/LgiClasses.h \ ../../../lgi/trunk/include/lgi/common/LgiCommon.h \ ../../../lgi/trunk/include/lgi/common/LgiDefs.h \ ../../../lgi/trunk/include/lgi/common/LgiInc.h \ ../../../lgi/trunk/include/lgi/common/LgiInterfaces.h \ ../../../lgi/trunk/include/lgi/common/LgiMsgs.h \ ../../../lgi/trunk/include/lgi/common/LgiNetInc.h \ ../../../lgi/trunk/include/lgi/common/LgiRes.h \ ../../../lgi/trunk/include/lgi/common/LgiString.h \ ../../../lgi/trunk/include/lgi/common/LgiUiBase.h \ ../../../lgi/trunk/include/lgi/common/Library.h \ ../../../lgi/trunk/include/lgi/common/LibraryUtils.h \ ../../../lgi/trunk/include/lgi/common/List.h \ ../../../lgi/trunk/include/lgi/common/ListItemCheckBox.h \ ../../../lgi/trunk/include/lgi/common/ListItemRadioBtn.h \ ../../../lgi/trunk/include/lgi/common/LMallocArray.h \ ../../../lgi/trunk/include/lgi/common/Mail.h \ ../../../lgi/trunk/include/lgi/common/Matrix.h \ ../../../lgi/trunk/include/lgi/common/Mem.h \ ../../../lgi/trunk/include/lgi/common/Menu.h \ ../../../lgi/trunk/include/lgi/common/Message.h \ ../../../lgi/trunk/include/lgi/common/Mime.h \ ../../../lgi/trunk/include/lgi/common/Mru.h \ ../../../lgi/trunk/include/lgi/common/Mutex.h \ ../../../lgi/trunk/include/lgi/common/Net.h \ ../../../lgi/trunk/include/lgi/common/NetTools.h \ ../../../lgi/trunk/include/lgi/common/Notifications.h \ ../../../lgi/trunk/include/lgi/common/OAuth2.h \ ../../../lgi/trunk/include/lgi/common/OptionsFile.h \ ../../../lgi/trunk/include/lgi/common/Palette.h \ ../../../lgi/trunk/include/lgi/common/Panel.h \ ../../../lgi/trunk/include/lgi/common/Password.h \ ../../../lgi/trunk/include/lgi/common/Path.h \ ../../../lgi/trunk/include/lgi/common/PixelRops.h \ ../../../lgi/trunk/include/lgi/common/Point.h \ ../../../lgi/trunk/include/lgi/common/Popup.h \ ../../../lgi/trunk/include/lgi/common/PopupList.h \ ../../../lgi/trunk/include/lgi/common/Printer.h \ ../../../lgi/trunk/include/lgi/common/Profile.h \ ../../../lgi/trunk/include/lgi/common/Progress.h \ ../../../lgi/trunk/include/lgi/common/ProgressDlg.h \ ../../../lgi/trunk/include/lgi/common/ProgressView.h \ ../../../lgi/trunk/include/lgi/common/Properties.h \ ../../../lgi/trunk/include/lgi/common/RadioGroup.h \ ../../../lgi/trunk/include/lgi/common/Range.h \ ../../../lgi/trunk/include/lgi/common/Rect.h \ ../../../lgi/trunk/include/lgi/common/RectF.h \ ../../../lgi/trunk/include/lgi/common/RefCount.h \ ../../../lgi/trunk/include/lgi/common/RegKey.h \ ../../../lgi/trunk/include/lgi/common/Res.h \ ../../../lgi/trunk/include/lgi/common/Rops.h \ ../../../lgi/trunk/include/lgi/common/ScrollBar.h \ ../../../lgi/trunk/include/lgi/common/SkinEngine.h \ ../../../lgi/trunk/include/lgi/common/Slider.h \ ../../../lgi/trunk/include/lgi/common/Splitter.h \ ../../../lgi/trunk/include/lgi/common/StatusBar.h \ ../../../lgi/trunk/include/lgi/common/Store3Defs.h \ ../../../lgi/trunk/include/lgi/common/Stream.h \ ../../../lgi/trunk/include/lgi/common/StringClass.h \ ../../../lgi/trunk/include/lgi/common/StringLayout.h \ ../../../lgi/trunk/include/lgi/common/StructuredIo.h \ ../../../lgi/trunk/include/lgi/common/StructuredLog.h \ ../../../lgi/trunk/include/lgi/common/SubProcess.h \ ../../../lgi/trunk/include/lgi/common/TableLayout.h \ ../../../lgi/trunk/include/lgi/common/TabView.h \ ../../../lgi/trunk/include/lgi/common/TextFile.h \ ../../../lgi/trunk/include/lgi/common/TextLabel.h \ ../../../lgi/trunk/include/lgi/common/TextLog.h \ ../../../lgi/trunk/include/lgi/common/TextView3.h \ ../../../lgi/trunk/include/lgi/common/Thread.h \ ../../../lgi/trunk/include/lgi/common/ThreadEvent.h \ ../../../lgi/trunk/include/lgi/common/Token.h \ ../../../lgi/trunk/include/lgi/common/ToolBar.h \ ../../../lgi/trunk/include/lgi/common/ToolTip.h \ ../../../lgi/trunk/include/lgi/common/TrayIcon.h \ ../../../lgi/trunk/include/lgi/common/Tree.h \ ../../../lgi/trunk/include/lgi/common/Undo.h \ ../../../lgi/trunk/include/lgi/common/Unicode.h \ ../../../lgi/trunk/include/lgi/common/UnicodeString.h \ ../../../lgi/trunk/include/lgi/common/UnrolledList.h \ ../../../lgi/trunk/include/lgi/common/Variant.h \ ../../../lgi/trunk/include/lgi/common/View.h \ ../../../lgi/trunk/include/lgi/common/Widgets.h \ ../../../lgi/trunk/include/lgi/common/Window.h \ ../../../lgi/trunk/include/lgi/common/XmlTree.h \ ../../../lgi/trunk/include/lgi/linux/Gtk/LgiOsClasses.h \ ../../../lgi/trunk/include/lgi/linux/Gtk/LgiOsDefs.h \ ../../../lgi/trunk/include/lgi/linux/Gtk/LgiWidget.h \ ../../../lgi/trunk/include/lgi/linux/Gtk/LgiWinManGlue.h \ ../../../lgi/trunk/include/lgi/linux/SymLookup.h \ ../../../lgi/trunk/include/lgi/mac/cocoa/LCocoaView.h \ ../../../lgi/trunk/include/lgi/mac/cocoa/LgiMac.h \ ../../../lgi/trunk/include/lgi/mac/cocoa/LgiOs.h \ ../../../lgi/trunk/include/lgi/mac/cocoa/LgiOsClasses.h \ ../../../lgi/trunk/include/lgi/mac/cocoa/LgiOsDefs.h \ ../../../lgi/trunk/include/lgi/mac/cocoa/ObjCWrapper.h \ ../../../lgi/trunk/include/lgi/mac/cocoa/SymLookup.h \ ../../../lgi/trunk/private/common/FontPriv.h \ ../../../lgi/trunk/private/common/ViewPriv.h \ ../../../lgi/trunk/private/linux/AppPriv.h \ ../../../lgi/trunk/src/common/Gdc2/15Bit.cpp \ ../../../lgi/trunk/src/common/Gdc2/16Bit.cpp \ ../../../lgi/trunk/src/common/Gdc2/24Bit.cpp \ ../../../lgi/trunk/src/common/Gdc2/32Bit.cpp \ ../../../lgi/trunk/src/common/Gdc2/8Bit.cpp \ ../../../lgi/trunk/src/common/Gdc2/Alpha.cpp \ ../../../lgi/trunk/src/common/Gdc2/Colour.cpp \ ../../../lgi/trunk/src/common/Gdc2/Filters/Filter.cpp \ ../../../lgi/trunk/src/common/Gdc2/Font/Charset.cpp \ ../../../lgi/trunk/src/common/Gdc2/Font/DisplayString.cpp \ ../../../lgi/trunk/src/common/Gdc2/Font/Font.cpp \ ../../../lgi/trunk/src/common/Gdc2/Font/FontSystem.cpp \ ../../../lgi/trunk/src/common/Gdc2/Font/FontType.cpp \ ../../../lgi/trunk/src/common/Gdc2/Font/StringLayout.cpp \ ../../../lgi/trunk/src/common/Gdc2/Font/TypeFace.cpp \ ../../../lgi/trunk/src/common/Gdc2/GdcCommon.cpp \ ../../../lgi/trunk/src/common/Gdc2/Path/Path.cpp \ ../../../lgi/trunk/src/common/Gdc2/Rect.cpp \ ../../../lgi/trunk/src/common/Gdc2/RopsCases.cpp \ ../../../lgi/trunk/src/common/Gdc2/Surface.cpp \ ../../../lgi/trunk/src/common/Gdc2/Tools/ColourReduce.cpp \ ../../../lgi/trunk/src/common/Gdc2/Tools/GdcTools.cpp \ ../../../lgi/trunk/src/common/General/Containers.cpp \ ../../../lgi/trunk/src/common/General/DateTime.cpp \ ../../../lgi/trunk/src/common/General/ExeCheck.cpp \ ../../../lgi/trunk/src/common/General/FileCommon.cpp \ ../../../lgi/trunk/src/common/General/Password.cpp \ ../../../lgi/trunk/src/common/General/Properties.cpp \ ../../../lgi/trunk/src/common/Hash/md5/md5.c \ ../../../lgi/trunk/src/common/Hash/md5/md5.h \ ../../../lgi/trunk/src/common/Hash/sha1/sha1.c \ ../../../lgi/trunk/src/common/Hash/sha1/sha1.h \ ../../../lgi/trunk/src/common/Lgi/Alert.cpp \ ../../../lgi/trunk/src/common/Lgi/AppCommon.cpp \ ../../../lgi/trunk/src/common/Lgi/Css.cpp \ ../../../lgi/trunk/src/common/Lgi/CssTools.cpp \ ../../../lgi/trunk/src/common/Lgi/DataDlg.cpp \ ../../../lgi/trunk/src/common/Lgi/DragAndDropCommon.cpp \ ../../../lgi/trunk/src/common/Lgi/FileSelect.cpp \ ../../../lgi/trunk/src/common/Lgi/FindReplace.cpp \ ../../../lgi/trunk/src/common/Lgi/FontSelect.cpp \ ../../../lgi/trunk/src/common/Lgi/GuiUtils.cpp \ ../../../lgi/trunk/src/common/Lgi/Input.cpp \ ../../../lgi/trunk/src/common/Lgi/LgiCommon.cpp \ ../../../lgi/trunk/src/common/Lgi/Library.cpp \ ../../../lgi/trunk/src/common/Lgi/LMsg.cpp \ ../../../lgi/trunk/src/common/Lgi/MemStream.cpp \ ../../../lgi/trunk/src/common/Lgi/MenuCommon.cpp \ ../../../lgi/trunk/src/common/Lgi/Mru.cpp \ ../../../lgi/trunk/src/common/Lgi/Mutex.cpp \ - ../../../lgi/trunk/src/common/Lgi/Object.cpp \ ../../../lgi/trunk/src/common/Lgi/OptionsFile.cpp \ ../../../lgi/trunk/src/common/Lgi/Rand.cpp \ ../../../lgi/trunk/src/common/Lgi/Stream.cpp \ ../../../lgi/trunk/src/common/Lgi/SubProcess.cpp \ ../../../lgi/trunk/src/common/Lgi/ThreadCommon.cpp \ ../../../lgi/trunk/src/common/Lgi/ThreadEvent.cpp \ - ../../../lgi/trunk/src/common/Lgi/ToolTip.cpp \ - ../../../lgi/trunk/src/common/Lgi/TrayIcon.cpp \ ../../../lgi/trunk/src/common/Lgi/Variant.cpp \ ../../../lgi/trunk/src/common/Lgi/ViewCommon.cpp \ ../../../lgi/trunk/src/common/Lgi/WindowCommon.cpp \ ../../../lgi/trunk/src/common/Net/Base64.cpp \ ../../../lgi/trunk/src/common/Net/MDStringToDigest.cpp \ ../../../lgi/trunk/src/common/Net/Net.cpp \ ../../../lgi/trunk/src/common/Net/NetTools.cpp \ ../../../lgi/trunk/src/common/Net/Uri.cpp \ ../../../lgi/trunk/src/common/Resource/LgiRes.cpp \ ../../../lgi/trunk/src/common/Resource/Res.cpp \ ../../../lgi/trunk/src/common/Skins/Gel/Gel.cpp \ ../../../lgi/trunk/src/common/Text/DocView.cpp \ ../../../lgi/trunk/src/common/Text/String.cpp \ ../../../lgi/trunk/src/common/Text/TextView3.cpp \ ../../../lgi/trunk/src/common/Text/Token.cpp \ ../../../lgi/trunk/src/common/Text/Unicode.cpp \ ../../../lgi/trunk/src/common/Text/Utf8.cpp \ ../../../lgi/trunk/src/common/Text/XmlTree.cpp \ ../../../lgi/trunk/src/common/Widgets/Bitmap.cpp \ ../../../lgi/trunk/src/common/Widgets/Box.cpp \ ../../../lgi/trunk/src/common/Widgets/Button.cpp \ ../../../lgi/trunk/src/common/Widgets/CheckBox.cpp \ ../../../lgi/trunk/src/common/Widgets/Combo.cpp \ ../../../lgi/trunk/src/common/Widgets/Edit.cpp \ ../../../lgi/trunk/src/common/Widgets/ItemContainer.cpp \ ../../../lgi/trunk/src/common/Widgets/List.cpp \ ../../../lgi/trunk/src/common/Widgets/Panel.cpp \ ../../../lgi/trunk/src/common/Widgets/Popup.cpp \ ../../../lgi/trunk/src/common/Widgets/Progress.cpp \ ../../../lgi/trunk/src/common/Widgets/ProgressDlg.cpp \ ../../../lgi/trunk/src/common/Widgets/RadioGroup.cpp \ ../../../lgi/trunk/src/common/Widgets/ScrollBar.cpp \ ../../../lgi/trunk/src/common/Widgets/Slider.cpp \ ../../../lgi/trunk/src/common/Widgets/Splitter.cpp \ ../../../lgi/trunk/src/common/Widgets/StatusBar.cpp \ ../../../lgi/trunk/src/common/Widgets/TableLayout.cpp \ ../../../lgi/trunk/src/common/Widgets/TabView.cpp \ ../../../lgi/trunk/src/common/Widgets/TextLabel.cpp \ ../../../lgi/trunk/src/common/Widgets/ToolBar.cpp \ ../../../lgi/trunk/src/common/Widgets/Tree.cpp \ + ../../../lgi/trunk/src/haiku/ShowFileProp_Haiku.cpp \ ../../../lgi/trunk/src/linux/General/File.cpp \ ../../../lgi/trunk/src/linux/General/Mem.cpp \ ../../../lgi/trunk/src/linux/General/ShowFileProp_Linux.cpp \ ../../../lgi/trunk/src/linux/Gtk/Gdc2.cpp \ ../../../lgi/trunk/src/linux/Gtk/LgiWidget.cpp \ ../../../lgi/trunk/src/linux/Gtk/MemDC.cpp \ ../../../lgi/trunk/src/linux/Gtk/PrintDC.cpp \ ../../../lgi/trunk/src/linux/Gtk/ScreenDC.cpp \ ../../../lgi/trunk/src/linux/Lgi/App.cpp \ ../../../lgi/trunk/src/linux/Lgi/ClipBoard.cpp \ ../../../lgi/trunk/src/linux/Lgi/DragAndDrop.cpp \ ../../../lgi/trunk/src/linux/Lgi/General.cpp \ ../../../lgi/trunk/src/linux/Lgi/Layout.cpp \ ../../../lgi/trunk/src/linux/Lgi/Menu.cpp \ ../../../lgi/trunk/src/linux/Lgi/Printer.cpp \ ../../../lgi/trunk/src/linux/Lgi/Thread.cpp \ ../../../lgi/trunk/src/linux/Lgi/View.cpp \ ../../../lgi/trunk/src/linux/Lgi/Widgets.cpp \ ../../../lgi/trunk/src/linux/Lgi/Window.cpp export Build=$(Build); \ $(MAKE) -C ../../../lgi/trunk -f Makefile.linux .SECONDEXPANSION: $(Objects): $(BuildDir)/%.o: $$(wildcard %.c*) mkdir -p $(@D) @echo $("); if (Id != NULL) { Existing = Msgs[Id]; if (Existing) { // Decide which email is older if (t.DateReceived < Existing.DateReceived) { // t is earlier, t replaces existing item Dupes[-1] = Existing; Msgs[Id] = t; } else { // existing is earlier, delete t Dupes[-1] = t; } } else { Msgs[Id] = t; } } else Print("No msgid at " + i + "\n"); } else Print("No item at " + i + "\n"); } if (Dupes.Length > 0) { if (MessageDlg(App, Sprintf(LoadString(1013), Dupes.Length), App.Name, 4) == 6) { for (i=0; i