diff --git a/Code/BayesianFilter.cpp b/Code/BayesianFilter.cpp --- a/Code/BayesianFilter.cpp +++ b/Code/BayesianFilter.cpp @@ -1,1855 +1,1855 @@ #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) + LMessage::Result OnEvent(LMessage *Msg) override { 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, 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; ProcessWords(t->Words, [this, t, &entries](auto w) { size_t nextErr = 0; 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, [this, Ws, Add, &status](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() + int Main() override { 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, [this, Type](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(); } 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), [&Ignore](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/Encryption/GnuPG.cpp b/Code/Encryption/GnuPG.cpp --- a/Code/Encryption/GnuPG.cpp +++ b/Code/Encryption/GnuPG.cpp @@ -1,1970 +1,1971 @@ /* https://lists.gnupg.org/pipermail/gnupg-users/2011-November/043223.html gpg --list-packets ? Mime hierarchy: Signed: multipart/signed "This is an OpenPGP/MIME signed message (RFC 4880 and 3156)" { multipart/mixed text/plain Body of the message } application/pgp-signature "-----BEGIN PGP SIGNATURE-----" Signed, key attached: multipart/signed "This is an OpenPGP/MIME signed message (RFC 4880 and 3156)" { multipart/mixed text/plain Body of the message application/pgp-keys "-----BEGIN PGP PUBLIC KEY BLOCK-----" } application/pgp-signature "-----BEGIN PGP SIGNATURE-----" Signed, HTML, attachment, key: multipart/signed { multipart/mixed multipart/mixed multipart/alternative text/plain text/html application/octet-stream application/pgp-keys } application/pgp-signature Encrypted: multipart/encrypted application/pgp-encrypted application/octet-stream { multipart/mixed multipart/mixed text/plain } Encrypted + key attached: multipart/encrypted application/pgp-encrypted application/octet-stream { multipart/mixed multipart/mixed text/plain application/pgp-keys } */ #include "lgi/common/Lgi.h" #include "Scribe.h" #include "GnuPG.h" #include "lgi/common/TextLabel.h" #include "lgi/common/Button.h" #include "lgi/common/CheckBox.h" #include "lgi/common/Combo.h" #include "lgi/common/Css.h" #include "lgi/common/ThreadEvent.h" #include "lgi/common/SubProcess.h" #include "ScribeListAddr.h" #include "Store3Common.h" #include "lgi/common/LgiRes.h" #include "resdefs.h" #include "lgi/common/CssTools.h" ////////////////////////////////////////////////////////////////////////////////////////// enum Ctrls { IDC_SIGN = 700, IDC_ENCRYPT, IDC_ATTACH_PUB_KEY, IDC_DECRYPT, IDC_INSTALL }; static LColour cGood (0, 204, 0); static LColour cWarn (255, 154, 0); static LColour cError (255, 0, 0); static LColour cTxt (L_TEXT); #define cDefaultListItemColour 0 #define PANEL_BORDER_PX 2 static const char *GpgInstall = "https://www.gnupg.org/download/index.en.html"; static const char *GpgBin = "gpg" LGI_EXECUTABLE_EXT; static LString GpgBinPath; #define SECONDS * 1000 #define MINUTES * 60 #define GPG_KEY_STALE_TIMEOUT (10 MINUTES) #define DecryptStatus(val) \ if (callback) callback(val); \ return; typedef LArray 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); } void GetPassword(LViewI *Parent, LString Addr, std::function Callback) { for (unsigned i=0; iDoModal([this, Dlg, Addr, Callback](auto dlg, auto ctrlId) { if (ctrlId) { UserPassword &p = Psw.New(); p.Email = Addr; p.Password = Dlg->GetStr(); p.Ts = LCurrentTime(); 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(NULL); break; } case IDC_INSTALL: { LExecute(GpgInstall); break; } } return 0; } void DeleteChildSegments(LDataPropI *d) { LDataIt 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; } void MailUiGpg::Decrypt(std::function callback) { // Get the connector... GpgConnector *Conn = d->App->GetGpgConnector(); if (!Conn) { d->NotInstalled(); DecryptStatus(1); } // Find the right attachment... Mail *m = d->Ui->GetItem(); if (!m) { d->SetError(LLoadString(IDS_GNUPG_ERR_NO_MAIL)); DecryptStatus(1); } LDataPropI *Root = m->GetObject()->GetObj(FIELD_MIME_SEG); if (!Root) { d->SetError(LLoadString(IDS_GNUPG_ERR_NO_ROOT)); DecryptStatus(1); } auto Mt = Root->GetStr(FIELD_MIME_TYPE); if (!Mt || _stricmp(Mt, sMultipartEncrypted)) { d->SetError(LLoadString(IDS_GNUPG_ERR_WRONG_MIME)); DecryptStatus(1); } LDataI *EncryptedObj = NULL; LArray Objs; if (!m->GetAttachmentObjs(Objs)) - DecryptStatus(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)); 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); } } LDataIt 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)); DecryptStatus(1); } d->GetPassword(d->Ui, ToEmail[0], [this, callback, EncryptedObj, Conn, m](auto Pass) { 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)); DecryptStatus(1); } DecryptStatus(0); }); } 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)); 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)); 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); 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); 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)); DecryptStatus(1); } else { m->AttachFile(a); } } else { d->SetError("Allocation failed."); DecryptStatus(1); } } } // Re-write the MIME hierarchy to have the message and attachments encrypted // 1) Get the password d->GetPassword(d->Ui, FromEmail.Get(), [this, callback, InputRoot=Root, uSign, uEncrypt, m, PrivKeyId](auto Psw) { LDataI *LocalRoot = InputRoot; if (!Psw) { d->SetStatus(LLoadString(IDS_GNUPG_ERR_SIGN_ENC_CANCEL)); DecryptStatus(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->SetError(LLoadString(IDS_GNUPG_ERR_TEMP_WRITE)); DecryptStatus(1); } f.SetSize(0); f.SetPos(0); LMime Mime(ScribeTempPath()); Store3ToGMime(&Mime, LocalRoot); if (!Mime.GetBoundary()) { // No boundary... so set it and propagate the change back char b[64]; CreateMimeBoundary(b, sizeof(b)); Mime.SetBoundary(b); LocalRoot->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 (!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) { 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(); if (!uEncrypt) { // Just signing... move MIME tree into child node LDataI *NewRoot = LocalRoot->GetStore()->Create(MAGIC_ATTACHMENT); if (!NewRoot) { d->SetError(LLoadString(IDS_GNUPG_ERR_NEW_ATTACH_FAIL)); DecryptStatus(1); } // Copy over the root node headers NewRoot->SetStr(FIELD_INTERNET_HEADER, LocalRoot->GetStr(FIELD_INTERNET_HEADER)); // Reparent the old root to the new root, and then attach that to the message... if (!LocalRoot->Save(NewRoot) || !m->GetObject()->SetObj(FIELD_MIME_SEG, NewRoot)) { d->SetError(LLoadString(IDS_GNUPG_ERR_REPARENT)); DecryptStatus(1); } LocalRoot = 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()); if (uEncrypt) { LDataIt To = m->GetTo(); for (LDataPropI *Recip = To->First(); Recip; Recip = To->Next()) { 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; LSubProcess Proc(GpgBinPath, Args); char Buf[256]; if (!Proc.Start(true, true)) { d->SetError(LLoadString(IDS_GNUPG_ERR_CANT_START)); DecryptStatus(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)); DecryptStatus(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)); DecryptStatus(1); } // 3) Import the encrypted message and replace contents of MIME tree. LArray InData, OutData; if (!ReadFile(OutData, OutFile)) DecryptStatus(1); #ifndef _DEBUG // Clean up temporary files... FileDev->Delete(InFile, false); FileDev->Delete(OutFile, false); #endif if (uEncrypt) { // Clear out all existing attachments... DeleteChildSegments(LocalRoot); } // Setup the root MIME node to have the right type and fields... LAutoStreamI Data; { // 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(LocalRoot->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"); LocalRoot->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))); LocalRoot->SetStream(Data); LocalRoot->Save(); } if (uEncrypt) { // Attach some app info... LDataI *AppInfo = LocalRoot->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(LocalRoot); } { // Attach the new data to the email... LDataI *File = LocalRoot->GetStore()->Create(MAGIC_ATTACHMENT); if (!File) { d->SetError(LLoadString(IDS_GNUPG_ERR_NEW_ATTACH_FAIL)); DecryptStatus(1); } 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(LocalRoot); } // Tell the UI that the object has changed... LArray ChangeArr; ChangeArr.Add(m->GetObject()); d->App->SetContext(_FL); d->App->OnChange(ChangeArr, 0); DecryptStatus(0); }); } 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. DecryptStatus(0); } if (!d->Enc->Value() && !d->Sign->Value()) { DecryptStatus(0); // No need to sign &| encrypt } Mail *m = d->Ui->GetItem(); if (!m) { d->SetError(LLoadString(IDS_GNUPG_ERR_NOMSG)); DecryptStatus(1); } SignEncrypt(d->Sign->Value() != 0, d->Enc->Value() != 0, d->Attach->Value() != 0, [this, m, callback](auto status) { if (!status) { // Send the email.. m->Send(true); // Close the window... d->Ui->Quit(); } // Bypass the normal code DecryptStatus(1); }); } } 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/Exp_Scribe.cpp b/Code/Exp_Scribe.cpp --- a/Code/Exp_Scribe.cpp +++ b/Code/Exp_Scribe.cpp @@ -1,905 +1,906 @@ #include "Scribe.h" #include "lgi/common/List.h" #include "lgi/common/ProgressDlg.h" #include "lgi/common/Store3.h" #include "lgi/common/LgiRes.h" #include "lgi/common/FileSelect.h" #include "ScribeFolderSelect.h" #include "../Resources/resdefs.h" #include "FolderTask.h" #define OnError(...) \ { \ Errors.Add(in->Type(), Errors.Find(in->Type()) + 1); \ LgiTrace(__VA_ARGS__); \ break; \ } #define OnSkip() \ { \ Skipped.Add(in->Type(), Skipped.Find(in->Type()) + 1); \ break; \ } #define OnCreate() \ { \ Created.Add(in->Type(), Created.Find(in->Type()) + 1); \ } struct ExportParams { bool AllFolders = false; bool ExceptTrashSpam = false; LString DestFolders; // Mail3 path for dest folders; LString DestPath; // Sub folder path for export LString::Array SrcPaths; }; struct Mail3Folders { ScribeWnd *App = NULL; LAutoPtr Store; LAutoPtr Root; Mail3Folders(ScribeWnd *app) : App(app) { } Mail3Folders(Mail3Folders &src) { App = src.App; if (src) { Store = src.Store; Root = src.Root; } else LAssert(!"Src not loaded."); } operator bool() { return Store != NULL && Root != NULL; } void LoadFolders(const char *FilePath) { if (!Store) Store.Reset(App->CreateDataStore(FilePath, true)); if (Store && !Root) { if (Root.Reset(new ScribeFolder)) { Root->App = App; Root->SetObject(Store->GetRoot(), false, _FL); } } } void Unload() { Root.Reset(); Store.Reset(); } bool CheckDirty(ScribeFolder *f) { if (f->GetDirty()) return true; for (auto c = f->GetChildFolder(); c; c = c->GetNextFolder()) if (CheckDirty(c)) return true; return false; } bool IsDirty() { if (!Root) return false; return CheckDirty(Root); } }; struct ScribeExportTask : public FolderTask { int FolderLoadErrors = 0; LHashTbl,int> Created, Errors, Skipped; LMailStore *SrcStore = NULL; // Source data store Mail3Folders Dst; ScribeFolder *Spam = NULL; ScribeFolder *Trash = NULL; ExportParams Params; // Working state, used to iterate over the exporting process while // being able to yield to the OS at regular intervals. enum ExportState { ExpNone, ExpGetNext, ExpLoadFolders, ExpItems, ExpFinished, ExpCleanup } State = ExpNone; LString::Array InputPaths; ScribeFolder *SrcFolder = NULL; LArray SrcItems; ScribeFolder *DstFolder = NULL; LDataFolderI *DstObj = NULL; LDataStoreI *DstStore = NULL; LHashTbl,LDataI*> DstObjMap; ScribeExportTask(struct ScribeExportDlg *dlg); LString ContactKey(Contact *c); bool TimeSlice(); bool CopyAttachments(LDataI *outMail, LDataPropI *outSeg, LDataPropI *inSeg, LString &err); LString MakePath(LString path) { LString sep = "/"; auto p = Params.DestPath.SplitDelimit(sep); p += path.Strip(sep).SplitDelimit(sep).Slice(1); return sep + sep.Join(p); } void CollectPaths(ScribeFolder *f, LString::Array &paths) { paths.Add(f->GetPath()); for (auto c = f->GetChildFolder(); c; c = c->GetNextFolder()) CollectPaths(c, paths); } ScribeFolder *GetFolder(LString Path, Store3ItemTypes CreateItemType = MAGIC_NONE) { if (!Dst.Root) { LAssert(!"No root loaded."); return NULL; } Dst.Root->LoadFolders(); bool Create = false; auto parts = Path.SplitDelimit("/"); ScribeFolder *f = Dst.Root; for (auto p: parts) { auto c = f->GetSubFolder(p); if (!c) { if (CreateItemType != MAGIC_NONE) { c = f->CreateSubDirectory(p, CreateItemType); if (!c) { Errors.Add(MAGIC_FOLDER, Errors.Find(MAGIC_FOLDER)+1); return NULL; } Create = true; } else return NULL; } f = c; } if (Create) Created.Add(MAGIC_FOLDER, Created.Find(MAGIC_FOLDER)+1); else Skipped.Add(MAGIC_FOLDER, Skipped.Find(MAGIC_FOLDER)+1); return f; } void OnComplete() { Store3ItemTypes types[] = { MAGIC_FOLDER, MAGIC_MAIL, MAGIC_CONTACT, MAGIC_CALENDAR, MAGIC_GROUP, MAGIC_FILTER }; LStringPipe html; html.Print("\n" "
Mail export complete.
\n" "
\n" "\n" "\n"); for (int i=0; i\n", name.Get(), created, errStyle, errors, skipped); } html.Print("
Type Created Errors Skipped
%s %i %i %i
\n" "
\n" "Created: new item created in destination store.
\n" "Error: there was an error replicating item.
\n" "Skipped: the item already existed in the destination store.
\n" ); LHtmlMsg(NULL, App, html.NewLStr(), "Export", MB_OK); } LString GetOrCreateMessageId(LDataI &obj) { auto msgId = obj.GetStr(FIELD_MESSAGE_ID); if (msgId) return msgId; // Check the headers: auto hdrs = obj.GetStr(FIELD_INTERNET_HEADER); if (hdrs) { LAutoString Header(InetGetHeaderField(hdrs, "Message-ID")); if (Header) { auto ids = ParseIdList(Header); auto id = ids[0]; obj.SetStr(FIELD_MESSAGE_ID, id); obj.Save(); return id; } } // Msg has no ID and no header... create one. auto from = obj.GetObj(FIELD_FROM); if (!from) { LgiTrace("%s:%i - No from for email: %p\n", _FL, &obj); return LString(); } auto fromEmail = from->GetStr(FIELD_EMAIL); LVariant Email; const char *At = fromEmail ? strchr(fromEmail, '@') : NULL; if (!At) { if (App->GetOptions()->GetValue(OPT_Email, Email) && Email.Str()) At = strchr(Email.Str(), '@'); else At = "@domain.com"; } if (!At) { LgiTrace("%s:%i - No at in email: %p\n", _FL, &obj); return LString(); } 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); obj.SetStr(FIELD_MESSAGE_ID, m); obj.Save(); return m; } LString ObjToId(LDataI &obj) { switch (obj.Type()) { case MAGIC_MAIL: { return obj.GetStr(FIELD_MESSAGE_ID); } case MAGIC_CONTACT: { auto fn = obj.GetStr(FIELD_FIRST_NAME); auto ln = obj.GetStr(FIELD_LAST_NAME); auto em = obj.GetStr(FIELD_EMAIL); LString s; s.Printf("%s,%s,%s", fn, ln, em); return s; } case MAGIC_CALENDAR: { auto sub = obj.GetStr(FIELD_CAL_SUBJECT); auto start = obj.GetDate(FIELD_CAL_START_UTC); LString s; s.Printf("%s," LPrintfInt64, sub, start?start->Ts():0); return s; break; } case MAGIC_GROUP: { return obj.GetStr(FIELD_GROUP_NAME); } case MAGIC_FILTER: { return obj.GetStr(FIELD_FILTER_NAME); } default: { LAssert(!"Impl me."); break; } } return LString(); } bool CheckModified(LDataI *in, LDataI *out) { if (!out) // No existing object return true; auto inMod = in->GetDate(FIELD_DATE_MODIFIED); auto outMod = in->GetDate(FIELD_DATE_MODIFIED); if (!inMod || !outMod || !inMod->IsValid() || !outMod->IsValid()) return true; // Can't tell... no dates stored. bool mod = *inMod > *outMod; - if (mod) - { - int asd=0; - } return mod; } void MakeDstObjMap() { DstObjMap.Empty(); if (!DstFolder || !DstObj) return; auto &c = DstObj->Children(); for (auto t = c.First(); t; t = c.Next()) { auto Id = ObjToId(*t); if (Id) DstObjMap.Add(Id, t); } } }; struct ScribeExportDlg : public LDialog, public LDataEventsI { ScribeWnd *App = NULL; LMailStore *SrcStore = NULL; LList *Lst = NULL; ExportParams Params; Mail3Folders Dst; ScribeExportDlg(ScribeWnd *app, LMailStore *srcStore) : SrcStore(srcStore), Dst(app) { SetParent(App = app); if (!SrcStore) SrcStore = App->GetDefaultMailStore(); 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()) { Params.SrcPaths = LString(s.Str()).SplitDelimit(":"); for (auto p: Params.SrcPaths) Lst->Insert(new LListItem(p)); 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()); OnAll(); } } 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; } 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)); } int OnNotify(LViewI *c, LNotification n) { switch (c->GetId()) { case IDC_ALL: { OnAll(); break; } case IDC_SET_DEST: { auto s = new LFileSelect(this); s->Type("Scribe Folders", "*.mail3"); s->Type("All Files", LGI_ALL_FILES); s->Open([this](auto dlg, auto status) { if (status) SetCtrlName(IDC_DEST, dlg->Name()); delete dlg; }); break; } case IDC_ADD_SRC_FOLDER: { if (!Lst) break; auto s = new FolderDlg(this, App); s->DoModal([this, s](auto dlg, auto status) { if (status && ValidStr(s->Get())) { bool Has = false; for (auto n: *Lst) { if (Stricmp(n->GetText(), s->Get()) == 0) { Has = true; break; } } if (!Has) { Lst->Insert(new LListItem(s->Get())); 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: { if (!Dst) Dst.LoadFolders(GetCtrlName(IDC_DEST)); if (Dst.Root) { auto s = new FolderDlg(this, App, MAGIC_NONE, Dst.Root); s->DoModal([this, s](auto dlg, auto ctrlId) { if (ctrlId) SetCtrlName(IDC_FOLDER, s->Get()); delete dlg; }); } else LgiMsg(this, "Couldn't load mail3 store.", AppName); break; } case IDOK: { Params.AllFolders = GetCtrlValue(IDC_ALL) != 0; Params.ExceptTrashSpam = GetCtrlValue(IDC_NO_SPAM_TRASH) != 0; Params.DestPath = GetCtrlName(IDC_FOLDER); Params.DestFolders = GetCtrlName(IDC_DEST); if (Lst) { Params.SrcPaths.Empty(); Params.SrcPaths.SetFixedLength(false); for (auto i: *Lst) Params.SrcPaths.Add(i->GetText()); } if (!Dst) Dst.LoadFolders(Params.DestFolders); // 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; } }; ScribeExportTask::ScribeExportTask(ScribeExportDlg *dlg) : FolderTask(dlg->Dst.Root, LAutoPtr(NULL), NULL, NULL), Dst(dlg->Dst), SrcStore(dlg->SrcStore) { SetDescription("Initializing..."); SetType("Folders"); LAssert(SrcStore); Params = dlg->Params; if (Params.ExceptTrashSpam) { Spam = App->GetFolder("/Spam"); Trash = App->GetFolder(FOLDER_TRASH); } // Work out the folders we need to operate on: if (Params.AllFolders) CollectPaths(SrcStore->Root, InputPaths); else InputPaths = Params.SrcPaths; SetRange(InputPaths.Length()); // Kick off the processing... State = ExpGetNext; SetPulse(PULSE_MS); SetAlwaysOnTop(true); } LString ScribeExportTask::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; } #define ExportFolderStatus(b) \ { if (onStatus) onStatus(b); \ return; } bool ScribeExportTask::TimeSlice() { if (IsCancelled()) return false; switch (State) { case ExpGetNext: { if (InputPaths.Length() == 0) { State = ExpFinished; break; } // Load up the next SrcFolder... auto src = InputPaths[0]; InputPaths.DeleteAt(0, true); auto dst = MakePath(src); SrcFolder = App->GetFolder(src, SrcStore); if (!SrcFolder) { FolderLoadErrors++; return true; } DstStore = NULL; DstFolder = GetFolder(dst, SrcFolder->GetItemType()); if (!DstFolder) { return true; } State = ExpLoadFolders; SrcFolder->LoadThings(this, [this](auto status) { if (status == Store3Success) { // Load a list of things to process... SrcItems.Empty(); auto SrcObj = dynamic_cast(SrcFolder->GetObject()); if (SrcObj) { auto &c = SrcObj->Children(); for (auto t = c.First(); t; t = c.Next()) SrcItems.Add(t); } DstFolder->LoadThings(this, [this](auto status) { if (status == Store3Success) { State = ExpItems; SetDescription(SrcFolder->GetPath()); if (DstFolder->GetObject()) DstObj = dynamic_cast(DstFolder->GetObject()); else LAssert(!"No object?"); if (DstObj) DstStore = DstObj->GetStore(); else LAssert(!"No object?"); MakeDstObjMap(); } else State = ExpGetNext; }); } else { State = ExpGetNext; } }); break; } case ExpLoadFolders: { // No-op, but we should probably time out... break; } case ExpItems: { if (!SrcFolder || !DstFolder || !DstStore) { State = ExpGetNext; break; } auto Trans = DstStore->StartTransaction(); auto StartTs = LCurrentTime(); int Processed = 0; while ( SrcItems.Length() > 0 && LCurrentTime() - StartTs < WORK_SLICE_MS) { auto in = SrcItems[0]; SrcItems.DeleteAt(0); switch (in->Type()) { case MAGIC_MAIL: { auto Id = GetOrCreateMessageId(*in); if (!Id) OnError("%s:%i - Email %p has no MsgId\n", _FL, in) if (DstObjMap.Find(Id)) OnSkip() // Create new mail... auto outMail = DstStore->Create(MAGIC_MAIL); outMail->CopyProps(*in); // Now create all the attachments auto inSeg = dynamic_cast(in->GetObj(FIELD_MIME_SEG)); LDataI *outSeg = NULL; if (inSeg) { outSeg = DstStore->Create(MAGIC_ATTACHMENT); if (!outSeg) OnError("%s:%i - Failed to create attachment\n", _FL) else { outSeg->CopyProps(*inSeg); auto outMime = outSeg->GetStr(FIELD_MIME_TYPE); if (!outMime) { auto hdrs = outSeg->GetStr(FIELD_INTERNET_HEADER); if (!hdrs) { // This is going to cause an assert later outSeg->SetStr(FIELD_MIME_TYPE, sAppOctetStream); // LgiTrace("%s:%i - Setting default mime on %p\n", _FL, outSeg); } } if (outMail->SetObj(FIELD_MIME_SEG, outSeg) < Store3Delayed) OnError("%s:%i - Failed to attach seg to mail.\n", _FL) else { LString err; if (!CopyAttachments(outMail, outSeg, inSeg, err)) OnError("%s:%i - CopyAttachments failed\n", _FL) else OnCreate() } } } else OnCreate() outMail->Save(DstFolder->GetObject()); break; } default: { // Is the object already in the dst map? auto Id = ObjToId(*in); auto existing = DstObjMap.Find(Id); if (!CheckModified(in, existing)) OnSkip(); auto outObj = DstStore->Create(in->Type()); if (!outObj) { OnError("%s:%i - %s failed to create %s\n", _FL, DstStore->GetStr(FIELD_STORE_TYPE), Store3ItemTypeName((Store3ItemTypes)in->Type())) } if (!outObj->CopyProps(*in)) OnError("%s:%i - CopyProps failed.\n", _FL) if (outObj->Save(DstFolder->GetObject()) < Store3Delayed) OnError("%s:%i - Save failed\n", _FL) OnCreate(); break; } } Processed++; } if (SrcItems.Length() == 0) { SrcFolder = NULL; DstFolder = NULL; DstStore = NULL; State = ExpGetNext; (*this)++; // move progress... } // LgiTrace("Processed: %i\n", Processed); break; } case ExpFinished: { OnComplete(); State = ExpCleanup; return true; } case ExpCleanup: { if (Dst.IsDirty()) return true; // We're done... return false; } + default: + { + LAssert(!"Not impl."); + return false; + } } return true; } // This should copy all the child objects of 'inSeg' to new child objects of 'outSeg' bool ScribeExportTask::CopyAttachments(LDataI *outMail, LDataPropI *outSeg, LDataPropI *inSeg, LString &err) { #define ERR(str) \ { err = str; return false; } if (!outMail || !outSeg || !inSeg) ERR("param error"); auto children = inSeg->GetList(FIELD_MIME_SEG); if (!children) return true; // Nothing to copy... for (auto i = children->First(); i; i = children->Next()) { auto inMime = i->GetStr(FIELD_MIME_TYPE); if (!inMime) continue; auto o = outMail->GetStore()->Create(MAGIC_ATTACHMENT); if (!o) ERR("couldn't create attachment"); if (!o->CopyProps(*i)) ERR("copy attachment properties failed"); auto outData = dynamic_cast(outSeg); if (!outData) ERR("outSeg isn't a LDataI object"); if (!o->Save(outData)) ERR("failed to save attachment to output mail"); if (!CopyAttachments(outMail, o, i, err)) return false; // but leave the error message untouched. } return true; } void ExportScribe(ScribeWnd *App, LMailStore *Store) { auto Dlg = new ScribeExportDlg(App, Store); Dlg->DoModal([Dlg, App](auto dlg, auto ctrlId) { if (ctrlId && Dlg->Dst) { new ScribeExportTask(Dlg); } delete dlg; }); } 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,258 @@ #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, 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(); } void ChooseFolderDlg::InsertFile(const char *f) { if (!Lst) return; 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 (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) break; auto s = new LFileSelect(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"); 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: { if (!Folder) break; auto Dlg = new FolderDlg(this, App, Type); Dlg->DoModal([this, Dlg](auto dlg, auto ctrlId) { if (ctrlId) { auto f = Dlg->Get(); if (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.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(); auto Dlg = new ChooseFolderDlg(Parent, false, LLoadString(IDS_MBOX_IMPORT), LLoadString(IDS_MBOX_SELECT_FOLDER), Path); Dlg->DoModal([Dlg, Parent](auto dlg, auto ctrlId) { if (ctrlId && Dlg->DestFolder) { ScribeFolder *Folder = Parent->GetFolder(Dlg->DestFolder); if (Folder) { 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(); auto Dlg = new ChooseFolderDlg(Parent, true, LLoadString(IDS_MBOX_EXPORT), LLoadString(IDS_MBOX_EXPORT_FOLDER), Path); Dlg->DoModal([Dlg, Parent](auto dlg, auto ctrlId) { if (ctrlId && Dlg->DestFolder) { ScribeFolder *Folder = Parent->GetFolder(Dlg->DestFolder); if (Folder) { for (auto File: Dlg->SrcFiles) { if (!LFileExists(File) || - LgiMsg(Parent, LLoadString(IDS_ERROR_FILE_EXISTS), AppName, MB_YESNO, File) == IDYES) + LgiMsg( Parent, + LLoadString(IDS_ERROR_FILE_EXISTS), + AppName, + MB_YESNO, + File.Get()) == IDYES) { LAutoPtr F(new LFile); if (F->Open(File, O_WRITE)) Folder->Export(Folder->AutoCast(F), sMimeMbox); } } } } 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,604 +1,608 @@ // #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, 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, NULL); } else { LgiTrace("%s:%i - Unsupported address book format '%s'\n", _FL, File); } return Status; } void Import_MozillaAddressBook(ScribeWnd *App) { LFileSelect Select; 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, &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... auto Dlg = new ChooseFolderDlg(App, false, AppName, "Select input files and destination directory", DefaultFolder, MAGIC_CONTACT, &Files); Dlg->DoModal([App, Dlg](auto dlg, auto id) { 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 void Import_MozillaMail(ScribeWnd *App) { char Path[MAX_PATH_LEN]; 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)) { 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; iGetCurrentFolder(); 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) { 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); + else LgiMsg(App, + LLoadString(IDS_ERROR_FOLDER_DOESNT_EXIST), + AppName, + MB_OK, + Dlg->DestFolder.Get()); } delete dlg; }); } diff --git a/Code/PreviewPanel.cpp b/Code/PreviewPanel.cpp --- a/Code/PreviewPanel.cpp +++ b/Code/PreviewPanel.cpp @@ -1,982 +1,982 @@ /* ** FILE: ScribePreview.cpp ** AUTHOR: Matthew Allen ** DATE: 31/8/99 ** DESCRIPTION: Scribe Mail Preview UI ** ** Copyright (C) 1999, Matthew Allen ** fret@memecode.com */ #include #include #include #include #include "lgi/common/Lgi.h" #include "lgi/common/TextView3.h" #include "lgi/common/Html.h" #include "lgi/common/Scripting.h" #include "Scribe.h" #include "PreviewPanel.h" #include "resdefs.h" #include "ScribeListAddr.h" #include "Calendar.h" #include "Components.h" #include "../src/common/Text/HtmlPriv.h" #define OPT_PreviewSize "LPreviewPanel::OpenSize" #define OPT_PreviewOpen "LPreviewPanel::IsOpen" #if defined(WIN32) && defined(_DEBUG) #define DEBUG_FOCUS 1 #else #define DEBUG_FOCUS 0 #endif #ifdef MAC #define HEADER_POS 1 #else #define HEADER_POS 0 #endif class LPreviewPanelPrivate : public LDocumentEnv, public LScriptContext { public: LPreviewPanel *Panel; ScribeWnd *App; LDocView *TextCtrl; Thing *Item; Thing *Pulse; LRect TxtPos; int Time; LCapabilityTarget::CapsHash MissingCaps; MissingCapsBar *Bar; bool IgnoreShowImgNotify; Contact *CtxMenuContact; // Dynamic header content Html1::LHtml *Header; int HeaderY; ScribeDom *HeaderDom; LString HeaderMailFile; LString HeaderMailTemplate; LString HeaderContactFile; LString HeaderContactTemplate; LString HeaderGroupFile; LString HeaderGroupTemplate; // Scripting LAutoPtr ScriptObj; // Methods LPreviewPanelPrivate(LPreviewPanel *p) : Panel(p) { HeaderY = 88; Header = 0; HeaderDom = 0; Bar = 0; IgnoreShowImgNotify = false; CtxMenuContact = NULL; Item = 0; Pulse = 0; TextCtrl = 0; Time = -1; TxtPos.ZOff(-1, -1); } ~LPreviewPanelPrivate() { DeleteObj(Bar); } - char *GetIncludeFile(char *FileName) + char *GetIncludeFile(char *FileName) override { return 0; } - bool AppendItems(LSubMenu *Menu, const char *Param, int Base) + bool AppendItems(LSubMenu *Menu, const char *Param, int Base) override { if (!Menu) return false; Mailto mt(App, Param); CtxMenuContact = mt.To.Length() > 0 ? Contact::LookupEmail(mt.To[0]->sAddr) : NULL; if (CtxMenuContact) Menu->AppendItem(LLoadString(IDS_OPEN_CONTACT), IDM_OPEN, true); else Menu->AppendItem(LLoadString(IDS_ADD_CONTACTS), IDM_NEW_CONTACT, true); return true; } - bool OnMenu(LDocView *View, int Id, void *Context) + bool OnMenu(LDocView *View, int Id, void *Context) override { if (Id == IDM_NEW_CONTACT) { if (Context) { Html1::LTag *a = (Html1::LTag*) Context; char *Name = WideToUtf8(a->Text()); const char *Email = 0; a->Get("href", Email); ListAddr *La = new ListAddr(App, Email, Name); if (La) { La->AddToContacts(true); DeleteObj(La); } } } else if (Id == IDM_OPEN && CtxMenuContact) { CtxMenuContact->DoUI(); CtxMenuContact = NULL; } else return false; return true; } - bool OnNavigate(LDocView *Parent, const char *Uri) + bool OnNavigate(LDocView *Parent, const char *Uri) override { Mailto m(App, Uri); if (m.To[0]) { Mail *email = App->CreateMail(); if (email) { m.Apply(email); email->DoUI(); return true; } } return false; } - LDocumentEnv::LoadType GetContent(LoadJob *&j) + LDocumentEnv::LoadType GetContent(LoadJob *&j) override { LUri i; if (!j) goto GetContentError; if (_strnicmp(j->Uri, "LC_", 3) == 0) goto GetContentError; i.Set(j->Uri); if (!i.sProtocol || !i.sPath) goto GetContentError; if (_stricmp(i.sProtocol, "file") == 0) { char p[MAX_PATH_LEN]; strcpy_s(p, sizeof(p), i.sPath); #ifdef WIN32 char *c; while (c = strchr(p, '/')) *c = '\\'; #endif if (LFileExists(p)) { j->pDC.Reset(GdcD->Load(p)); return LoadImmediate; } } else if ( !_stricmp(i.sProtocol, "http") || !_stricmp(i.sProtocol, "https") || !_stricmp(i.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. Worker = App->GetImageLoader(); if (Worker) { Worker->AddJob(j); j = 0; return LoadDeferred; } } GetContentError: return LoadError; } bool trace(LScriptArguments &Args) { LgiTrace("Script: "); for (unsigned i=0; iCastString()); } LgiTrace("\n"); return true; } bool encodeURI(LScriptArguments &Args) { if (Args.Length() == 1) { LUri u; *Args.GetReturn() = u.EncodeStr(Args[0]->CastString()); return true; } return false; } bool getElementById(LScriptArguments &Args) { if (Args.Length() == 1 && Header) { LDom *e = Header->getElementById(Args[0]->CastString()); if (e) { *Args.GetReturn() = e; return true; } } return false; } // Convert dynamic fields into string values... LString OnDynamicContent(LDocView *Parent, const char *Code) override { if (!HeaderDom) return NULL; LVariant v; if (!HeaderDom->GetValue(Code, v)) return NULL; return v.CastString(); } void SetEngine(LScriptEngine *Eng) {} - GHostFunc *GetCommands(); + GHostFunc *GetCommands() override; void SetGlobals(LCompiledCode *obj) { if (!obj) return; // Set global 'Thing' variable to the current object. LVariant v = (LDom*)Item; obj->Set("Thing", v); // Set 'document' variable to the document viewer object. if (TextCtrl) { v = (LDom*)TextCtrl; obj->Set("document", v); } } - bool OnCompileScript(LDocView *Parent, char *Script, const char *Language, const char *MimeType) + bool OnCompileScript(LDocView *Parent, char *Script, const char *Language, const char *MimeType) override { LScriptEngine *Engine = App->GetScriptEngine(); if (!Engine || !Script) return false; if (!ScriptObj) { if (ScriptObj.Reset(new LCompiledCode)) SetGlobals(ScriptObj); } if (!ScriptObj) return false; SetLog(LScribeScript::Inst->GetLog()); const char *FileName = "script.html"; if (Item->Type() == MAGIC_MAIL) FileName = HeaderMailFile; else if (Item->Type() == MAGIC_CONTACT) FileName = HeaderContactFile; return Engine->Compile(ScriptObj, this, Script, FileName); } - bool OnExecuteScript(LDocView *Parent, char *Script) + bool OnExecuteScript(LDocView *Parent, char *Script) override { LScriptEngine *Engine = App->GetScriptEngine(); if (Engine && Script) { if (!ScriptObj) { if (ScriptObj.Reset(new LCompiledCode)) SetGlobals(ScriptObj); } // Run the fragment of code. if (Engine->RunTemporary(ScriptObj, Script)) { return true; } } return false; } }; GHostFunc Cmds[] = { GHostFunc("getElementById", "", (ScriptCmd)&LPreviewPanelPrivate::getElementById), GHostFunc("encodeURI", "", (ScriptCmd)&LPreviewPanelPrivate::encodeURI), GHostFunc("trace", "", (ScriptCmd)&LPreviewPanelPrivate::trace), GHostFunc(0, 0, 0) }; GHostFunc *LPreviewPanelPrivate::GetCommands() { return Cmds; } LPreviewPanel::LPreviewPanel(ScribeWnd *app) { d = new LPreviewPanelPrivate(this); d->App = app; // This allows us to hook iconv conversion events LFontSystem::Inst()->Register(this); // This allows us to hook missing image library events GdcD->Register(this); } LPreviewPanel::~LPreviewPanel() { DeleteObj(d); } LMessage::Param LPreviewPanel::OnEvent(LMessage *Msg) { switch (Msg->Msg()) { case M_NEEDS_CAP: { LAutoString c((char*)Msg->A()); NeedsCapability(c); return 0; } case M_UPDATE: { OnPosChange(); break; } } return LLayout::OnEvent(Msg); } void LPreviewPanel::OnCloseInstaller() { d->Bar = NULL; } void LPreviewPanel::OnInstall(CapsHash *Caps, bool Status) { if (Caps && Status) { LDataI *Obj; if (d->Item && (Obj = d->Item->GetObject()) && Obj->Type() == MAGIC_MAIL) { Mail *m = d->Item->IsMail(); if (m) { // This temporarily removes the attachments that will be // deleted by the call to ParseHeaders after this... m->ClearCachedItems(); } Obj->ParseHeaders(); LArray Change; Change.Add(Obj); d->App->SetContext(_FL); d->App->OnChange(Change, FIELD_INTERNET_HEADER); } OnThing(d->Item, true); } else { // Install failed... } } bool LPreviewPanel::NeedsCapability(const char *Name, const char *Param) { if (!InThread()) { PostEvent(M_NEEDS_CAP, (LMessage::Param)NewStr(Name)); } else { if (!Name) return false; if (d->MissingCaps.Find(Name)) return true; d->MissingCaps.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 : d->MissingCaps) 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 (!d->Bar) { d->Bar = new MissingCapsBar(this, &d->MissingCaps, msg, d->App, Actions, Back); d->Bar->Attach(this); OnPosChange(); } else { d->Bar->SetMsg(msg); } } return true; } LDocView *LPreviewPanel::GetDoc(const char *MimeType) { return d->TextCtrl; } bool LPreviewPanel::SetDoc(LDocView *v, const char *MimeType) { if (v != d->TextCtrl) { DeleteObj(d->TextCtrl); if ((d->TextCtrl = v)) { LCapabilityClient *cc = dynamic_cast(d->TextCtrl); if (cc) cc->Register(this); if (IsAttached()) return v->Attach(this); } } return true; } void LPreviewPanel::OnPaint(LSurface *pDC) { pDC->Colour(L_MED); pDC->Rectangle(); #ifdef MAC if (d->Header) { LRect r = d->Header->GetPos(); pDC->Colour(Rgb24(0xB0, 0xB0, 0xB0), 24); pDC->Line(r.x1-1, r.y1-1, r.x2, r.y1-1); pDC->Line(r.x1-1, r.y1-1, r.x1-1, r.y2); } #endif } void LPreviewPanel::OnPosChange() { int y = 0; LRect c = GetClient(); if (d->Header) { LRect r(HEADER_POS, HEADER_POS, c.X()-(HEADER_POS<<1), c.Y()-(HEADER_POS<<1)); d->Header->SetPos(r); LPoint Size = d->Header->Layout(true); if (Size.y > 0) { /* This size limit is now implemented as CSS in the HTML itself. */ d->HeaderY = Size.y; if (d->HeaderY != r.Y()) { r.Set(HEADER_POS, HEADER_POS, c.X()-(HEADER_POS<<1), HEADER_POS+d->HeaderY-1); d->Header->SetPos(r); } } y += r.Y(); } if (d->Bar) { LRect r(0, y, c.X()-1, y + d->Bar->Y() - 1); d->Bar->SetPos(r); y += d->Bar->Y(); } if (d->TextCtrl) { LRect r(0, y, c.X()-1, c.Y()-1); d->TextCtrl->SetPos(r); d->TextCtrl->Visible(true); } } bool AttachViewToPanel(LDocView *v, void *p) { LPreviewPanel *pp = (LPreviewPanel*)p; return v->Attach(pp); } Thing *LPreviewPanel::GetCurrent() { return d->Item; } bool LPreviewPanel::CallMethod(const char *Name, LVariant *Dst, LArray &Arg) { ScribeDomType Method = StrToDom(Name); *Dst = false; switch (Method) { case SdShowRemoteContent: if (d->TextCtrl) { bool Always = Arg.Length() > 0 ? Arg[0]->CastBool() : false; if (Always) { auto m = d->Item->IsMail(); if (m) { auto From = m->GetFrom(); if (From) d->App->RemoteContent_AddSender(From->GetStr(FIELD_EMAIL), true); else LgiTrace("%s:%i - No from address.\n", _FL); } else LgiTrace("%s:%i - Not an email.\n", _FL); } d->IgnoreShowImgNotify = true; d->TextCtrl->SetLoadImages(true); d->IgnoreShowImgNotify = false; PostEvent(M_UPDATE); *Dst = true; } break; case SdSetHtml: if (d->TextCtrl && Arg.Length() > 0) { d->TextCtrl->Name(Arg[0]->Str()); *Dst = true; } break; default: return false; } return true; } void LPreviewPanel::OnThing(Thing *item, bool ChangeEvent) { d->MissingCaps.Empty(); DeleteObj(d->Bar); if (d->Item && d->TextCtrl && d->TextCtrl->IsDirty() && !dynamic_cast(d->TextCtrl)) { Mail *m = d->Item->IsMail(); if (m) { MailUi *Ui = dynamic_cast(m->GetUI()); bool AlreadyDirty = Ui ? Ui->IsDirty() : false; if (AlreadyDirty) { LgiMsg( this, "Email already open and edited.\n" "Preview changes lost.", AppName); } else { m->SetBody(d->TextCtrl->Name()); m->SetDirty(); if (Ui) Ui->OnLoad(); } } } d->Item = item; char *Mem = 0; if (!d->Item || d->Item->Type() != MAGIC_MAIL) { DeleteObj(d->Header); DeleteObj(d->HeaderDom); } if (d->Item) { d->ScriptObj.Reset(); if (d->TextCtrl && d->TextCtrl->IsAttached()) { d->TxtPos = d->TextCtrl->GetPos(); } switch ((uint32_t)d->Item->Type()) { case MAGIC_MAIL: { Mail *m = d->Item->IsMail(); if (m) { if (!d->Header) { if (!d->HeaderMailTemplate) { char Base[] = "PreviewMail.html"; d->HeaderMailFile = LFindFile("PreviewMailCustom.html"); if (d->HeaderMailFile || (d->HeaderMailFile = LFindFile(Base))) d->HeaderMailTemplate = LFile(d->HeaderMailFile).Read(); else d->HeaderMailTemplate.Printf("Failed to find '%s'", Base); } if (d->HeaderMailTemplate) { d->Header = new Html1::LHtml(100, HEADER_POS, HEADER_POS, GetPos().X()-1, d->HeaderY); if (d->Header) { d->HeaderDom = new ScribeDom(d->App); d->Header->SetEnv(d); d->Header->Attach(this); Invalidate(); } } } if (!TestFlag(m->GetFlags(), MAIL_READ) && !ChangeEvent) { LVariant MarkReadAfterPreview; d->App->GetOptions()->GetValue(OPT_MarkReadAfterPreview, MarkReadAfterPreview); if (MarkReadAfterPreview.CastInt32()) { d->Pulse = d->Item; LVariant Secs = 5; d->App->GetOptions()->GetValue(OPT_MarkReadAfterSeconds, Secs); if (Secs.CastInt32()) { d->Time = Secs.CastInt32(); } else { m->SetFlags(m->GetFlags() | MAIL_READ); } } } if (!m->CreateView(this, (const char*)NULL, false, 512<<10, true)) { DeleteObj(d->TextCtrl); } if (d->Header && d->HeaderMailTemplate) { if (d->HeaderDom) d->HeaderDom->Email = m; d->Header->Name(d->HeaderMailTemplate); } } break; } case MAGIC_CONTACT: { Contact *c = d->Item->IsContact(); if (c) { if (!d->Header) { if (!d->HeaderContactTemplate) { char Base[] = "PreviewContact.html"; if ((d->HeaderContactFile = LFindFile(Base))) d->HeaderContactTemplate = LFile(d->HeaderContactFile).Read(); else d->HeaderContactTemplate.Printf("Failed to find '%s'", Base); } if (d->HeaderContactTemplate) { d->Header = new Html1::LHtml(100, HEADER_POS, HEADER_POS, GetPos().X()-1, d->HeaderY); if (d->Header) { d->HeaderDom = new ScribeDom(d->App); d->Header->SetEnv(d); d->Header->Attach(this); Invalidate(); } } } if (d->Header && d->HeaderContactTemplate) { if (d->HeaderDom) { d->HeaderDom->Con = c; } d->Header->Name(d->HeaderContactTemplate); } } break; } case MAGIC_GROUP: { ContactGroup *g = d->Item->IsGroup(); if (g) { if (!d->Header) { if (!d->HeaderGroupTemplate) { char Base[] = "PreviewGroup.html"; if ((d->HeaderGroupFile = LFindFile(Base))) d->HeaderGroupTemplate = LFile(d->HeaderGroupFile).Read(); else d->HeaderGroupTemplate.Printf("Failed to find '%s'", Base); } if (d->HeaderGroupTemplate) { d->Header = new Html1::LHtml(100, HEADER_POS, HEADER_POS, GetPos().X()-1, d->HeaderY); if (d->Header) { d->HeaderDom = new ScribeDom(d->App); d->Header->SetEnv(d); d->Header->Attach(this); Invalidate(); } } } if (d->Header && d->HeaderGroupTemplate) { if (d->HeaderDom) { d->HeaderDom->Grp = g; } d->Header->Name(d->HeaderGroupTemplate); } } break; } case MAGIC_CALENDAR: { Calendar *c = d->Item->IsCalendar(); if (c) { LStringPipe p; LDateTime Start, End; uint64 StartTs, EndTs; char s[256]; if (c->GetField(FIELD_CAL_START_UTC, Start)) { Start.Get(s, sizeof(s)); Start.Get(StartTs); p.Print("Start: %s\n", s); if (c->GetField(FIELD_CAL_END_UTC, End)) { End.Get(s, sizeof(s)); End.Get(EndTs); int Min = (int) ((EndTs - StartTs) / LDateTime::Second64Bit / 60); if (Min >= 24 * 60) { double Days = (double)Min / 24.0 / 60.0; p.Print("End: %s (%.1f day%s)\n", s, Days, Days == 1.0 ? "" : "s"); } else { int Hrs = Min / 60; int Mins = Min % 60; p.Print("End: %s (%i:%02i)\n", s, Hrs, Mins); } } } const char *Str = 0; if (c->GetField(FIELD_CAL_SUBJECT, Str)) p.Print("Subject: %s\n", Str); if (c->GetField(FIELD_CAL_LOCATION, Str)) p.Print("Location: %s\n", Str); if (c->GetField(FIELD_CAL_NOTES, Str)) p.Print("Notes: %s\n", Str); LAutoString Txt(p.NewStr()); if (!dynamic_cast(d->TextCtrl)) DeleteObj(d->TextCtrl); if (!d->TextCtrl) { d->TextCtrl = d->App->CreateTextControl(100, "text/plain", false); if (d->TextCtrl) { d->TextCtrl->Visible(false); d->TextCtrl->Sunken(false); d->TextCtrl->Attach(this); } } if (d->TextCtrl) { d->TextCtrl->SetReadOnly(true); d->TextCtrl->Name(Txt); } } break; } case MAGIC_FILTER: { if (!dynamic_cast(d->TextCtrl)) DeleteObj(d->TextCtrl); if (!d->TextCtrl) { LRect c = GetClient(); d->TextCtrl = new Html1::LHtml(100, 0, 0, c.X(), c.Y(), d); if (d->TextCtrl) { d->TextCtrl->Visible(false); d->TextCtrl->Sunken(false); d->TextCtrl->Attach(this); } } if (d->TextCtrl) { Filter *f = d->Item->IsFilter(); if (f) { LAutoString Desc = f->DescribeHtml(); if (Desc) { d->TextCtrl->Name(Desc); d->TextCtrl->Visible(true); } } } break; } } } else { DeleteObj(d->TextCtrl); } if (d->TextCtrl) { d->TextCtrl->SetCaret(0, false); d->TextCtrl->UnSelectAll(); } OnPosChange(); DeleteArray(Mem); } void LPreviewPanel::OnPulse() { if (d->Time > 0) { d->Time--; } else if (d->Time == 0) { if (d->Item == d->Pulse) { Mail *m = d->Item->IsMail(); if (m) { m->SetFlags(m->GetFlags() | MAIL_READ); } } d->Pulse = 0; d->Time = -1; } } int LPreviewPanel::OnNotify(LViewI *v, LNotification n) { switch (v->GetId()) { case IDC_TEXT: { if (d->Item && d->TextCtrl) { if (n.Type == LNotifyShowImagesChanged && !d->IgnoreShowImgNotify) { bool LdImg = d->TextCtrl->GetLoadImages(); if (LdImg == true) { DeleteObj(d->Bar); OnPosChange(); } } Mail *m = d->Item->IsMail(); if (m) { m->OnNotify(v, n); } } break; } } return 0; } diff --git a/Code/ScribeApp.cpp b/Code/ScribeApp.cpp --- a/Code/ScribeApp.cpp +++ b/Code/ScribeApp.cpp @@ -1,12728 +1,12726 @@ /* ** 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) 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, LDataIt 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) { // 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 = 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); #ifndef WIN32 printf("%s\n", GetFullAppName(true).Get()); #endif auto Type = d->GetInstallMode(); if (Type == LOptionsFile::UnknownMode) { // This may make the mode more clear... if (LoadOptions()) Type = d->GetInstallMode(); } if (Type == LOptionsFile::UnknownMode) { d->AskUserForInstallMode([this](auto selectedMode) { d->SetInstallMode(selectedMode); if (!d->Options) d->Options = new LOptionsFile(selectedMode, OptionsFileName); Construct1(); }); } else Construct1(); } void ScribeWnd::Construct1() { 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(); LFont *m = LMenu::GetFont(); if (m) { m->PointSize(m->PointSize() + SzAdj); m->Create(); } } } else { GetOptions()->SetValue(OPT_UiFontSize, SizeAdj = 2); } // Resources and languages SetLanguage(); // 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](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); SetLanguage(); Construct2(); } else // User canceled { ScribeState = ScribeExiting; LCloseApp(); } delete dlg; }); } } else Construct2(); } void ScribeWnd::Construct2() { #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 = ScribeConstructed; OnCreate(); } void ScribeWnd::Construct3() { 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([&](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; }); } void ScribeWnd::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(NULL, "The resource file 'Scribe.lr8' is missing.", AppName); ScribeState = ScribeExiting; LCloseApp(); } } 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(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()); 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))) { 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()) { 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() { LgiTrace("ScribeWnd::OnCreate. ScribeState=%i\n", ScribeState); if (IsAttached() && ScribeState == ScribeConstructed) { ScribeState = ScribeInitializing; Construct3(); } } 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.NewLStr().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; 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 = 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) { 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); return false; } 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 (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.NewLStr(); 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); } } 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(const char *_Full, bool CreateIfMissing) { LString Full(_Full); auto Ext = LGetExtension(Full); if (Ext) { if (!_stricmp(Ext, "mail2")) { - LgiMsg(this, LLoadString(IDS_MAIL2_DEPRECATED), AppName, MB_OK, Full); + LgiMsg(this, LLoadString(IDS_MAIL2_DEPRECATED), AppName, MB_OK, Full.Get()); } 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); + LgiTrace("%s:%i - Not a valid mail store extension: %s\n", _FL, Full.Get()); LAssert(!"Not a valid mail store extension."); } } - else LgiTrace("%s:%i - No extension for CreateDataStore: %s\n", _FL, Full); + else LgiTrace("%s:%i - No extension for CreateDataStore: %s\n", _FL, Full.Get()); 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, 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); } 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) { auto Prog = new MailStoreUpgrade(this, Store); Prog->DoModal(NULL); } else { continue; } } else if (MsState == Store3Error) { auto ErrMsg = Store->GetStr(FIELD_ERROR); 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 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) { 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); Status = true; 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 = NULL; 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; } 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) { if (Callback) Callback(false); return; } } // Set loading flags CmdSend.Enabled(false); CmdReceive.Enabled(false); CmdPreview.Enabled(false); bool Status = LoadMailStores(); if (Tree) { for (auto a: Accounts) { if (!a->Receive.Disabled() && a->Receive.IsPersistant()) a->Receive.Connect(0, false); } } using BoolFn = std::function; auto FinishLoad = new BoolFn ( [this, PrevState, Callback](bool Status) { if (ScribeState == ScribeExiting) { LCloseApp(); } else { 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 { (*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(); } } 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.NewLStr(); 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(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 Msg; bool Password; UserInput() { Password = false; } }; void ScribeWnd::GetUserInput(LView *Parent, LString Msg, bool Password, std::function Callback) { if (InThread()) { 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."); 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: { LAutoPtr i((UserInput*)Msg->A()); LAssert(i); LAssert(InThread()); // Least we get stuck in an infinite loop 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); f->GetMessageById(a, [&](auto r) { 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; } void ScribeWnd::ThingPrint(std::function Callback, ThingType *m, LPrinter *Printer, LView *Parent, int MaxPages) { d->PrintMaxPages = MaxPages; if (!Printer) Printer = GetPrinter(); if (!Printer) { if (Callback) Callback(false); return; } Thing *t = dynamic_cast(m); if (!t) { if (Callback) Callback(false); return; } ScribePrintContext Events(this, t); 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 += ""; 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: { 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 *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(NULL); } }); break; } case IDM_REPLICATE: { 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. auto ShowDialog = [&]() { auto Dlg = new SecurityDlg(this); Dlg->DoModal(NULL); }; GPassword p; if (p.Serialize(GetOptions(), OPT_UserPermPassword, false)) { 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 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(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: { 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(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: { 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) { LVariant SpamPath, ProbablyPath; GetOptions()->GetValue(OPT_SpamFolder, SpamPath); GetOptions()->GetValue(OPT_BayesMoveTo, ProbablyPath); if (m == BayesFilter) { ScribeFolder *Spam = GetFolder(SpamPath.Str()); if (!Spam) { 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; } } } if (Spam) { LVariant v; GetOptions()->SetValue(OPT_HasSpam, v = 1); } } 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: { ExportScribe(this, NULL/* default mail store */); 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; auto Dlg = new Store3Progress(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, Dlg](auto status) { LAssert(InThread()); if (WorkOffline) WorkOffline->Checked(Offline); delete Dlg; }); 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); // Also look at all the contact sources... auto Srcs = 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); } } } } // 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 (auto 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, ...) 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() 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 IsDef = Def.IsDefault(); if (!IsDef) { // Ask the user... 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.NewLStr(); } 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()); } // 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) { default: break; case PermRequireUser: { - bool Status = false; - GPassword p; 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); 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); bool Status = strcmp(Pass, d->GetStr()) == 0; if (Status) { CurrentAuthLevel = PermRequireUser; auto i = Menu->FindItem(IDM_LOGOUT); if (i) i->Enabled(true); 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)) { 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); 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; } */ 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)) { LDataIt 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 { auto d = new LAlert(this, AppName, LLoadString(IDS_ERROR_NO_CONFIG_SEND), LLoadString(IDS_CONFIGURE), LLoadString(IDS_CANCEL)); 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 auto a = new LAlert(this, AppName, LLoadString(IDS_ERROR_NO_CONFIG_RECEIVE), LLoadString(IDS_CONFIGURE), LLoadString(IDS_CANCEL)); 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 LDataIt Sc = Src->GetList(FIELD_MIME_SEG); LDataIt 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()); auto Ask = new LAlert(this, AppName, Msg, LLoadString(IDS_SAVE_TO_OUTBOX), LLoadString(IDS_CANCEL)); Ask->DoModal([this, Msgs, Outbox](auto dlg, auto id) { switch (id) { case 1: // Save To Outbox { for (size_t i=0; iSave(Outbox); } break; } case 2: // Cancel { for (size_t i=0; iOnDelete(); } 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 = NULL; 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); } t->SetUI(); t->SetParentFolder(NULL); t->SetObject(NULL, false, _FL); if (Fld) Fld->Update(); 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 (auto &f: Folders) { if (f.Store) Trans.New() = f.Store->StartTransaction(); } 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. Otherwise 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) break; Outbox->GetMessageById(Transfer->Send->MsgId, [this, Transfer](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()) { LDataIt 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)); } } } // FIXME: // 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/ScribeFolder.cpp b/Code/ScribeFolder.cpp --- a/Code/ScribeFolder.cpp +++ b/Code/ScribeFolder.cpp @@ -1,4486 +1,4486 @@ /* ** 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" #include "FolderTask.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 ScribeFolderPriv { public: int8 IsInbox = -1; bool InUpdateUnread = false; LAutoPtr DsBase; LAutoPtr DsUnread; LAutoPtr FilePromise; }; ////////////////////////////////////////////////////////////////////////////// ScribeFolder::ScribeFolder() { d = new ScribeFolderPriv; } 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); } } void ScribeFolder::GetMessageById(const char *Id, std::function Callback) { if (!Id) { if (Callback) Callback(NULL); return; } LoadThings(NULL, [&](auto s) { - if (s <= Store3Loading) + if (s < Store3Delayed) { if (Callback) Callback(NULL); return; } for (auto t : Items) { Mail *r = t->IsMail(); if (!r) continue; 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; } void ScribeFolder::SetFolderPerms(LView *Parent, ScribeAccessType Access, ScribePerm Perm, std::function Callback) { ScribePerm Current = GetFolderPerms(Access); int Field = (Access == ScribeReadAccess) ? FIELD_FOLDER_PERM_READ : FIELD_FOLDER_PERM_WRITE; if (GetObject() && Current != Perm) { App->GetAccessLevel(Parent, Current, GetPath(), [&](auto Allow) { if (Allow) { GetObject()->SetInt(Field, Perm); SetDirty(); } if (Callback) Callback(Allow); }); } 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; } Store3Status ScribeFolder::DeleteAllThings(std::function Callback) { if (!GetFldObj()) return Store3Error; Store3Status r = GetFldObj()->DeleteAllChildren(); if (r == Store3Error) { LAssert(!"DeleteAllChildren failed."); } else if (r == Store3Success) { LAssert(Items.Length() == 0); OnUpdateUnRead(0, true); Update(); } return r; } Store3Status ScribeFolder::DeleteThing(Thing *t, std::function Callback) { 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, std::function Callback) { if (!t) { if (Callback) Callback(Store3Error); return Store3Error; } auto ParentFolder = GetFolder(); auto Path = ParentFolder ? ParentFolder->GetPath() : NULL; auto OnAllow = [this, t, Callback]() { // 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); if (Callback) Callback(Store3Error); return; } // saving a thing that already has an item on disk auto Obj = GetObject(); 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. 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); if (Callback) Callback(Store3Error); } }; if (App) App->GetAccessLevel(App, GetWriteAccess(), Path, [OnAllow](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: { auto Dlg = new FolderNameDlg(mt, GetName(true)); Dlg->DoModal([this, Dlg, mt](auto dlg, auto id) { if (id && ValidStr(Dlg->Name)) { // check for folder name conflicts... ScribeFolder *ParentFolder = GetFolder(); LString Path; if (ParentFolder) Path = ParentFolder->GetPath(); if (Path) { 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); } delete dlg; }); break; } case IDM_EXPORT: { LString DropName = LGetLeaf(GetDropFileName()); if (!DropName) { LgiTrace("%s:%i - Failed to create folder name.\n", _FL); break; } auto s = new LFileSelect(mt); s->Name(DropName); s->Save([&](auto dlg, auto status) { 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(); LArray Del; for (ScribeFolder *c = GetChildFolder(); c; c = c->GetNextFolder()) Del.Add(c->GetObject()); if (Del.Length()) GetObject()->GetStore()->Delete(Del, false); 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: { auto s = new LFileSelect(mt); s->Type("Email Template", "*.txt;*.eml"); s->Open([this](auto dlg, auto id) { if (id) { LArray Recip; for (auto i: Items) { Recip.Add(new ListAddr(i->IsContact())); } App->MailMerge(Recip, dlg->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 Store3Status ScribeFolder::LoadThings(LViewI *Parent, std::function Callback) { int OldUnRead = GetUnRead(); auto FldObj = GetFldObj(); if (!FldObj) { LgiTrace("%s:%i - No folder object.\n", _FL); return Store3Error; } if (!Parent) Parent = App; auto ContinueLoading = [&]() { WhenLoaded(_FL, [this, OldUnRead, Callback]() { // 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 || !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); Items.Empty(); IsLoaded(false); Update(); } else if (Access == Store3Success) { ContinueLoading(); } 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); } 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()) return; SerializeFieldWidths(); if (View()) { SetSort(View()->GetSortCol(), View()->GetSortAscending()); } OpenFolderProperties(this, Tab, [this](auto repop) { if (repop) { 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()); } auto DoMove = [&]() { 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 { // If this folder is currently selected... if (Select()) { // Insert item into list t->SetFieldArray(FieldArray); } if (CopyOnly) { 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 { 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) { // 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); } } } } }; 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(NULL, [&](auto Status) { 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 = NULL; int Hdrs = 0; bool NewMsg = true; LArray Buf; int64 Pos = 0; bool Eof = false; bool IsMessageHdr(LString c) { // check that it's a from line auto parts = c.SplitDelimit(" \r"); if (parts.Length() >= 7 && parts.Length() <= 9 && parts[0].Equals("From")) { return true; } return false; } struct Blk { uint8_t *ptr; ssize_t size; }; struct Blocks : public LArray { ssize_t Bytes = 0; Blocks(LMemQueue *q) { q->Iterate([this](auto ptr, auto size) { auto &b = New(); b.ptr = ptr; b.size = size; Bytes += size; return true; }); } LString GetLine(size_t idx, size_t offset) { char buf[256]; int ch = 0; for (size_t i = idx; i < Length(); i++) { auto &b = (*this)[i]; auto p = b.ptr + offset; auto end = b.ptr + b.size; while (p < end) { if (*p == '\n' || ch == sizeof(buf)-1) return LString(buf, ch); buf[ch++] = *p++; } } return LString(); } bool ValidateSeparator(LString ln) { auto p = ln.SplitDelimit(); if (p.Length() < 7) return false; if (!p[0].Equals("From")) return false; if (p[1].Find("@") < 0) return false; bool hasYear = false; bool hasTime = false; for (int i=2; i= 1800 && val < 2200) hasYear = true; } else if (s.Find(":") > 0) { int colons = 0, nonDigits = 0; for (auto p = s.Get(); *p; p++) if (*p == ':') colons++; else if (!IsDigit(*p)) nonDigits++; if (colons == 2 && nonDigits == 0) hasTime = true; } } return hasYear && hasTime; } ssize_t FindBoundary(ssize_t start) { const char *key = "\nFrom "; const char *k = key + 1; size_t idx = 0; ssize_t offset = 0; if (Bytes == 0) return -1; if (start < 0 || start >= Bytes) { LAssert(!"Start out of range."); return -1; } // Seek to the right starting block... while (idx < Length()) { auto &b = (*this)[idx]; if (start < b.size) break; start -= b.size; offset += b.size; idx++; } // Start searching for the key... while (idx < Length()) { auto &b = (*this)[idx]; auto end = b.ptr + b.size; for (auto p = b.ptr + start; p < end; p++) { if (*k == *p) { if (*++k == 0) { // Found the "From " part, but lets check the rest of the line. // Should be in the format: // From sender date more-info auto blkAddr = (p - b.ptr) - 4; LString ln = GetLine(idx, blkAddr); if (ln && ValidateSeparator(ln)) return offset + blkAddr; } } else k = key; } offset += b.size; idx++; } return -1; } LRange FindMsg(MboxParser &parser) { auto start = FindBoundary(0); if (start > 0) LgiTrace("%s:%i - Usually the start should be 0, but it's " LPrintfSSizeT "?\n", _FL, start); if (start >= 0) { auto end = FindBoundary(start + 5); if (end > start) { return LRange(start, end - start); } else if (parser.Eof) { return LRange(start, Bytes); } } return LRange(-1, 0); } }; public: MboxParser(LStreamI *s) : LStringPipe(128 << 10) { Src = s; // _debug = true; Buf.Length(128 << 10); } bool ReadSource() { auto rd = Src->Read(Buf.AddressOf(), Buf.Length()); if (rd <= 0) { // Src stream is empty or in an error state.. Eof = true; return false; } auto wr = Write(Buf.AddressOf(), Buf.Length()); if (wr <= 0) { LgiTrace("%s:%i - Failed to write to local buffer.\n", _FL); return false; } return true; } LAutoPtr ReadMessage() { LAutoPtr m; while (true) { Blocks blks(this); auto r = blks.FindMsg(*this); if (r.Start >= 0) { LMemStream *ms = NULL; /* LgiTrace("ReadMsg " LPrintfInt64 " %s\n", Pos, r.GetStr()); auto InitPos = Pos; */ Pos += r.Len; m.Reset(ms = new LMemStream(this, r.Start, r.Len)); /* Debugging... auto key = "The package name is vmware_addons."; auto base = ms->GetBasePtr(); auto result = Strnistr(base, key, m->GetSize()); if (result) LgiTrace("Found the Key @ " LPrintfInt64 "\n", InitPos + (result - base)); */ break; } else if (!ReadSource()) { r = blks.FindMsg(*this); if (r.Start >= 0) m.Reset(new LMemStream(this, r.Start, r.Len)); break; } } return m; } }; class ImportFolderTask : public FolderTask { LDataStoreI::StoreTrans trans; LAutoPtr Parser; 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(); bool Eof = false; if (!Parser) Parser.Reset(new MboxParser(Stream)); while ( Parser && LCurrentTime() - Start < WORK_SLICE_MS && !IsCancelled()) { auto Msg = Parser->ReadMessage(); if (Msg) { 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(); } else { Eof = true; break; } } else { Eof = true; break; } Value(Stream->GetPos()); } return !Eof; } }; 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); } */ }; class FolderExportTask : public LProgressDlg { LAutoPtr Out; ScribeFolder *Folder; LString MimeType; int Idx; bool HasError = false; 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; FolderExportTask(LAutoPtr out, ScribeFolder *folder, LString mimeType) : LProgressDlg(folder->App) { Out = out; Folder = folder; MimeType = mimeType; Idx = 0; Ts = LCurrentTime(); Folder->App->OnFolderTask(this, true); bool Mbox = _stricmp(MimeType, sMimeMbox) == 0; // Clear the files contents Out->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); } ~FolderExportTask() { Folder->App->OnFolderTask(this, false); } bool OnRequestClose(bool OsClose) { 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) { if (t->Export(Out, MimeType, NULL)) { Value(Idx); } else { HasError = true; LgiMsg(this, "Error exporting items.", AppName); 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; } void ScribeFolder::ExportAsync(LAutoPtr f, const char *MimeType, std::function Callback) { if (!MimeType) { LAssert(!"No Mimetype"); if (Callback) Callback(NULL); return; } LoadThings(NULL, [&](auto Status) { FolderExportTask *Task = NULL; if (Status == Store3Success) Task = new FolderExportTask(f, this, MimeType); if (Callback) Callback(Task); }); } ThingType::IoProgress ScribeFolder::Export(IoProgressImplArgs) { IoProgress ErrStatus(Store3Error); if (!mimeType) { ErrStatus.errMsg = "No mimetype."; if (cb) cb(&ErrStatus, NULL); return ErrStatus; } 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; } 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[] { Value.Empty(); LoadThings(); // Use in sync mode, no callback // This call back HAS to set value one way or another... if (Array) { 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; return true; } } } } else if (Value.SetList()) { 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: () { 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; } default: break; } return false; } void ScribeFolder::OnRethread() { if (GetThreaded()) { Thread(); } } diff --git a/Code/ScribeFolderTree.cpp b/Code/ScribeFolderTree.cpp --- a/Code/ScribeFolderTree.cpp +++ b/Code/ScribeFolderTree.cpp @@ -1,826 +1,826 @@ /* ** 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; } LDataIt ac = a->GetList(FIELD_MIME_SEG); LDataIt 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); LDataIt 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) }; auto Dlg = new CreateSubFolderDlg(this, i, Enable); Dlg->DoModal([this, Dlg, Item, Type](auto dlg, auto code) { if (code && ValidStr(Dlg->SubName) && Dlg->SubType >= 0) { // check the name doesn't conflict.. auto Path = Item->GetPath(); if (Path) { LString s; - s.Printf("%s/%s", Path.Get(), Dlg->SubName); + s.Printf("%s/%s", Path.Get(), Dlg->SubName.Get()); if (App->GetFolder(s)) { LgiMsg(this, LLoadString(IDS_SUBFLD_NAME_CLASH), AppName, MB_OK); Dlg->SubName.Empty(); } } if (Dlg->SubName) { // insert the folder... Item->CreateSubDirectory(Dlg->SubName, Type[Dlg->SubType]); } } delete dlg; }); } 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; } bool CopyOp = (KeyState & LGI_EF_CTRL) != 0; auto FinishFolderOp = [&](int Res) { 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) { // Work out the index for (LTreeItem *Item = NewParent->GetChild(); Item && Item!=Leaf; Item=Item->GetNext()) { Index++; } Index++; } } else if (Res == 2) { // Attach child NewParent = Leaf; } else return; if (SystemFolder >= 0 && NewParent->GetObject()->GetStore() != OldParent->GetObject()->GetStore()) { 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 { LDataFolderI *fo = Folder->GetFldObj(); if (NewParent == OldParent) { // 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) { // 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) { 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) { 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,1447 +1,1447 @@ #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) { auto Name = GetName(); DropFileName.Reset(MakeFileName(Name ? Name : "group", "xml")); } return DropFileName; } bool ContactGroup::GetDropFiles(LString::Array &Files) { auto fn = GetDropFileName(); if (!fn) return false; if (!LFileExists(fn)) { LAutoPtr F(new LFile); if (F->Open(fn, O_WRITE)) { F->SetSize(0); Export(AutoCast(F), sMimeXml); } } if (!LFileExists(DropFileName)) return false; Files.Add(DropFileName.Get()); return true; } bool ContactGroup::GetFormats(bool Export, LString::Array &MimeTypes) { MimeTypes.Add(sTextXml); return MimeTypes.Length() > 0; } Thing::IoProgress ContactGroup::Import(IoProgressImplArgs) { if (Stricmp(mimeType, sTextXml) && Stricmp(mimeType, sMimeXml)) IoProgressNotImpl(); LXmlTree Tree; LXmlTag r, *t; if (!Tree.Read(&r, stream)) IoProgressError("Xml parse error."); if (!r.IsTag(ContactGroupObj)) IoProgressError("No ContactGroup tag."); - if (t = r.GetChildTag(ContactGroupName)) + if ((t = r.GetChildTag(ContactGroupName))) GetObject()->SetStr(FIELD_GROUP_NAME, t->GetContent()); else IoProgressError("No Name tag."); - if (t = r.GetChildTag(ContactGroupList)) + if ((t = r.GetChildTag(ContactGroupList))) GetObject()->SetStr(FIELD_GROUP_LIST, t->GetContent()); else IoProgressError("No List tag."); - if (t = r.GetChildTag(ContactGroupDateModified)) + if ((t = r.GetChildTag(ContactGroupDateModified))) { LDateTime dt; if (dt.Set(t->GetContent())) GetObject()->SetDate(FIELD_DATE_MODIFIED, &dt); } IoProgressSuccess(); } Thing::IoProgress ContactGroup::Export(IoProgressImplArgs) { if (Stricmp(mimeType, sMimeXml)) IoProgressNotImpl(); auto Name = GetName(); LVariant Addr; GetVariant(ContactGroupList, Addr); auto Modified = GetObject()->GetDate(FIELD_DATE_MODIFIED); LXmlTag r(ContactGroupObj), *t; if ((t = r.CreateTag(ContactGroupName))) t->SetContent(Name); if ((t = r.CreateTag(ContactGroupList))) t->SetContent(Addr.Str()); if (Modified && Modified->IsValid() && (t = r.CreateTag(ContactGroupDateModified))) t->SetContent(Modified->Get()); LXmlTree tree; if (tree.Write(&r, stream)) IoProgressError("Failed to write xml."); IoProgressSuccess(); } 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; iSetStr(FIELD_GROUP_NAME, Value.Str()); break; } case SdList: // Type: String[] { if (Array) { auto Idx = Atoi(Array); auto t = LString(GetObject()->GetStr(FIELD_GROUP_LIST)).SplitDelimit(ListDelimiters); if (t.IdxCheck(Idx)) t[Idx] = Value.Str(); else t.New() = Value.Str(); GetObject()->SetStr(FIELD_GROUP_LIST, LString("\n").Join(t)); } else { GetObject()->SetStr(FIELD_GROUP_LIST, Value.Str()); } break; } case SdDateModified: { return SetDateField(FIELD_DATE_MODIFIED, Value); } default: { return false; } } return true; } bool ContactGroup::GetVariant(const char *Name, LVariant &Value, const char *Array) { ScribeDomType Fld = StrToDom(Name); switch (Fld) { case SdName: // Type: String { Value = GetObject()->GetStr(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: { return GetDateField(FIELD_DATE_MODIFIED, Value); } 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) { auto obj = dynamic_cast(i); if (obj) obj->OnDelete(); else LAssert(!"What type of object is this?"); } } } break; } case IDM_MERGE_FILE: { auto s = new LFileSelect(App); s->Type("Email Template", "*.txt;*.eml"); s->Open([this](auto dlg, auto status) { 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) { if (!GetFolder()) { if (Into) SetParentFolder(Into); else if (App) SetParentFolder(App->GetFolder(FOLDER_GROUPS)); } if (!GetFolder()) return false; LDateTime Now; GetObject()->SetDate(FIELD_DATE_MODIFIED, &Now.SetNow()); auto 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(); Quit(); 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/Store3Mail3/Mail3Contact.cpp b/Code/Store3Mail3/Mail3Contact.cpp --- a/Code/Store3Mail3/Mail3Contact.cpp +++ b/Code/Store3Mail3/Mail3Contact.cpp @@ -1,248 +1,242 @@ #include "lgi/common/Lgi.h" #include "Mail3.h" #define ALL_FIELDS() \ _("Uid", "TEXT", FIELD_UID) \ _("Title", "TEXT", FIELD_TITLE) \ _("First", "TEXT", FIELD_FIRST_NAME) \ _("Last", "TEXT", FIELD_LAST_NAME) \ _("Email", "TEXT", FIELD_EMAIL) \ _("AltEmail", "TEXT", FIELD_ALT_EMAIL) \ _("Nick", "TEXT", FIELD_NICK) \ _("Spouse", "TEXT", FIELD_SPOUSE) \ _("Notes", "TEXT", FIELD_NOTE) \ _("TimeZone", "TEXT", FIELD_TIMEZONE) \ _("Plugins", "TEXT", FIELD_PLUGIN_ASSOC) \ \ _("HomeStreet", "TEXT", FIELD_HOME_STREET) \ _("HomeSuburb", "TEXT", FIELD_HOME_SUBURB) \ _("HomePostCode", "TEXT", FIELD_HOME_POSTCODE) \ _("HomeState", "TEXT", FIELD_HOME_STATE) \ _("HomeCountry", "TEXT", FIELD_HOME_COUNTRY) \ _("HomePhone", "TEXT", FIELD_HOME_PHONE) \ _("HomeMobile", "TEXT", FIELD_HOME_MOBILE) \ _("HomeIM", "TEXT", FIELD_HOME_IM) \ _("HomeFax", "TEXT", FIELD_HOME_FAX) \ _("HomeWebpage", "TEXT", FIELD_HOME_WEBPAGE) \ \ _("WorkStreet", "TEXT", FIELD_WORK_STREET) \ _("WorkSuburb", "TEXT", FIELD_WORK_SUBURB) \ _("WorkPostCode", "TEXT", FIELD_WORK_POSTCODE) \ _("WorkState", "TEXT", FIELD_WORK_STATE) \ _("WorkCountry", "TEXT", FIELD_WORK_COUNTRY) \ _("WorkPhone", "TEXT", FIELD_WORK_PHONE) \ _("WorkMobile", "TEXT", FIELD_WORK_MOBILE) \ _("WorkIM", "TEXT", FIELD_WORK_IM) \ _("WorkFax", "TEXT", FIELD_WORK_FAX) \ _("WorkWebpage", "TEXT", FIELD_WORK_WEBPAGE) \ _("Company", "TEXT", FIELD_COMPANY) \ \ _("Image", "BLOB", FIELD_CONTACT_IMAGE) \ _("JSON", "TEXT", FIELD_CONTACT_JSON) \ \ _("DateModified", "TEXT", FIELD_DATE_MODIFIED) // UTC LVariantType FieldToType(int Fld) { switch (Fld) { case FIELD_DATE_MODIFIED: return GV_DATETIME; case FIELD_CONTACT_IMAGE: return GV_BINARY; } return GV_STRING; } GMail3Def TblContact[] = { {"Id", "INTEGER PRIMARY KEY AUTOINCREMENT"}, {"ParentId", "INTEGER"}, #define _(SqlName, SqlType, Id) {SqlName, SqlType}, ALL_FIELDS() #undef _ {0, 0} }; LMail3Contact::LMail3Contact(LMail3Store *store) : LMail3Thing(store) { } LMail3Contact::~LMail3Contact() { f.DeleteObjects(); } bool LMail3Contact::DbDelete() { char s[256]; // Delete the contact sprintf_s(s, sizeof(s), "delete from " MAIL3_TBL_CONTACT " where Id=" LPrintfInt64, Id); LMail3Store::LStatement Del(Store, s); if (!Del.Exec()) return false; return true; } Store3CopyImpl(LMail3Contact) { f.DeleteObjects(); #define _(SqlName, SqlType, Id) \ if (Id != FIELD_CONTACT_IMAGE) \ { \ auto s = p.GetStr(Id); \ if (s) f.Add(Id, new LString(s)); \ } ALL_FIELDS() #undef _ const LVariant *v = p.GetVar(FIELD_CONTACT_IMAGE); if (v && v->Type == GV_BINARY) Image = *v; return true; } bool LMail3Contact::Serialize(LMail3Store::LStatement &s, bool Write) { int i = 0; if (Write) { DateMod.SetNow(); DateMod.ToUtc(); } else f.DeleteObjects(); SERIALIZE_INT64(Id, i++); SERIALIZE_INT64(ParentId, i++); #define SERIALIZE_HASHED_GSTR(id, idx) \ { \ if (Write) \ { \ LString *v = f.Find(id); \ /*LgiTrace("Write %i, %s -> %i\n", id, v.Get(), idx);*/ \ if (!s.SetStr(idx, v ? v->Get() : NULL)) \ return false; \ } \ else \ { \ const char *v = s.GetStr(idx); \ /*LgiTrace("Read %i -> %i, %s\n", idx, id, v.Get());*/ \ if (v) \ f.Add(id, new LString(v)); \ } \ idx++; \ } #define _(SqlName, SqlType, Id) \ if (FieldToType(Id) == GV_STRING) \ { \ SERIALIZE_HASHED_GSTR(Id, i); \ } ALL_FIELDS() #undef _ if (Write) { if (Image.Type == GV_BINARY) s.SetBinary(i, "Image", &Image); i++; } else { s.GetBinary(i++, &Image); } - - if (Id == 58) - { - int asd=0; - } - SERIALIZE_DATE(DateMod, i++); return true; } const char *LMail3Contact::GetStr(int id) { LString *s = f.Find(id); return s ? s->Get() : NULL; } Store3Status LMail3Contact::SetStr(int id, const char *str) { if (str) { LString *v = f.Find(id); if (v) { *v = str; return Store3Success; } return f.Add(id, new LString(str)) ? Store3Success : Store3Error; } f.Delete(Id); return Store3Success; } const LDateTime *LMail3Contact::GetDate(int id) { switch (id) { case FIELD_DATE_MODIFIED: return &DateMod; } return NULL; } Store3Status LMail3Contact::SetDate(int id, const LDateTime *i) { switch (id) { case FIELD_DATE_MODIFIED: DateMod = i; return i ? Store3Success : Store3Error; } return Store3Error; } LVariant *LMail3Contact::GetVar(int id) { switch (id) { case FIELD_CONTACT_IMAGE: return &Image; } return NULL; } Store3Status LMail3Contact::SetVar(int id, LVariant *i) { if (!i) return Store3Error; switch (id) { case FIELD_CONTACT_IMAGE: { Image = *i; return Store3Success; } } return Store3Error; } diff --git a/MacCocoa/Scribe.xcodeproj/project.pbxproj b/MacCocoa/Scribe.xcodeproj/project.pbxproj --- a/MacCocoa/Scribe.xcodeproj/project.pbxproj +++ b/MacCocoa/Scribe.xcodeproj/project.pbxproj @@ -1,1963 +1,1963 @@ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 340E2A1E278CEA7200F0B4C8 /* LgiCocoa.framework in Copy Files */ = {isa = PBXBuildFile; fileRef = 34749B44278B042500C8197E /* LgiCocoa.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 340FA6A9237E7E2D0043D1F2 /* EmojiMap.png in Resources */ = {isa = PBXBuildFile; fileRef = 340FA6A8237E7E2D0043D1F2 /* EmojiMap.png */; }; 340FAAEA23888A570017BC62 /* WebdavContact.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 340FAADC23888A570017BC62 /* WebdavContact.cpp */; }; 3416149A238484F800A65477 /* WebdavFolder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 34161495238484F700A65477 /* WebdavFolder.cpp */; }; 3416149B238484F800A65477 /* WebdavCalendar.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 34161496238484F700A65477 /* WebdavCalendar.cpp */; }; 3416149C238484F800A65477 /* WebdavThread.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 34161498238484F800A65477 /* WebdavThread.cpp */; }; 34167B38279FEBA8005422F6 /* libchardet.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 34167B34279FEAAF005422F6 /* libchardet.dylib */; }; 341A02B823BD702800F0FF48 /* SpellCheckAspell.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 341A02B723BD702800F0FF48 /* SpellCheckAspell.cpp */; }; 341A78802699058600872FEB /* DatePopup.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 341A787F2699058600872FEB /* DatePopup.cpp */; }; 341BF47123231D0700BC3AB0 /* Jpeg.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 341BF47023231D0600BC3AB0 /* Jpeg.cpp */; }; 342BAE482588AFA8006EF16E /* TextConvert.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 342BAE3A2588AFA8006EF16E /* TextConvert.cpp */; }; 34364F8924CCE9FF00272F13 /* Themes in Resources */ = {isa = PBXBuildFile; fileRef = 34364F8824CCE9FF00272F13 /* Themes */; }; 34417D89205FB69E00D2E80C /* MailFlags.c in Sources */ = {isa = PBXBuildFile; fileRef = 34417D87205FB69E00D2E80C /* MailFlags.c */; }; 34417D8E205FD4A500D2E80C /* Ftp.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 34417D8D205FD4A500D2E80C /* Ftp.cpp */; }; 34417DBE205FD5B800D2E80C /* HorzRuleBlock.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 34417DBD205FD5B800D2E80C /* HorzRuleBlock.cpp */; }; 3445E5731F1F40DF00D0A824 /* EmojiTools.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3445E5721F1F40DF00D0A824 /* EmojiTools.cpp */; }; 3445E5751F1F413200D0A824 /* EmojiMap.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3445E5741F1F413200D0A824 /* EmojiMap.cpp */; }; 3445E5771F1F416500D0A824 /* SpellCheckMac.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3445E5761F1F416500D0A824 /* SpellCheckMac.mm */; }; 3445E57B1F1F41AF00D0A824 /* GnuPG.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3445E5791F1F41AF00D0A824 /* GnuPG.cpp */; }; 3445E5841F1F428400D0A824 /* BlockCursor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3445E57E1F1F428400D0A824 /* BlockCursor.cpp */; }; 3445E5851F1F428400D0A824 /* RichTextEdit.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3445E57F1F1F428400D0A824 /* RichTextEdit.cpp */; }; 3445E5861F1F428400D0A824 /* RichTextEditPriv.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3445E5801F1F428400D0A824 /* RichTextEditPriv.cpp */; }; 3445E5871F1F428400D0A824 /* ImageBlock.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3445E5821F1F428400D0A824 /* ImageBlock.cpp */; }; 3445E5881F1F428400D0A824 /* TextBlock.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3445E5831F1F428400D0A824 /* TextBlock.cpp */; }; 3445E58A1F1F42BA00D0A824 /* ObjectInspector.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3445E5891F1F42BA00D0A824 /* ObjectInspector.cpp */; }; 345F31461CC7A3B400656B8B /* Icons-16.png in Resources */ = {isa = PBXBuildFile; fileRef = 345F31451CC7A3B400656B8B /* Icons-16.png */; }; 345F31481CC7A40F00656B8B /* Gif.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 345F31471CC7A40F00656B8B /* Gif.cpp */; }; 345F314A1CC7A44C00656B8B /* Lzw.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 345F31491CC7A44C00656B8B /* Lzw.cpp */; }; 345F314C1CC7A46B00656B8B /* xgate-icons-32.png in Resources */ = {isa = PBXBuildFile; fileRef = 345F314B1CC7A46B00656B8B /* xgate-icons-32.png */; }; 346E1A47231292C60019B4AA /* PreviewMail.html in Resources */ = {isa = PBXBuildFile; fileRef = 346E1A44231292C60019B4AA /* PreviewMail.html */; }; 346E1A48231292C60019B4AA /* PreviewContact.html in Resources */ = {isa = PBXBuildFile; fileRef = 346E1A45231292C60019B4AA /* PreviewContact.html */; }; 346E1A49231292C60019B4AA /* PreviewGroup.html in Resources */ = {isa = PBXBuildFile; fileRef = 346E1A46231292C60019B4AA /* PreviewGroup.html */; }; 346E1A4D231293570019B4AA /* tray_error.png in Resources */ = {isa = PBXBuildFile; fileRef = 346E1A4A231293570019B4AA /* tray_error.png */; }; 346E1A4E231293570019B4AA /* tray_small.png in Resources */ = {isa = PBXBuildFile; fileRef = 346E1A4B231293570019B4AA /* tray_small.png */; }; 346E1A4F231293570019B4AA /* tray_mail.png in Resources */ = {isa = PBXBuildFile; fileRef = 346E1A4C231293570019B4AA /* tray_mail.png */; }; 34749B5F278B08B200C8197E /* LgiCocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 34749B44278B042500C8197E /* LgiCocoa.framework */; }; 3474FC5F226DE0960064E0AD /* libBtree.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3474FC4E226DCCDD0064E0AD /* libBtree.a */; }; 3474FC60226DE0960064E0AD /* libbzip2.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3474FC49226DCCD10064E0AD /* libbzip2.a */; }; 3477C3581CC1149F0028B84B /* LgiMain.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C3571CC1149F0028B84B /* LgiMain.cpp */; }; 3477C3621CC226450028B84B /* ScribeMain.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C3611CC226450028B84B /* ScribeMain.cpp */; }; 3477C3AF1CC227ED0028B84B /* AddressSelect.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C3631CC227ED0028B84B /* AddressSelect.cpp */; }; 3477C3B11CC227ED0028B84B /* BayesDlg.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C3651CC227ED0028B84B /* BayesDlg.cpp */; }; 3477C3B21CC227ED0028B84B /* BayesianFilter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C3661CC227ED0028B84B /* BayesianFilter.cpp */; }; 3477C3B31CC227ED0028B84B /* Calendar.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C3671CC227ED0028B84B /* Calendar.cpp */; }; 3477C3B41CC227ED0028B84B /* CalendarView.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C3681CC227ED0028B84B /* CalendarView.cpp */; }; 3477C3B51CC227ED0028B84B /* Components.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C3691CC227ED0028B84B /* Components.cpp */; }; 3477C3B91CC227ED0028B84B /* Exp_Scribe.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C36D1CC227ED0028B84B /* Exp_Scribe.cpp */; }; - 3477C3BE1CC227ED0028B84B /* GNewMailDlg.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C3721CC227ED0028B84B /* GNewMailDlg.cpp */; }; + 3477C3BE1CC227ED0028B84B /* NewMailDlg.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C3721CC227ED0028B84B /* NewMailDlg.cpp */; }; 3477C3BF1CC227ED0028B84B /* SearchView.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C3731CC227ED0028B84B /* SearchView.cpp */; }; 3477C3C01CC227ED0028B84B /* HtmlToText.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C3741CC227ED0028B84B /* HtmlToText.cpp */; }; 3477C3C21CC227ED0028B84B /* Imp_Eml.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C3761CC227ED0028B84B /* Imp_Eml.cpp */; }; 3477C3C31CC227ED0028B84B /* Imp_Eudora.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C3771CC227ED0028B84B /* Imp_Eudora.cpp */; }; 3477C3C41CC227ED0028B84B /* Imp_Mozilla.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C3781CC227ED0028B84B /* Imp_Mozilla.cpp */; }; 3477C3C51CC227ED0028B84B /* Imp_NetscapeContacts.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C3791CC227ED0028B84B /* Imp_NetscapeContacts.cpp */; }; 3477C3C71CC227ED0028B84B /* ImpExp_Mbox.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C37B1CC227ED0028B84B /* ImpExp_Mbox.cpp */; }; 3477C3CC1CC227ED0028B84B /* ManageMailStores.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C3801CC227ED0028B84B /* ManageMailStores.cpp */; }; 3477C3CD1CC227ED0028B84B /* MContainer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C3811CC227ED0028B84B /* MContainer.cpp */; }; 3477C3CE1CC227ED0028B84B /* OptionsDlg.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C3821CC227ED0028B84B /* OptionsDlg.cpp */; }; 3477C3CF1CC227ED0028B84B /* PreviewPanel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C3831CC227ED0028B84B /* PreviewPanel.cpp */; }; 3477C3D01CC227ED0028B84B /* PrintContext.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C3841CC227ED0028B84B /* PrintContext.cpp */; }; 3477C3D11CC227ED0028B84B /* PrintPreview.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C3851CC227ED0028B84B /* PrintPreview.cpp */; }; 3477C3D31CC227ED0028B84B /* ReplicateDlg.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C3871CC227ED0028B84B /* ReplicateDlg.cpp */; }; 3477C3D41CC227ED0028B84B /* ScribeAbout.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C3881CC227ED0028B84B /* ScribeAbout.cpp */; }; 3477C3D51CC227ED0028B84B /* ScribeAccount.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C3891CC227ED0028B84B /* ScribeAccount.cpp */; }; 3477C3D61CC227ED0028B84B /* ScribeAccountPreview.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C38A1CC227ED0028B84B /* ScribeAccountPreview.cpp */; }; 3477C3D71CC227ED0028B84B /* ScribeAccountUI.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C38B1CC227ED0028B84B /* ScribeAccountUI.cpp */; }; 3477C3D91CC227ED0028B84B /* ScribeApp.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C38D1CC227ED0028B84B /* ScribeApp.cpp */; }; 3477C3DA1CC227ED0028B84B /* ScribeAttachment.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C38E1CC227ED0028B84B /* ScribeAttachment.cpp */; }; 3477C3DB1CC227ED0028B84B /* ScribeContact.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C38F1CC227ED0028B84B /* ScribeContact.cpp */; }; 3477C3DC1CC227ED0028B84B /* ScribeFilter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C3901CC227ED0028B84B /* ScribeFilter.cpp */; }; 3477C3DD1CC227ED0028B84B /* ScribeFinder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C3911CC227ED0028B84B /* ScribeFinder.cpp */; }; 3477C3DE1CC227ED0028B84B /* ScribeFolder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C3921CC227ED0028B84B /* ScribeFolder.cpp */; }; 3477C3DF1CC227ED0028B84B /* ScribeFolderDlg.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C3931CC227ED0028B84B /* ScribeFolderDlg.cpp */; }; 3477C3E01CC227ED0028B84B /* ScribeFolderProp.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C3941CC227ED0028B84B /* ScribeFolderProp.cpp */; }; 3477C3E11CC227ED0028B84B /* ScribeFolderSelect.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C3951CC227ED0028B84B /* ScribeFolderSelect.cpp */; }; 3477C3E21CC227ED0028B84B /* ScribeFolderTree.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C3961CC227ED0028B84B /* ScribeFolderTree.cpp */; }; 3477C3E31CC227ED0028B84B /* ScribeGroup.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C3971CC227ED0028B84B /* ScribeGroup.cpp */; }; 3477C3E41CC227ED0028B84B /* ScribeItemList.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C3981CC227ED0028B84B /* ScribeItemList.cpp */; }; 3477C3E51CC227ED0028B84B /* ScribeLangDlg.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C3991CC227ED0028B84B /* ScribeLangDlg.cpp */; }; 3477C3E61CC227ED0028B84B /* ScribeListAddr.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C39A1CC227ED0028B84B /* ScribeListAddr.cpp */; }; 3477C3E71CC227ED0028B84B /* ScribeMail.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C39B1CC227ED0028B84B /* ScribeMail.cpp */; }; 3477C3E91CC227ED0028B84B /* ScribePageSetup.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C39D1CC227ED0028B84B /* ScribePageSetup.cpp */; }; 3477C3EA1CC227ED0028B84B /* ScribePassword.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C39E1CC227ED0028B84B /* ScribePassword.cpp */; }; 3477C3EC1CC227ED0028B84B /* ScribeRepair.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C3A01CC227ED0028B84B /* ScribeRepair.cpp */; }; 3477C3ED1CC227ED0028B84B /* ScribeSendReceive.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C3A11CC227ED0028B84B /* ScribeSendReceive.cpp */; }; 3477C3EE1CC227ED0028B84B /* ScribeSockets.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C3A21CC227ED0028B84B /* ScribeSockets.cpp */; }; 3477C3EF1CC227ED0028B84B /* ScribeStatusPanel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C3A31CC227ED0028B84B /* ScribeStatusPanel.cpp */; }; 3477C3F01CC227ED0028B84B /* ScribeThing.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C3A41CC227ED0028B84B /* ScribeThing.cpp */; }; 3477C3F11CC227ED0028B84B /* ScribeUi.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C3A51CC227ED0028B84B /* ScribeUi.cpp */; }; 3477C3F21CC227ED0028B84B /* ScribeUtils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C3A61CC227ED0028B84B /* ScribeUtils.cpp */; }; 3477C3F41CC227ED0028B84B /* Scripting.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C3A81CC227ED0028B84B /* Scripting.cpp */; }; 3477C3F51CC227ED0028B84B /* SecurityDlg.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C3A91CC227ED0028B84B /* SecurityDlg.cpp */; }; 3477C3F81CC227ED0028B84B /* Store3Common.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C3AC1CC227ED0028B84B /* Store3Common.cpp */; }; 3477C3F91CC227ED0028B84B /* TitlePage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C3AD1CC227ED0028B84B /* TitlePage.cpp */; }; 3477C3FC1CC22E690028B84B /* Base64.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C3FB1CC22E690028B84B /* Base64.cpp */; }; 3477C3FF1CC22EF60028B84B /* MonthView.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C3FD1CC22EF60028B84B /* MonthView.cpp */; }; 3477C4001CC22EF60028B84B /* YearView.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C3FE1CC22EF60028B84B /* YearView.cpp */; }; 3477C4021CC22F340028B84B /* TimeZone.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C4011CC22F340028B84B /* TimeZone.cpp */; }; 3477C4041CC22F680028B84B /* ExeCheck.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C4031CC22F680028B84B /* ExeCheck.cpp */; }; 3477C4091CC2301F0028B84B /* GdcTools.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C4081CC2301F0028B84B /* GdcTools.cpp */; }; 3477C40B1CC230A30028B84B /* Tnef.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C40A1CC230A30028B84B /* Tnef.cpp */; }; 3477C40D1CC230C30028B84B /* Mail.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C40C1CC230C30028B84B /* Mail.cpp */; }; 3477C40F1CC231190028B84B /* Imp_OutlookExpress.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C40E1CC231190028B84B /* Imp_OutlookExpress.cpp */; }; 3477C4111CC231470028B84B /* OpenSSLSocket.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C4101CC231470028B84B /* OpenSSLSocket.cpp */; }; 3477C41B1CC231890028B84B /* ScribeImap_Attachment.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C4151CC231890028B84B /* ScribeImap_Attachment.cpp */; }; 3477C41C1CC231890028B84B /* ScribeImap_Folder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C4161CC231890028B84B /* ScribeImap_Folder.cpp */; }; 3477C41D1CC231890028B84B /* ScribeImap_Mail.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C4171CC231890028B84B /* ScribeImap_Mail.cpp */; }; 3477C41E1CC231890028B84B /* ScribeImap_Store.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C4181CC231890028B84B /* ScribeImap_Store.cpp */; }; 3477C41F1CC231890028B84B /* ScribeImap_Thread.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C4191CC231890028B84B /* ScribeImap_Thread.cpp */; }; 3477C4291CC2319B0028B84B /* Mail3Attachment.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C4211CC2319B0028B84B /* Mail3Attachment.cpp */; }; 3477C42A1CC2319B0028B84B /* Mail3Calendar.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C4221CC2319B0028B84B /* Mail3Calendar.cpp */; }; 3477C42B1CC2319B0028B84B /* Mail3Contact.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C4231CC2319B0028B84B /* Mail3Contact.cpp */; }; 3477C42C1CC2319B0028B84B /* Mail3Filter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C4241CC2319B0028B84B /* Mail3Filter.cpp */; }; 3477C42D1CC2319B0028B84B /* Mail3Folder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C4251CC2319B0028B84B /* Mail3Folder.cpp */; }; 3477C42E1CC2319B0028B84B /* Mail3Group.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C4261CC2319B0028B84B /* Mail3Group.cpp */; }; 3477C42F1CC2319B0028B84B /* Mail3Mail.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C4271CC2319B0028B84B /* Mail3Mail.cpp */; }; 3477C4301CC2319B0028B84B /* Mail3Store.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C4281CC2319B0028B84B /* Mail3Store.cpp */; }; 3477C4321CC233980028B84B /* Imp_CsvContacts.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C4311CC233980028B84B /* Imp_CsvContacts.cpp */; }; 3477C4341CC233CF0028B84B /* Db-Csv.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C4331CC233CF0028B84B /* Db-Csv.cpp */; }; 3477C4361CC234200028B84B /* Http.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C4351CC234200028B84B /* Http.cpp */; }; 3477C43E1CC234C80028B84B /* TimePopup.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C43D1CC234C80028B84B /* TimePopup.cpp */; }; 3477C4401CC234E60028B84B /* XmlTreeUi.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C43F1CC234E60028B84B /* XmlTreeUi.cpp */; }; 3477C4421CC235490028B84B /* FilterUi.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C4411CC235490028B84B /* FilterUi.cpp */; }; 3477C4461CC235810028B84B /* Html.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C4431CC235810028B84B /* Html.cpp */; }; 3477C4471CC235810028B84B /* HtmlCommon.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C4441CC235810028B84B /* HtmlCommon.cpp */; }; 3477C4481CC235810028B84B /* HtmlParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C4451CC235810028B84B /* HtmlParser.cpp */; }; 3477C44C1CC235C60028B84B /* OptionsFile.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C44B1CC235C60028B84B /* OptionsFile.cpp */; }; 3477C44E1CC2624B0028B84B /* ControlTree.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C44D1CC2624B0028B84B /* ControlTree.cpp */; }; 3477C4501CC262670028B84B /* ColourSelect.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C44F1CC262670028B84B /* ColourSelect.cpp */; }; 3477C4561CC262AF0028B84B /* LexCpp.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C4521CC262AF0028B84B /* LexCpp.cpp */; }; 3477C4571CC262AF0028B84B /* ScriptCompiler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C4531CC262AF0028B84B /* ScriptCompiler.cpp */; }; 3477C4581CC262AF0028B84B /* ScriptLibrary.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C4541CC262AF0028B84B /* ScriptLibrary.cpp */; }; 3477C4591CC262AF0028B84B /* ScriptVM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C4551CC262AF0028B84B /* ScriptVM.cpp */; }; 3477C45B1CC263560028B84B /* SharedMemory.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C45A1CC263560028B84B /* SharedMemory.cpp */; }; 3477C45E1CC26AAB0028B84B /* Mime.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C45C1CC26AAB0028B84B /* Mime.cpp */; }; 3477C45F1CC26AAB0028B84B /* MailImap.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C45D1CC26AAB0028B84B /* MailImap.cpp */; }; 3477C4621CC26AE00028B84B /* Growl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C4601CC26AE00028B84B /* Growl.cpp */; }; 3477C4631CC26AE00028B84B /* SoftwareUpdate.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C4611CC26AE00028B84B /* SoftwareUpdate.cpp */; }; 3477C4651CC26BBF0028B84B /* HttpTools.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C4641CC26BBF0028B84B /* HttpTools.cpp */; }; 3477C4671CC26BF30028B84B /* DrawListSurface.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C4661CC26BF30028B84B /* DrawListSurface.cpp */; }; 3477C4691CC26C1C0028B84B /* vCard-vCal.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C4681CC26C1C0028B84B /* vCard-vCal.cpp */; }; 3477C46B1CC26DAB0028B84B /* Path.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C46A1CC26DAB0028B84B /* Path.cpp */; }; 3477C4E71CC27D2B0028B84B /* MailHttp.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C4E61CC27D2B0028B84B /* MailHttp.cpp */; }; 3477C4E91CC27D870028B84B /* Browser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C4E81CC27D870028B84B /* Browser.cpp */; }; 3477C4EB1CC27DC90028B84B /* Password.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C4EA1CC27DC90028B84B /* Password.cpp */; }; 3477C4EF1CC27F500028B84B /* ZoomView.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3477C4EE1CC27F500028B84B /* ZoomView.cpp */; }; 3477C5201CC311A00028B84B /* Scribe.lr8 in Resources */ = {isa = PBXBuildFile; fileRef = 3477C51F1CC311A00028B84B /* Scribe.lr8 */; }; 3477C5231CC315FC0028B84B /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3477C5221CC315FC0028B84B /* Cocoa.framework */; }; 347CF82723F64FD00028F610 /* flags.png in Resources */ = {isa = PBXBuildFile; fileRef = 347CF82623F64FD00028F610 /* flags.png */; }; 34918095222F1C6F00944FC4 /* OAuth2.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 34918094222F1C6F00944FC4 /* OAuth2.cpp */; }; 3497D54F2399D288009FCC14 /* Add Senders To Contacts.script in CopyFiles */ = {isa = PBXBuildFile; fileRef = 3497D5482399D260009FCC14 /* Add Senders To Contacts.script */; }; 3497D5502399D288009FCC14 /* Delete Attachments.script in CopyFiles */ = {isa = PBXBuildFile; fileRef = 3497D5472399D260009FCC14 /* Delete Attachments.script */; }; 3497D5512399D288009FCC14 /* Delete Duplicate Messages.script in CopyFiles */ = {isa = PBXBuildFile; fileRef = 3497D5492399D260009FCC14 /* Delete Duplicate Messages.script */; }; 3497D5522399D288009FCC14 /* Mail Filters Menu.script in CopyFiles */ = {isa = PBXBuildFile; fileRef = 3497D5462399D260009FCC14 /* Mail Filters Menu.script */; }; 3497D5542399D2AB009FCC14 /* resdefs.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 3497D5532399D2A5009FCC14 /* resdefs.h */; }; 349AB0BE217B395B00573982 /* Png.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 349AB0BD217B395B00573982 /* Png.cpp */; }; 349AB0C0217B3D9500573982 /* scribe-mac-icon.icns in Resources */ = {isa = PBXBuildFile; fileRef = 349AB0BF217B3D9500573982 /* scribe-mac-icon.icns */; }; 34A66681237968A200ED0212 /* FolderCalendarSource.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 34A66680237968A200ED0212 /* FolderCalendarSource.cpp */; }; 34AB68F6275240BC0045380B /* ScribeScripts.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 34AB68E8275240B40045380B /* ScribeScripts.h */; }; 34C1BF90238CAEC500658B99 /* calendar.html in CopyFiles */ = {isa = PBXBuildFile; fileRef = 34C1BF71238CA23500658B99 /* calendar.html */; }; 34C1BF91238CAEC500658B99 /* contacts.html in CopyFiles */ = {isa = PBXBuildFile; fileRef = 34C1BF75238CA23500658B99 /* contacts.html */; }; 34C1BF92238CAEC500658B99 /* email.html in CopyFiles */ = {isa = PBXBuildFile; fileRef = 34C1BF73238CA23500658B99 /* email.html */; }; 34C1BF93238CAEC500658B99 /* features.html in CopyFiles */ = {isa = PBXBuildFile; fileRef = 34C1BF72238CA23500658B99 /* features.html */; }; 34C1BF94238CAEC500658B99 /* filters.html in CopyFiles */ = {isa = PBXBuildFile; fileRef = 34C1BF7D238CA23600658B99 /* filters.html */; }; 34C1BF95238CAEC500658B99 /* help.css in CopyFiles */ = {isa = PBXBuildFile; fileRef = 34C1BF78238CA23600658B99 /* help.css */; }; 34C1BF96238CAEC500658B99 /* import.html in CopyFiles */ = {isa = PBXBuildFile; fileRef = 34C1BF7E238CA23600658B99 /* import.html */; }; 34C1BF97238CAEC500658B99 /* index.html in CopyFiles */ = {isa = PBXBuildFile; fileRef = 34C1BF7C238CA23600658B99 /* index.html */; }; 34C1BF98238CAEC500658B99 /* install.html in CopyFiles */ = {isa = PBXBuildFile; fileRef = 34C1BF76238CA23500658B99 /* install.html */; }; 34C1BF99238CAEC500658B99 /* intro.html in CopyFiles */ = {isa = PBXBuildFile; fileRef = 34C1BF79238CA23600658B99 /* intro.html */; }; 34C1BF9A238CAEC500658B99 /* menu.html in CopyFiles */ = {isa = PBXBuildFile; fileRef = 34C1BF77238CA23500658B99 /* menu.html */; }; 34C1BF9B238CAEC500658B99 /* plugins.html in CopyFiles */ = {isa = PBXBuildFile; fileRef = 34C1BF7B238CA23600658B99 /* plugins.html */; }; 34C1BF9C238CAEC500658B99 /* print.html in CopyFiles */ = {isa = PBXBuildFile; fileRef = 34C1BF7F238CA23600658B99 /* print.html */; }; 34C1BF9D238CAEC500658B99 /* scripting.html in CopyFiles */ = {isa = PBXBuildFile; fileRef = 34C1BF7A238CA23600658B99 /* scripting.html */; }; 34C1BF9E238CAEC500658B99 /* ui.html in CopyFiles */ = {isa = PBXBuildFile; fileRef = 34C1BF74238CA23500658B99 /* ui.html */; }; 34C1BF9F238CB05A00658B99 /* About64px.png in CopyFiles */ = {isa = PBXBuildFile; fileRef = 34DD329B23153B2300FA2B35 /* About64px.png */; }; 34C1D42528403C2400FCC98D /* libchardet.dylib in Copy Files */ = {isa = PBXBuildFile; fileRef = 34167B34279FEAAF005422F6 /* libchardet.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 34C1D42628403CD300FCC98D /* libjpeg9a.dylib in Copy Files */ = {isa = PBXBuildFile; fileRef = 34D6AEE2283AF0A100897A16 /* libjpeg9a.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 34C1D42728403CDC00FCC98D /* libpng15.dylib in Copy Files */ = {isa = PBXBuildFile; fileRef = 34D6AEE6283AF0A100897A16 /* libpng15.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 34C1D42828403CE800FCC98D /* libz_local.dylib in Copy Files */ = {isa = PBXBuildFile; fileRef = 34D6AEEE283AF0A100897A16 /* libz_local.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 34C1D42928403CF000FCC98D /* libiconv.dylib in Copy Files */ = {isa = PBXBuildFile; fileRef = 34D6AEE0283AF0A100897A16 /* libiconv.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 34C9A80B23C9615300129861 /* NoFace160.png in Resources */ = {isa = PBXBuildFile; fileRef = 34C9A7FC23C9615200129861 /* NoFace160.png */; }; 34C9A80C23C9615300129861 /* NoFace80.png in Resources */ = {isa = PBXBuildFile; fileRef = 34C9A80A23C9615200129861 /* NoFace80.png */; }; 34D2892F275B3EF5005961A8 /* RemoteCalendarSource.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 34D2892E275B3EF5005961A8 /* RemoteCalendarSource.cpp */; }; 34DD329823153AF000FA2B35 /* Title.html in Resources */ = {isa = PBXBuildFile; fileRef = 34DD329423153AF000FA2B35 /* Title.html */; }; 34DD329923153AF000FA2B35 /* About.html in Resources */ = {isa = PBXBuildFile; fileRef = 34DD329523153AF000FA2B35 /* About.html */; }; 34DD329A23153AF000FA2B35 /* Title.png in Resources */ = {isa = PBXBuildFile; fileRef = 34DD329623153AF000FA2B35 /* Title.png */; }; 34DD329C23153B2300FA2B35 /* About64px.png in Resources */ = {isa = PBXBuildFile; fileRef = 34DD329B23153B2300FA2B35 /* About64px.png */; }; 34DED72A23AC319E0049E01F /* libsqlite.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 34DED72623AC30C30049E01F /* libsqlite.a */; }; 34EE8BB52748A6EC00F12915 /* HomoglyphsTable.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 34EE8BB32748A6EC00F12915 /* HomoglyphsTable.cpp */; }; 34EE8BB62748A6EC00F12915 /* Homoglyphs.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 34EE8BB42748A6EC00F12915 /* Homoglyphs.cpp */; }; 34F1BD0C2442B0340062DCF4 /* libssl.3.dylib in Copy Files */ = {isa = PBXBuildFile; fileRef = 34F1BD082442B0050062DCF4 /* libssl.3.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 34F1BD0D2442B03B0062DCF4 /* libcrypto.3.dylib in Copy Files */ = {isa = PBXBuildFile; fileRef = 34F1BD092442B0050062DCF4 /* libcrypto.3.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 34FB966F237D07740039423C /* WebdavStore.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 34FB9661237D07740039423C /* WebdavStore.cpp */; }; 34FB968A23835BB40039423C /* WebDav.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 34FB968923835BB40039423C /* WebDav.cpp */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 34167B33279FEAAF005422F6 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 34749B52278B06A200C8197E /* scribeLibs.xcodeproj */; proxyType = 2; remoteGlobalIDString = 87DEE21A1B6B43ACB718C926; remoteInfo = chardet; }; 34167B35279FEAC2005422F6 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 34749B52278B06A200C8197E /* scribeLibs.xcodeproj */; proxyType = 1; remoteGlobalIDString = 63F1B6C35FC9475F8A6274EC; remoteInfo = chardet; }; 34749B43278B042500C8197E /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 34749B3F278B042500C8197E /* LgiCocoa.xcodeproj */; proxyType = 2; remoteGlobalIDString = 3477C2681CBF020F0028B84B; remoteInfo = LgiCocoa; }; 34749B5A278B06B900C8197E /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 34749B3F278B042500C8197E /* LgiCocoa.xcodeproj */; proxyType = 1; remoteGlobalIDString = 3477C2671CBF020F0028B84B; remoteInfo = LgiCocoa; }; 3474FC48226DCCD10064E0AD /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 34417DAB205FD56300D2E80C /* bzip2.xcodeproj */; proxyType = 2; remoteGlobalIDString = 34EDF6C9205DCE8C001DA47E; remoteInfo = bzip2; }; 3474FC4D226DCCDD0064E0AD /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 3477C4DF1CC27C9F0028B84B /* Btree.xcodeproj */; proxyType = 2; remoteGlobalIDString = 3477C4A81CC2792F0028B84B; remoteInfo = Btree; }; 3474FC59226DE08D0064E0AD /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 34417DAB205FD56300D2E80C /* bzip2.xcodeproj */; proxyType = 1; remoteGlobalIDString = 34EDF6C8205DCE8C001DA47E; remoteInfo = bzip2; }; 3474FC5B226DE08D0064E0AD /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 3477C4DF1CC27C9F0028B84B /* Btree.xcodeproj */; proxyType = 1; remoteGlobalIDString = 3477C4A71CC2792F0028B84B; remoteInfo = Btree; }; 34D6AEDB283AF0A100897A16 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 34749B52278B06A200C8197E /* scribeLibs.xcodeproj */; proxyType = 2; remoteGlobalIDString = FF72102AEA49492FB655D92A; remoteInfo = example; }; 34D6AEDD283AF0A100897A16 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 34749B52278B06A200C8197E /* scribeLibs.xcodeproj */; proxyType = 2; remoteGlobalIDString = 594564A70D2647C992398CB6; remoteInfo = iconv; }; 34D6AEDF283AF0A100897A16 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 34749B52278B06A200C8197E /* scribeLibs.xcodeproj */; proxyType = 2; remoteGlobalIDString = B3BAD2B1AEF34065AAEDBBFC; remoteInfo = libiconv; }; 34D6AEE1283AF0A100897A16 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 34749B52278B06A200C8197E /* scribeLibs.xcodeproj */; proxyType = 2; remoteGlobalIDString = 752092F92C094FEFA6322AA9; remoteInfo = libjpeg9a; }; 34D6AEE3283AF0A100897A16 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 34749B52278B06A200C8197E /* scribeLibs.xcodeproj */; proxyType = 2; remoteGlobalIDString = 1860D095485B4874BD28CD68; remoteInfo = libjpeg9a_static; }; 34D6AEE5283AF0A100897A16 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 34749B52278B06A200C8197E /* scribeLibs.xcodeproj */; proxyType = 2; remoteGlobalIDString = 82B90DD671F24451898C0426; remoteInfo = libpng15; }; 34D6AEE7283AF0A100897A16 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 34749B52278B06A200C8197E /* scribeLibs.xcodeproj */; proxyType = 2; remoteGlobalIDString = 31AFC44E6C6649BB91C96E18; remoteInfo = libpng15_static; }; 34D6AEE9283AF0A100897A16 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 34749B52278B06A200C8197E /* scribeLibs.xcodeproj */; proxyType = 2; remoteGlobalIDString = 505D5B97A5D94B03A6D24F5B; remoteInfo = minigzip; }; 34D6AEEB283AF0A100897A16 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 34749B52278B06A200C8197E /* scribeLibs.xcodeproj */; proxyType = 2; remoteGlobalIDString = 41E3CCF185644EB3A43F7D64; remoteInfo = pngtest; }; 34D6AEED283AF0A100897A16 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 34749B52278B06A200C8197E /* scribeLibs.xcodeproj */; proxyType = 2; remoteGlobalIDString = 37BD6E3953634A6B9444B6F7; remoteInfo = zlib; }; 34D6AEEF283AF0A100897A16 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 34749B52278B06A200C8197E /* scribeLibs.xcodeproj */; proxyType = 2; remoteGlobalIDString = 619D79627D09459B9BF2FFDC; remoteInfo = zlib_static; }; 34D6AEF1283AF0B300897A16 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 34749B52278B06A200C8197E /* scribeLibs.xcodeproj */; proxyType = 1; remoteGlobalIDString = FAEB745E38014DB5912D04F5; remoteInfo = libjpeg9a; }; 34D6AEF3283AF0B900897A16 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 34749B52278B06A200C8197E /* scribeLibs.xcodeproj */; proxyType = 1; remoteGlobalIDString = A47CE3DD6D7E44FF984E327F; remoteInfo = libpng15; }; 34D6AEF5283AF0BD00897A16 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 34749B52278B06A200C8197E /* scribeLibs.xcodeproj */; proxyType = 1; remoteGlobalIDString = 7D1CF078C60E4238813E5BB7; remoteInfo = zlib; }; 34D6AEF7283AF0E400897A16 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 34749B52278B06A200C8197E /* scribeLibs.xcodeproj */; proxyType = 1; remoteGlobalIDString = C84F72522656464CBED4EA91; remoteInfo = libiconv; }; 34DED72523AC30C30049E01F /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 3477C50B1CC3026F0028B84B /* sqlite.xcodeproj */; proxyType = 2; remoteGlobalIDString = 3477C4FC1CC3021B0028B84B; remoteInfo = sqlite; }; 34DED72723AC31950049E01F /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 3477C50B1CC3026F0028B84B /* sqlite.xcodeproj */; proxyType = 1; remoteGlobalIDString = 3477C4FB1CC3021B0028B84B; remoteInfo = sqlite; }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ 3477C51C1CC304850028B84B /* Copy Files */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( 34C1D42928403CF000FCC98D /* libiconv.dylib in Copy Files */, 34C1D42828403CE800FCC98D /* libz_local.dylib in Copy Files */, 34C1D42728403CDC00FCC98D /* libpng15.dylib in Copy Files */, 34C1D42628403CD300FCC98D /* libjpeg9a.dylib in Copy Files */, 34C1D42528403C2400FCC98D /* libchardet.dylib in Copy Files */, 340E2A1E278CEA7200F0B4C8 /* LgiCocoa.framework in Copy Files */, 34F1BD0D2442B03B0062DCF4 /* libcrypto.3.dylib in Copy Files */, 34F1BD0C2442B0340062DCF4 /* libssl.3.dylib in Copy Files */, ); name = "Copy Files"; runOnlyForDeploymentPostprocessing = 0; }; 3497D54E2399D27A009FCC14 /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = Scripts; dstSubfolderSpec = 7; files = ( 34AB68F6275240BC0045380B /* ScribeScripts.h in CopyFiles */, 3497D5542399D2AB009FCC14 /* resdefs.h in CopyFiles */, 3497D54F2399D288009FCC14 /* Add Senders To Contacts.script in CopyFiles */, 3497D5502399D288009FCC14 /* Delete Attachments.script in CopyFiles */, 3497D5512399D288009FCC14 /* Delete Duplicate Messages.script in CopyFiles */, 3497D5522399D288009FCC14 /* Mail Filters Menu.script in CopyFiles */, ); runOnlyForDeploymentPostprocessing = 0; }; 34C1BF8F238CAEAD00658B99 /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = Help; dstSubfolderSpec = 7; files = ( 34C1BF9F238CB05A00658B99 /* About64px.png in CopyFiles */, 34C1BF90238CAEC500658B99 /* calendar.html in CopyFiles */, 34C1BF91238CAEC500658B99 /* contacts.html in CopyFiles */, 34C1BF92238CAEC500658B99 /* email.html in CopyFiles */, 34C1BF93238CAEC500658B99 /* features.html in CopyFiles */, 34C1BF94238CAEC500658B99 /* filters.html in CopyFiles */, 34C1BF95238CAEC500658B99 /* help.css in CopyFiles */, 34C1BF96238CAEC500658B99 /* import.html in CopyFiles */, 34C1BF97238CAEC500658B99 /* index.html in CopyFiles */, 34C1BF98238CAEC500658B99 /* install.html in CopyFiles */, 34C1BF99238CAEC500658B99 /* intro.html in CopyFiles */, 34C1BF9A238CAEC500658B99 /* menu.html in CopyFiles */, 34C1BF9B238CAEC500658B99 /* plugins.html in CopyFiles */, 34C1BF9C238CAEC500658B99 /* print.html in CopyFiles */, 34C1BF9D238CAEC500658B99 /* scripting.html in CopyFiles */, 34C1BF9E238CAEC500658B99 /* ui.html in CopyFiles */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 340FA6A8237E7E2D0043D1F2 /* EmojiMap.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = EmojiMap.png; path = ../../Resources/EmojiMap.png; sourceTree = ""; }; 340FAADC23888A570017BC62 /* WebdavContact.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = WebdavContact.cpp; path = ../../Code/Store3Webdav/WebdavContact.cpp; sourceTree = ""; }; 34161495238484F700A65477 /* WebdavFolder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = WebdavFolder.cpp; path = ../../Code/Store3Webdav/WebdavFolder.cpp; sourceTree = ""; }; 34161496238484F700A65477 /* WebdavCalendar.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = WebdavCalendar.cpp; path = ../../Code/Store3Webdav/WebdavCalendar.cpp; sourceTree = ""; }; 34161497238484F700A65477 /* WebdavStorePriv.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = WebdavStorePriv.h; path = ../../Code/Store3Webdav/WebdavStorePriv.h; sourceTree = ""; }; 34161498238484F800A65477 /* WebdavThread.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = WebdavThread.cpp; path = ../../Code/Store3Webdav/WebdavThread.cpp; sourceTree = ""; }; 34161499238484F800A65477 /* WebdavStore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = WebdavStore.h; path = ../../Code/Store3Webdav/WebdavStore.h; sourceTree = ""; }; 341A02B723BD702800F0FF48 /* SpellCheckAspell.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = SpellCheckAspell.cpp; path = ../../../../lgi/trunk/src/common/Text/SpellCheckAspell.cpp; sourceTree = ""; }; 341A787F2699058600872FEB /* DatePopup.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = DatePopup.cpp; path = ../../../../Lgi/trunk/src/common/Widgets/DatePopup.cpp; sourceTree = ""; }; 341BF47023231D0600BC3AB0 /* Jpeg.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Jpeg.cpp; path = ../../../../Lgi/trunk/src/common/Gdc2/Filters/Jpeg.cpp; sourceTree = ""; }; 3425DC6A23F788AE00BC025E /* SpellCheck.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SpellCheck.h; path = ../../../../Lgi/trunk/include/lgi/common/SpellCheck.h; sourceTree = ""; }; 342BAE3A2588AFA8006EF16E /* TextConvert.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = TextConvert.cpp; path = ../../../../Lgi/trunk/src/common/Text/TextConvert.cpp; sourceTree = ""; }; 34364F8824CCE9FF00272F13 /* Themes */ = {isa = PBXFileReference; lastKnownFileType = folder; name = Themes; path = ../../Resources/Themes; sourceTree = ""; }; 34417D87205FB69E00D2E80C /* MailFlags.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = MailFlags.c; path = ../../Utils/Tables/MailFlags.c; sourceTree = ""; }; 34417D88205FB69E00D2E80C /* Tables.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Tables.h; path = ../../Utils/Tables/Tables.h; sourceTree = ""; }; 34417D8D205FD4A500D2E80C /* Ftp.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Ftp.cpp; path = ../../../../Lgi/trunk/src/common/Net/Ftp.cpp; sourceTree = ""; }; 34417DAB205FD56300D2E80C /* bzip2.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = bzip2.xcodeproj; path = "../../libs/bzip2-1.0.6/mac/bzip2.xcodeproj"; sourceTree = ""; }; 34417DBD205FD5B800D2E80C /* HorzRuleBlock.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = HorzRuleBlock.cpp; path = ../../../../Lgi/trunk/src/common/Widgets/Editor/HorzRuleBlock.cpp; sourceTree = ""; }; 3445E5721F1F40DF00D0A824 /* EmojiTools.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = EmojiTools.cpp; path = ../../../../Lgi/trunk/src/common/Text/Emoji/EmojiTools.cpp; sourceTree = ""; }; 3445E5741F1F413200D0A824 /* EmojiMap.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = EmojiMap.cpp; path = ../../../../Lgi/trunk/src/common/Text/Emoji/EmojiMap.cpp; sourceTree = ""; }; 3445E5761F1F416500D0A824 /* SpellCheckMac.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = SpellCheckMac.mm; path = ../../../../lgi/trunk/src/mac/cocoa/SpellCheckMac.mm; sourceTree = ""; }; 3445E5791F1F41AF00D0A824 /* GnuPG.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = GnuPG.cpp; path = ../../Code/Encryption/GnuPG.cpp; sourceTree = ""; }; 3445E57A1F1F41AF00D0A824 /* GnuPG.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GnuPG.h; path = ../../Code/Encryption/GnuPG.h; sourceTree = ""; }; 3445E57E1F1F428400D0A824 /* BlockCursor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = BlockCursor.cpp; path = ../../../../Lgi/trunk/src/common/Widgets/Editor/BlockCursor.cpp; sourceTree = ""; }; 3445E57F1F1F428400D0A824 /* RichTextEdit.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = RichTextEdit.cpp; path = ../../../../Lgi/trunk/src/common/Widgets/Editor/RichTextEdit.cpp; sourceTree = ""; }; 3445E5801F1F428400D0A824 /* RichTextEditPriv.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = RichTextEditPriv.cpp; path = ../../../../Lgi/trunk/src/common/Widgets/Editor/RichTextEditPriv.cpp; sourceTree = ""; }; 3445E5811F1F428400D0A824 /* RichTextEditPriv.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RichTextEditPriv.h; path = ../../../../Lgi/trunk/src/common/Widgets/Editor/RichTextEditPriv.h; sourceTree = ""; }; 3445E5821F1F428400D0A824 /* ImageBlock.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ImageBlock.cpp; path = ../../../../Lgi/trunk/src/common/Widgets/Editor/ImageBlock.cpp; sourceTree = ""; }; 3445E5831F1F428400D0A824 /* TextBlock.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = TextBlock.cpp; path = ../../../../Lgi/trunk/src/common/Widgets/Editor/TextBlock.cpp; sourceTree = ""; }; 3445E5891F1F42BA00D0A824 /* ObjectInspector.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ObjectInspector.cpp; path = ../../Code/ObjectInspector.cpp; sourceTree = ""; }; 345921031F2565D100098DFD /* BayesianFilter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BayesianFilter.h; path = ../../Code/BayesianFilter.h; sourceTree = ""; }; 345921041F2565D100098DFD /* Calendar.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Calendar.h; path = ../../Code/Calendar.h; sourceTree = ""; }; 345921051F2565D100098DFD /* CalendarView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CalendarView.h; path = ../../Code/CalendarView.h; sourceTree = ""; }; 345921061F2565D100098DFD /* Components.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Components.h; path = ../../Code/Components.h; sourceTree = ""; }; 345921071F2565D100098DFD /* DomType.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DomType.h; path = ../../Code/DomType.h; sourceTree = ""; }; 345921081F2565D100098DFD /* DomTypeValues.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DomTypeValues.h; path = ../../Code/DomTypeValues.h; sourceTree = ""; }; - 345921091F2565D100098DFD /* GNewMailDlg.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GNewMailDlg.h; path = ../../Code/GNewMailDlg.h; sourceTree = ""; }; + 345921091F2565D100098DFD /* NewMailDlg.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NewMailDlg.h; path = ../../Code/NewMailDlg.h; sourceTree = ""; }; 3459210A1F2565D100098DFD /* SearchView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SearchView.h; path = ../../Code/SearchView.h; sourceTree = ""; }; 3459210B1F2565D100098DFD /* Imp_Outlook.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Imp_Outlook.h; path = ../../Code/Imp_Outlook.h; sourceTree = ""; }; 3459210C1F2565D100098DFD /* ManageMailStores.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ManageMailStores.h; path = ../../Code/ManageMailStores.h; sourceTree = ""; }; 3459210D1F2565D100098DFD /* ObjectInspector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ObjectInspector.h; path = ../../Code/ObjectInspector.h; sourceTree = ""; }; 3459210E1F2565D100098DFD /* PreviewPanel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PreviewPanel.h; path = ../../Code/PreviewPanel.h; sourceTree = ""; }; 3459210F1F2565D100098DFD /* PrintContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PrintContext.h; path = ../../Code/PrintContext.h; sourceTree = ""; }; 345921101F2565D100098DFD /* PrintPreview.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PrintPreview.h; path = ../../Code/PrintPreview.h; sourceTree = ""; }; 345921111F2565D100098DFD /* ReplicateDlg.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ReplicateDlg.h; path = ../../Code/ReplicateDlg.h; sourceTree = ""; }; 345921121F2565D100098DFD /* resource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = resource.h; path = ../../Code/resource.h; sourceTree = ""; }; 345921131F2565D100098DFD /* Scribe.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Scribe.h; path = ../../Code/Scribe.h; sourceTree = ""; }; 345921141F2565D100098DFD /* ScribeAccountPreview.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ScribeAccountPreview.h; path = ../../Code/ScribeAccountPreview.h; sourceTree = ""; }; 345921151F2565D100098DFD /* ScribeAccountUI.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ScribeAccountUI.h; path = ../../Code/ScribeAccountUI.h; sourceTree = ""; }; 345921161F2565D100098DFD /* ScribeDefs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ScribeDefs.h; path = ../../Code/ScribeDefs.h; sourceTree = ""; }; 345921171F2565D100098DFD /* ScribeFolderDlg.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ScribeFolderDlg.h; path = ../../Code/ScribeFolderDlg.h; sourceTree = ""; }; 345921181F2565D100098DFD /* ScribeFolderSelect.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ScribeFolderSelect.h; path = ../../Code/ScribeFolderSelect.h; sourceTree = ""; }; 345921191F2565D100098DFD /* ScribeInc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ScribeInc.h; path = ../../Code/ScribeInc.h; sourceTree = ""; }; 3459211A1F2565D100098DFD /* ScribeItemDefs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ScribeItemDefs.h; path = ../../Code/ScribeItemDefs.h; sourceTree = ""; }; 3459211B1F2565D100098DFD /* ScribeListAddr.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ScribeListAddr.h; path = ../../Code/ScribeListAddr.h; sourceTree = ""; }; 3459211C1F2565D100098DFD /* ScribePageSetup.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ScribePageSetup.h; path = ../../Code/ScribePageSetup.h; sourceTree = ""; }; 3459211D1F2565D100098DFD /* ScribePrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ScribePrivate.h; path = ../../Code/ScribePrivate.h; sourceTree = ""; }; 3459211E1F2565D100098DFD /* ScribeSharedMem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ScribeSharedMem.h; path = ../../Code/ScribeSharedMem.h; sourceTree = ""; }; 3459211F1F2565D100098DFD /* ScribeSpellCheck.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ScribeSpellCheck.h; path = ../../Code/ScribeSpellCheck.h; sourceTree = ""; }; 345921201F2565D100098DFD /* ScribeStatusPanel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ScribeStatusPanel.h; path = ../../Code/ScribeStatusPanel.h; sourceTree = ""; }; 345921211F2565D100098DFD /* ScribeUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ScribeUtils.h; path = ../../Code/ScribeUtils.h; sourceTree = ""; }; 345921221F2565D100098DFD /* Store3Common.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Store3Common.h; path = ../../Code/Store3Common.h; sourceTree = ""; }; 345921261F2565EC00098DFD /* Mail.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Mail.h; path = ../../../../Lgi/trunk/include/lgi/common/Mail.h; sourceTree = ""; }; 345C53CF23F163D30035E9CA /* Scribe.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Scribe.entitlements; sourceTree = ""; }; 345F31451CC7A3B400656B8B /* Icons-16.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Icons-16.png"; path = "../../Resources/Icons-16.png"; sourceTree = ""; }; 345F31471CC7A40F00656B8B /* Gif.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Gif.cpp; path = ../../../../Lgi/trunk/src/common/Gdc2/Filters/Gif.cpp; sourceTree = ""; }; 345F31491CC7A44C00656B8B /* Lzw.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Lzw.cpp; path = ../../../../Lgi/trunk/src/common/Gdc2/Filters/Lzw.cpp; sourceTree = ""; }; 345F314B1CC7A46B00656B8B /* xgate-icons-32.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "xgate-icons-32.png"; path = "../../Resources/xgate-icons-32.png"; sourceTree = ""; }; 346E1A44231292C60019B4AA /* PreviewMail.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; name = PreviewMail.html; path = ../../Resources/PreviewMail.html; sourceTree = ""; }; 346E1A45231292C60019B4AA /* PreviewContact.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; name = PreviewContact.html; path = ../../Resources/PreviewContact.html; sourceTree = ""; }; 346E1A46231292C60019B4AA /* PreviewGroup.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; name = PreviewGroup.html; path = ../../Resources/PreviewGroup.html; sourceTree = ""; }; 346E1A4A231293570019B4AA /* tray_error.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = tray_error.png; path = ../../Resources/tray_error.png; sourceTree = ""; }; 346E1A4B231293570019B4AA /* tray_small.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = tray_small.png; path = ../../Resources/tray_small.png; sourceTree = ""; }; 346E1A4C231293570019B4AA /* tray_mail.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = tray_mail.png; path = ../../Resources/tray_mail.png; sourceTree = ""; }; 34749B3F278B042500C8197E /* LgiCocoa.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = LgiCocoa.xcodeproj; path = ../../../Lgi/trunk/src/mac/cocoa/LgiCocoa.xcodeproj; sourceTree = ""; }; 34749B52278B06A200C8197E /* scribeLibs.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = scribeLibs.xcodeproj; path = "../../libs/build-x64/scribeLibs.xcodeproj"; sourceTree = ""; }; 3477C21E1CBEFD940028B84B /* Scribe.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Scribe.app; sourceTree = BUILT_PRODUCTS_DIR; }; 3477C22C1CBEFD940028B84B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 3477C3571CC1149F0028B84B /* LgiMain.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = LgiMain.cpp; path = ../../../../Lgi/trunk/src/common/Lgi/LgiMain.cpp; sourceTree = ""; }; 3477C3611CC226450028B84B /* ScribeMain.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ScribeMain.cpp; path = ../../Code/ScribeMain.cpp; sourceTree = ""; }; 3477C3631CC227ED0028B84B /* AddressSelect.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = AddressSelect.cpp; path = ../../Code/AddressSelect.cpp; sourceTree = ""; }; 3477C3651CC227ED0028B84B /* BayesDlg.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = BayesDlg.cpp; path = ../../Code/BayesDlg.cpp; sourceTree = ""; }; 3477C3661CC227ED0028B84B /* BayesianFilter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = BayesianFilter.cpp; path = ../../Code/BayesianFilter.cpp; sourceTree = ""; }; 3477C3671CC227ED0028B84B /* Calendar.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Calendar.cpp; path = ../../Code/Calendar.cpp; sourceTree = ""; }; 3477C3681CC227ED0028B84B /* CalendarView.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CalendarView.cpp; path = ../../Code/CalendarView.cpp; sourceTree = ""; }; 3477C3691CC227ED0028B84B /* Components.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Components.cpp; path = ../../Code/Components.cpp; sourceTree = ""; }; 3477C36D1CC227ED0028B84B /* Exp_Scribe.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Exp_Scribe.cpp; path = ../../Code/Exp_Scribe.cpp; sourceTree = ""; }; - 3477C3721CC227ED0028B84B /* GNewMailDlg.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = GNewMailDlg.cpp; path = ../../Code/GNewMailDlg.cpp; sourceTree = ""; }; + 3477C3721CC227ED0028B84B /* NewMailDlg.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = NewMailDlg.cpp; path = ../../Code/NewMailDlg.cpp; sourceTree = ""; }; 3477C3731CC227ED0028B84B /* SearchView.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = SearchView.cpp; path = ../../Code/SearchView.cpp; sourceTree = ""; }; 3477C3741CC227ED0028B84B /* HtmlToText.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = HtmlToText.cpp; path = ../../Code/HtmlToText.cpp; sourceTree = ""; }; 3477C3761CC227ED0028B84B /* Imp_Eml.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Imp_Eml.cpp; path = ../../Code/Imp_Eml.cpp; sourceTree = ""; }; 3477C3771CC227ED0028B84B /* Imp_Eudora.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Imp_Eudora.cpp; path = ../../Code/Imp_Eudora.cpp; sourceTree = ""; }; 3477C3781CC227ED0028B84B /* Imp_Mozilla.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Imp_Mozilla.cpp; path = ../../Code/Imp_Mozilla.cpp; sourceTree = ""; }; 3477C3791CC227ED0028B84B /* Imp_NetscapeContacts.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Imp_NetscapeContacts.cpp; path = ../../Code/Imp_NetscapeContacts.cpp; sourceTree = ""; }; 3477C37B1CC227ED0028B84B /* ImpExp_Mbox.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ImpExp_Mbox.cpp; path = ../../Code/ImpExp_Mbox.cpp; sourceTree = ""; }; 3477C3801CC227ED0028B84B /* ManageMailStores.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ManageMailStores.cpp; path = ../../Code/ManageMailStores.cpp; sourceTree = ""; }; 3477C3811CC227ED0028B84B /* MContainer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = MContainer.cpp; path = ../../Code/MContainer.cpp; sourceTree = ""; }; 3477C3821CC227ED0028B84B /* OptionsDlg.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = OptionsDlg.cpp; path = ../../Code/OptionsDlg.cpp; sourceTree = ""; }; 3477C3831CC227ED0028B84B /* PreviewPanel.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = PreviewPanel.cpp; path = ../../Code/PreviewPanel.cpp; sourceTree = ""; }; 3477C3841CC227ED0028B84B /* PrintContext.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = PrintContext.cpp; path = ../../Code/PrintContext.cpp; sourceTree = ""; }; 3477C3851CC227ED0028B84B /* PrintPreview.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = PrintPreview.cpp; path = ../../Code/PrintPreview.cpp; sourceTree = ""; }; 3477C3871CC227ED0028B84B /* ReplicateDlg.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ReplicateDlg.cpp; path = ../../Code/ReplicateDlg.cpp; sourceTree = ""; }; 3477C3881CC227ED0028B84B /* ScribeAbout.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ScribeAbout.cpp; path = ../../Code/ScribeAbout.cpp; sourceTree = ""; }; 3477C3891CC227ED0028B84B /* ScribeAccount.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ScribeAccount.cpp; path = ../../Code/ScribeAccount.cpp; sourceTree = ""; }; 3477C38A1CC227ED0028B84B /* ScribeAccountPreview.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ScribeAccountPreview.cpp; path = ../../Code/ScribeAccountPreview.cpp; sourceTree = ""; }; 3477C38B1CC227ED0028B84B /* ScribeAccountUI.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ScribeAccountUI.cpp; path = ../../Code/ScribeAccountUI.cpp; sourceTree = ""; }; 3477C38D1CC227ED0028B84B /* ScribeApp.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ScribeApp.cpp; path = ../../Code/ScribeApp.cpp; sourceTree = ""; }; 3477C38E1CC227ED0028B84B /* ScribeAttachment.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ScribeAttachment.cpp; path = ../../Code/ScribeAttachment.cpp; sourceTree = ""; }; 3477C38F1CC227ED0028B84B /* ScribeContact.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ScribeContact.cpp; path = ../../Code/ScribeContact.cpp; sourceTree = ""; }; 3477C3901CC227ED0028B84B /* ScribeFilter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ScribeFilter.cpp; path = ../../Code/ScribeFilter.cpp; sourceTree = ""; }; 3477C3911CC227ED0028B84B /* ScribeFinder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ScribeFinder.cpp; path = ../../Code/ScribeFinder.cpp; sourceTree = ""; }; 3477C3921CC227ED0028B84B /* ScribeFolder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ScribeFolder.cpp; path = ../../Code/ScribeFolder.cpp; sourceTree = ""; }; 3477C3931CC227ED0028B84B /* ScribeFolderDlg.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ScribeFolderDlg.cpp; path = ../../Code/ScribeFolderDlg.cpp; sourceTree = ""; }; 3477C3941CC227ED0028B84B /* ScribeFolderProp.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ScribeFolderProp.cpp; path = ../../Code/ScribeFolderProp.cpp; sourceTree = ""; }; 3477C3951CC227ED0028B84B /* ScribeFolderSelect.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ScribeFolderSelect.cpp; path = ../../Code/ScribeFolderSelect.cpp; sourceTree = ""; }; 3477C3961CC227ED0028B84B /* ScribeFolderTree.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ScribeFolderTree.cpp; path = ../../Code/ScribeFolderTree.cpp; sourceTree = ""; }; 3477C3971CC227ED0028B84B /* ScribeGroup.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ScribeGroup.cpp; path = ../../Code/ScribeGroup.cpp; sourceTree = ""; }; 3477C3981CC227ED0028B84B /* ScribeItemList.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ScribeItemList.cpp; path = ../../Code/ScribeItemList.cpp; sourceTree = ""; }; 3477C3991CC227ED0028B84B /* ScribeLangDlg.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ScribeLangDlg.cpp; path = ../../Code/ScribeLangDlg.cpp; sourceTree = ""; }; 3477C39A1CC227ED0028B84B /* ScribeListAddr.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ScribeListAddr.cpp; path = ../../Code/ScribeListAddr.cpp; sourceTree = ""; }; 3477C39B1CC227ED0028B84B /* ScribeMail.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ScribeMail.cpp; path = ../../Code/ScribeMail.cpp; sourceTree = ""; }; 3477C39D1CC227ED0028B84B /* ScribePageSetup.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ScribePageSetup.cpp; path = ../../Code/ScribePageSetup.cpp; sourceTree = ""; }; 3477C39E1CC227ED0028B84B /* ScribePassword.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ScribePassword.cpp; path = ../../Code/ScribePassword.cpp; sourceTree = ""; }; 3477C3A01CC227ED0028B84B /* ScribeRepair.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ScribeRepair.cpp; path = ../../Code/ScribeRepair.cpp; sourceTree = ""; }; 3477C3A11CC227ED0028B84B /* ScribeSendReceive.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ScribeSendReceive.cpp; path = ../../Code/ScribeSendReceive.cpp; sourceTree = ""; }; 3477C3A21CC227ED0028B84B /* ScribeSockets.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ScribeSockets.cpp; path = ../../Code/ScribeSockets.cpp; sourceTree = ""; }; 3477C3A31CC227ED0028B84B /* ScribeStatusPanel.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ScribeStatusPanel.cpp; path = ../../Code/ScribeStatusPanel.cpp; sourceTree = ""; }; 3477C3A41CC227ED0028B84B /* ScribeThing.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ScribeThing.cpp; path = ../../Code/ScribeThing.cpp; sourceTree = ""; }; 3477C3A51CC227ED0028B84B /* ScribeUi.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ScribeUi.cpp; path = ../../Code/ScribeUi.cpp; sourceTree = ""; }; 3477C3A61CC227ED0028B84B /* ScribeUtils.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ScribeUtils.cpp; path = ../../Code/ScribeUtils.cpp; sourceTree = ""; }; 3477C3A81CC227ED0028B84B /* Scripting.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Scripting.cpp; path = ../../Code/Scripting.cpp; sourceTree = ""; }; 3477C3A91CC227ED0028B84B /* SecurityDlg.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = SecurityDlg.cpp; path = ../../Code/SecurityDlg.cpp; sourceTree = ""; }; 3477C3AC1CC227ED0028B84B /* Store3Common.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Store3Common.cpp; path = ../../Code/Store3Common.cpp; sourceTree = ""; }; 3477C3AD1CC227ED0028B84B /* TitlePage.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = TitlePage.cpp; path = ../../Code/TitlePage.cpp; sourceTree = ""; }; 3477C3FB1CC22E690028B84B /* Base64.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Base64.cpp; path = ../../../../Lgi/trunk/src/common/Net/Base64.cpp; sourceTree = ""; }; 3477C3FD1CC22EF60028B84B /* MonthView.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = MonthView.cpp; path = ../../../../Lgi/trunk/src/common/Widgets/MonthView.cpp; sourceTree = ""; }; 3477C3FE1CC22EF60028B84B /* YearView.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = YearView.cpp; path = ../../../../Lgi/trunk/src/common/Widgets/YearView.cpp; sourceTree = ""; }; 3477C4011CC22F340028B84B /* TimeZone.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = TimeZone.cpp; path = ../../../../Lgi/trunk/src/common/General/TimeZone.cpp; sourceTree = ""; }; 3477C4031CC22F680028B84B /* ExeCheck.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ExeCheck.cpp; path = ../../../../Lgi/trunk/src/common/General/ExeCheck.cpp; sourceTree = ""; }; 3477C4081CC2301F0028B84B /* GdcTools.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = GdcTools.cpp; path = ../../../../Lgi/trunk/src/common/Gdc2/Tools/GdcTools.cpp; sourceTree = ""; }; 3477C40A1CC230A30028B84B /* Tnef.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Tnef.cpp; path = ../../../../Lgi/trunk/src/common/General/Tnef.cpp; sourceTree = ""; }; 3477C40C1CC230C30028B84B /* Mail.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Mail.cpp; path = ../../../../Lgi/trunk/src/common/Net/Mail.cpp; sourceTree = ""; }; 3477C40E1CC231190028B84B /* Imp_OutlookExpress.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Imp_OutlookExpress.cpp; path = ../../Code/Imp_OutlookExpress.cpp; sourceTree = ""; }; 3477C4101CC231470028B84B /* OpenSSLSocket.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = OpenSSLSocket.cpp; path = ../../../../Lgi/trunk/src/common/Net/OpenSSLSocket.cpp; sourceTree = ""; }; 3477C4151CC231890028B84B /* ScribeImap_Attachment.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ScribeImap_Attachment.cpp; path = ../../Code/Store3Imap/ScribeImap_Attachment.cpp; sourceTree = ""; }; 3477C4161CC231890028B84B /* ScribeImap_Folder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ScribeImap_Folder.cpp; path = ../../Code/Store3Imap/ScribeImap_Folder.cpp; sourceTree = ""; }; 3477C4171CC231890028B84B /* ScribeImap_Mail.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ScribeImap_Mail.cpp; path = ../../Code/Store3Imap/ScribeImap_Mail.cpp; sourceTree = ""; }; 3477C4181CC231890028B84B /* ScribeImap_Store.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ScribeImap_Store.cpp; path = ../../Code/Store3Imap/ScribeImap_Store.cpp; sourceTree = ""; }; 3477C4191CC231890028B84B /* ScribeImap_Thread.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ScribeImap_Thread.cpp; path = ../../Code/Store3Imap/ScribeImap_Thread.cpp; sourceTree = ""; }; 3477C41A1CC231890028B84B /* ScribeImap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ScribeImap.h; path = ../../Code/Store3Imap/ScribeImap.h; sourceTree = ""; }; 3477C4201CC2319B0028B84B /* Mail3.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Mail3.h; path = ../../Code/Store3Mail3/Mail3.h; sourceTree = ""; }; 3477C4211CC2319B0028B84B /* Mail3Attachment.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Mail3Attachment.cpp; path = ../../Code/Store3Mail3/Mail3Attachment.cpp; sourceTree = ""; }; 3477C4221CC2319B0028B84B /* Mail3Calendar.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Mail3Calendar.cpp; path = ../../Code/Store3Mail3/Mail3Calendar.cpp; sourceTree = ""; }; 3477C4231CC2319B0028B84B /* Mail3Contact.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Mail3Contact.cpp; path = ../../Code/Store3Mail3/Mail3Contact.cpp; sourceTree = ""; }; 3477C4241CC2319B0028B84B /* Mail3Filter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Mail3Filter.cpp; path = ../../Code/Store3Mail3/Mail3Filter.cpp; sourceTree = ""; }; 3477C4251CC2319B0028B84B /* Mail3Folder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Mail3Folder.cpp; path = ../../Code/Store3Mail3/Mail3Folder.cpp; sourceTree = ""; }; 3477C4261CC2319B0028B84B /* Mail3Group.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Mail3Group.cpp; path = ../../Code/Store3Mail3/Mail3Group.cpp; sourceTree = ""; }; 3477C4271CC2319B0028B84B /* Mail3Mail.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Mail3Mail.cpp; path = ../../Code/Store3Mail3/Mail3Mail.cpp; sourceTree = ""; }; 3477C4281CC2319B0028B84B /* Mail3Store.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Mail3Store.cpp; path = ../../Code/Store3Mail3/Mail3Store.cpp; sourceTree = ""; }; 3477C4311CC233980028B84B /* Imp_CsvContacts.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Imp_CsvContacts.cpp; path = ../../Code/Imp_CsvContacts.cpp; sourceTree = ""; }; 3477C4331CC233CF0028B84B /* Db-Csv.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "Db-Csv.cpp"; path = "../../../../Lgi/trunk/src/common/Db/Db-Csv.cpp"; sourceTree = ""; }; 3477C4351CC234200028B84B /* Http.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Http.cpp; path = ../../../../Lgi/trunk/src/common/Net/Http.cpp; sourceTree = ""; }; 3477C43D1CC234C80028B84B /* TimePopup.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = TimePopup.cpp; path = ../../../../Lgi/trunk/src/common/Widgets/TimePopup.cpp; sourceTree = ""; }; 3477C43F1CC234E60028B84B /* XmlTreeUi.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = XmlTreeUi.cpp; path = ../../../../Lgi/trunk/src/common/Text/XmlTreeUi.cpp; sourceTree = ""; }; 3477C4411CC235490028B84B /* FilterUi.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = FilterUi.cpp; path = ../../../../Lgi/trunk/src/common/Widgets/FilterUi.cpp; sourceTree = ""; }; 3477C4431CC235810028B84B /* Html.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Html.cpp; path = ../../../../Lgi/trunk/src/common/Text/Html.cpp; sourceTree = ""; }; 3477C4441CC235810028B84B /* HtmlCommon.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = HtmlCommon.cpp; path = ../../../../Lgi/trunk/src/common/Text/HtmlCommon.cpp; sourceTree = ""; }; 3477C4451CC235810028B84B /* HtmlParser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = HtmlParser.cpp; path = ../../../../Lgi/trunk/src/common/Text/HtmlParser.cpp; sourceTree = ""; }; 3477C44B1CC235C60028B84B /* OptionsFile.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = OptionsFile.cpp; path = ../../../Lgi/trunk/src/common/Lgi/OptionsFile.cpp; sourceTree = SOURCE_ROOT; }; 3477C44D1CC2624B0028B84B /* ControlTree.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ControlTree.cpp; path = ../../../../Lgi/trunk/src/common/Widgets/ControlTree.cpp; sourceTree = ""; }; 3477C44F1CC262670028B84B /* ColourSelect.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ColourSelect.cpp; path = ../../../../Lgi/trunk/src/common/Widgets/ColourSelect.cpp; sourceTree = ""; }; 3477C4521CC262AF0028B84B /* LexCpp.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = LexCpp.cpp; path = ../../../../Lgi/trunk/src/common/Coding/LexCpp.cpp; sourceTree = ""; }; 3477C4531CC262AF0028B84B /* ScriptCompiler.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ScriptCompiler.cpp; path = ../../../../Lgi/trunk/src/common/Coding/ScriptCompiler.cpp; sourceTree = ""; }; 3477C4541CC262AF0028B84B /* ScriptLibrary.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ScriptLibrary.cpp; path = ../../../../Lgi/trunk/src/common/Coding/ScriptLibrary.cpp; sourceTree = ""; }; 3477C4551CC262AF0028B84B /* ScriptVM.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ScriptVM.cpp; path = ../../../../Lgi/trunk/src/common/Coding/ScriptVM.cpp; sourceTree = ""; }; 3477C45A1CC263560028B84B /* SharedMemory.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = SharedMemory.cpp; path = ../../../../Lgi/trunk/src/common/General/SharedMemory.cpp; sourceTree = ""; }; 3477C45C1CC26AAB0028B84B /* Mime.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Mime.cpp; path = ../../../../Lgi/trunk/src/common/Net/Mime.cpp; sourceTree = ""; }; 3477C45D1CC26AAB0028B84B /* MailImap.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = MailImap.cpp; path = ../../../../Lgi/trunk/src/common/Net/MailImap.cpp; sourceTree = ""; }; 3477C4601CC26AE00028B84B /* Growl.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Growl.cpp; path = ../../../../Lgi/trunk/src/common/General/Growl.cpp; sourceTree = ""; }; 3477C4611CC26AE00028B84B /* SoftwareUpdate.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = SoftwareUpdate.cpp; path = ../../../../Lgi/trunk/src/common/General/SoftwareUpdate.cpp; sourceTree = ""; }; 3477C4641CC26BBF0028B84B /* HttpTools.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = HttpTools.cpp; path = ../../../../Lgi/trunk/src/common/Net/HttpTools.cpp; sourceTree = ""; }; 3477C4661CC26BF30028B84B /* DrawListSurface.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = DrawListSurface.cpp; path = ../../../../Lgi/trunk/src/common/Gdc2/DrawListSurface.cpp; sourceTree = ""; }; 3477C4681CC26C1C0028B84B /* vCard-vCal.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "vCard-vCal.cpp"; path = "../../../../Lgi/trunk/src/common/Text/vCard-vCal.cpp"; sourceTree = ""; }; 3477C46A1CC26DAB0028B84B /* Path.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Path.cpp; path = ../../../../Lgi/trunk/src/common/Gdc2/Path/Path.cpp; sourceTree = ""; }; 3477C4DF1CC27C9F0028B84B /* Btree.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Btree.xcodeproj; path = ../../libs/Btree/Btree.xcodeproj; sourceTree = ""; }; 3477C4E61CC27D2B0028B84B /* MailHttp.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = MailHttp.cpp; path = ../../../../Lgi/trunk/src/common/Net/MailHttp.cpp; sourceTree = ""; }; 3477C4E81CC27D870028B84B /* Browser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Browser.cpp; path = ../../../../Lgi/trunk/src/common/Lgi/Browser.cpp; sourceTree = ""; }; 3477C4EA1CC27DC90028B84B /* Password.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Password.cpp; path = ../../../../Lgi/trunk/src/common/General/Password.cpp; sourceTree = ""; }; 3477C4EE1CC27F500028B84B /* ZoomView.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ZoomView.cpp; path = ../../../../Lgi/trunk/src/common/Widgets/ZoomView.cpp; sourceTree = ""; }; 3477C50B1CC3026F0028B84B /* sqlite.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = sqlite.xcodeproj; path = ../Code/Sqlite/v3.6.14/sqlite.xcodeproj; sourceTree = SOURCE_ROOT; }; 3477C51F1CC311A00028B84B /* Scribe.lr8 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; name = Scribe.lr8; path = ../../Resources/Scribe.lr8; sourceTree = ""; }; 3477C5221CC315FC0028B84B /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = /Users/matthew/Code/Lgi/trunk/src/mac/cocoa/../../../../../../../../System/Library/Frameworks/Cocoa.framework; sourceTree = ""; }; 347CF82623F64FD00028F610 /* flags.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = flags.png; path = ../../Resources/flags.png; sourceTree = ""; }; 3481AAAA25DC8C820032ACB7 /* FileTransferProgress.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FileTransferProgress.h; path = ../../../../Lgi/trunk/include/lgi/common/FileTransferProgress.h; sourceTree = ""; }; 34918093222F1C5F00944FC4 /* OAuth2.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = OAuth2.h; path = ../../../../Lgi/trunk/include/lgi/common/OAuth2.h; sourceTree = ""; }; 34918094222F1C6F00944FC4 /* OAuth2.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = OAuth2.cpp; path = ../../../../Lgi/trunk/src/common/Net/OAuth2.cpp; sourceTree = ""; }; 3497D5462399D260009FCC14 /* Mail Filters Menu.script */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "Mail Filters Menu.script"; path = "../../Scripts/Mail Filters Menu.script"; sourceTree = ""; }; 3497D5472399D260009FCC14 /* Delete Attachments.script */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "Delete Attachments.script"; path = "../../Scripts/Delete Attachments.script"; sourceTree = ""; }; 3497D5482399D260009FCC14 /* Add Senders To Contacts.script */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "Add Senders To Contacts.script"; path = "../../Scripts/Add Senders To Contacts.script"; sourceTree = ""; }; 3497D5492399D260009FCC14 /* Delete Duplicate Messages.script */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "Delete Duplicate Messages.script"; path = "../../Scripts/Delete Duplicate Messages.script"; sourceTree = ""; }; 3497D5532399D2A5009FCC14 /* resdefs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = resdefs.h; path = ../../Resources/resdefs.h; sourceTree = ""; }; 349AB0BD217B395B00573982 /* Png.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Png.cpp; path = ../../../../Lgi/trunk/src/common/Gdc2/Filters/Png.cpp; sourceTree = ""; }; 349AB0BF217B3D9500573982 /* scribe-mac-icon.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; name = "scribe-mac-icon.icns"; path = "../../Resources/scribe-mac-icon.icns"; sourceTree = ""; }; 34A66680237968A200ED0212 /* FolderCalendarSource.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = FolderCalendarSource.cpp; path = ../../Code/FolderCalendarSource.cpp; sourceTree = ""; }; 34AB68E8275240B40045380B /* ScribeScripts.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ScribeScripts.h; path = ../../Scripts/ScribeScripts.h; sourceTree = ""; }; 34C1BF71238CA23500658B99 /* calendar.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; name = calendar.html; path = ../../Help/calendar.html; sourceTree = ""; }; 34C1BF72238CA23500658B99 /* features.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; name = features.html; path = ../../Help/features.html; sourceTree = ""; }; 34C1BF73238CA23500658B99 /* email.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; name = email.html; path = ../../Help/email.html; sourceTree = ""; }; 34C1BF74238CA23500658B99 /* ui.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; name = ui.html; path = ../../Help/ui.html; sourceTree = ""; }; 34C1BF75238CA23500658B99 /* contacts.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; name = contacts.html; path = ../../Help/contacts.html; sourceTree = ""; }; 34C1BF76238CA23500658B99 /* install.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; name = install.html; path = ../../Help/install.html; sourceTree = ""; }; 34C1BF77238CA23500658B99 /* menu.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; name = menu.html; path = ../../Help/menu.html; sourceTree = ""; }; 34C1BF78238CA23600658B99 /* help.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; name = help.css; path = ../../Help/help.css; sourceTree = ""; }; 34C1BF79238CA23600658B99 /* intro.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; name = intro.html; path = ../../Help/intro.html; sourceTree = ""; }; 34C1BF7A238CA23600658B99 /* scripting.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; name = scripting.html; path = ../../Help/scripting.html; sourceTree = ""; }; 34C1BF7B238CA23600658B99 /* plugins.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; name = plugins.html; path = ../../Help/plugins.html; sourceTree = ""; }; 34C1BF7C238CA23600658B99 /* index.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; name = index.html; path = ../../Help/index.html; sourceTree = ""; }; 34C1BF7D238CA23600658B99 /* filters.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; name = filters.html; path = ../../Help/filters.html; sourceTree = ""; }; 34C1BF7E238CA23600658B99 /* import.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; name = import.html; path = ../../Help/import.html; sourceTree = ""; }; 34C1BF7F238CA23600658B99 /* print.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; name = print.html; path = ../../Help/print.html; sourceTree = ""; }; 34C9A7FC23C9615200129861 /* NoFace160.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = NoFace160.png; path = ../../Resources/NoFace160.png; sourceTree = ""; }; 34C9A80A23C9615200129861 /* NoFace80.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = NoFace80.png; path = ../../Resources/NoFace80.png; sourceTree = ""; }; 34D2892E275B3EF5005961A8 /* RemoteCalendarSource.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = RemoteCalendarSource.cpp; path = ../../Code/RemoteCalendarSource.cpp; sourceTree = ""; }; 34DD329423153AF000FA2B35 /* Title.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; name = Title.html; path = ../../Resources/Title.html; sourceTree = ""; }; 34DD329523153AF000FA2B35 /* About.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; name = About.html; path = ../../Resources/About.html; sourceTree = ""; }; 34DD329623153AF000FA2B35 /* Title.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = Title.png; path = ../../Resources/Title.png; sourceTree = ""; }; 34DD329B23153B2300FA2B35 /* About64px.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = About64px.png; path = ../../Resources/About64px.png; sourceTree = ""; }; 34EE8BA02748A0F100F12915 /* ControlTree.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ControlTree.h; path = ../../../../Lgi/trunk/include/lgi/common/ControlTree.h; sourceTree = ""; }; 34EE8BAE2748A26800F12915 /* OpenSSLSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OpenSSLSocket.h; path = ../../../../Lgi/trunk/include/lgi/common/OpenSSLSocket.h; sourceTree = ""; }; 34EE8BAF2748A32C00F12915 /* Html.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Html.h; path = ../../../../Lgi/trunk/include/lgi/common/Html.h; sourceTree = ""; }; 34EE8BB32748A6EC00F12915 /* HomoglyphsTable.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = HomoglyphsTable.cpp; path = ../../../../Lgi/trunk/src/common/Text/Homoglyphs/HomoglyphsTable.cpp; sourceTree = ""; }; 34EE8BB42748A6EC00F12915 /* Homoglyphs.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Homoglyphs.cpp; path = ../../../../Lgi/trunk/src/common/Text/Homoglyphs/Homoglyphs.cpp; sourceTree = ""; }; 34F1BD082442B0050062DCF4 /* libssl.3.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libssl.3.dylib; path = ../../../../codelib/openssl/libssl.3.dylib; sourceTree = ""; }; 34F1BD092442B0050062DCF4 /* libcrypto.3.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libcrypto.3.dylib; path = ../../../../codelib/openssl/libcrypto.3.dylib; sourceTree = ""; }; 34F6152127E55CA700FE5B0C /* EventTargetThread.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = EventTargetThread.h; path = ../../../../lgi/trunk/include/lgi/common/EventTargetThread.h; sourceTree = ""; }; 34FB9661237D07740039423C /* WebdavStore.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = WebdavStore.cpp; path = ../../Code/Store3Webdav/WebdavStore.cpp; sourceTree = ""; }; 34FB968923835BB40039423C /* WebDav.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = WebDav.cpp; path = ../../../../Lgi/trunk/src/common/Net/WebDav.cpp; sourceTree = ""; }; 34FB968B23835BE70039423C /* WebDav.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = WebDav.h; path = ../../../../Lgi/trunk/include/lgi/common/WebDav.h; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 3477C21B1CBEFD940028B84B /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 3477C5231CC315FC0028B84B /* Cocoa.framework in Frameworks */, 34749B5F278B08B200C8197E /* LgiCocoa.framework in Frameworks */, 34167B38279FEBA8005422F6 /* libchardet.dylib in Frameworks */, 34DED72A23AC319E0049E01F /* libsqlite.a in Frameworks */, 3474FC5F226DE0960064E0AD /* libBtree.a in Frameworks */, 3474FC60226DE0960064E0AD /* libbzip2.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 34167B2E279FEAAF005422F6 /* Products */ = { isa = PBXGroup; children = ( 34167B34279FEAAF005422F6 /* libchardet.dylib */, 34D6AEDC283AF0A100897A16 /* example */, 34D6AEDE283AF0A100897A16 /* iconv */, 34D6AEE0283AF0A100897A16 /* libiconv.dylib */, 34D6AEE2283AF0A100897A16 /* libjpeg9a.dylib */, 34D6AEE4283AF0A100897A16 /* libjpeg9a_static.a */, 34D6AEE6283AF0A100897A16 /* libpng15.dylib */, 34D6AEE8283AF0A100897A16 /* libpng15_static.a */, 34D6AEEA283AF0A100897A16 /* minigzip */, 34D6AEEC283AF0A100897A16 /* pngtest */, 34D6AEEE283AF0A100897A16 /* libz_local.dylib */, 34D6AEF0283AF0A100897A16 /* libzlib_static.a */, ); name = Products; sourceTree = ""; }; 34417D83205FB69400D2E80C /* Utils */ = { isa = PBXGroup; children = ( 3477C3A61CC227ED0028B84B /* ScribeUtils.cpp */, 345921211F2565D100098DFD /* ScribeUtils.h */, 34417D87205FB69E00D2E80C /* MailFlags.c */, 34417D88205FB69E00D2E80C /* Tables.h */, ); name = Utils; sourceTree = ""; }; 3445E56E1F1F40CF00D0A824 /* Emoji */ = { isa = PBXGroup; children = ( 3445E5741F1F413200D0A824 /* EmojiMap.cpp */, 3445E5721F1F40DF00D0A824 /* EmojiTools.cpp */, ); name = Emoji; sourceTree = ""; }; 3445E5781F1F41A400D0A824 /* Gpg */ = { isa = PBXGroup; children = ( 3445E5791F1F41AF00D0A824 /* GnuPG.cpp */, 3445E57A1F1F41AF00D0A824 /* GnuPG.h */, ); name = Gpg; sourceTree = ""; }; 3445E57C1F1F426300D0A824 /* Controls */ = { isa = PBXGroup; children = ( 34EE8BB42748A6EC00F12915 /* Homoglyphs.cpp */, 34EE8BB32748A6EC00F12915 /* HomoglyphsTable.cpp */, 349AB0BC217B393100573982 /* Html */, 3445E57D1F1F426B00D0A824 /* RichEdit */, ); name = Controls; sourceTree = ""; }; 3445E57D1F1F426B00D0A824 /* RichEdit */ = { isa = PBXGroup; children = ( 34417DBD205FD5B800D2E80C /* HorzRuleBlock.cpp */, 3445E57E1F1F428400D0A824 /* BlockCursor.cpp */, 3445E57F1F1F428400D0A824 /* RichTextEdit.cpp */, 3445E5801F1F428400D0A824 /* RichTextEditPriv.cpp */, 3445E5811F1F428400D0A824 /* RichTextEditPriv.h */, 3445E5821F1F428400D0A824 /* ImageBlock.cpp */, 3445E5831F1F428400D0A824 /* TextBlock.cpp */, ); name = RichEdit; sourceTree = ""; }; 345027F527405C87007F5F2A /* Ui */ = { isa = PBXGroup; children = ( 3477C3881CC227ED0028B84B /* ScribeAbout.cpp */, 3477C3991CC227ED0028B84B /* ScribeLangDlg.cpp */, 3477C3A51CC227ED0028B84B /* ScribeUi.cpp */, 3477C39A1CC227ED0028B84B /* ScribeListAddr.cpp */, 3459211B1F2565D100098DFD /* ScribeListAddr.h */, 3477C3911CC227ED0028B84B /* ScribeFinder.cpp */, 3477C3941CC227ED0028B84B /* ScribeFolderProp.cpp */, 3477C3961CC227ED0028B84B /* ScribeFolderTree.cpp */, 3477C38A1CC227ED0028B84B /* ScribeAccountPreview.cpp */, 345921141F2565D100098DFD /* ScribeAccountPreview.h */, 3477C38B1CC227ED0028B84B /* ScribeAccountUI.cpp */, 345921151F2565D100098DFD /* ScribeAccountUI.h */, 3477C3731CC227ED0028B84B /* SearchView.cpp */, 3477C3A91CC227ED0028B84B /* SecurityDlg.cpp */, 3477C39E1CC227ED0028B84B /* ScribePassword.cpp */, 3477C3931CC227ED0028B84B /* ScribeFolderDlg.cpp */, 3477C3981CC227ED0028B84B /* ScribeItemList.cpp */, 3477C3951CC227ED0028B84B /* ScribeFolderSelect.cpp */, 345921181F2565D100098DFD /* ScribeFolderSelect.h */, 345921171F2565D100098DFD /* ScribeFolderDlg.h */, 3477C3801CC227ED0028B84B /* ManageMailStores.cpp */, 3477C3A31CC227ED0028B84B /* ScribeStatusPanel.cpp */, 345921201F2565D100098DFD /* ScribeStatusPanel.h */, 3459210C1F2565D100098DFD /* ManageMailStores.h */, 3445E5891F1F42BA00D0A824 /* ObjectInspector.cpp */, 3459210D1F2565D100098DFD /* ObjectInspector.h */, 3477C3831CC227ED0028B84B /* PreviewPanel.cpp */, 3459210E1F2565D100098DFD /* PreviewPanel.h */, 3477C3871CC227ED0028B84B /* ReplicateDlg.cpp */, 345921111F2565D100098DFD /* ReplicateDlg.h */, 3459210A1F2565D100098DFD /* SearchView.h */, 3477C3821CC227ED0028B84B /* OptionsDlg.cpp */, 3481AAAA25DC8C820032ACB7 /* FileTransferProgress.h */, ); name = Ui; sourceTree = ""; }; 3450280327405C96007F5F2A /* BayesianFilter */ = { isa = PBXGroup; children = ( 3477C3651CC227ED0028B84B /* BayesDlg.cpp */, 3477C3661CC227ED0028B84B /* BayesianFilter.cpp */, 345921031F2565D100098DFD /* BayesianFilter.h */, ); name = BayesianFilter; sourceTree = ""; }; 3450280427405CB9007F5F2A /* Import Export */ = { isa = PBXGroup; children = ( 3477C4681CC26C1C0028B84B /* vCard-vCal.cpp */, 3459210B1F2565D100098DFD /* Imp_Outlook.h */, 3477C4311CC233980028B84B /* Imp_CsvContacts.cpp */, 3477C3761CC227ED0028B84B /* Imp_Eml.cpp */, 3477C3771CC227ED0028B84B /* Imp_Eudora.cpp */, 3477C3781CC227ED0028B84B /* Imp_Mozilla.cpp */, 3477C3791CC227ED0028B84B /* Imp_NetscapeContacts.cpp */, 3477C40E1CC231190028B84B /* Imp_OutlookExpress.cpp */, 3477C37B1CC227ED0028B84B /* ImpExp_Mbox.cpp */, 3477C36D1CC227ED0028B84B /* Exp_Scribe.cpp */, ); name = "Import Export"; sourceTree = ""; }; 3450280527405CF3007F5F2A /* Storage */ = { isa = PBXGroup; children = ( 34FB968823835B920039423C /* Webdav */, 3450280727405D00007F5F2A /* Mail3 */, 3450280627405CFB007F5F2A /* Imap */, ); name = Storage; sourceTree = ""; }; 3450280627405CFB007F5F2A /* Imap */ = { isa = PBXGroup; children = ( 3477C4151CC231890028B84B /* ScribeImap_Attachment.cpp */, 3477C4161CC231890028B84B /* ScribeImap_Folder.cpp */, 3477C4171CC231890028B84B /* ScribeImap_Mail.cpp */, 3477C4181CC231890028B84B /* ScribeImap_Store.cpp */, 3477C4191CC231890028B84B /* ScribeImap_Thread.cpp */, 3477C41A1CC231890028B84B /* ScribeImap.h */, ); name = Imap; sourceTree = ""; }; 3450280727405D00007F5F2A /* Mail3 */ = { isa = PBXGroup; children = ( 3477C4201CC2319B0028B84B /* Mail3.h */, 3477C4211CC2319B0028B84B /* Mail3Attachment.cpp */, 3477C4221CC2319B0028B84B /* Mail3Calendar.cpp */, 3477C4231CC2319B0028B84B /* Mail3Contact.cpp */, 3477C4241CC2319B0028B84B /* Mail3Filter.cpp */, 3477C4251CC2319B0028B84B /* Mail3Folder.cpp */, 3477C4261CC2319B0028B84B /* Mail3Group.cpp */, 3477C4271CC2319B0028B84B /* Mail3Mail.cpp */, 3477C4281CC2319B0028B84B /* Mail3Store.cpp */, ); name = Mail3; sourceTree = ""; }; 3450280827405D13007F5F2A /* Printing */ = { isa = PBXGroup; children = ( 3477C39D1CC227ED0028B84B /* ScribePageSetup.cpp */, 3459211C1F2565D100098DFD /* ScribePageSetup.h */, 3477C3841CC227ED0028B84B /* PrintContext.cpp */, 3459210F1F2565D100098DFD /* PrintContext.h */, 3477C3851CC227ED0028B84B /* PrintPreview.cpp */, 345921101F2565D100098DFD /* PrintPreview.h */, ); name = Printing; sourceTree = ""; }; 3450280927405D35007F5F2A /* Scripting */ = { isa = PBXGroup; children = ( 3477C3A81CC227ED0028B84B /* Scripting.cpp */, 3477C4521CC262AF0028B84B /* LexCpp.cpp */, 3477C4531CC262AF0028B84B /* ScriptCompiler.cpp */, 3477C4541CC262AF0028B84B /* ScriptLibrary.cpp */, 3477C4551CC262AF0028B84B /* ScriptVM.cpp */, ); name = Scripting; sourceTree = ""; }; 3450280A27405D68007F5F2A /* Objects */ = { isa = PBXGroup; children = ( 3477C38E1CC227ED0028B84B /* ScribeAttachment.cpp */, 3477C38F1CC227ED0028B84B /* ScribeContact.cpp */, 3477C3971CC227ED0028B84B /* ScribeGroup.cpp */, 3477C3921CC227ED0028B84B /* ScribeFolder.cpp */, 3477C3A41CC227ED0028B84B /* ScribeThing.cpp */, 3477C39B1CC227ED0028B84B /* ScribeMail.cpp */, 3477C3901CC227ED0028B84B /* ScribeFilter.cpp */, ); name = Objects; sourceTree = ""; }; 3450280B27405D91007F5F2A /* Protocols */ = { isa = PBXGroup; children = ( 3477C3A21CC227ED0028B84B /* ScribeSockets.cpp */, 34918093222F1C5F00944FC4 /* OAuth2.h */, 34918094222F1C6F00944FC4 /* OAuth2.cpp */, 3477C40C1CC230C30028B84B /* Mail.cpp */, 3477C4E61CC27D2B0028B84B /* MailHttp.cpp */, 3477C45D1CC26AAB0028B84B /* MailImap.cpp */, 3477C3A11CC227ED0028B84B /* ScribeSendReceive.cpp */, 345921261F2565EC00098DFD /* Mail.h */, ); name = Protocols; sourceTree = ""; }; 3450280C27405DDC007F5F2A /* Headers */ = { isa = PBXGroup; children = ( 345921121F2565D100098DFD /* resource.h */, 345921161F2565D100098DFD /* ScribeDefs.h */, 345921191F2565D100098DFD /* ScribeInc.h */, 3459211E1F2565D100098DFD /* ScribeSharedMem.h */, 3459211D1F2565D100098DFD /* ScribePrivate.h */, 3459211A1F2565D100098DFD /* ScribeItemDefs.h */, 345921131F2565D100098DFD /* Scribe.h */, ); name = Headers; sourceTree = ""; }; 346FACFB1D3C81E600FFEBCE /* Spelling */ = { isa = PBXGroup; children = ( 3459211F1F2565D100098DFD /* ScribeSpellCheck.h */, 3425DC6A23F788AE00BC025E /* SpellCheck.h */, 34417D8D205FD4A500D2E80C /* Ftp.cpp */, 341A02B723BD702800F0FF48 /* SpellCheckAspell.cpp */, 3445E5761F1F416500D0A824 /* SpellCheckMac.mm */, ); name = Spelling; sourceTree = ""; }; 34749B40278B042500C8197E /* Products */ = { isa = PBXGroup; children = ( 34749B44278B042500C8197E /* LgiCocoa.framework */, ); name = Products; sourceTree = ""; }; 3474FC45226DCCD10064E0AD /* Products */ = { isa = PBXGroup; children = ( 3474FC49226DCCD10064E0AD /* libbzip2.a */, ); name = Products; sourceTree = ""; }; 3474FC4A226DCCDD0064E0AD /* Products */ = { isa = PBXGroup; children = ( 3474FC4E226DCCDD0064E0AD /* libBtree.a */, ); name = Products; sourceTree = ""; }; 3477C2151CBEFD940028B84B = { isa = PBXGroup; children = ( 3477C2201CBEFD940028B84B /* Scribe */, 3477C5211CC315EB0028B84B /* Link */, 3477C21F1CBEFD940028B84B /* Products */, 34DED72923AC319E0049E01F /* Frameworks */, ); sourceTree = ""; }; 3477C21F1CBEFD940028B84B /* Products */ = { isa = PBXGroup; children = ( 3477C21E1CBEFD940028B84B /* Scribe.app */, ); name = Products; sourceTree = ""; }; 3477C2201CBEFD940028B84B /* Scribe */ = { isa = PBXGroup; children = ( 345C53CF23F163D30035E9CA /* Scribe.entitlements */, 3477C35F1CC226370028B84B /* Core */, 34C1BF70238CA21700658B99 /* Help */, 3477C2241CBEFD940028B84B /* Lgi */, 3477C51E1CC3118E0028B84B /* Resources */, 346FACFB1D3C81E600FFEBCE /* Spelling */, 34417D83205FB69400D2E80C /* Utils */, ); path = Scribe; sourceTree = ""; }; 3477C2241CBEFD940028B84B /* Lgi */ = { isa = PBXGroup; children = ( 3477C3FB1CC22E690028B84B /* Base64.cpp */, 3477C4E81CC27D870028B84B /* Browser.cpp */, 3477C44F1CC262670028B84B /* ColourSelect.cpp */, 3445E57C1F1F426300D0A824 /* Controls */, 3477C44D1CC2624B0028B84B /* ControlTree.cpp */, 34EE8BA02748A0F100F12915 /* ControlTree.h */, 341A787F2699058600872FEB /* DatePopup.cpp */, 3477C4331CC233CF0028B84B /* Db-Csv.cpp */, 3477C4661CC26BF30028B84B /* DrawListSurface.cpp */, 3445E56E1F1F40CF00D0A824 /* Emoji */, 34F6152127E55CA700FE5B0C /* EventTargetThread.h */, 3477C4031CC22F680028B84B /* ExeCheck.cpp */, 3477C4411CC235490028B84B /* FilterUi.cpp */, 3477C4081CC2301F0028B84B /* GdcTools.cpp */, 349AB0BB217B391200573982 /* Graphics */, 3477C4601CC26AE00028B84B /* Growl.cpp */, 3477C4351CC234200028B84B /* Http.cpp */, 3477C4641CC26BBF0028B84B /* HttpTools.cpp */, 3477C3571CC1149F0028B84B /* LgiMain.cpp */, 3477C45C1CC26AAB0028B84B /* Mime.cpp */, 3477C3FD1CC22EF60028B84B /* MonthView.cpp */, 3477C4101CC231470028B84B /* OpenSSLSocket.cpp */, 34EE8BAE2748A26800F12915 /* OpenSSLSocket.h */, 3477C44B1CC235C60028B84B /* OptionsFile.cpp */, 3477C4EA1CC27DC90028B84B /* Password.cpp */, 3477C45A1CC263560028B84B /* SharedMemory.cpp */, 3477C4611CC26AE00028B84B /* SoftwareUpdate.cpp */, 342BAE3A2588AFA8006EF16E /* TextConvert.cpp */, 3477C43D1CC234C80028B84B /* TimePopup.cpp */, 3477C4011CC22F340028B84B /* TimeZone.cpp */, 3477C40A1CC230A30028B84B /* Tnef.cpp */, 3477C43F1CC234E60028B84B /* XmlTreeUi.cpp */, 3477C3FE1CC22EF60028B84B /* YearView.cpp */, ); name = Lgi; sourceTree = ""; }; 3477C35F1CC226370028B84B /* Core */ = { isa = PBXGroup; children = ( 3477C3631CC227ED0028B84B /* AddressSelect.cpp */, 3477C3691CC227ED0028B84B /* Components.cpp */, - 3477C3721CC227ED0028B84B /* GNewMailDlg.cpp */, + 3477C3721CC227ED0028B84B /* NewMailDlg.cpp */, 3477C3741CC227ED0028B84B /* HtmlToText.cpp */, 3477C3811CC227ED0028B84B /* MContainer.cpp */, 3477C3891CC227ED0028B84B /* ScribeAccount.cpp */, 3477C38D1CC227ED0028B84B /* ScribeApp.cpp */, 3477C3611CC226450028B84B /* ScribeMain.cpp */, 3477C3A01CC227ED0028B84B /* ScribeRepair.cpp */, 3477C3AC1CC227ED0028B84B /* Store3Common.cpp */, 3477C3AD1CC227ED0028B84B /* TitlePage.cpp */, 345921061F2565D100098DFD /* Components.h */, 345921071F2565D100098DFD /* DomType.h */, 345921081F2565D100098DFD /* DomTypeValues.h */, - 345921091F2565D100098DFD /* GNewMailDlg.h */, + 345921091F2565D100098DFD /* NewMailDlg.h */, 345921221F2565D100098DFD /* Store3Common.h */, 3450280327405C96007F5F2A /* BayesianFilter */, 34A666722379687D00ED0212 /* Calendar */, 3445E5781F1F41A400D0A824 /* Gpg */, 3450280C27405DDC007F5F2A /* Headers */, 3450280427405CB9007F5F2A /* Import Export */, 3450280A27405D68007F5F2A /* Objects */, 3450280827405D13007F5F2A /* Printing */, 3450280B27405D91007F5F2A /* Protocols */, 3450280927405D35007F5F2A /* Scripting */, 3450280527405CF3007F5F2A /* Storage */, 345027F527405C87007F5F2A /* Ui */, ); name = Core; sourceTree = ""; }; 3477C51E1CC3118E0028B84B /* Resources */ = { isa = PBXGroup; children = ( 3477C22C1CBEFD940028B84B /* Info.plist */, 34364F8824CCE9FF00272F13 /* Themes */, 347CF82623F64FD00028F610 /* flags.png */, 34C9A80A23C9615200129861 /* NoFace80.png */, 34C9A7FC23C9615200129861 /* NoFace160.png */, 3497D5382399D246009FCC14 /* Scripts */, 340FA6A8237E7E2D0043D1F2 /* EmojiMap.png */, 34DD329B23153B2300FA2B35 /* About64px.png */, 34DD329523153AF000FA2B35 /* About.html */, 34DD329623153AF000FA2B35 /* Title.png */, 34DD329423153AF000FA2B35 /* Title.html */, 346E1A4A231293570019B4AA /* tray_error.png */, 346E1A4C231293570019B4AA /* tray_mail.png */, 346E1A4B231293570019B4AA /* tray_small.png */, 346E1A45231292C60019B4AA /* PreviewContact.html */, 346E1A46231292C60019B4AA /* PreviewGroup.html */, 346E1A44231292C60019B4AA /* PreviewMail.html */, 349AB0BF217B3D9500573982 /* scribe-mac-icon.icns */, 345F314B1CC7A46B00656B8B /* xgate-icons-32.png */, 345F31451CC7A3B400656B8B /* Icons-16.png */, 3477C51F1CC311A00028B84B /* Scribe.lr8 */, ); name = Resources; sourceTree = ""; }; 3477C5211CC315EB0028B84B /* Link */ = { isa = PBXGroup; children = ( 34F1BD092442B0050062DCF4 /* libcrypto.3.dylib */, 34F1BD082442B0050062DCF4 /* libssl.3.dylib */, 3477C5221CC315FC0028B84B /* Cocoa.framework */, ); name = Link; sourceTree = ""; }; 3497D5382399D246009FCC14 /* Scripts */ = { isa = PBXGroup; children = ( 34AB68E8275240B40045380B /* ScribeScripts.h */, 3497D5532399D2A5009FCC14 /* resdefs.h */, 3497D5482399D260009FCC14 /* Add Senders To Contacts.script */, 3497D5472399D260009FCC14 /* Delete Attachments.script */, 3497D5492399D260009FCC14 /* Delete Duplicate Messages.script */, 3497D5462399D260009FCC14 /* Mail Filters Menu.script */, ); name = Scripts; sourceTree = ""; }; 349AB0BB217B391200573982 /* Graphics */ = { isa = PBXGroup; children = ( 341BF47023231D0600BC3AB0 /* Jpeg.cpp */, 349AB0BD217B395B00573982 /* Png.cpp */, 345F31491CC7A44C00656B8B /* Lzw.cpp */, 345F31471CC7A40F00656B8B /* Gif.cpp */, 3477C4EE1CC27F500028B84B /* ZoomView.cpp */, 3477C46A1CC26DAB0028B84B /* Path.cpp */, ); name = Graphics; sourceTree = ""; }; 349AB0BC217B393100573982 /* Html */ = { isa = PBXGroup; children = ( 34EE8BAF2748A32C00F12915 /* Html.h */, 3477C4431CC235810028B84B /* Html.cpp */, 3477C4441CC235810028B84B /* HtmlCommon.cpp */, 3477C4451CC235810028B84B /* HtmlParser.cpp */, ); name = Html; sourceTree = ""; }; 34A666722379687D00ED0212 /* Calendar */ = { isa = PBXGroup; children = ( 3477C3671CC227ED0028B84B /* Calendar.cpp */, 3477C3681CC227ED0028B84B /* CalendarView.cpp */, 34A66680237968A200ED0212 /* FolderCalendarSource.cpp */, 34D2892E275B3EF5005961A8 /* RemoteCalendarSource.cpp */, 345921041F2565D100098DFD /* Calendar.h */, 345921051F2565D100098DFD /* CalendarView.h */, ); name = Calendar; sourceTree = ""; }; 34C1BF70238CA21700658B99 /* Help */ = { isa = PBXGroup; children = ( 34C1BF71238CA23500658B99 /* calendar.html */, 34C1BF75238CA23500658B99 /* contacts.html */, 34C1BF73238CA23500658B99 /* email.html */, 34C1BF72238CA23500658B99 /* features.html */, 34C1BF7D238CA23600658B99 /* filters.html */, 34C1BF78238CA23600658B99 /* help.css */, 34C1BF7E238CA23600658B99 /* import.html */, 34C1BF7C238CA23600658B99 /* index.html */, 34C1BF76238CA23500658B99 /* install.html */, 34C1BF79238CA23600658B99 /* intro.html */, 34C1BF77238CA23500658B99 /* menu.html */, 34C1BF7B238CA23600658B99 /* plugins.html */, 34C1BF7F238CA23600658B99 /* print.html */, 34C1BF7A238CA23600658B99 /* scripting.html */, 34C1BF74238CA23500658B99 /* ui.html */, ); name = Help; sourceTree = ""; }; 34DED71523AC30C30049E01F /* Products */ = { isa = PBXGroup; children = ( 34DED72623AC30C30049E01F /* libsqlite.a */, ); name = Products; sourceTree = ""; }; 34DED72923AC319E0049E01F /* Frameworks */ = { isa = PBXGroup; children = ( 34749B52278B06A200C8197E /* scribeLibs.xcodeproj */, 34749B3F278B042500C8197E /* LgiCocoa.xcodeproj */, 34417DAB205FD56300D2E80C /* bzip2.xcodeproj */, 3477C4DF1CC27C9F0028B84B /* Btree.xcodeproj */, 3477C50B1CC3026F0028B84B /* sqlite.xcodeproj */, ); name = Frameworks; sourceTree = ""; }; 34FB968823835B920039423C /* Webdav */ = { isa = PBXGroup; children = ( 340FAADC23888A570017BC62 /* WebdavContact.cpp */, 34161496238484F700A65477 /* WebdavCalendar.cpp */, 34161495238484F700A65477 /* WebdavFolder.cpp */, 34161499238484F800A65477 /* WebdavStore.h */, 34161497238484F700A65477 /* WebdavStorePriv.h */, 34161498238484F800A65477 /* WebdavThread.cpp */, 34FB968B23835BE70039423C /* WebDav.h */, 34FB968923835BB40039423C /* WebDav.cpp */, 34FB9661237D07740039423C /* WebdavStore.cpp */, ); name = Webdav; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 3477C21D1CBEFD940028B84B /* Scribe */ = { isa = PBXNativeTarget; buildConfigurationList = 3477C22F1CBEFD940028B84B /* Build configuration list for PBXNativeTarget "Scribe" */; buildPhases = ( 3477C21A1CBEFD940028B84B /* Sources */, 3477C21B1CBEFD940028B84B /* Frameworks */, 3477C21C1CBEFD940028B84B /* Resources */, 3477C51C1CC304850028B84B /* Copy Files */, 34C1BF8F238CAEAD00658B99 /* CopyFiles */, 3497D54E2399D27A009FCC14 /* CopyFiles */, 3488015523FCF01800D24534 /* ShellScript */, ); buildRules = ( ); dependencies = ( 34D6AEF8283AF0E400897A16 /* PBXTargetDependency */, 34D6AEF6283AF0BD00897A16 /* PBXTargetDependency */, 34D6AEF4283AF0B900897A16 /* PBXTargetDependency */, 34D6AEF2283AF0B300897A16 /* PBXTargetDependency */, 34749B5B278B06B900C8197E /* PBXTargetDependency */, 34167B36279FEAC2005422F6 /* PBXTargetDependency */, 34DED72823AC31950049E01F /* PBXTargetDependency */, 3474FC5A226DE08D0064E0AD /* PBXTargetDependency */, 3474FC5C226DE08D0064E0AD /* PBXTargetDependency */, ); name = Scribe; productName = Scribe; productReference = 3477C21E1CBEFD940028B84B /* Scribe.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 3477C2161CBEFD940028B84B /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 1240; ORGANIZATIONNAME = Memecode; TargetAttributes = { 3477C21D1CBEFD940028B84B = { CreatedOnToolsVersion = 7.3; DevelopmentTeam = 2AV9WN2LD8; ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.ApplicationGroups.Mac = { enabled = 0; }; com.apple.HardenedRuntime = { enabled = 1; }; }; }; }; }; buildConfigurationList = 3477C2191CBEFD940028B84B /* Build configuration list for PBXProject "Scribe" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 3477C2151CBEFD940028B84B; productRefGroup = 3477C21F1CBEFD940028B84B /* Products */; projectDirPath = ""; projectReferences = ( { ProductGroup = 3474FC4A226DCCDD0064E0AD /* Products */; ProjectRef = 3477C4DF1CC27C9F0028B84B /* Btree.xcodeproj */; }, { ProductGroup = 3474FC45226DCCD10064E0AD /* Products */; ProjectRef = 34417DAB205FD56300D2E80C /* bzip2.xcodeproj */; }, { ProductGroup = 34749B40278B042500C8197E /* Products */; ProjectRef = 34749B3F278B042500C8197E /* LgiCocoa.xcodeproj */; }, { ProductGroup = 34167B2E279FEAAF005422F6 /* Products */; ProjectRef = 34749B52278B06A200C8197E /* scribeLibs.xcodeproj */; }, { ProductGroup = 34DED71523AC30C30049E01F /* Products */; ProjectRef = 3477C50B1CC3026F0028B84B /* sqlite.xcodeproj */; }, ); projectRoot = ""; targets = ( 3477C21D1CBEFD940028B84B /* Scribe */, ); }; /* End PBXProject section */ /* Begin PBXReferenceProxy section */ 34167B34279FEAAF005422F6 /* libchardet.dylib */ = { isa = PBXReferenceProxy; fileType = "compiled.mach-o.dylib"; path = libchardet.dylib; remoteRef = 34167B33279FEAAF005422F6 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 34749B44278B042500C8197E /* LgiCocoa.framework */ = { isa = PBXReferenceProxy; fileType = wrapper.framework; path = LgiCocoa.framework; remoteRef = 34749B43278B042500C8197E /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 3474FC49226DCCD10064E0AD /* libbzip2.a */ = { isa = PBXReferenceProxy; fileType = archive.ar; path = libbzip2.a; remoteRef = 3474FC48226DCCD10064E0AD /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 3474FC4E226DCCDD0064E0AD /* libBtree.a */ = { isa = PBXReferenceProxy; fileType = "compiled.mach-o.dylib"; path = libBtree.a; remoteRef = 3474FC4D226DCCDD0064E0AD /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 34D6AEDC283AF0A100897A16 /* example */ = { isa = PBXReferenceProxy; fileType = "compiled.mach-o.executable"; path = example; remoteRef = 34D6AEDB283AF0A100897A16 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 34D6AEDE283AF0A100897A16 /* iconv */ = { isa = PBXReferenceProxy; fileType = "compiled.mach-o.executable"; path = iconv; remoteRef = 34D6AEDD283AF0A100897A16 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 34D6AEE0283AF0A100897A16 /* libiconv.dylib */ = { isa = PBXReferenceProxy; fileType = "compiled.mach-o.dylib"; path = libiconv.dylib; remoteRef = 34D6AEDF283AF0A100897A16 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 34D6AEE2283AF0A100897A16 /* libjpeg9a.dylib */ = { isa = PBXReferenceProxy; fileType = "compiled.mach-o.dylib"; path = libjpeg9a.dylib; remoteRef = 34D6AEE1283AF0A100897A16 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 34D6AEE4283AF0A100897A16 /* libjpeg9a_static.a */ = { isa = PBXReferenceProxy; fileType = archive.ar; path = libjpeg9a_static.a; remoteRef = 34D6AEE3283AF0A100897A16 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 34D6AEE6283AF0A100897A16 /* libpng15.dylib */ = { isa = PBXReferenceProxy; fileType = "compiled.mach-o.dylib"; name = libpng15.dylib; path = libpng15.15.4.0.dylib; remoteRef = 34D6AEE5283AF0A100897A16 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 34D6AEE8283AF0A100897A16 /* libpng15_static.a */ = { isa = PBXReferenceProxy; fileType = archive.ar; path = libpng15_static.a; remoteRef = 34D6AEE7283AF0A100897A16 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 34D6AEEA283AF0A100897A16 /* minigzip */ = { isa = PBXReferenceProxy; fileType = "compiled.mach-o.executable"; path = minigzip; remoteRef = 34D6AEE9283AF0A100897A16 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 34D6AEEC283AF0A100897A16 /* pngtest */ = { isa = PBXReferenceProxy; fileType = "compiled.mach-o.executable"; path = pngtest; remoteRef = 34D6AEEB283AF0A100897A16 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 34D6AEEE283AF0A100897A16 /* libz_local.dylib */ = { isa = PBXReferenceProxy; fileType = "compiled.mach-o.dylib"; name = libz_local.dylib; path = libz_local.1.2.5.dylib; remoteRef = 34D6AEED283AF0A100897A16 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 34D6AEF0283AF0A100897A16 /* libzlib_static.a */ = { isa = PBXReferenceProxy; fileType = archive.ar; path = libzlib_static.a; remoteRef = 34D6AEEF283AF0A100897A16 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 34DED72623AC30C30049E01F /* libsqlite.a */ = { isa = PBXReferenceProxy; fileType = archive.ar; path = libsqlite.a; remoteRef = 34DED72523AC30C30049E01F /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXReferenceProxy section */ /* Begin PBXResourcesBuildPhase section */ 3477C21C1CBEFD940028B84B /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 346E1A4E231293570019B4AA /* tray_small.png in Resources */, 340FA6A9237E7E2D0043D1F2 /* EmojiMap.png in Resources */, 34C9A80C23C9615300129861 /* NoFace80.png in Resources */, 347CF82723F64FD00028F610 /* flags.png in Resources */, 3477C5201CC311A00028B84B /* Scribe.lr8 in Resources */, 34C9A80B23C9615300129861 /* NoFace160.png in Resources */, 34364F8924CCE9FF00272F13 /* Themes in Resources */, 34DD329C23153B2300FA2B35 /* About64px.png in Resources */, 346E1A4F231293570019B4AA /* tray_mail.png in Resources */, 345F314C1CC7A46B00656B8B /* xgate-icons-32.png in Resources */, 346E1A4D231293570019B4AA /* tray_error.png in Resources */, 34DD329923153AF000FA2B35 /* About.html in Resources */, 34DD329A23153AF000FA2B35 /* Title.png in Resources */, 345F31461CC7A3B400656B8B /* Icons-16.png in Resources */, 34DD329823153AF000FA2B35 /* Title.html in Resources */, 346E1A47231292C60019B4AA /* PreviewMail.html in Resources */, 346E1A48231292C60019B4AA /* PreviewContact.html in Resources */, 349AB0C0217B3D9500573982 /* scribe-mac-icon.icns in Resources */, 346E1A49231292C60019B4AA /* PreviewGroup.html in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 3488015523FCF01800D24534 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "# Type a script or drag a script file from your workspace to insert its path.\npython3 ${PROJECT_DIR}/PostBuildStep.py ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}\n"; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 3477C21A1CBEFD940028B84B /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 3445E5751F1F413200D0A824 /* EmojiMap.cpp in Sources */, 3477C3EF1CC227ED0028B84B /* ScribeStatusPanel.cpp in Sources */, 3477C3C31CC227ED0028B84B /* Imp_Eudora.cpp in Sources */, 3477C3CE1CC227ED0028B84B /* OptionsDlg.cpp in Sources */, 3477C3E91CC227ED0028B84B /* ScribePageSetup.cpp in Sources */, 3445E5851F1F428400D0A824 /* RichTextEdit.cpp in Sources */, 3477C4111CC231470028B84B /* OpenSSLSocket.cpp in Sources */, 3477C3C01CC227ED0028B84B /* HtmlToText.cpp in Sources */, 3477C4631CC26AE00028B84B /* SoftwareUpdate.cpp in Sources */, 3477C4EB1CC27DC90028B84B /* Password.cpp in Sources */, 3477C44C1CC235C60028B84B /* OptionsFile.cpp in Sources */, 3477C4591CC262AF0028B84B /* ScriptVM.cpp in Sources */, 34A66681237968A200ED0212 /* FolderCalendarSource.cpp in Sources */, 34417D89205FB69E00D2E80C /* MailFlags.c in Sources */, 3477C41B1CC231890028B84B /* ScribeImap_Attachment.cpp in Sources */, 3477C41F1CC231890028B84B /* ScribeImap_Thread.cpp in Sources */, 3477C3F91CC227ED0028B84B /* TitlePage.cpp in Sources */, 3477C42E1CC2319B0028B84B /* Mail3Group.cpp in Sources */, 3477C4291CC2319B0028B84B /* Mail3Attachment.cpp in Sources */, 3477C3B51CC227ED0028B84B /* Components.cpp in Sources */, 34EE8BB62748A6EC00F12915 /* Homoglyphs.cpp in Sources */, 3477C45E1CC26AAB0028B84B /* Mime.cpp in Sources */, 3477C3F51CC227ED0028B84B /* SecurityDlg.cpp in Sources */, 3477C3C41CC227ED0028B84B /* Imp_Mozilla.cpp in Sources */, 3477C3D11CC227ED0028B84B /* PrintPreview.cpp in Sources */, 3477C3E21CC227ED0028B84B /* ScribeFolderTree.cpp in Sources */, 3477C3E71CC227ED0028B84B /* ScribeMail.cpp in Sources */, 341BF47123231D0700BC3AB0 /* Jpeg.cpp in Sources */, 3477C4091CC2301F0028B84B /* GdcTools.cpp in Sources */, 34417DBE205FD5B800D2E80C /* HorzRuleBlock.cpp in Sources */, 3445E5731F1F40DF00D0A824 /* EmojiTools.cpp in Sources */, 3477C45B1CC263560028B84B /* SharedMemory.cpp in Sources */, 34FB968A23835BB40039423C /* WebDav.cpp in Sources */, 3477C3C71CC227ED0028B84B /* ImpExp_Mbox.cpp in Sources */, 349AB0BE217B395B00573982 /* Png.cpp in Sources */, 3477C3E31CC227ED0028B84B /* ScribeGroup.cpp in Sources */, 3477C3B41CC227ED0028B84B /* CalendarView.cpp in Sources */, 3477C3CF1CC227ED0028B84B /* PreviewPanel.cpp in Sources */, 3477C42F1CC2319B0028B84B /* Mail3Mail.cpp in Sources */, 3477C4691CC26C1C0028B84B /* vCard-vCal.cpp in Sources */, 341A02B823BD702800F0FF48 /* SpellCheckAspell.cpp in Sources */, 34FB966F237D07740039423C /* WebdavStore.cpp in Sources */, 3477C41E1CC231890028B84B /* ScribeImap_Store.cpp in Sources */, 3477C3FC1CC22E690028B84B /* Base64.cpp in Sources */, 3477C4321CC233980028B84B /* Imp_CsvContacts.cpp in Sources */, 3477C4621CC26AE00028B84B /* Growl.cpp in Sources */, 3477C3BF1CC227ED0028B84B /* SearchView.cpp in Sources */, 3477C3DC1CC227ED0028B84B /* ScribeFilter.cpp in Sources */, 3477C4421CC235490028B84B /* FilterUi.cpp in Sources */, 342BAE482588AFA8006EF16E /* TextConvert.cpp in Sources */, 3477C3D31CC227ED0028B84B /* ReplicateDlg.cpp in Sources */, 3477C4651CC26BBF0028B84B /* HttpTools.cpp in Sources */, 3477C42B1CC2319B0028B84B /* Mail3Contact.cpp in Sources */, 3477C3D91CC227ED0028B84B /* ScribeApp.cpp in Sources */, 3477C3CC1CC227ED0028B84B /* ManageMailStores.cpp in Sources */, 3477C3E51CC227ED0028B84B /* ScribeLangDlg.cpp in Sources */, 3477C3ED1CC227ED0028B84B /* ScribeSendReceive.cpp in Sources */, 3477C43E1CC234C80028B84B /* TimePopup.cpp in Sources */, 3477C3D51CC227ED0028B84B /* ScribeAccount.cpp in Sources */, 3477C3D71CC227ED0028B84B /* ScribeAccountUI.cpp in Sources */, 3477C4401CC234E60028B84B /* XmlTreeUi.cpp in Sources */, 3477C42C1CC2319B0028B84B /* Mail3Filter.cpp in Sources */, 3477C4501CC262670028B84B /* ColourSelect.cpp in Sources */, 3477C3B31CC227ED0028B84B /* Calendar.cpp in Sources */, 3477C42A1CC2319B0028B84B /* Mail3Calendar.cpp in Sources */, 3477C42D1CC2319B0028B84B /* Mail3Folder.cpp in Sources */, 3477C3DE1CC227ED0028B84B /* ScribeFolder.cpp in Sources */, 3477C3DF1CC227ED0028B84B /* ScribeFolderDlg.cpp in Sources */, 3477C4481CC235810028B84B /* HtmlParser.cpp in Sources */, 3477C3621CC226450028B84B /* ScribeMain.cpp in Sources */, 3445E57B1F1F41AF00D0A824 /* GnuPG.cpp in Sources */, 3477C4671CC26BF30028B84B /* DrawListSurface.cpp in Sources */, 3477C3B11CC227ED0028B84B /* BayesDlg.cpp in Sources */, 3477C3DD1CC227ED0028B84B /* ScribeFinder.cpp in Sources */, 3477C3AF1CC227ED0028B84B /* AddressSelect.cpp in Sources */, 3477C3E01CC227ED0028B84B /* ScribeFolderProp.cpp in Sources */, 3477C4561CC262AF0028B84B /* LexCpp.cpp in Sources */, 3477C3D01CC227ED0028B84B /* PrintContext.cpp in Sources */, 34D2892F275B3EF5005961A8 /* RemoteCalendarSource.cpp in Sources */, 3477C3C51CC227ED0028B84B /* Imp_NetscapeContacts.cpp in Sources */, 3477C3D41CC227ED0028B84B /* ScribeAbout.cpp in Sources */, 3477C3F11CC227ED0028B84B /* ScribeUi.cpp in Sources */, 3477C46B1CC26DAB0028B84B /* Path.cpp in Sources */, 340FAAEA23888A570017BC62 /* WebdavContact.cpp in Sources */, 3477C3F41CC227ED0028B84B /* Scripting.cpp in Sources */, 3477C3EA1CC227ED0028B84B /* ScribePassword.cpp in Sources */, 3445E5871F1F428400D0A824 /* ImageBlock.cpp in Sources */, 3477C3F01CC227ED0028B84B /* ScribeThing.cpp in Sources */, 341A78802699058600872FEB /* DatePopup.cpp in Sources */, 3477C3FF1CC22EF60028B84B /* MonthView.cpp in Sources */, 34417D8E205FD4A500D2E80C /* Ftp.cpp in Sources */, 3445E5771F1F416500D0A824 /* SpellCheckMac.mm in Sources */, 3477C4461CC235810028B84B /* Html.cpp in Sources */, 3477C3DA1CC227ED0028B84B /* ScribeAttachment.cpp in Sources */, 3477C44E1CC2624B0028B84B /* ControlTree.cpp in Sources */, 3477C4571CC262AF0028B84B /* ScriptCompiler.cpp in Sources */, 3477C4301CC2319B0028B84B /* Mail3Store.cpp in Sources */, 345F31481CC7A40F00656B8B /* Gif.cpp in Sources */, 3477C40D1CC230C30028B84B /* Mail.cpp in Sources */, 3477C3EE1CC227ED0028B84B /* ScribeSockets.cpp in Sources */, 3477C3D61CC227ED0028B84B /* ScribeAccountPreview.cpp in Sources */, 3477C4341CC233CF0028B84B /* Db-Csv.cpp in Sources */, 3477C4E71CC27D2B0028B84B /* MailHttp.cpp in Sources */, 3477C3DB1CC227ED0028B84B /* ScribeContact.cpp in Sources */, 3477C4EF1CC27F500028B84B /* ZoomView.cpp in Sources */, 3477C4581CC262AF0028B84B /* ScriptLibrary.cpp in Sources */, 3477C3B21CC227ED0028B84B /* BayesianFilter.cpp in Sources */, 3445E5881F1F428400D0A824 /* TextBlock.cpp in Sources */, 3477C3CD1CC227ED0028B84B /* MContainer.cpp in Sources */, 3477C3581CC1149F0028B84B /* LgiMain.cpp in Sources */, 3445E58A1F1F42BA00D0A824 /* ObjectInspector.cpp in Sources */, 3416149A238484F800A65477 /* WebdavFolder.cpp in Sources */, 3477C4471CC235810028B84B /* HtmlCommon.cpp in Sources */, 3477C40B1CC230A30028B84B /* Tnef.cpp in Sources */, 3477C3E41CC227ED0028B84B /* ScribeItemList.cpp in Sources */, 3477C3E61CC227ED0028B84B /* ScribeListAddr.cpp in Sources */, 3477C3F81CC227ED0028B84B /* Store3Common.cpp in Sources */, 345F314A1CC7A44C00656B8B /* Lzw.cpp in Sources */, 3477C3F21CC227ED0028B84B /* ScribeUtils.cpp in Sources */, 3477C4021CC22F340028B84B /* TimeZone.cpp in Sources */, 3477C41D1CC231890028B84B /* ScribeImap_Mail.cpp in Sources */, 3477C4001CC22EF60028B84B /* YearView.cpp in Sources */, 3477C3EC1CC227ED0028B84B /* ScribeRepair.cpp in Sources */, 3416149C238484F800A65477 /* WebdavThread.cpp in Sources */, 34918095222F1C6F00944FC4 /* OAuth2.cpp in Sources */, 3477C3B91CC227ED0028B84B /* Exp_Scribe.cpp in Sources */, 3477C3E11CC227ED0028B84B /* ScribeFolderSelect.cpp in Sources */, 3477C45F1CC26AAB0028B84B /* MailImap.cpp in Sources */, 3477C4E91CC27D870028B84B /* Browser.cpp in Sources */, 3477C4041CC22F680028B84B /* ExeCheck.cpp in Sources */, 34EE8BB52748A6EC00F12915 /* HomoglyphsTable.cpp in Sources */, - 3477C3BE1CC227ED0028B84B /* GNewMailDlg.cpp in Sources */, + 3477C3BE1CC227ED0028B84B /* NewMailDlg.cpp in Sources */, 3445E5841F1F428400D0A824 /* BlockCursor.cpp in Sources */, 3445E5861F1F428400D0A824 /* RichTextEditPriv.cpp in Sources */, 3477C3C21CC227ED0028B84B /* Imp_Eml.cpp in Sources */, 3416149B238484F800A65477 /* WebdavCalendar.cpp in Sources */, 3477C40F1CC231190028B84B /* Imp_OutlookExpress.cpp in Sources */, 3477C4361CC234200028B84B /* Http.cpp in Sources */, 3477C41C1CC231890028B84B /* ScribeImap_Folder.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 34167B36279FEAC2005422F6 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = chardet; targetProxy = 34167B35279FEAC2005422F6 /* PBXContainerItemProxy */; }; 34749B5B278B06B900C8197E /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = LgiCocoa; targetProxy = 34749B5A278B06B900C8197E /* PBXContainerItemProxy */; }; 3474FC5A226DE08D0064E0AD /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = bzip2; targetProxy = 3474FC59226DE08D0064E0AD /* PBXContainerItemProxy */; }; 3474FC5C226DE08D0064E0AD /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = Btree; targetProxy = 3474FC5B226DE08D0064E0AD /* PBXContainerItemProxy */; }; 34D6AEF2283AF0B300897A16 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = libjpeg9a; targetProxy = 34D6AEF1283AF0B300897A16 /* PBXContainerItemProxy */; }; 34D6AEF4283AF0B900897A16 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = libpng15; targetProxy = 34D6AEF3283AF0B900897A16 /* PBXContainerItemProxy */; }; 34D6AEF6283AF0BD00897A16 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = zlib; targetProxy = 34D6AEF5283AF0BD00897A16 /* PBXContainerItemProxy */; }; 34D6AEF8283AF0E400897A16 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = libiconv; targetProxy = 34D6AEF7283AF0E400897A16 /* PBXContainerItemProxy */; }; 34DED72823AC31950049E01F /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = sqlite; targetProxy = 34DED72723AC31950049E01F /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ 3477C22D1CBEFD940028B84B /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ARCHS = x86_64; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = NO; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; FRAMEWORK_SEARCH_PATHS = ../../../Lgi/trunk/src/mac/cocoa/build/Debug; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( MAC, "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; HEADER_SEARCH_PATHS = ( ../Code, ../Code/Sqlite, ../Resources, ../Utils/Tables, ../../libs/libchardet/include, ../../libs/libchardet/src, "../../libs/aspell-0.60.6.1/interfaces/cc", "../../libs/build-x64/libpng", ../../../lgi/trunk/include, ../../../lgi/trunk/private/common, ../../../lgi/trunk/include/lgi/mac/cocoa, "../../../../codelib/libjpeg-9a", ../../../../codelib/libpng, "/usr/local/Cellar/openssl@1.1/1.1.1q/include", ); MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; OTHER_CFLAGS = "-DLGI_COCOA=1"; OTHER_CPLUSPLUSFLAGS = "-DLGI_COCOA=1"; SDKROOT = macosx; }; name = Debug; }; 3477C22E1CBEFD940028B84B /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ARCHS = x86_64; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = NO; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; FRAMEWORK_SEARCH_PATHS = ../../../Lgi/trunk/src/mac/cocoa/build/Release; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_PREPROCESSOR_DEFINITIONS = MAC; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; HEADER_SEARCH_PATHS = ( ../Code, ../Code/Sqlite, ../Resources, ../Utils/Tables, ../../libs/libchardet/include, ../../libs/libchardet/src, "../../libs/aspell-0.60.6.1/interfaces/cc", "../../libs/build-x64/libpng", ../../../lgi/trunk/include, ../../../lgi/trunk/private/common, ../../../lgi/trunk/include/lgi/mac/cocoa, "../../../../codelib/libjpeg-9a", ../../../../codelib/libpng, "/usr/local/Cellar/openssl@1.1/1.1.1q/include", ); MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; OTHER_CFLAGS = "-DLGI_COCOA=1"; OTHER_CPLUSPLUSFLAGS = "-DLGI_COCOA=1"; SDKROOT = macosx; }; name = Release; }; 3477C2301CBEFD940028B84B /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "c++14"; CLANG_ENABLE_OBJC_ARC = NO; CLANG_ENABLE_OBJC_WEAK = YES; CODE_SIGN_ENTITLEMENTS = Scribe/Scribe.entitlements; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 21; ENABLE_HARDENED_RUNTIME = YES; GCC_C_LANGUAGE_STANDARD = c11; HEADER_SEARCH_PATHS = ( ../Code, ../Code/Sqlite, ../Resources, ../Utils/Tables, ../../libs/libchardet/include, ../../libs/libchardet/src, "../../libs/aspell-0.60.6.1/interfaces/cc", "../../libs/build-x64/libpng", ../../../lgi/trunk/include, ../../../lgi/trunk/private/common, ../../../lgi/trunk/include/lgi/mac/cocoa, "../../../../codelib/libjpeg-9a", ../../../../codelib/libpng, "/usr/local/Cellar/openssl@1.1/1.1.1q/include", /opt/local/libexec/openssl11/include, ); INFOPLIST_FILE = Scribe/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; OTHER_CFLAGS = ( "-D__NCURSES_H=1", "-DNCURSES_UNCTRL_H_incl=1", ); OTHER_CODE_SIGN_FLAGS = "--timestamp"; OTHER_CPLUSPLUSFLAGS = ( "-DLGI_COCOA=1", "-DMAC", "-DSCRIBE_APP", "-DPOSIX", "-D_DEBUG", ); PRODUCT_BUNDLE_IDENTIFIER = com.memecode.scribe; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; STRIP_INSTALLED_PRODUCT = NO; WARNING_CFLAGS = "-Wno-nullability-completeness"; }; name = Debug; }; 3477C2311CBEFD940028B84B /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "c++14"; CLANG_ENABLE_OBJC_ARC = NO; CLANG_ENABLE_OBJC_WEAK = YES; CODE_SIGN_ENTITLEMENTS = Scribe/Scribe.entitlements; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 21; ENABLE_HARDENED_RUNTIME = YES; GCC_C_LANGUAGE_STANDARD = c11; HEADER_SEARCH_PATHS = ( ../Code, ../Code/Sqlite, ../Resources, ../Utils/Tables, ../../libs/libchardet/include, ../../libs/libchardet/src, "../../libs/aspell-0.60.6.1/interfaces/cc", "../../libs/build-x64/libpng", ../../../lgi/trunk/include, ../../../lgi/trunk/private/common, ../../../lgi/trunk/include/lgi/mac/cocoa, "../../../../codelib/libjpeg-9a", ../../../../codelib/libpng, "/usr/local/Cellar/openssl@1.1/1.1.1q/include", /opt/local/libexec/openssl11/include, ); INFOPLIST_FILE = Scribe/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; OTHER_CFLAGS = ( "-D__NCURSES_H=1", "-DNCURSES_UNCTRL_H_incl=1", ); OTHER_CODE_SIGN_FLAGS = "--timestamp"; OTHER_CPLUSPLUSFLAGS = ( "-DLGI_COCOA=1", "-DMAC", "-DSCRIBE_APP", "-DPOSIX", ); PRODUCT_BUNDLE_IDENTIFIER = com.memecode.scribe; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; STRIP_INSTALLED_PRODUCT = NO; WARNING_CFLAGS = "-Wno-nullability-completeness"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 3477C2191CBEFD940028B84B /* Build configuration list for PBXProject "Scribe" */ = { isa = XCConfigurationList; buildConfigurations = ( 3477C22D1CBEFD940028B84B /* Debug */, 3477C22E1CBEFD940028B84B /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 3477C22F1CBEFD940028B84B /* Build configuration list for PBXNativeTarget "Scribe" */ = { isa = XCConfigurationList; buildConfigurations = ( 3477C2301CBEFD940028B84B /* Debug */, 3477C2311CBEFD940028B84B /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 3477C2161CBEFD940028B84B /* Project object */; }