diff --git a/Code/Components.cpp b/Code/Components.cpp --- a/Code/Components.cpp +++ b/Code/Components.cpp @@ -1,808 +1,810 @@ /* Missing capabilities and their installation: 1) An instance of a LCapabilityClient will receive a call to it's NeedsCapability function. e.g. The parent class of ScribeAccount (LCapabilityClient) calls into the ScribeWnd::NeedsCapability (a virtual function of the parent LCapabilityTarget class). 2) ScribeWnd::NeedsCapability puts up a MissingCapsBar to show the user what is happening. 3) The user clicks one of the action buttons and the MissingCapsBar calls CapabilityInstaller::StartAction which is implemented in ScribeWnd::StartAction. 4) If the action is to install some component the CapabilityInstaller::StartAction method is called to begin the process of querying the memecode site for suitable downloads and then downloading the files. 5) That function creates a CapabilityInstallerPriv::InstallJob instance with the capability to install. This is run in a worker thread. 6) CapabilityInstallerPriv::Main loops through all the jobs, queries the memecode site for downloads, and then initiates the HTTP downloads to a temporary folder. 7) If the output folder is writable it just moves the files straight to the destination folder. Otherwise it will call the Updater.exe to move the files, which will request admin permissions as part of it's manifest. 8) Finally an M_UPDATE message is posted back to the MissingCapsBar::OnEvent method which sets the final message and then creates a timer to hide itself. 9) After the timer expires the LCapabilityTarget::OnCloseInstaller (implemented in ScribeWnd::OnCloseInstaller) is called to delete the MissingCapsBar. */ #include "lgi/common/Lgi.h" #include "lgi/common/Button.h" #include "lgi/common/Http.h" #include "lgi/common/DisplayString.h" #include "Scribe.h" #include "Components.h" #include "resdefs.h" -#define MISSING_CAPS_BAR_COUNTDOWN 15 // seconds +#define MISSING_CAPS_BAR_COUNTDOWN 5 // seconds #define MISSING_ACTION_BASE 100 struct MissingCapsBarPriv { LColour Back; int CountDown; LString::Array Msg; LArray Strs; MissingCapsBarPriv() : Back(0xd2, 0x40, 0x40) { CountDown = -1; } ~MissingCapsBarPriv() { Strs.DeleteObjects(); } void SetMsg(const char *m) { Msg.Empty(); Msg = LString(m).SplitDelimit("\n"); Strs.DeleteObjects(); } }; MissingCapsBar::MissingCapsBar( LCapabilityTarget *owner, LCapabilityTarget::CapsHash *a, const char *msg, CapabilityInstaller *inst, LArray &actions, LColour *background) { d = new MissingCapsBarPriv(); Owner = owner; Installer = inst; Caps = a; if (background) d->Back = *background; d->SetMsg(msg); int ContentY = (int)d->Msg.Length() * LSysFont->GetHeight(); for (unsigned i=0; iBack.c32()); b->GetCss(true)->NoPaintColor(Bk); ContentY = MAX(ContentY, b->Y()); if (i == actions.Length() - 1) b->Default(true); Btns.Add(b); AddView(b); } } LRect r(0, 0, 200, ContentY + 8); SetPos(r); } MissingCapsBar::~MissingCapsBar() { if (Progress) { if (Progress->Lock(_FL)) { Progress->Ui = NULL; Progress->Unlock(); Progress->DecRef(); } else LAssert(0); Progress = NULL; } DeleteObj(d); } void MissingCapsBar::Empty() { Actions.Length(0); Btns.DeleteObjects(); } void MissingCapsBar::SetMsg(const char *m) { d->SetMsg(m); Invalidate(); } void MissingCapsBar::OnCreate() { AttachChildren(); SetPulse(1000); } void MissingCapsBar::OnPosChange() { LRect b = GetClient(); int x = b.x2 - 4; for (ssize_t i=Btns.Length()-1; i>=0; i--) { LButton *Btn = Btns[i]; LRect r = Btn->GetPos(); r.x2 = x; r.x1 = x - Btn->X() + 1; x = r.x1 - 4; int Px = (b.Y() - Btn->Y()) >> 1; r.y1 = Px; r.y2 = Px + Btn->Y() - 1; Btn->SetPos(r); } if (ProgCtrl) { LRect r = ProgCtrl->GetPos(); r.Offset((x - r.X()) - r.x1, ((b.Y() - r.Y()) / 2) - r.y1); ProgCtrl->SetPos(r); } } void MissingCapsBar::OnPaint(LSurface *pDC) { LRect c = GetClient(); pDC->Colour(d->Back); pDC->Rectangle(); if (d->Msg.Length()) { if (!d->Strs.Length()) { for (unsigned i=0; iMsg.Length(); i++) { d->Strs.Add(new LDisplayString(LSysFont, d->Msg[i])); } } LSysFont->Transparent(true); LSysFont->Colour(LColour(255, 255, 255), d->Back); int Px = Y() - (LSysFont->GetHeight() * (int)d->Msg.Length()); Px >>= 1; for (unsigned i=0; iStrs.Length(); i++) { d->Strs[i]->Draw(pDC, 10, Px + (i * LSysFont->GetHeight()), &c); } } } bool MissingCapsBar::Pour(LRegion &r) { LRect *p = FindLargest(r); if (!p) return false; LRect rc = *p; rc.y2 = rc.y1 + Y() - 1; SetPos(rc); return true; } int MissingCapsBar::OnNotify(LViewI *c, LNotification n) { if (c->GetId() == IDOK) { Detach(); Owner->OnCloseInstaller(); delete this; } else if (c->GetId() >= MISSING_ACTION_BASE) { int Idx = c->GetId() - MISSING_ACTION_BASE; #if DEBUG_CAPABILITIES LgiTrace("%s:%i - Idx=%i/%i\n", _FL, Idx, Btns.Length()); #endif if (Idx < (int)Btns.Length()) { c = Btns[Idx]; const char *Action = c->Name(); if ((Progress = Installer->StartAction(this, Caps, Action))) { #if DEBUG_CAPABILITIES LgiTrace("%s:%i - StartAction=%p\n", _FL, Idx, Progress); #endif c->Enabled(false); } else { #if DEBUG_CAPABILITIES LgiTrace("%s:%i - StartAction failed\n", _FL, Idx, Progress); #endif Detach(); Owner->OnCloseInstaller(); delete this; } } } return 0; } void MissingCapsBar::OnPulse() { if (Progress) { bool HasProg = Progress->TotalSize > 0 && !Progress->Finished; bool HasCtrl = ProgCtrl != NULL; if (HasProg ^ HasCtrl) { if (HasProg) { // Create ctrl ProgCtrl = new LProgressView(-1, 0, 0, 150, 12, NULL); ProgCtrl->SetRange(Progress->TotalSize); ProgCtrl->Attach(this); } else { DeleteObj(ProgCtrl); } OnPosChange(); } if (ProgCtrl) { ProgCtrl->Value(Progress->CurrentPos); } } if (d->CountDown > 0) { d->CountDown--; if (d->CountDown <= 0) { // This detach means the LWindow::Pour won't alloc space for the MissingCapsBar Detach(); // This will remove the ownering windows ptr to us and repour the window. Owner->OnCloseInstaller(); // We can now RIP delete this; } } } LMessage::Param MissingCapsBar::OnEvent(LMessage *m) { switch (m->Msg()) { case M_UPDATE: { if (Progress && Progress->Lock(_FL)) { bool Finished = !IsFinished && Progress->Finished; bool HasError = Progress->HasError; if (Progress->Msg) d->SetMsg(Progress->Msg); Progress->Msg.Empty(); Progress->Unlock(); Invalidate(); if (Finished) { IsFinished = true; if (HasError) { Btns.DeleteObjects(); auto Ok = new LButton(IDOK, 0, 0, -1, -1, LLoadString(IDS_OK)); Btns.Add(Ok); Ok->Attach(this); OnPosChange(); } - - // Is the case that the caller doesn't delete us then setup - // a count down to automatically remove the install bar. - d->CountDown = MISSING_CAPS_BAR_COUNTDOWN; + else + { + // Is the case that the caller doesn't delete us then setup + // a count down to automatically remove the install bar. + d->CountDown = MISSING_CAPS_BAR_COUNTDOWN; + } if (ProgCtrl) ProgCtrl->Value(Progress->TotalSize); if (Progress->Lock(_FL)) { Progress->Ui = NULL; Progress->Unlock(); } Progress->DecRef(); Progress = NULL; // Tell the owner Owner->OnInstall(Caps, !HasError); // We _may_ get deleted here... so we can't call our parents "OnEvent" return 0; } } break; } } return LLayout::OnEvent(m); } class ProgressSocket : public LSocket { int64 *Prog; public: ProgressSocket(int64 *p) { Prog = p; } ssize_t Read( void *Data, ssize_t Len, int Flags) { ssize_t r = LSocket::Read(Data, Len, Flags); if (Prog && r > 0) *Prog += r; return r; } }; /////////////////////////////////////////////////////////////////////////////////// class CapabilityInstallerPriv : public LMutex, public LThread { public: struct InstallJob { InstallProgress *Prog; LAutoString Component; }; private: LAutoString TempFolder; LArray Jobs; bool Loop; public: LString Uri, Proxy, App, Version; CapabilityInstallerPriv(const char *TmpFolder) : LMutex("ComponentInstaller.Mutex"), LThread("ComponentInstaller.Thread") { TempFolder.Reset(NewStr(TmpFolder)); Loop = true; } ~CapabilityInstallerPriv() { Loop = false; while (!IsExited()) LSleep(1); Jobs.DeleteObjects(); } void Msg(InstallProgress *Prog, const char *Fmt, ...) { char buffer[512]; va_list arg; va_start(arg, Fmt); vsprintf_s(buffer, sizeof(buffer), Fmt, arg); va_end(arg); LgiTrace("CapabilityInstaller: %s\n", buffer); if (Prog->Lock(_FL)) { Prog->Msg = buffer; if (Prog->Ui) Prog->Ui->PostEvent(M_UPDATE); Prog->Unlock(); } } void AddJob(InstallJob *j) { if (Lock(_FL)) { Jobs.Add(j); Unlock(); } } int Main() { while (Loop) { InstallJob *j = NULL; if (Lock(_FL)) { if (Jobs.Length()) { j = Jobs[0]; Jobs.DeleteAt(0, true); } Unlock(); } if (j) { bool Success = false; char OutputPath[MAX_PATH_LEN]; #ifdef MAC LMakePath(OutputPath, sizeof(OutputPath), LGetExeFile(), "Contents/Frameworks"); #else strcpy_s(OutputPath, sizeof(OutputPath), LGetExePath()); #endif Msg(j->Prog, "Getting download site..."); LHttp Http; if (Proxy) { LUri u(Proxy); Http.SetProxy(u.sHost, u.Port?u.Port:HTTP_PORT); } char Url[512]; const char *Os = LGetOsName(); #ifdef _MSC_VER int ch = #endif sprintf_s(Url, sizeof(Url), "%s?os=%s&wordsize=%i&app=%s&version=%s&component=%s", Uri.Get(), Os, (int)(sizeof(NativeInt)<<3), App.Get(), Version.Get(), j->Component.Get()); #ifdef _MSC_VER #if _MSC_VER == _MSC_VER_VS2008 ch += sprintf_s(Url+ch, sizeof(Url)-ch, "&tags=vc9"); #elif _MSC_VER == _MSC_VER_VS2013 ch += sprintf_s(Url+ch, sizeof(Url)-ch, "&tags=vc12"); #elif _MSC_VER == _MSC_VER_VS2015 ch += sprintf_s(Url+ch, sizeof(Url)-ch, "&tags=vc14"); #elif _MSC_VER == _MSC_VER_VS2017 ch += sprintf_s(Url+ch, sizeof(Url)-ch, "&tags=vc15"); #elif _MSC_VER >= _MSC_VER_VS2019 ch += sprintf_s(Url+ch, sizeof(Url)-ch, "&tags=vc16"); #else #error "Impl me." #endif #endif #if DEBUG_CAPABILITIES LgiTrace("%s:%i - ComponentURL: %s\n", _FL, Url); #endif LStringPipe Xml; int Status = 0; LUri u(Url); LAutoPtr Sock(new LSocket); Sock->SetTimeout(15 * 1000); if (!Http.Open(Sock, u.sHost, u.Port?u.Port:HTTP_PORT)) Msg(j->Prog, "Error: connection to %s failed.", u.sHost.Get()); else { if (!Http.Get(Url, NULL, &Status, &Xml, NULL)) Msg(j->Prog, "Error: HTTP get failed."); else { if (Status != 200) Msg(j->Prog, "Error: HTTP status %i", Status); else { LXmlTree t; LXmlTag r; if (!t.Read(&r, &Xml)) Msg(j->Prog, "Error: XML parsing error."); else { if (!r.IsTag("components")) Msg(j->Prog, "Error: Unexpected XML."); else { // Collect list of files to download LArray InFiles, TmpFiles; int64 TotalBytes = 0; for (auto c: r.Children) { if (c->IsTag("file") && c->GetContent()) { InFiles.Add(c->GetContent()); int Size = c->GetAsInt("size"); if (Size > 0) TotalBytes += Size; } } if (InFiles.Length() == 0) { Msg(j->Prog, "Error: No downloads available (%s)", Url); } else { j->Prog->CurrentPos = 0; j->Prog->TotalSize = TotalBytes; Msg(j->Prog, "Downloading..."); for (unsigned i=0; iProg, "Error: No filename in download URL."); break; } File++; char OutPath[MAX_PATH_LEN]; LMakePath(OutPath, sizeof(OutPath), TempFolder, File); if (!Out.Open(OutPath, O_WRITE)) { Msg(j->Prog, "Error: Can't open '%s' for writing.", OutPath); break; } Sock.Reset(new ProgressSocket(&j->Prog->CurrentPos)); if (!Http.Open(Sock, u.sHost, u.Port?u.Port:HTTP_PORT)) { Msg(j->Prog, "Error: Can't connect to '%s'.", u.sHost.Get()); break; } if (!Http.Get(InFiles[i], NULL, &Status, &Out, NULL)) { Msg(j->Prog, "Error: HTTP get failed."); break; } if (Status == 200) { TmpFiles.Add(NewStr(OutPath)); } else { Msg(j->Prog, "Error: Download component '%s' failed with HTTP %i.", InFiles[i], Status); Out.Close(); FileDev->Delete(OutPath, false); } } if (TmpFiles.Length() == InFiles.Length()) { // Check to see if the install folder is writable... char p[MAX_PATH_LEN]; LMakePath(p, sizeof(p), OutputPath, "write_test.txt"); LFile f; bool Writable = f.Open(p, O_WRITE) != 0; if (Writable) { f.Close(); FileDev->Delete(p, false); } if (Writable) { // We can move them into place ourself... int Copied = 0; for (unsigned i=0; iCopy(t, p, &CopyStatus)) { Copied++; FileDev->Delete(t, false); } else { Msg(j->Prog, "Error: File copy failed."); LgiTrace("%s:%i - Copy '%s' failed during install component: %i (%s)\n", _FL, l, CopyStatus.GetCode(), CopyStatus.GetMsg().Get()); } } } if (Copied == TmpFiles.Length()) { Msg(j->Prog, "Component download completed."); Success = true; } } else { // Use privledge escalation to install the files... #ifdef WIN32 char Updater[MAX_PATH_LEN]; LMakePath(Updater, sizeof(Updater), OutputPath, "Updater.exe"); if (LFileExists(Updater)) { LStringPipe p; for (unsigned i=0; iProg, "Component download completed."); Success = true; } else Msg(j->Prog, "Error: Updater component returned an error."); } else { Msg(j->Prog, "Error: Failed to run the updater component."); } } else { Msg(j->Prog, "Error: Updater '%s' missing.", Updater); } #else LAssert(!"Impl privledge escalation for installing software."); #endif } if (Success && LFontSystem::Inst()) LFontSystem::Inst()->ResetLibCheck(); } } } } } } } #if defined(_DEBUG) && !DEBUG_CAPABILITIES if (!Success) LgiTrace("%s:%i - ComponentURL: %s\n", _FL, Url); #endif if (j->Prog->Lock(_FL)) { j->Prog->Finished = true; j->Prog->HasError = !Success; if (j->Prog->Ui) j->Prog->Ui->PostEvent(M_UPDATE); j->Prog->Unlock(); } j->Prog->DecRef(); DeleteObj(j); } else LSleep(200); } return 0; } }; CapabilityInstaller::CapabilityInstaller(const char *App, const char *Version, const char *Uri, const char *TmpPath, const char *Proxy) { d = new CapabilityInstallerPriv(TmpPath); d->App = App; d->Version = Version; d->Proxy = Proxy; d->Uri = Uri; d->Run(); } CapabilityInstaller::~CapabilityInstaller() { DeleteObj(d); } InstallProgress *CapabilityInstaller::StartAction(MissingCapsBar *Bar, LCapabilityTarget::CapsHash *Components, const char *Action) { if (!Components || !Action) { LgiTrace("%s:%i - CapabilityInstaller::StartAction invalid params\n", _FL); return NULL; } InstallProgress *p = NULL; if (d->Lock(_FL)) { if (!d->Proxy) d->Proxy = GetHttpProxy(); p = new InstallProgress; if (p) { p->IncRef(); // for the UI view... p->Ui = Bar; for (auto k : *Components) { CapabilityInstallerPriv::InstallJob *j = new CapabilityInstallerPriv::InstallJob; j->Prog = p; p->IncRef(); // The job owns a reference... j->Component.Reset(NewStr(k.key)); #if DEBUG_CAPABILITIES LgiTrace("%s:%i - AddJob(%s)\n", _FL, k); #endif d->AddJob(j); } } else { LgiTrace("%s:%i - Alloc failed.\n", _FL); } d->Unlock(); } else { LgiTrace("%s:%i - Couldn't lock.\n", _FL); } return p; } diff --git a/Code/PreviewPanel.cpp b/Code/PreviewPanel.cpp --- a/Code/PreviewPanel.cpp +++ b/Code/PreviewPanel.cpp @@ -1,985 +1,988 @@ /* ** 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); } LString GetIncludeFile(const char *FileName) override { return NULL; } 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) 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) 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) 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) {} LHostFunc *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) 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) 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; } }; LHostFunc Cmds[] = { LHostFunc("getElementById", "", (ScriptCmd)&LPreviewPanelPrivate::getElementById), LHostFunc("encodeURI", "", (ScriptCmd)&LPreviewPanelPrivate::encodeURI), LHostFunc("trace", "", (ScriptCmd)&LPreviewPanelPrivate::trace), LHostFunc(0, 0, 0) }; LHostFunc *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) { + LFontSystem::Inst()->ResetLibCheck(); + 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) { - if (d->Item == item) + if (d->Item == item && !ChangeEvent) return; 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; }