diff --git a/Code/Scribe.h b/Code/Scribe.h --- a/Code/Scribe.h +++ b/Code/Scribe.h @@ -1,2544 +1,2542 @@ /*hdr ** FILE: Scribe.h ** AUTHOR: Matthew Allen ** DATE: 22/10/97 ** DESCRIPTION: Scribe email application ** ** Copyright (C) 1998-2003 Matthew Allen ** fret@memecode.com */ // Includes #include #include #include "lgi/common/Lgi.h" #include "lgi/common/DragAndDrop.h" #include "lgi/common/DateTime.h" #include "lgi/common/Password.h" #include "lgi/common/vCard-vCal.h" #include "lgi/common/WordStore.h" #include "lgi/common/SharedMemory.h" #include "lgi/common/XmlTreeUi.h" #include "lgi/common/Mime.h" #include "lgi/common/OptionsFile.h" #include "lgi/common/TextLog.h" #include "lgi/common/Menu.h" #include "lgi/common/ToolBar.h" #include "lgi/common/Combo.h" #include "lgi/common/Printer.h" // Gui controls #include "lgi/common/Panel.h" #include "lgi/common/DocView.h" #include "lgi/common/List.h" #include "lgi/common/Tree.h" #include "lgi/common/ListItemCheckBox.h" // Storage #include "lgi/common/Store3.h" // App Includes #include "ScribeInc.h" #include "ScribeUtils.h" #include "ScribeDefs.h" #include "DomType.h" class ListAddr; // The field definition type struct ItemFieldDef { const char *DisplayText; ScribeDomType Dom; LVariantType Type; int FieldId; // Was 'Id' int CtrlId; const char *Option; bool UtcConvert; }; //////////////////////////////////////////////////////////////////////////////////////////// // Classes class MailTree; class LMailStore; class ScribeWnd; class Thing; class Mail; class Contact; class ThingUi; class MailUi; class ScribeFolder; class ContactUi; class FolderPropertiesDlg; class ScribeAccount; class Filter; class Attachment; class Calendar; class CalendarSource; class AttachmentList; struct ItemFieldDef; class FolderDlg; class Filter; class ContactGroup; class ScribeBehaviour; class AccountletThread; class ThingList; class LSpellCheck; //////////////////////////////////////////////////////////////////////// // Scripting support #include "lgi/common/Scripting.h" /// Script callback types. See 'api.html' in the Scripts folder for more details. enum LScriptCallbackType { LCallbackNull, LToolsMenu, LThingContextMenu, LThingUiToolbar, LApplicationToolbar, LMailOnBeforeSend, // "OnBeforeMailSend" LMailOnAfterReceive, LBeforeInstallBar, LInstallComponent, LFolderContextMenu, LOnTimer, LRenderMail, LOnLoad }; struct LScript; struct LScriptCallback { LScriptCallbackType Type = LCallbackNull; LScript *Script = NULL; LFunctionInfo *Func = NULL; int Param = 0; double fParam = 0.0; LVariant Data; uint64 PrevTs = 0; bool OnSecond = false; }; struct LScript { LAutoPtr Code; LArray Callbacks; }; typedef void (*ConsoleClosingCallback)(class LScriptConsole *Console, void *user_data); class LScriptConsole : public LWindow { ScribeWnd *App; LTextLog *Txt; ConsoleClosingCallback Callback; void *CallbackData; bool OnViewKey(LView *v, LKey &k); public: LScriptConsole(ScribeWnd *app, ConsoleClosingCallback callback, void *callback_data); ~LScriptConsole(); void Write(const char *s, int64 Len); bool OnRequestClose(bool OsShuttingDown); }; /// This class is a wrapper around a user interface element used for /// Scripting. The script engine needs to be able to store information /// pertaining to the menu item's callbacks along with the sub menu. class LScriptUi : public LDom { public: LScriptUi *Parent; LSubMenu *Sub; LToolBar *Toolbar; LArray Callbacks; LArray Subs; LScriptUi() { Parent = 0; Sub = 0; Toolbar = 0; } LScriptUi(LSubMenu *s) { Parent = 0; Toolbar = 0; Sub = s; } LScriptUi(LToolBar *t) { Parent = 0; Toolbar = t; Sub = 0; } ~LScriptUi() { Subs.DeleteObjects(); } bool GetVariant(const char *Name, LVariant &Value, const char *Arr = NULL) override { if (Sub) return Sub->GetVariant(Name, Value, Arr); LDomProperty Method = LStringToDomProp(Name); if (Method == ObjLength) { if (Toolbar) Value = (int64)Toolbar->Length(); } else return false; return true; } bool CallMethod(const char *MethodName, LVariant *ReturnValue, LArray &Args) override { if (Sub) return Sub->CallMethod(MethodName, ReturnValue, Args); return false; } bool SetupCallbacks(ScribeWnd *App, ThingUi *Parent, Thing *t, LScriptCallbackType Type); bool ExecuteCallbacks(ScribeWnd *App, ThingUi *Parent, Thing *t, int Cmd); }; class LScribeScript : public LScriptContext { LScriptEngine *Eng; struct LScribeScriptPriv *d; public: ScribeWnd *App; static LScribeScript *Inst; LScribeScript(ScribeWnd *app); ~LScribeScript(); void ShowScriptingWindow(bool show); LAutoString GetDataFolder(); LStream *GetLog(); GHostFunc *GetCommands(); char *GetIncludeFile(char *FileName); // System void SetEngine(LScriptEngine *eng); bool MsgBox(LScriptArguments &Args); // Paths bool GetSystemPath(LScriptArguments &Args); bool GetScribeTempPath(LScriptArguments &Args); bool JoinPath(LScriptArguments &Args); // Folders bool GetFolder(LScriptArguments &Args); bool GetSourceFolders(LScriptArguments &Args); bool CreateSubFolder(LScriptArguments &Args); bool LoadFolder(LScriptArguments &Args); bool FolderSelect(LScriptArguments &Args); bool BrowseFolder(LScriptArguments &Args); // Things bool CreateThing(LScriptArguments &Args); bool MoveThing(LScriptArguments &Args); bool SaveThing(LScriptArguments &Args); bool DeleteThing(LScriptArguments &Args); bool ShowThingWindow(LScriptArguments &Args); bool FilterDoActions(LScriptArguments &Args); bool LookupContact(LScriptArguments &Args); // Callbacks bool AddToolsMenuItem(LScriptArguments &Args); bool AddCallback(LScriptArguments &Args); // UI bool MenuAddItem(LScriptArguments &Args); bool MenuAddSubmenu(LScriptArguments &Args); bool ToolbarAddItem(LScriptArguments &Args); }; //////////////////////////////////////////////////////////////////////// class ScribePassword { class ScribePasswordPrivate *d; public: ScribePassword(LOptionsFile *p, const char *opt, int check, int pwd, int confirm); ~ScribePassword(); bool IsOk(); bool Load(LView *dlg); bool Save(); void OnNotify(LViewI *c, LNotification &n); }; class ChooseFolderDlg : public LDialog { ScribeWnd *App; LEdit *Folder; int Type; bool Export; LList *Lst; void InsertFile(const char *f); public: LString DestFolder; LString::Array SrcFiles; ChooseFolderDlg ( ScribeWnd *parent, bool IsExport, const char *Title, const char *Msg, char *DefFolder = NULL, int FolderType = MAGIC_MAIL, LString::Array *Files = NULL ); int OnNotify(LViewI *Ctrl, LNotification n); }; #define IoProgressImplArgs LAutoPtr stream, const char *mimeType, IoProgressCallback cb #define IoProgressFnArgs IoProgressImplArgs = NULL #define IoProgressError(err) \ { \ IoProgress p(Store3Error, err); \ if (cb) cb(&p, stream); \ return p; \ } #define IoProgressSuccess() \ { \ IoProgress p(Store3Success); \ if (cb) cb(&p, stream); \ return p; \ } #define IoProgressNotImpl() \ { \ IoProgress p(Store3NotImpl); \ if (cb) cb(&p, stream); \ return p; \ } class ScribeClass ThingType : public LDom, public LDataUserI { bool Dirty = false; bool WillDirty = true; bool Loaded = false; protected: bool GetDirty() { return Dirty; } bool OnError(const char *File, int Line) { _lgi_assert(false, "Object Missing", File, Line); return false; } // Callbacks struct ThingEventInfo { const char *File = NULL; int Line = 0; std::function Callback; }; LArray OnLoadCallbacks; public: struct IoProgress; typedef std::function IoProgressCallback; struct IoProgress { // This is the main result to look at: // Store3NotImpl - typically means the mime type is wrong. // Store3Error - an error occured. // Store3Delayed - means the operation will take a long time. // However progress is report via 'prog' if not NULL. // And the 'onComplete' handler will be called at the end. // Store3Success - the operation successfully completed. Store3Status status = Store3NotImpl; // Optional progress for the operation. Really only relevant for // status == Store3Delayed. Progress *prog = NULL; // Optional error message for the operation. Relevant if // status == Store3Error. LString errMsg; IoProgress(Store3Status s, const char *err = NULL) { status = s; if (err) errMsg = err; } operator bool() { return status > Store3Error; } }; template LAutoPtr AutoCast(LAutoPtr ap) { return LAutoPtr(ap.Release()); } static LArray DirtyThings; ScribeWnd *App = NULL; ThingType(); virtual ~ThingType(); virtual Store3ItemTypes Type() { return MAGIC_NONE; } virtual bool SetDirty(bool b = true); void SetWillDirty(bool c) { WillDirty = c; } virtual bool Save(ScribeFolder *Into) { return false; } virtual void OnProperties(int Tab = -1) {} virtual ScribeFolder *GetFolder() = 0; virtual Store3Status SetFolder(ScribeFolder *f, int Param = -1) = 0; virtual bool IsPlaceHolder() { return false; } // Events void WhenLoaded(const char *file, int line, std::function Callback, int index = -1); bool IsLoaded(int Set = -1); // Printing virtual void OnPrintHeaders(struct ScribePrintContext &Context) { LAssert(!"Impl me."); } virtual void OnPrintText(ScribePrintContext &Context, LPrintPageRanges &Pages) { LAssert(!"Impl me."); } virtual int OnPrintHtml(ScribePrintContext &Context, LPrintPageRanges &Pages, LSurface *RenderedHtml) { LAssert(!"Impl me."); return 0; } }; class MailContainerIter; class ScribeClass MailContainer { friend class MailContainerIter; List Iters; public: virtual ~MailContainer(); virtual size_t Length() { return 0; } virtual ssize_t IndexOf(Mail *m) { return -1; } virtual Mail *operator [](size_t i) { return NULL; } }; class ScribeClass MailContainerIter { friend class MailContainer; protected: MailContainer *Container; public: MailContainerIter(); ~MailContainerIter(); void SetContainer(MailContainer *c); }; class ScribeClass ThingStorage // External storage information { public: int Data; ThingStorage() { Data = 0; } virtual ~ThingStorage() {} }; class ThingUi; class ScribeClass Thing : public ThingType, public LListItem, public LDragDropSource, public LRefCount { friend class ScribeWnd; friend class ScribeFolder; ScribeFolder *_ParentFolder = NULL; protected: LArray FieldArray; LAutoString DropFileName; // This structure allows the app to move objects between // mail stores. After a delayed write to the new mail store // the old item needs to be removed. This keeps track of // where that old item is. Don't assume that the Obj pointer // is valid... check in the folder's items first. struct ThingReference { LString Path; Thing *Obj; ThingReference() { Obj = NULL; } } DeleteOnAdd; public: ThingStorage *Data = NULL; Thing(ScribeWnd *app, LDataI *object = 0); ~Thing(); // Dom bool CallMethod(const char *MethodName, LVariant *ReturnValue, LArray &Args) override; // D'n'd bool GetData(LArray &Data) override; bool GetFormats(LDragFormats &Formats) override; bool OnBeginDrag(LMouse &m) override; // Import / Export virtual bool GetFormats(bool Export, LString::Array &MimeTypes) { return false; } // Anything implementing 2 functions should mostly be using one of these // to "return" an IoProgress and process any callback: // IoProgressError(msg) // IoProgressNotImpl() // IoProgressSuccess() virtual IoProgress Import(IoProgressFnArgs) = 0; virtual IoProgress Export(IoProgressFnArgs) = 0; /// This exports all the selected items void ExportAll(LViewI *Parent, const char *ExportMimeType, std::function Callback); // UI bool OnKey(LKey &k) override; // Thing ScribeFolder *GetFolder() override { return _ParentFolder; } void SetParentFolder(ScribeFolder *f); Store3Status SetFolder(ScribeFolder *f, int Param = -1) override; LDataI *DefaultObject(LDataI *arg = 0); virtual ThingUi *DoUI(MailContainer *c = 0) { return NULL; } virtual ThingUi *GetUI() { return 0; } virtual bool SetUI(ThingUi *ui = 0) { return false; } virtual uint32_t GetFlags() { return 0; } virtual void OnCreate() override; virtual bool OnDelete(); virtual int *GetDefaultFields() { return 0; } virtual const char *GetFieldText(int Field) { return 0; } virtual void DoContextMenu(LMouse &m, LView *Parent = 0) {} virtual Thing &operator =(Thing &c) { LAssert(0); return *this; } virtual char *GetDropFileName() = 0; virtual bool GetDropFiles(LString::Array &Files) { return false; } virtual void OnSerialize(bool Write) {} // Interfaces virtual Mail *IsMail() { return 0; } virtual Contact *IsContact() { return 0; } virtual ContactGroup *IsGroup() { return 0; } virtual Filter *IsFilter() { return 0; } virtual Attachment *IsAttachment() { return 0; } virtual Calendar *IsCalendar() { return 0; } void SetFieldArray(LArray &i) { FieldArray = i; } void OnMove(); bool SetField(int Field, int n); bool SetField(int Field, double n); bool SetField(int Field, char *n); bool SetField(int Field, LDateTime &n); bool GetField(int Field, int &n); bool GetField(int Field, double &n); bool GetField(int Field, const char *&n); bool GetField(int Field, LDateTime &n); bool DeleteField(int Field); }; class ThingUi : public LWindow { friend class MailUiGpg; bool _Dirty; char *_Name; protected: Thing *_Item; bool _Running; void SetItem(Thing *i) { _Item = i; } public: ScribeWnd *App; static LArray All; ThingUi(Thing *item, const char *name); ~ThingUi(); virtual bool SetDirty(bool d, bool ui = true); bool IsDirty() { return _Dirty; } bool OnRequestClose(bool OsShuttingDown); bool OnViewKey(LView *v, LKey &k); virtual void OnDirty(bool Dirty) {} virtual void OnLoad() = 0; virtual void OnSave() = 0; virtual void OnChange() {} virtual AttachmentList *GetAttachments() { return 0; } virtual bool AddRecipient(AddressDescriptor *Addr) { return false; } }; class ThingFilter { public: virtual bool TestThing(Thing *Thing) = 0; }; class ScribeClass Attachment : public Thing { friend class Mail; protected: Mail *Msg; Mail *Owner; bool IsResizing; LString Buf; // D'n'd LAutoString DropSourceFile; bool GetFormats(LDragFormats &Formats) override; bool GetData(LArray &Data) override; void _New(LDataI *object); public: enum Encoding { OCTET_STREAM, PLAIN_TEXT, BASE64, QUOTED_PRINTABLE, }; Attachment(ScribeWnd *App, Attachment *import = 0); Attachment(ScribeWnd *App, LDataI *object, const char *import = 0); ~Attachment(); bool ImportFile(const char *FileName); bool ImportStream(const char *FileName, const char *MimeType, LAutoStreamI Stream); Thing &operator =(Thing &c) override; LDATA_INT64_PROP(Size, FIELD_SIZE); LDATA_STR_PROP(Name, FIELD_NAME); LDATA_STR_PROP(MimeType, FIELD_MIME_TYPE); LDATA_STR_PROP(ContentId, FIELD_CONTENT_ID); LDATA_STR_PROP(Charset, FIELD_CHARSET); LDATA_STR_PROP(InternetHeaders, FIELD_INTERNET_HEADER); // LDom support bool GetVariant(const char *Name, LVariant &Value, const char *Array = NULL) override; bool CallMethod(const char *MethodName, LVariant *Ret, LArray &Args) override; void OnOpen(LView *Parent, char *Dest = 0); void OnDeleteAttachment(LView *Parent, bool Ask); void OnSaveAs(LView *Parent); void OnMouseClick(LMouse &m) override; bool OnKey(LKey &k) override; Store3ItemTypes Type() override { return MAGIC_ATTACHMENT; } bool Get(char **ptr, ssize_t *size); bool Set(char *ptr, ssize_t size); bool Set(LAutoStreamI Stream); Attachment *IsAttachment() override { return this; } LAutoString MakeFileName(); bool GetIsResizing(); void SetIsResizing(bool b); bool IsMailMessage(); bool IsVCalendar(); bool IsVCard(); // The owner is the mail that this is attached to Mail *GetOwner() { return Owner; } void SetOwner(Mail *msg); // The msg is the mail that this message/rfc822 attachment is rendered into Mail *GetMsg(); void SetMsg(Mail *m); LStreamI *GotoObject(const char *file, int line); int Sizeof(); bool Serialize(LFile &f, bool Write); IoProgress Import(IoProgressFnArgs) override { return Store3Error; } IoProgress Export(IoProgressFnArgs) override { return Store3Error; } bool SaveTo(char *FileName, bool Quite = false, LView *Parent = 0); const char *GetText(int i) override; char *GetDropFileName() override; bool GetDropFiles(LString::Array &Files) override; }; class ScribeClass Contact : public Thing { friend class ContactUi; protected: class ContactPriv *d = NULL; ContactUi *Ui = NULL; public: static List Everyone; static Contact *LookupEmail(const char *Email); static LHashTbl, int> PropMap; static int DefaultContactFields[]; Contact(ScribeWnd *app, LDataI *object = 0); ~Contact(); LDATA_STR_PROP(First, FIELD_FIRST_NAME); LDATA_STR_PROP(Last, FIELD_LAST_NAME); LDATA_STR_PROP(Email, FIELD_EMAIL); bool Get(const char *Opt, const char *&Value); bool Set(const char *Opt, const char *Value); bool Get(const char *Opt, int &Value); bool Set(const char *Opt, int Value); // operators Thing &operator =(Thing &c) override; Contact *IsContact() override { return this; } // Dom bool GetVariant(const char *Name, LVariant &Value, const char *Array = 0) override; bool SetVariant(const char *Name, LVariant &Value, const char *Array = 0) override; bool CallMethod(const char *MethodName, LVariant *ReturnValue, LArray &Args) override; // Events void OnMouseClick(LMouse &m) override; // Printing void OnPrintHeaders(struct ScribePrintContext &Context) override; void OnPrintText(ScribePrintContext &Context, LPrintPageRanges &Pages) override; // Misc Store3ItemTypes Type() override { return MAGIC_CONTACT; } ThingUi *DoUI(MailContainer *c = 0) override; int Compare(LListItem *Arg, ssize_t Field) override; bool IsAssociatedWith(char *PluginName); char *GetLocalTime(const char *TimeZone = 0); // Email address int GetAddrCount(); LString::Array GetEmails(); LString GetAddrAt(int i); bool HasEmail(LString email); // Serialization size_t SizeofField(const char *Name); size_t Sizeof(); bool Serialize(LFile &f, bool Write); bool Save(ScribeFolder *Into = 0) override; // ListItem const char *GetText(int i) override; int *GetDefaultFields() override; const char *GetFieldText(int Field) override; int GetImage(int Flags = 0) override { return ICON_CONTACT; } // Import/Export bool GetFormats(bool Export, LString::Array &MimeTypes) override; IoProgress Import(IoProgressFnArgs) override; IoProgress Export(IoProgressFnArgs) override; char *GetDropFileName() override; bool GetDropFiles(LString::Array &Files) override; }; #define ContactGroupName "Name" #define ContactGroupList "List" #define ContactGroupDateModified "DateModified" extern ItemFieldDef GroupFieldDefs[]; class ContactGroup : public Thing { friend class GroupUi; class GroupUi *Ui; class ContactGroupPrivate *d; LString DateCache; public: LDateTime UsedTs; LDATA_STR_PROP(Name, FIELD_GROUP_NAME); ContactGroup(ScribeWnd *app, LDataI *object = 0); ~ContactGroup(); // operators Thing &operator =(Thing &c) override; ContactGroup *IsGroup() override { return this; } // Dom bool GetVariant(const char *Name, LVariant &Value, const char *Array = NULL) override; bool CallMethod(const char *MethodName, LVariant *ReturnValue, LArray &Args) override; // Events void OnMouseClick(LMouse &m) override; void OnSerialize(bool Write) override; // Misc Store3ItemTypes Type() override { return MAGIC_GROUP; } ThingUi *DoUI(MailContainer *c = 0) override; int Compare(LListItem *Arg, ssize_t Field) override; IoProgress Import(IoProgressFnArgs) override { return Store3NotImpl; } IoProgress Export(IoProgressFnArgs) override { return Store3NotImpl; } bool GetAddresses(List &a); LString::Array GetAddresses(); char *GetDropFileName() override; bool GetDropFiles(LString::Array &Files) override; // Serialization bool Save(ScribeFolder *Into = 0) override; // ListItem const char *GetText(int i) override; int *GetDefaultFields() override; const char *GetFieldText(int Field) override; int GetImage(int Flags = 0) override { return ICON_CONTACT_GROUP; } }; struct LGroupMapArray : public LArray { LString toString() { LString::Array a; for (auto i: *this) a.Add(i->GetName()); return LString(",").Join(a); } }; class LGroupMap : public LHashTbl,LGroupMapArray*> { ScribeWnd *App; void Index(ContactGroup *grp); public: LGroupMap(ScribeWnd *app); ~LGroupMap(); }; ///////////////////////////////////////////////////////////// // Mail threading // // See: http://www.jwz.org/doc/threading.html struct ThingSortParams { int SortAscend; int SortField; }; class MContainer { int Lines; int Depth; bool Open; bool Next; void Dump(LStream &s, int Depth = 0); public: typedef LHashTbl, MContainer*> ContainerHash; // Container data Mail *Message; MContainer *Parent; LArray Children; List Refs; int Index; // Cache data List RefCache; // Debug #ifdef _DEBUG LAutoString MsgId; #endif // Methods MContainer(const char *Id, Mail *m = 0); ~MContainer(); void SetMail(Mail *m); Mail *GetTop(); bool HasChild(MContainer *m); void AddChild(MContainer *m); void RemoveChild(MContainer *m); int CountMessages(); void Pour(int &index, int depth, int tree, bool next, ThingSortParams *folder); void OnPaint(LSurface *pDC, LRect &r, LItemColumn *c, LColour Fore, LColour Back, LFont *Font, const char *Txt); static void Prune(int &ParentIndex, LArray &L); static void Thread(List &In, LArray &Out); }; extern int ListItemCompare(LListItem *a, LListItem *b, NativeInt Data); extern int ContainerIndexer(Thing *a, Thing *b, NativeInt Data); extern const char *Store3ItemTypeName(Store3ItemTypes t); extern int GetFolderVersion(const char *Path); extern bool CreateMailHeaders(ScribeWnd *App, LStream &Out, LDataI *Mail, MailProtocol *Protocol); //////////////////////////////////////////////////////////// // Thing sorting // The old way extern int ContainerCompare(MContainer **a, MContainer **b); extern int ThingCompare(Thing *a, Thing *b, NativeInt Data); // The new way extern int ContainerSorter(MContainer *&a, MContainer *&b, ThingSortParams *Params); extern int ThingSorter(Thing *a, Thing *b, ThingSortParams *Data); ///////////////////////////////////////////////////////////// class MailViewOwner : public LCapabilityTarget { public: virtual LDocView *GetDoc(const char *MimeType) = 0; virtual bool SetDoc(LDocView *v, const char *MimeType) = 0; }; ///////////////////////////////////////////////////////////// // The core mail object struct MailPrintContext; class ScribeClass Mail : public Thing, public MailContainer, public LDefaultDocumentEnv { friend class MailUi; friend class MailPropDlg; friend class ScribeFolder; friend class Attachment; friend class ScribeWnd; private: class MailPrivate *d; static LHashTbl,Mail*> MessageIdMap; // List item preview int PreviewCacheX; List PreviewCache; int64_t TotalSizeCache; int64_t FlagsCache; MailUi *Ui; int Cursor; // Stores the cursor position in reply/forward format until the UI needs it Attachment *ParentFile; List Attachments; Mail *PreviousMail; // the mail we are replying to / forwarding void _New(); void _Delete(); bool _GetListItems(List &l, bool All); // All=false is just the selected items void SetListRead(bool Read); void SetFlagsCache(int64_t NewFlags, bool IgnoreReceipt, bool UpdateScreen); // LDocumentEnv impl List Actions; bool OnNavigate(LDocView *Parent, const char *Uri) override; bool AppendItems(LSubMenu *Menu, const char *Param, int Base = 1000) override; bool OnMenu(LDocView *View, int Id, void *Context) override; LoadType GetContent(LoadJob *&j) override; public: static bool PreviewLines; static bool AdjustDateTz; static int DefaultMailFields[]; static bool RunMailPipes; static List NewMailLst; constexpr static float MarkColourMix = 0.9f; uint8_t SendAttempts; enum NewEmailState { NewEmailNone, NewEmailLoading, NewEmailFilter, NewEmailBayes, NewEmailGrowl, NewEmailTray }; NewEmailState NewEmail; Mail(ScribeWnd *app, LDataI *object = 0); ~Mail(); bool SetObject(LDataI *o, bool InDataDestuctor, const char *File, int Line) override; LDATA_STR_PROP(Label, FIELD_LABEL); LDATA_STR_PROP(FwdMsgId, FIELD_FWD_MSG_ID); LDATA_STR_PROP(BounceMsgId, FIELD_BOUNCE_MSG_ID); LDATA_STR_PROP(Subject, FIELD_SUBJECT); LDATA_STR_PROP(Body, FIELD_TEXT); LDATA_STR_PROP(BodyCharset, FIELD_CHARSET); LDATA_STR_PROP(Html, FIELD_ALTERNATE_HTML); LDATA_STR_PROP(HtmlCharset, FIELD_HTML_CHARSET); LDATA_STR_PROP(InternetHeader, FIELD_INTERNET_HEADER); LDATA_INT_TYPE_PROP(EmailPriority, Priority, FIELD_PRIORITY, MAIL_PRIORITY_NORMAL); LDATA_INT32_PROP(AccountId, FIELD_ACCOUNT_ID); LDATA_INT64_PROP(MarkColour, FIELD_COLOUR); LDATA_STR_PROP(References, FIELD_REFERENCES); LDATA_DATE_PROP(DateReceived, FIELD_DATE_RECEIVED); LDATA_DATE_PROP(DateSent, FIELD_DATE_SENT); LDATA_INT_TYPE_PROP(Store3State, Loaded, FIELD_LOADED, Store3Loaded); LVariant GetServerUid(); bool SetServerUid(LVariant &v); const char *GetFromStr(int id) { LDataPropI *From = GetObject() ? GetObject()->GetObj(FIELD_FROM) : 0; return From ? From->GetStr(id) : NULL; } LDataPropI *GetFrom() { return GetObject() ? GetObject()->GetObj(FIELD_FROM) : 0; } LDataPropI *GetReply() { return GetObject() ? GetObject()->GetObj(FIELD_REPLY) : 0; } GDataIt GetTo() { return GetObject() ? GetObject()->GetList(FIELD_TO) : 0; } bool GetAttachmentObjs(LArray &Objs); LDataI *GetFileAttachPoint(); // Operators Mail *IsMail() override { return this; } Thing &operator =(Thing &c) override; OsView Handle(); ThingUi *GetUI() override; bool SetUI(ThingUi *ui) override; // References and ID's MContainer *Container; const char *GetMessageId(bool Create = false); bool SetMessageId(const char *val); LAutoString GetThreadIndex(int TruncateChars = 0); static Mail *GetMailFromId(const char *Id); bool MailMessageIdMap(bool Add = true); bool GetReferences(List &Ids); void GetThread(List &Thread); LString GetMailRef(); bool ResizeImage(Attachment *a); // MailContainer size_t Length() override; ssize_t IndexOf(Mail *m) override; Mail *operator [](size_t i) override; // Dom bool GetVariant(const char *Name, LVariant &Value, const char *Array = NULL) override; bool SetVariant(const char *Name, LVariant &Value, const char *Array = NULL) override; bool CallMethod(const char *MethodName, LVariant *ReturnValue, LArray &Args) override; // Events void OnCreate() override; bool OnBeforeSend(struct ScribeEnvelope *Out); void OnAfterSend(); bool OnBeforeReceive(); bool OnAfterReceive(LStreamI *Msg); void OnMouseClick(LMouse &m) override; void OnProperties(int Tab = -1) override; void OnInspect(); void OnReply(Mail *m, bool All, bool MarkOriginal); bool OnForward(Mail *m, bool MarkOriginal, int WithAttachments = -1); bool OnBounce(Mail *m, bool MarkOriginal, int WithAttachments = -1); void OnReceipt(Mail *m); int OnNotify(LViewI *Ctrl, LNotification n) override; // Printing void OnPrintHeaders(ScribePrintContext &Context) override; void OnPrintText(ScribePrintContext &Context, LPrintPageRanges &Pages) override; int OnPrintHtml(ScribePrintContext &Context, LPrintPageRanges &Pages, LSurface *RenderedHtml) override; // Misc uint32_t GetFlags() override; void SetFlags(ulong i, bool IgnoreReceipt = false, bool Update = true); void DeleteAsSpam(LView *View); const char *GetFieldText(int Field) override; LAutoString GetCharSet(); char *GetNewText(int Max = 64 << 10, const char *AsCp = "utf-8"); int *GetDefaultFields() override; Store3ItemTypes Type() override { return MAGIC_MAIL; } void DoContextMenu(LMouse &m, LView *Parent = 0) override; int Compare(LListItem *Arg, ssize_t Field) override; char *GetDropFileName() override; bool GetDropFiles(LString::Array &Files) override; LAutoString GetSig(bool HtmlVersion, ScribeAccount *Account = 0); bool LoadFromFile(char *File); void PrepSend(); void NewRecipient(char *Email, char *Name = 0); void ClearCachedItems(); bool Send(bool Now); void CreateMailHeaders(); bool AddCalendarEvent(LViewI *Parent, bool AddPopupReminder, LString *Msg); LArray GetCalendarAttachments(); // UI LDocView *CreateView(MailViewOwner *Owner, LString MimeType, bool Sunken, size_t MaxBytes, bool NoEdit = false); ThingUi *DoUI(MailContainer *c = 0) override; // Alt HTML bool HasAlternateHtml(Attachment **Attach = 0); char *GetAlternateHtml(List *Refs = 0); // dynamically allocated ptr bool WriteAlternateHtml(char *File = NULL, int FileLen = 0); // defaults to TEMP dir // Account stuff void ProcessTextForResponse(Mail *From, LOptionsFile *Options, ScribeAccount *Account); void WrapAndQuote(LStringPipe &Pipe, const char *QuoteStr, int WrapAt = -1); ScribeAccount *GetAccountSentTo(); // Access int64 TotalSizeof(); bool Save(ScribeFolder *Into = 0) override; // Attachments Attachment *AttachFile(LView *Parent, const char *FileName); bool AttachFile(Attachment *File); bool DeleteAttachment(Attachment *File); bool GetAttachments(List *Attachments); bool HasAttachments() { return Attachments.Length() > 0; } bool UnloadAttachments(); // Import / Export bool GetFormats(bool Export, LString::Array &MimeTypes) override; IoProgress Import(IoProgressFnArgs) override; IoProgress Export(IoProgressFnArgs) override; // ListItem void Update() override; const char *GetText(int i) override; int GetImage(int SelFlags = 0) override; void OnMeasure(LPoint *Info) override; void OnPaintColumn(LItem::ItemPaintCtx &Ctx, int i, LItemColumn *c) override; void OnPaint(LItem::ItemPaintCtx &Ctx) override; }; inline const char *toString(Mail::NewEmailState s) { #define _(s) case Mail::s: return #s; switch (s) { _(NewEmailNone) _(NewEmailLoading) _(NewEmailFilter) _(NewEmailBayes) _(NewEmailGrowl) _(NewEmailTray) } #undef _ LAssert(0); return "#invalidNewEmailState"; } // this is where the items reside // and it forms a leaf on the mail box tree // to the user it looks like a folder class ScribeClass ScribeFolder : public ThingType, public LTreeItem, public LDragDropSource, public MailContainer { friend class MailTree; friend class FolderPropertiesDlg; friend class ThingList; friend class ScribeWnd; protected: class ThingContainerPriv *d; ThingList *View(); LString DropFileName; LString GetDropFileName(); // UI cache LAutoString NameCache; int ChildUnRead = 0; LTreeItem *LoadOnDemand = NULL; LAutoPtr Loading; LArray FieldArray; void SerializeFieldWidths(bool Write = false); void EmptyFieldList(); void SetLoadFolder(Thing *t) { if (t) t->SetParentFolder(this); } bool HasFieldId(int Id); // Tree item stuff void _PourText(LPoint &Size) override; void _PaintText(LItem::ItemPaintCtx &Ctx) override; int _UnreadChildren(); void UpdateOsUnread(); // Debugging state enum FolderState { FldState_Idle, FldState_Loading, FldState_Populating, } CurState = FldState_Idle; public: List Items; ScribeFolder(); ~ScribeFolder(); // Object LArray &GetFieldArray() { return FieldArray; } LDataFolderI *GetFldObj() { return dynamic_cast(GetObject()); } bool SetObject(LDataI *o, bool InDataDestuctor, const char *File, int Line) override; // ThingType Store3ItemTypes Type() override { return GetObject() ? (Store3ItemTypes)GetObject()->Type() : MAGIC_NONE; } ScribeFolder *GetFolder() override { return dynamic_cast(LTreeItem::GetParent()); } ScribeFolder *GetChildFolder() { return dynamic_cast(LTreeItem::GetChild()); } ScribeFolder *GetNextFolder() { return dynamic_cast(LTreeItem::GetNext()); } Store3Status SetFolder(ScribeFolder *f, int Param = -1) override; ScribeFolder *IsFolder() { return this; } Store3Status CopyTo(ScribeFolder *NewParent, int NewIndex = -1); // MailContainer size_t Length() override; ssize_t IndexOf(Mail *m) override; Mail *operator [](size_t i) override; /// Update the unread count void OnUpdateUnRead ( /// Increments the count, or zero if a child folder is changing. int Offset, /// Re-scan the folder bool ScanItems ); // Methods LDATA_INT32_PROP(UnRead, FIELD_UNREAD); LDATA_INT_TYPE_PROP(Store3ItemTypes, ItemType, FIELD_FOLDER_TYPE, MAGIC_MAIL); LDATA_INT32_PROP(Open, FIELD_FOLDER_OPEN); LDATA_INT32_PROP(SortIndex, FIELD_FOLDER_INDEX); LDATA_INT64_PROP(Items, FIELD_FOLDER_ITEMS); // Cached item count LDATA_INT_TYPE_PROP(ScribePerm, ReadAccess, FIELD_FOLDER_PERM_READ, PermRequireNone); LDATA_INT_TYPE_PROP(ScribePerm, WriteAccess, FIELD_FOLDER_PERM_WRITE, PermRequireNone); LDATA_ENUM_PROP(SystemFolderType, FIELD_SYSTEM_FOLDER, Store3SystemFolder); void SetSort(int Col, bool Ascend, bool CanDirty = true); int GetSortAscend() { return GetObject()->GetInt(FIELD_SORT) > 0; } int GetSortCol() { return abs((int)GetObject()->GetInt(FIELD_SORT)) - 1; } int GetSortField(); void ReSort(); bool Save(ScribeFolder *Into = 0) override; bool ReindexField(int OldIndex, int NewIndex); void CollectSubFolderMail(ScribeFolder *To = 0); bool InsertThing(Thing *Item); bool MoveTo(LArray &Items, bool CopyOnly = false, LArray *Status = NULL); bool Delete(LArray &Items, bool ToTrash); void SetDefaultFields(bool Force = false); bool Thread(); ScribePerm GetFolderPerms(ScribeAccessType Access); void SetFolderPerms(LView *Parent, ScribeAccessType Access, ScribePerm Perm, std::function Callback); bool GetThreaded(); void SetThreaded(bool t); // void Update(); void GetMessageById(const char *Id, std::function Callback); void SetLoadOnDemand(); void SortSubfolders(); void DoContextMenu(LMouse &m); void OnItemType(); bool IsInTrash(); bool SortItems(); // Virtuals: /// /// These methods can be used in a synchronous or asynchronous manner: /// sync: Call with 'Callback=NULL' and use the return value. /// If the function needs to show a dialog (like to get permissions from /// the user) then it'll return Store3Delayed immediately. /// async: Call with a valid callback, and the method will possibly wait /// for the user and then either return Store3Error or Store3Success. virtual Store3Status LoadThings(LViewI *Parent = NULL, std::function Callback = NULL); virtual Store3Status WriteThing(Thing *t, std::function Callback = NULL); virtual Store3Status DeleteThing(Thing *t, std::function Callback = NULL); virtual Store3Status DeleteAllThings( std::function Callback = NULL); virtual bool LoadFolders(); virtual bool UnloadThings(); virtual bool IsWriteable() { return true; } virtual bool IsPublicFolders() { return false; } virtual void OnProperties(int Tab = -1) override; virtual ScribeFolder *CreateSubDirectory(const char *Name, int Type); virtual void OnRename(char *NewName); virtual void OnDelete(); virtual LString GetPath(); virtual ScribeFolder *GetSubFolder(const char *Path); virtual void Populate(ThingList *List); virtual bool CanHaveSubFolders(Store3ItemTypes Type = MAGIC_MAIL) { return GetItemType() != MAGIC_ANY; } virtual void OnRethread(); // Name void SetName(const char *Name, bool Encode); LString GetName(bool Decode); // Serialization int Sizeof(); bool Serialize(LFile &f, bool Write); // Tree Item const char *GetText(int i=0) override; int GetImage(int Flags = 0) override; void OnExpand(bool b) override; bool OnKey(LKey &k) override; void Update() override; // Drag'n'drop bool GetFormats(LDragFormats &Formats) override; bool OnBeginDrag(LMouse &m) override; void OnEndData() override; bool GetData(LArray &Data) override; void OnReceiveFiles(LArray &Files); // Import/Export bool GetFormats(bool Export, LString::Array &MimeTypes); IoProgress Import(IoProgressFnArgs); IoProgress Export(IoProgressFnArgs); - bool Import(LStreamI &f, const char *MimeType); - void Export(LStreamI &f, const char *MimeType, std::function Callback = NULL); void ExportAsync(LAutoPtr f, const char *MimeType, std::function Callback = NULL); const char *GetStorageMimeType(); // Dom bool GetVariant(const char *Name, LVariant &Value, const char *Array = NULL) override; bool SetVariant(const char *Name, LVariant &Value, const char *Array = NULL) override; bool CallMethod(const char *MethodName, LVariant *ReturnValue, LArray &Args) override; }; ////////////////////////////////////////////////////////////// class Filter; class FilterCondition { protected: bool TestData(Filter *F, LVariant &v, LStream *Log); public: // Data LAutoString Source; // Data Source (used to be "int Field") LAutoString Value; // Constant char Op; uint8_t Not; // Methods FilterCondition(); bool Set(class LXmlTag *t); // Test condition against email bool Test(Filter *F, Mail *m, LStream *Log); FilterCondition &operator =(FilterCondition &c); // Object ThingUi *DoUI(MailContainer *c = 0); }; class FilterAction : public LListItem, public LDataPropI { LCombo *TypeCbo; LEdit *ArgEdit; LButton *Btn; public: // Data FilterActionTypes Type; LAutoString Arg1; // Methods FilterAction(LDataStoreI *Store); ~FilterAction(); bool Set(LXmlTag *t); bool Get(LXmlTag *t); bool Do(Filter *F, ScribeWnd *App, Mail *&m, LStream *log); void Browse(ScribeWnd *App, LView *Parent); void DescribeHtml(Filter *Flt, LStream &s); LDataPropI &operator =(LDataPropI &p); // List item const char *GetText(int Col = 0); void OnMeasure(LPoint *Info); bool Select(); void Select(bool b); void OnPaintColumn(LItem::ItemPaintCtx &Ctx, int i, LItemColumn *c); int OnNotify(LViewI *c, LNotification n); // Object ThingUi *DoUI(MailContainer *c = 0); // bool Serialize(ObjProperties &f, bool Write); }; class ScribeClass Filter : public Thing { friend class FilterUi; protected: class FilterUi *Ui; class FilterPrivate *d; static int MaxIndex; // Ui LListItemCheckBox *ChkIncoming; LListItemCheckBox *ChkOutgoing; LListItemCheckBox *ChkInternal; bool IgnoreCheckEvents; // Current Mail **Current; // XML I/O LAutoPtr ConditionsCache; LAutoPtr Parse(bool Actions); // Methods bool EvaluateTree(LXmlTag *n, Mail *m, bool &Stop, LStream *Log); bool EvaluateXml(Mail *m, bool &Stop, LStream *Log); public: Filter(ScribeWnd *app, LDataI *object = 0); ~Filter(); LDATA_STR_PROP(Name, FIELD_FILTER_NAME); LDATA_STR_PROP(ConditionsXml, FIELD_FILTER_CONDITIONS_XML); LDATA_STR_PROP(ActionsXml, FIELD_FILTER_ACTIONS_XML); LDATA_STR_PROP(Script, FIELD_FILTER_SCRIPT); LDATA_INT32_PROP(Index, FIELD_FILTER_INDEX); LDATA_INT32_PROP(StopFiltering, FIELD_STOP_FILTERING); LDATA_INT32_PROP(Incoming, FIELD_FILTER_INCOMING); LDATA_INT32_PROP(Outgoing, FIELD_FILTER_OUTGOING); LDATA_INT32_PROP(Internal, FIELD_FILTER_INTERNAL); int Compare(LListItem *Arg, ssize_t Field) override; Thing &operator =(Thing &c) override; Filter *IsFilter() override { return this; } static void Reindex(ScribeFolder *Folder); int *GetDefaultFields() override; const char *GetFieldText(int Field) override; // Methods void Empty(); LAutoString DescribeHtml(); // Import / Export char *GetDropFileName() override; bool GetDropFiles(LString::Array &Files) override; bool GetFormats(bool Export, LString::Array &MimeTypes) override; IoProgress Import(IoProgressFnArgs) override; IoProgress Export(IoProgressFnArgs) override; // Dom bool Evaluate(char *s, LVariant &v); bool SetVariant(const char *Name, LVariant &Value, const char *Array = NULL) override; bool GetVariant(const char *Name, LVariant &Value, const char *Array = NULL) override; bool CallMethod(const char *MethodName, LVariant *ReturnValue, LArray &Args) override; // Filter bool Test(Mail *m, bool &Stop, LStream *Log = 0); bool DoActions(Mail *&m, bool &Stop, LStream *Log = 0); Mail *GetCurrent() { return Current?*Current:0; } /// This filters all the mail in 'Email'. Anything that is handled by a filter /// is removed from the list, leaving just the unfiltered mail. static int ApplyFilters ( /// [In] The window of the filtering caller LView *Parent, /// [In] List of all the filter to test and/or apply List &Filters, /// [In/Out] The email to filter. After the call anything that has been /// acted on by a filter will be removed from the list. List &Email ); // Object Store3ItemTypes Type() override { return MAGIC_FILTER; } ThingUi *DoUI(MailContainer *c = 0) override; bool Serialize(LFile &f, bool Write); bool Save(ScribeFolder *Into = 0) override; void OnMouseClick(LMouse &m) override; void AddAction(FilterAction *a); // List item const char *GetText(int i) override; int GetImage(int Flags) override; void OnPaint(ItemPaintCtx &Ctx) override; void OnColumnNotify(int Col, int64 Data) override; // Index Filter *GetFilterAt(int Index); }; ////////////////////////////////////////////////////////////////////// class Accountlet; enum AccountThreadState { ThreadIdle, ThreadSetup, ThreadConnecting, ThreadTransfer, ThreadWaiting, ThreadDeleting, ThreadDone, ThreadCancel, ThreadError }; enum ReceiveAction { MailNoop, MailDelete, MailDownloadAndDelete, MailDownload, MailUpload, MailHeaders, }; enum ReceiveStatus { MailReceivedNone, MailReceivedWaiting, // This has been given to the main thread MailReceivedOk, // and one of "Ok" or "Error" has to be MailReceivedError, // set to continue. MailReceivedMax, }; ScribeFunc const char *AccountThreadStateName(AccountThreadState i); ScribeFunc const char *ReceiveActionName(ReceiveAction i); ScribeFunc const char *ReceiveStatusName(ReceiveStatus i); class LScribeMime : public LMime { public: LScribeMime() : LMime(ScribeTempPath()) { } }; class LMimeStream : public LTempStream, public LScribeMime { public: LMimeStream(); bool Parse(); }; class MailTransferEvent { public: // Message LAutoPtr Rfc822Msg; ReceiveAction Action = MailNoop; ReceiveStatus Status = MailReceivedNone; int Index = 0; bool Explicit = false; LString Uid; int64 Size = 0; int64 StartWait = 0; // Header Listing class AccountMessage *Msg = NULL; LList *GetList(); // Sending ScribeEnvelope *Send = NULL; LString OutgoingHeaders; // Other class Accountlet *Account = NULL; }; #define RoProp(Type, Name) \ protected: \ Type Name; \ public: \ Type Get##Name() { return Name; } class AccountThread : public LThread, public LCancel { protected: Accountlet *Acc; void OnAfterMain(); public: // Object AccountThread(Accountlet *acc); ~AccountThread(); // Api Accountlet *GetAccountlet() { return Acc; } // 1st phase virtual void Disconnect(); // 2nd phase virtual bool Kill(); }; #define AccStrOption(func, opt) \ LVariant func(const char *Set = 0) { LVariant v; StrOption(opt, v, Set); return v; } #define AccIntOption(name, opt) \ int name(int Set = -1) { LVariant v; IntOption(opt, v, Set); return v.CastInt32(); } class ScribeClass Accountlet : public LStream { friend class ScribeAccount; friend class AccountletThread; friend class AccountThread; public: struct AccountletPriv { LArray Log; }; class AccountletLock { LMutex *l; public: bool Locked; AccountletPriv *d; AccountletLock(AccountletPriv *data, LMutex *lck, const char *file, int line) { d = data; l = lck; Locked = lck->Lock(file, line); } ~AccountletLock() { if (Locked) l->Unlock(); } }; typedef LAutoPtr I; private: AccountletPriv d; LMutex PrivLock; protected: // Data ScribeAccount *Account; LAutoPtr Thread; bool ConnectionStatus; MailProtocol *Client; uint64 LastOnline; LString TempPsw; bool Quiet; LView *Parent; // Pointers ScribeFolder *Root; LDataStoreI *DataStore; LMailStore *MailStore; // this memory is owned by ScribeWnd // Options const char *OptPassword; // Members LSocketI *CreateSocket(bool Sending, LCapabilityClient *Caps, bool RawLFCheck); bool WaitForTransfers(List &Files); void StrOption(const char *Opt, LVariant &v, const char *Set); void IntOption(const char *Opt, LVariant &v, int Set); // LStringPipe Line; ssize_t Write(const void *buf, ssize_t size, int flags); public: MailProtocolProgress Group; MailProtocolProgress Item; Accountlet(ScribeAccount *a); ~Accountlet(); Accountlet &operator =(const Accountlet &a) { LAssert(0); return *this; } I Lock(const char *File, int Line) { I a(new AccountletLock(&d, &PrivLock, File, Line)); if (!a->Locked) a.Reset(); return a; } // Methods bool Connect(LView *Parent, bool Quiet); bool Lock(); void Unlock(); virtual bool IsConfigured() { return false; } bool GetStatus() { return ConnectionStatus; } uint64 GetLastOnline() { return LastOnline; } ScribeAccount* GetAccount() { return Account; } bool IsCancelled(); void IsCancelled(bool b); ScribeWnd* GetApp(); const char* GetStateName(); ScribeFolder* GetRootFolder() { return Root; } RoProp(AccountThreadState, State); bool IsOnline() { if (DataStore) return DataStore->GetInt(FIELD_IS_ONLINE) != 0; return Thread != 0; } void OnEndSession() { if (DataStore) DataStore->SetInt(FIELD_IS_ONLINE, false); } // Commands void Disconnect(); void Kill(); // Data char *OptionName(const char *Opt, char *Dest, int DestLen); void Delete(); LThread *GetThread() { return Thread; } LMailStore *GetMailStore() { return MailStore; } LDataStoreI *GetDataStore() { return DataStore; } // General options virtual int UseSSL(int Set = -1) = 0; AccStrOption(Name, OPT_AccountName); AccIntOption(Disabled, OPT_AccountDisabled); AccIntOption(Id, OPT_AccountUID); AccIntOption(Expanded, OPT_AccountExpanded); bool GetPassword(GPassword *p); void SetPassword(GPassword *p); bool IsCheckDialup(); // Events void OnThreadDone(); void OnOnlineChange(bool Online); virtual void OnBeforeDelete(); // LDom impl bool GetVariant(const char *Name, LVariant &Value, const char *Array = NULL); bool SetVariant(const char *Name, LVariant &Value, const char *Array = NULL); // Virtuals virtual void Main(AccountletThread *Thread) = 0; virtual LVariant Server(const char *Set = 0) = 0; virtual LVariant UserName(const char *Set = 0) = 0; virtual void Enabled(bool b) = 0; virtual void OnPulse(char *s, int s_len) {} virtual bool IsReceive() { return false; } virtual bool InitMenus() { return false; } virtual void CreateMaps() = 0; virtual ScribeAccountletStatusIcon GetStatusIcon() { return STATUS_ERROR; } }; #undef RoProp class AccountletThread; class AccountIdentity : public Accountlet { public: AccountIdentity(ScribeAccount *a); AccStrOption(Name, OPT_AccIdentName); AccStrOption(Email, OPT_AccIdentEmail); AccStrOption(ReplyTo, OPT_AccIdentReply); AccStrOption(TextSig, OPT_AccIdentTextSig); AccStrOption(HtmlSig, OPT_AccIdentHtmlSig); AccIntOption(Sort, OPT_AccountSort); int UseSSL(int Set = -1) { return 0; } void Main(AccountletThread *Thread) {} LVariant Server(const char *Set = 0) { return LVariant(); } LVariant UserName(const char *Set = 0) { return LVariant(); } void Enabled(bool b) {} void CreateMaps(); bool IsValid(); bool GetVariant(const char *Name, LVariant &Value, const char *Array = NULL); bool SetVariant(const char *Name, LVariant &Value, const char *Array = NULL); }; struct ScribeEnvelope { LString MsgId; LString SourceFolder; LString From; LArray To; LString References; LString FwdMsgId; LString BounceMsgId; LString Rfc822; }; class SendAccountlet : public Accountlet { friend class ScribeAccount; friend class ScribeWnd; LMenuItem *SendItem; public: LArray Outbox; SendAccountlet(ScribeAccount *a); ~SendAccountlet(); // Methods void Main(AccountletThread *Thread); void Enabled(bool b); bool InitMenus(); void CreateMaps(); ScribeAccountletStatusIcon GetStatusIcon(); // Sending options AccStrOption(Server, OPT_SmtpServer); AccIntOption(Port, OPT_SmtpPort); AccStrOption(Domain, OPT_SmtpDomain); AccStrOption(UserName, OPT_SmtpName); AccIntOption(RequireAuthentication, OPT_SmtpAuth); AccIntOption(AuthType, OPT_SmtpAuthType); AccStrOption(PrefCharset1, OPT_SendCharset1); AccStrOption(PrefCharset2, OPT_SendCharset2); AccStrOption(HotFolder, OPT_SendHotFolder); AccIntOption(OnlySendThroughThisAccount, OPT_OnlySendThroughThis); /// Get/Set the SSL mode /// \sa #SSL_NONE, #SSL_STARTTLS or #SSL_DIRECT AccIntOption(UseSSL, OPT_SmtpSSL); bool IsConfigured() { LVariant hot = HotFolder(); if (hot.Str()) { bool Exists = LDirExists(hot.Str()); printf("%s:%i - '%s' exists = %i\n", _FL, hot.Str(), Exists); return Exists; } return ValidStr(Server().Str()); } bool GetVariant(const char *Name, LVariant &Value, const char *Array = NULL); bool SetVariant(const char *Name, LVariant &Value, const char *Array = NULL); }; typedef LHashTbl, LXmlTag*> MsgListHash; class MsgList : protected MsgListHash { LOptionsFile *Opts; LString Tag; bool Loaded; bool Load(); LXmlTag *LockId(const char *id, const char *file, int line); void Unlock(); public: typedef MsgListHash Parent; bool Dirty; MsgList(LOptionsFile *Opts, char *Tag); ~MsgList(); // Access methods bool Add(const char *id); bool Delete(const char *id); int Length(); bool Find(const char *id); void Empty(); LString::Array CopyKeys(); // Dates bool SetDate(char *id, LDateTime *dt); bool GetDate(char *id, LDateTime *dt); }; class ReceiveAccountlet : public Accountlet { friend class ScribeAccount; friend class ScribeWnd; friend class ImapThread; friend class ScpThread; LArray Actions; LList *Items; int SecondsTillOnline; LMenuItem *ReceiveItem; LMenuItem *PreviewItem; List *IdTemp; LAutoPtr SettingStore; LAutoPtr Msgs; LAutoPtr Spam; public: ReceiveAccountlet(ScribeAccount *a); ~ReceiveAccountlet(); // Props LList *GetItems() { return Items; } bool SetItems(LList *l); bool SetActions(LArray *a = NULL); bool IsReceive() { return true; } bool IsPersistant(); // Methods void Main(AccountletThread *Thread); void OnPulse(char *s, int s_len); bool OnIdle(); void Enabled(bool b); bool InitMenus(); int GetCheckTimeout(); void CreateMaps(); ScribeAccountletStatusIcon GetStatusIcon(); // Message list bool HasMsg(const char *Id); void AddMsg(const char *Id); void RemoveMsg(const char *Id); void RemoveAllMsgs(); int GetMsgs(); // Spam list void DeleteAsSpam(const char *Id); bool RemoveFromSpamIds(const char *Id); bool IsSpamId(const char *Id, bool Delete = false); // Receive options AccStrOption(Protocol, OPT_Pop3Protocol); ScribeProtocol ProtocolType() { return ProtocolStrToEnum(Protocol().Str()); } AccStrOption(Server, OPT_Pop3Server); AccIntOption(Port, OPT_Pop3Port); AccStrOption(UserName, OPT_Pop3Name); AccStrOption(DestinationFolder, OPT_Pop3Folder); AccIntOption(AutoReceive, OPT_Pop3AutoReceive); AccStrOption(CheckTimeout, OPT_Pop3CheckEvery); AccIntOption(LeaveOnServer, OPT_Pop3LeaveOnServer); AccIntOption(DeleteAfter, OPT_DeleteAfter); AccIntOption(DeleteDays, OPT_DeleteDays); AccIntOption(DeleteLarger, OPT_DeleteIfLarger); AccIntOption(DeleteSize, OPT_DeleteIfLargerSize); AccIntOption(DownloadLimit, OPT_MaxEmailSize); AccStrOption(Assume8BitCharset, OPT_Receive8BitCs); AccStrOption(AssumeAsciiCharset, OPT_ReceiveAsciiCs); AccIntOption(AuthType, OPT_ReceiveAuthType); AccStrOption(HotFolder, OPT_ReceiveHotFolder); AccIntOption(SecureAuth, OPT_ReceiveSecAuth); /// Get/Set the SSL mode /// \sa #SSL_NONE, #SSL_STARTTLS or #SSL_DIRECT AccIntOption(UseSSL, OPT_Pop3SSL); bool IsConfigured() { LVariant hot = HotFolder(); if (hot.Str() && LDirExists(hot.Str())) { return true; } LVariant v = Server(); bool s = ValidStr(v.Str()); v = Protocol(); if (!v.Str() || _stricmp(v.Str(), PROTOCOL_POP_OVER_HTTP) != 0) { v = UserName(); s &= ValidStr(v.Str()); } return s; } bool GetVariant(const char *Name, LVariant &Value, const char *Array = NULL); bool SetVariant(const char *Name, LVariant &Value, const char *Array = NULL); }; class ScribeAccount : public LDom, public LXmlTreeUi, public LCapabilityClient { friend class ScribeWnd; friend class ScribePopViewer; friend class AccountStatusPanel; friend class Accountlet; friend class SendAccountlet; friend class ReceiveAccountlet; protected: class ScribeAccountPrivate *d; ScribeWnd *Parent; ScribeFolder *&GetRoot(); void SetIndex(int i); public: // Data AccountIdentity Identity; SendAccountlet Send; ReceiveAccountlet Receive; LArray Views; // Object ScribeAccount(ScribeWnd *parent, int index); ~ScribeAccount(); // Lifespan bool IsValid(); bool Create(); bool Delete(); // Properties ScribeWnd *GetApp() { return Parent; } int GetIndex(); bool IsOnline(); void SetCheck(bool c); LMenuItem *GetMenuItem(); void SetMenuItem(LMenuItem *i); void OnEndSession() { Send.OnEndSession(); Receive.OnEndSession(); } // Commands void Stop(); bool Disconnect(); void Kill(); void SetDefaults(); // User interface void InitUI(LView *Parent, int Tab, std::function callback); bool InitMenus(); void SerializeUi(LView *Wnd, bool Load); int OnNotify(LViewI *Ctrl, LNotification &n); // Worker void OnPulse(char *s = NULL, int s_len = 0); void ReIndex(int i); void CreateMaps(); // LDom interface bool GetVariant(const char *Name, LVariant &Value, const char *Array = NULL) override; bool SetVariant(const char *Name, LVariant &Value, const char *Array = NULL) override; bool CallMethod(const char *MethodName, LVariant *ReturnValue, LArray &Args) override; }; ////////////////////////////////////////////////////////////////////// class ScribeClass ScribeDom : public LDom { ScribeWnd *App; public: Mail *Email; Contact *Con; ContactGroup *Grp; Calendar *Cal; Filter *Fil; ScribeDom(ScribeWnd *a); bool GetVariant(const char *Name, LVariant &Value, const char *Array = 0); }; ////////////////////////////////////////////////////////////////////// #include "BayesianFilter.h" #include "Components.h" class LMailStore { public: bool Default, Expanded; LString Name; LString Path; LDataStoreI *Store; ScribeFolder *Root; LMailStore() { Expanded = true; Default = false; Store = NULL; Root = NULL; } bool IsOk() { return Store != NULL && Root != NULL; } int Priority() { int Ver = Store ? (int)Store->GetInt(FIELD_VERSION) : 0; return Ver; } LMailStore &operator =(LMailStore &a) { LAssert(0); return *this; } }; struct OptionsInfo { LString File; char *Leaf; int Score; uint64 Mod; bool Usual; OptionsInfo(); OptionsInfo &operator =(char *p); LOptionsFile *Load(); }; class ScribeClass ScribeWnd : public LWindow, public LDom, public LDataEventsI, public BayesianFilter, public CapabilityInstaller, public LCapabilityTarget { friend class ScribeAccount; friend class Accountlet; friend class SendAccountlet; friend class ReceiveAccountlet; friend class AccountStatusPanel; friend class ScribeFolder; friend class OptionsDlg; friend class LoadWordStoreThread; friend struct ScribeReplicator; public: enum LayoutMode { OptionsLayout = 0, ///-------------------- /// | | /// Folders | List | /// | | /// |---------| /// | Preview | ///-------------------- FoldersListAndPreview = 1, ///----------------- /// | | /// Folders | List | /// | | ///----------------- /// Preview | ///----------------- PreviewOnBottom, ///----------------- /// | | /// Folders | List | /// | | ///----------------- FoldersAndList, ///--------------------------- /// | | | /// Folders | List | Preview | /// | | | ///--------------------------- ThreeColumn, }; enum AppState { ScribeConstructing, // 1) In ScribeWnd constructor OR one of it's dialogs. ScribeInitializing, // 2) In the ScribeWnd::OnCreate event. ScribeRunning, // 3) Normal fully initialized runtime. ScribeExiting, ScribeLoadingFolders, ScribeUnloadingFolders, }; AppState GetScribeState() { return ScribeState; } protected: class ScribeWndPrivate *d = NULL; LTrayIcon TrayIcon; // Ipc LSharedMemory *ScribeIpc = NULL; class ScribeIpcInstance *ThisInst = NULL; bool ShutdownIpc(); // Accounts List Accounts; // New Mail stuff class LNewMailDlg *NewMailDlg = NULL; static AppState ScribeState; DoEvery Ticker; int64 LastDrop = 0; // Static LSubMenu *File = NULL; LSubMenu *ContactsMenu = NULL; LSubMenu *Edit = NULL; LSubMenu *Help = NULL; LToolBar *Commands = NULL; // Dynamic LSubMenu *IdentityMenu = NULL; LMenuItem *DefaultIdentityItem = NULL; LSubMenu *MailMenu = NULL; LSubMenu *SendMenu = NULL, *ReceiveMenu = NULL, *PreviewMenu = NULL; LMenuItem *SendItem = NULL, *ReceiveItem = NULL, *PreviewItem = NULL; LSubMenu *NewTemplateMenu = NULL; LMenuItem *WorkOffline = NULL; // Commands LCommand CmdSend; LCommand CmdReceive; LCommand CmdPreview; // Storage LArray Folders; LArray FolderTasks; List PostValidateFree; // Main view LAutoPtr ImageList; LAutoPtr ToolbarImgs; class LBox *Splitter = NULL; ThingList *MailList = NULL; class DynamicHtml *TitlePage = NULL; class LSearchView *SearchView = NULL; MailTree *Tree = NULL; class LPreviewPanel *PreviewPanel = NULL; class AccountStatusPanel *StatusPanel = NULL; // Security ScribePerm CurrentAuthLevel = PermRequireNone; // Methods void SetupUi(); void SetupAccounts(); int AdjustAllObjectSizes(LDataI *Item); bool CleanFolders(ScribeFolder *f); LDataStoreI *CreateDataStore(char *Full, bool CreateIfMissing); void LoadFolders(std::function Callback); bool LoadMailStores(); bool ProcessFolder(LDataStoreI *&Store, int StoreIdx, char *StoreName); bool UnLoadFolders(); void AddFolderToMru(char *FileName); void AddContactsToMenu(LSubMenu *Menu); bool FindWordDb(char *Out, int OutSize, char *Name); void OnFolderChanged(LDataFolderI *folder); bool ValidateFolder(LMailStore *s, int Id); void GrowlOnMail(Mail *m); void GrowlInfo(LString title, LString text); bool OnTransfer(); ScribeDomType StrToDom(const char *Var) { return ::StrToDom(Var); } const char* DomToStr(ScribeDomType p) { return ::DomToStr(p); } void LoadImageResources(); void DoOnTimer(LScriptCallback *c); public: ScribeWnd(); ~ScribeWnd(); static bool IsUnitTest; const char *GetClass() override { return "ScribeWnd"; } void DoDebug(char *s); void Validate(LMailStore *s); // Dom bool GetVariant(const char *Name, LVariant &Value, const char *Array = NULL) override; bool CallMethod(const char *MethodName, LVariant *ReturnValue, LArray &Args) override; // --------------------------------------------------------------------- // Methods LAutoString GetDataFolder(); Thing *CreateThingOfType(Store3ItemTypes Type, LDataI *obj = 0); Thing *CreateItem(int Type, ScribeFolder *Folder = 0, bool Ui = true); Mail *CreateMail(Contact *c = 0, const char *Email = 0, const char *Name = 0); Mail *LookupMailRef(const char *MsgRef, bool TraceAllUids = false); bool CreateFolders(LAutoString &FileName); bool CompactFolders(LMailStore &Store, bool Interactive = true); void Send(int Which = -1, bool Quiet = false); void Receive(int Which); void Preview(int Which); void OnBeforeConnect(ScribeAccount *Account, bool Receive); void OnAfterConnect(ScribeAccount *Account, bool Receive); bool NeedsCapability(const char *Name, const char *Param = NULL) override; void OnInstall(CapsHash *Caps, bool Status) override; void OnCloseInstaller() override; bool HasFolderTasks() { return FolderTasks.Length() > 0; } int GetEventHandle(); void Update(int What = 0); void UpdateUnRead(ScribeFolder *Folder, int Delta); void ThingPrint(std::function Callback, ThingType *m, LPrinter *Info = NULL, LView *Parent = NULL, int MaxPage = -1); bool OpenAMail(ScribeFolder *Folder); void BuildDynMenus(); LDocView *CreateTextControl(int Id, const char *MimeType, bool Editor, Mail *m = 0); void SetLastDrop() { LastDrop = LCurrentTime(); } void SetListPane(LView *v); void SetLayout(LayoutMode Mode = OptionsLayout); bool IsMyEmail(const char *Email); bool SetItemPreview(LView *v); LOptionsFile::PortableType GetPortableType(); ScribeRemoteContent RemoteContent_GetSenderStatus(const char *Addr); void RemoteContent_ClearCache(); void RemoteContent_AddSender(const char *Addr, bool WhiteList); void SetDefaultHandler(); void OnSetDefaultHandler(bool Error, bool OldAssert); void SetCurrentIdentity(int i=-1); int GetCurrentIdentity(); bool MailReplyTo(Mail *m, bool All = false); bool MailForward(Mail *m); bool MailBounce(Mail *m); void MailMerge(LArray &Recip, const char *FileName, Mail *Source); void OnNewMail(List *NewMailObjs, bool Add = true); void OnNewMailSound(); void OnCommandLine(); void OnTrayClick(LMouse &m) override; void OnTrayMenu(LSubMenu &m) override; void OnTrayMenuResult(int MenuId) override; void OnFolderSelect(ScribeFolder *f); void AddThingSrc(ScribeFolder *src); void RemoveThingSrc(ScribeFolder *src); LArray GetThingSources(Store3ItemTypes Type); bool GetContacts(List &Contacts, ScribeFolder *f = 0, bool Deep = true); List *GetEveryone(); void HashContacts(LHashTbl,Contact*> &Contacts, ScribeFolder *Folder = 0, bool Deep = true); // CapabilityInstaller impl LAutoString GetHttpProxy() override; InstallProgress *StartAction(MissingCapsBar *Bar, LCapabilityTarget::CapsHash *Components, const char *Action) override; class HttpImageThread *GetImageLoader(); int GetMaxPages(); ScribeWnd::LayoutMode GetEffectiveLayoutMode(); ThingList *GetMailList() { return MailList; } // Gets the matching mail store for a given identity LMailStore *GetMailStoreForIdentity ( /// If this is NULL, assume the current identity const char *IdEmail = NULL ); ScribeFolder *GetFolder(int Id, LMailStore *s = NULL, bool Quiet = false); ScribeFolder *GetFolder(int Id, LDataI *s); ScribeFolder *GetFolder(const char *Name, LMailStore *s = NULL); ScribeFolder *GetCurrentFolder(); int GetFolderType(ScribeFolder *f); LImageList *GetIconImgList() { return ImageList; } LAutoPtr &GetToolbarImgList() { return ToolbarImgs; } LString GetResourceFile(SribeResourceType Type); DoEvery *GetTicker() { return &Ticker; } ScribeAccount *GetSendAccount(); ScribeAccount *GetCurrentAccount(); ThingFilter *GetThingFilter(); List *GetAccounts() { return &Accounts; } ScribeAccount *GetAccountById(int Id); ScribeAccount *GetAccountByEmail(const char *Email); LPrinter *GetPrinter(); int GetActiveThreads(); int GetToolbarHeight(); void GetFilters(List &Filters, bool JustIn, bool JustOut, bool JustInternal); bool OnFolderTask(LEventTargetI *Ptr, bool Add); LArray &GetStorageFolders() { return Folders; } LMailStore *GetDefaultMailStore(); LMailStore *GetMailStoreForPath(const char *Path); bool OnMailStore(LMailStore **MailStore, bool Add); ThingList *GetItemList() { return MailList; } LColour GetColour(int i); LMutex *GetLock(); LFont *GetPreviewFont(); LFont *GetBoldFont() { return LSysBold; } LToolBar *LoadToolbar(LViewI *Parent, const char *File, LAutoPtr &Img); class LVmDebuggerCallback *GetDebuggerCallback(); class GpgConnector *GetGpgConnector(); void GetUserInput(LView *Parent, LString Msg, bool Password, std::function Callback); int GetCalendarSources(LArray &Sources); Store3Status GetAccessLevel(LViewI *Parent, ScribePerm Required, const char *ResourceName, std::function Callback); void GetAccountSettingsAccess(LViewI *Parent, ScribeAccessType AccessType, std::function Callback); const char* EditCtrlMimeType(); LAutoString GetReplyXml(const char *MimeType); LAutoString GetForwardXml(const char *MimeType); bool GetHelpFilesPath(char *Path, int PathSize); bool LaunchHelp(const char *File); LAutoString ProcessSig(Mail *m, char *Xml, const char *MimeType); LString ProcessReplyForwardTemplate(Mail *m, Mail *r, char *Xml, int &Cursor, const char *MimeType); bool LogFilterActivity(); ScribeFolder *FindContainer(LDataFolderI *f); bool SaveDirtyObjects(int TimeLimitMs = 100); class LSpellCheck *CreateSpellObject(); class LSpellCheck *GetSpellThread(bool OverrideOpt = false); bool SetSpellThreadParams(LSpellCheck *Thread); void OnSpellerSettingChange(); bool OnMailTransferEvent(MailTransferEvent *e); LViewI *GetView() { return this; } char *GetUiTags(); struct MailStoreUpgradeParams { LAutoString OldFolders; LAutoString NewFolders; bool Quiet; LStream *Log; MailStoreUpgradeParams() { Quiet = false; Log = 0; } }; // Scripting support bool GetScriptCallbacks(LScriptCallbackType Type, LArray &Callbacks); LScriptCallback GetCallback(const char *CallbackMethodName); bool RegisterCallback(LScriptCallbackType Type, LScriptArguments &Args); LStream *ShowScriptingConsole(); bool ExecuteScriptCallback(LScriptCallback &c, LScriptArguments &Args, bool ReturnArgs = false); LScriptEngine *GetScriptEngine(); // Options LOptionsFile *GetOptions(bool Create = false) override; bool ScanForOptionsFiles(LArray &Inf, LSystemPath PathType); bool LoadOptions(); bool SaveOptions(); bool IsSending() { return false; } bool ShowToolbarText(); bool IsValid(); // Data events from storage back ends bool GetSystemPath(int Folder, LVariant &Path) override; void OnNew(LDataFolderI *parent, LArray &new_items, int pos, bool is_new) override; bool OnDelete(LDataFolderI *parent, LArray &items) override; bool OnMove(LDataFolderI *new_parent, LDataFolderI *old_parent, LArray &Items) override; void SetContext(const char *file, int line) override; bool OnChange(LArray &items, int FieldHint) override; void Post(LDataStoreI *store, void *Param) override { PostEvent(M_STORAGE_EVENT, store->Id, (LMessage::Param)Param); } void OnPropChange(LDataStoreI *store, int Prop, LVariantType Type) override; bool Match(LDataStoreI *store, LDataPropI *Addr, int Type, LArray &Matches) override; ContactGroup *FindGroup(char *Name); bool AddStore3EventHandler(LDataEventsI *callback); bool RemoveStore3EventHandler(LDataEventsI *callback); // --------------------------------------------------------------------- // Events void OnDelete(); int OnNotify(LViewI *Ctrl, LNotification n) override; void OnPaint(LSurface *pDC) override; LMessage::Result OnEvent(LMessage *Msg) override; int OnCommand(int Cmd, int Event, OsView Handle) override; void OnPulse() override; void OnPulseSecond(); bool OnRequestClose(bool OsShuttingDown) override; void OnSelect(List *l = 0, bool ChangeEvent = false); void OnReceiveFiles(LArray &Files) override; void OnUrl(const char *Url) override; void OnZoom(LWindowZoom Action) override; void OnCreate() override; void OnMinute(); void OnHour(); bool OnIdle(); void OnBayesAnalyse(const char *Msg, const char *WhiteListEmail) override; /// \returns true if spam bool OnBayesResult(const char *MailRef, double Rating) override; /// \returns true if spam bool OnBayesResult(Mail *m, double Rating); void OnScriptCompileError(const char *Source, Filter *f); }; //////////////////////////////////////////////////////////////////////////////////// #ifdef SCRIBE_APP #include "ScribePrivate.h" #endif diff --git a/Code/ScribeApp.cpp b/Code/ScribeApp.cpp --- a/Code/ScribeApp.cpp +++ b/Code/ScribeApp.cpp @@ -1,12701 +1,12704 @@ /* ** 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, GDataIt l, EmailAddressType CC) { while (Start && *Start) { LString Str; char *End = strchr(Start, ','); if (End) { Str.Set(Start, End-Start); Start = End + 1; } else { Str = Start; Start = 0; } if (Str) { ListAddr *a = new ListAddr(App); if (a) { a->CC = CC; if (_strnicmp(Str, "mailto:", 7) == 0) a->sAddr = Str(7,-1); else a->sAddr = Str; l->Insert(a); } } } } static char SoftwareUpdateUri[] = "http://www.memecode.com/update.php"; enum SoftwareStatus { SwError, SwCancel, SwOutOfDate, SwUpToDate }; static LString ExtractVer(const char *s) { char Buf[256], *Out = Buf; for (const char *In = s; *In && Out < Buf + sizeof(Buf) - 1; In++) { if (*In == ' ') break; if (IsDigit(*In) || *In == '.') *Out++ = *In; } *Out++ = 0; return LString(Buf); } void IsSoftwareUpToDate(LSoftwareUpdate::UpdateInfo &Info, ScribeWnd *Parent, bool WithUI, bool IncBetas, std::function callback) { // Software update? LAutoString Proxy = Parent->GetHttpProxy(); LSoftwareUpdate Update(AppName, SoftwareUpdateUri, Proxy); Update.CheckForUpdate( Info, WithUI?Parent:0, IncBetas, [Info, WithUI, Parent, callback](auto status, auto errorMsg) { if (status) { auto LocalVer = LString(ScribeVer).SplitDelimit("."); LString BuildVer = ExtractVer(Info.Build); LToken OnlineVer(BuildVer, "."); if (OnlineVer.Length() != LocalVer.Length()) { LgiTrace("%s:%i - Invalid online version number \"%s\"\n", _FL, Info.Version.Get()); if (callback) callback(SwError); return; } unsigned i; for (i=0; i o) { if (callback) callback(SwUpToDate); return; } } LDateTime Compile; LToken Date(__DATE__, " "); Compile.Month(LDateTime::MonthFromName(Date[0])); Compile.Day(atoi(Date[1])); Compile.Year(atoi(Date[2])); Compile.SetTime(__TIME__); bool DateGreaterThenCompile = Info.Date > Compile; if (callback) callback(DateGreaterThenCompile ? SwOutOfDate : SwUpToDate); return; } else if (WithUI) { if (Info.Cancel) { if (callback) callback(SwCancel); return; } LgiMsg(Parent, LLoadString(IDS_ERROR_SOFTWARE_UPDATE), AppName, MB_OK, errorMsg); } if (callback) callback(SwError); }); } bool UpgradeSoftware(const LSoftwareUpdate::UpdateInfo &Info, ScribeWnd *Parent, bool WithUI) { bool DownloadUpdate = true; if (WithUI) { char Ds[64]; Info.Date.Get(Ds, sizeof(Ds)); DownloadUpdate = LgiMsg(Parent, LLoadString(IDS_SOFTWARE_UPDATE_DOWNLOAD), AppName, MB_YESNO, (char*)Info.Build, (char*)Info.Uri, Ds) == IDYES; } if (!DownloadUpdate) return false; LAutoString Proxy = Parent->GetHttpProxy(); LSoftwareUpdate Update(AppName, SoftwareUpdateUri, Proxy, ScribeTempPath()); return Update.ApplyUpdate(Info, false, Parent); } void SoftwareUpdate(ScribeWnd *Parent, bool WithUI, bool IncBetas, std::function callback) { // Software update? LSoftwareUpdate::UpdateInfo Info; IsSoftwareUpToDate(Info, Parent, WithUI, IncBetas, [WithUI, Parent, Info, callback](auto s) { if (s == SwUpToDate) { if (WithUI) LgiMsg(Parent, LLoadString(IDS_SOFTWARE_CURRENT), AppName, MB_OK); if (callback) callback(false); // we're up to date } else if (s == SwOutOfDate) { auto status = UpgradeSoftware(Info, Parent, WithUI); if (callback) callback(status); // update is going to happen } }); } const char *AppName = "Scribe"; char HelpFile[] = "index.html"; const char OptionsFileName[] = "ScribeOptions"; const char AuthorEmailAddr[] = "fret@memecode.com"; const char AuthorHomepage[] = "http://www.memecode.com"; const char ApplicationHomepage[] = "http://www.memecode.com/scribe.php"; const char CommercialHomepage[] = "http://www.memecode.com/inscribe.php"; const char FaqHomepage[] = "http://www.memecode.com/scribe/faq.php"; const char *DefaultFolderNames[16]; Store3ItemTypes DefaultFolderTypes[] = { MAGIC_MAIL, // Inbox MAGIC_MAIL, // Outbox MAGIC_MAIL, // Sent MAGIC_ANY, // Trash MAGIC_CONTACT, // Contacts MAGIC_MAIL, // Templates MAGIC_FILTER, // Filters MAGIC_CALENDAR, // Calendar Events MAGIC_GROUP, // Groups MAGIC_MAIL, // Spam MAGIC_NONE, MAGIC_NONE, MAGIC_NONE, MAGIC_NONE }; extern void Log(char *File, char *Str, ...); ////////////////////////////////////////////////////////////////////////////// void LogMsg(char *str, ...) { #ifdef _DEBUG char f[256]; LMakePath(f, sizeof(f), LGetExePath(), "log.txt"); if (str) { char buffer[256]; va_list arg; va_start(arg ,str); vsprintf_s(buffer, sizeof(buffer), str, arg); va_end(arg); LFile File; while (!File.Open(f, O_WRITE)) { LSleep(5); } File.Seek(File.GetSize(), SEEK_SET); File.Write(buffer, strlen(buffer)); } else { FileDev->Delete(f, false); } #endif } LString GetFullAppName(bool Platform) { LString Ret = AppName; if (Platform) { LString s; const char *Build = #ifndef _DEBUG "Release"; #else "Debug"; #endif LArray Ver; int Os = LGetOs(&Ver); const char *OsName = LGetOsName(); if (Os == LGI_OS_WIN9X) { switch (Ver[1]) { case 0: OsName = "Win95"; break; case 10: OsName = "Win98"; break; case 90: OsName = "WinME"; break; } } else if (Os == LGI_OS_WIN32 || Os == LGI_OS_WIN64) { if (Ver[0] < 5) { OsName = "WinNT"; } else if (Ver[0] == 5) { if (Ver[1] == 0) OsName = "Win2k"; else OsName = "WinXP"; } else if (Ver[0] == 6) { if (Ver[1] == 0) OsName = "Vista"; else if (Ver[1] == 1) OsName = "Win7"; else if (Ver[1] == 2) OsName = "Win8"; else if (Ver[1] == 3) OsName = "Win8.1"; } else if (Ver[0] == 10) { OsName = "Win10"; } else if (Ver[0] == 11) { // What's the chances eh? OsName = "Win11"; } } s.Printf(" v%s (%s v", ScribeVer, OsName); Ret += s; for (unsigned i=0; iId); Ret += s; } s.Printf(")"); Ret += s; } return Ret; } bool MatchWord(char *Str, char *Word) { bool Status = false; if (Str && Word) { #define IsWord(c) ( IsDigit(c) || IsAlpha(c) ) for (char *s=stristr(Str, Word); s; s=stristr(s+1, Word)) { char *e = s + strlen(Word); if ( (s<=Str || !IsWord(s[-1]) ) && (e[0] == 0 || !IsWord(e[0])) ) { return true; } } } return Status; } ////////////////////////////////////////////////////////////////////////////// ScribePanel::ScribePanel(ScribeWnd *app, const char *name, int size, bool open) : LPanel(name, size, open) { App = app; } bool ScribePanel::Pour(LRegion &r) { if (App) { SetClosedSize(App->GetToolbarHeight()); } return LPanel::Pour(r); } ////////////////////////////////////////////////////////////////////////////// class NoContactType : public Contact { LString NoFace80Path; LString NoFace160Path; public: NoContactType(ScribeWnd *wnd) : Contact(wnd) { } Thing &operator =(Thing &c) override { return *this; } bool GetVariant(const char *Name, LVariant &Value, const char *Array) override { ScribeDomType Fld = StrToDom(Name); int Px = Array ? atoi(Array) : 80; LString &Str = Px == 160 ? NoFace160Path : NoFace80Path; if (!Str) { LString f; f.Printf("NoFace%i.png", Px); Str = LFindFile(f); LAssert(Str != NULL); // This should always resolve. } if (!Str) return false; if (Fld == SdImageHtml) { LString html; html.Printf("\n", Str.Get()); Value = html; return true; } else if (Fld == SdImage) { Value = Str; return true; } return false; } }; class ScribeWndPrivate : public LBrowser::LBrowserEvents, public LVmDebuggerCallback, public LHtmlStaticInst { LOptionsFile::PortableType InstallMode = LOptionsFile::UnknownMode; public: ScribeWnd *App; uint64 LastTs = 0; int ClipboardFormat = 0; LFont *PreviewFont = NULL; int PrintMaxPages = -1; int NewMailTimeout = -1; bool SendAfterReceive = false; bool IngoreOnClose = false; LAutoString UiTags; LAutoPtr Growl; LArray TrayMenuContacts; bool ExitAfterSend = false; LToolButton *ShowConsoleBtn = NULL; LString MulPassword; LBox *SubSplit = NULL, *SearchSplit = NULL; LArray ThingSources; int LastLayout = 0; LMenuItem *DisableUserFilters = NULL; LOptionsFile *Options = NULL; HttpImageThread *ImageLoader = NULL; int LastMinute = -1, LastHour = -1; LArray Store3EventCallbacks; LAutoPtr PrintOptions; LHashTbl, LString> ResFiles; // These are for the LDataEventsI callbacks to store source context // Mainly for debugging where various events came from. const char *CtxFile = NULL; int CtxLine = 0; // Contact no face images LAutoRefPtr NoContact; // Remote content white/blacklists bool RemoteContent_Init = false; LString::Array RemoteWhiteLst, RemoteBlackLst; // Spell checking int AppWndHnd; LAutoPtr SpellerThread; // Missing caps LCapabilityTarget::CapsHash MissingCaps; MissingCapsBar *Bar = NULL; LString ErrSource; // Script file that has an error. Filter *ErrFilter = NULL; // Filter that has scripting error. // Load state bool FoldersLoaded = false; // Bayesian filter LStringPipe BayesLog; // Thread item processing LArray Transfers; // Scripting... LAutoPtr Engine; LArray Scripts; LArray CurrentScripts; LScript *CurrentScript() { return CurrentScripts.Length() ? CurrentScripts.Last() : NULL; } int NextToolMenuId = IDM_TOOL_SCRIPT_BASE; LAutoPtr ScriptToolbar; LArray OnSecondTimerCallbacks; // Encryption LAutoPtr GpgInst; // Unit tests LAutoPtr UnitTestServer; class ScribeTextControlFactory : public LViewFactory { ScribeWnd *Wnd; LView *NewView(const char *Class, LRect *Pos, const char *Text) { if (!_stricmp(Class, "ScribeTextView")) return Wnd->CreateTextControl(-1, 0, true); return NULL; } public: ScribeTextControlFactory(ScribeWnd *wnd) { Wnd = wnd; } } TextControlFactory; ScribeWndPrivate(ScribeWnd *app) : App(app), TextControlFactory(app) { NoContact = new NoContactType(app); NoContact->DecRef(); // 2->1 AppWndHnd = LEventSinkMap::Dispatch.AddSink(App); #ifdef WIN32 ClipboardFormat = RegisterClipboardFormat( #ifdef UNICODE L"Scribe.Item" #else "Scribe.Item" #endif ); #endif LScribeScript::Inst = new LScribeScript(App); if (Engine.Reset(new LScriptEngine(App, LScribeScript::Inst, this))) Engine->SetConsole(LScribeScript::Inst->GetLog()); } ~ScribeWndPrivate() { // Why do we need this? ~LView will take care of it? // LEventSinkMap::Dispatch.RemoveSink(App); DeleteObj(Options); Scripts.DeleteObjects(); DeleteObj(ImageLoader); Engine.Reset(); DeleteObj(LScribeScript::Inst); } LGrowl *GetGrowl() { if (!Growl && Growl.Reset(new LGrowl)) { LAutoPtr r(new LGrowl::LRegister); r->App = "Scribe"; r->IconUrl = "http://memecode.com/images/scribe/growl-app.png"; LGrowl::LNotifyType &NewMail = r->Types.New(); NewMail.Name = "new-mail"; NewMail.IconUrl = "http://memecode.com/images/scribe/growl-new-mail.png"; NewMail.Enabled = true; LGrowl::LNotifyType &Cal = r->Types.New(); Cal.Name = "calendar"; Cal.IconUrl = "http://memecode.com/images/scribe/growl-calendar.png"; Cal.Enabled = true; LGrowl::LNotifyType &Debug = r->Types.New(); Debug.Name = "debug"; Debug.IconUrl = "http://memecode.com/images/scribe/growl-bug.png"; Debug.Enabled = false; LGrowl::LNotifyType &Info = r->Types.New(); Info.IconUrl = "http://memecode.com/images/scribe/growl-note.png"; Info.Name = "info"; Info.Enabled = true; Growl->Register(r); } return Growl; } LVmDebugger *AttachVm(LVirtualMachine *Vm, LCompiledCode *Code, const char *Assembly) { LVariant v; if (Options) Options->GetValue(OPT_ScriptDebugger, v); if (v.CastInt32()) return new LVmDebuggerWnd(App, this, Vm, Code, NULL); return NULL; } bool CompileScript(LAutoPtr &Output, const char *FileName, const char *Source) { LCompiler c; return c.Compile(Output, Engine->GetSystemContext(), LScribeScript::Inst, FileName, Source, NULL); } bool OnSearch(LBrowser *br, const char *txt) { char Path[256]; if (!App->GetHelpFilesPath(Path, sizeof(Path))) return false; LToken Terms(txt, ", "); LStringPipe p; p.Print("\n

Search Results

\n
    \n"); LDirectory Dir; for (int b = Dir.First(Path, "*.html"); b; b = Dir.Next()) { if (!Dir.IsDir()) { char Path[256]; Dir.Path(Path, sizeof(Path)); LFile f; if (f.Open(Path, O_READ)) { LXmlTree t(GXT_NO_DOM); LXmlTag r; if (t.Read(&r, &f)) { char *PrevName = 0; char PrevUri[256] = ""; for (auto c: r.Children) { if (c->IsTag("a")) { char *Name = c->GetAttr("name"); if (Name) { PrevName = Name; } } else if (c->GetContent()) { bool Hit = false; for (unsigned i=0; !Hit && iGetContent(), Terms[i]) != 0; } if (Hit) { LStringPipe Uri(256); char *Leaf = strrchr(Path, DIR_CHAR); Leaf = Leaf ? Leaf + 1 : Path; Uri.Print("file://%s", Path); if (PrevName) Uri.Print("#%s", PrevName); LAutoString UriStr(Uri.NewStr()); if (_stricmp(UriStr, PrevUri)) { p.Print("
  • %s", UriStr.Get(), Leaf); if (PrevName) p.Print("#%s", PrevName); p.Print("\n"); strcpy_s(PrevUri, sizeof(PrevUri), UriStr); } } } } } } } } p.Print("
\n\n\n"); LAutoString Html(p.NewStr()); br->SetHtml(Html); return true; } void AskUserForInstallMode(std::function callback) { auto Dlg = new LAlert(App, AppName, LLoadString(IDS_PORTABLE_Q), LLoadString(IDS_HELP), LLoadString(IDS_DESKTOP), LLoadString(IDS_PORTABLE)); Dlg->SetButtonCallback(1, [&](auto idx) { App->LaunchHelp("install.html"); }); Dlg->DoModal([callback](auto dlg, auto Btn) { if (Btn == 1) { // Help LAssert(!"Help btn should use callback."); } else if (Btn == 2) { // Desktop if (callback) callback(LOptionsFile::DesktopMode); } else if (Btn == 3) { // Portable if (callback) callback(LOptionsFile::PortableMode); } else { delete dlg; LAppInst->Exit(1); } delete dlg; }); } LOptionsFile::PortableType GetInstallMode() { if (InstallMode == LOptionsFile::UnknownMode) { if (LAppInst->GetOption("portable")) { InstallMode = LOptionsFile::PortableMode; LgiTrace("Selecting portable mode based on -portable switch.\n"); } else if (LAppInst->GetOption("desktop")) { InstallMode = LOptionsFile::DesktopMode; LgiTrace("Selecting portable mode based on -desktop switch.\n"); } } if (InstallMode == LOptionsFile::UnknownMode) { bool PortableIsPossible = true; char Inst[MAX_PATH_LEN] = ""; LGetSystemPath(LSP_APP_INSTALL, Inst, sizeof(Inst)); // Do write check char Wr[MAX_PATH_LEN]; LMakePath(Wr, sizeof(Wr), Inst, "_write_test.txt"); LFile f; if (f.Open(Wr, O_WRITE)) { // Clean up f.Close(); FileDev->Delete(Wr, false); } else { // No write perms PortableIsPossible = false; } if (PortableIsPossible && LAppInst->IsElevated()) { // Check if the install is in some read only location: // e.g. c:\Program Files char Pm[MAX_PATH_LEN]; if (LGetSystemPath(LSP_USER_APPS, Pm, sizeof(Pm))) { size_t n = strlen(Pm); PortableIsPossible = _strnicmp(Pm, Inst, n) != 0; // LgiMsg(App, "%i\n%s\n%s", AppName, MB_OK, PortableIsPossible, Pm, Inst); } else LgiTrace("%s:%i - Failed to get paths.", _FL); } if (PortableIsPossible) { // 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 LString App = GetFullAppName(true); printf("%s\n", App.Get()); #endif LgiTrace("const this=%p\n", this); auto AppConstruct1 = [this]() { if (!d->Options && !LoadOptions()) { ScribeState = ScribeExiting; return; } ScribeOptionsDefaults(d->Options); LVariant GlyphSub; if (GetOptions()->GetValue(OPT_GlyphSub, GlyphSub)) { bool UseGlyphSub = GlyphSub.CastInt32() != 0; LSysFont->SubGlyphs(UseGlyphSub); LSysBold->SubGlyphs(UseGlyphSub); LFontSystem::Inst()->SetDefaultGlyphSub(UseGlyphSub); } else { GetOptions()->SetValue(OPT_GlyphSub, GlyphSub = LFontSystem::Inst()->GetDefaultGlyphSub()); } { // Limit the size of the 'Scribe.txt' log file char p[MAX_PATH_LEN]; if (LgiTraceGetFilePath(p, sizeof(p))) { int64 Sz = LFileSize(p); #define MiB * 1024 * 1024 if (Sz > (3 MiB)) FileDev->Delete(p); } } // Process pre-UI options LVariant SizeAdj; int SzAdj = SizeAdj.CastInt32(); if (GetOptions()->GetValue(OPT_UiFontSize, SizeAdj) && (SzAdj = SizeAdj.CastInt32()) >= 0 && SzAdj < 5) { SzAdj -= 2; if (SzAdj) { int Pt = LSysFont->PointSize(); LSysFont->PointSize(Pt + SzAdj); LSysFont->Create(); LSysBold->PointSize(Pt + SzAdj); LSysBold->Create(); LFont *m = LMenu::GetFont(); if (m) { m->PointSize(m->PointSize() + SzAdj); m->Create(); } } } else { GetOptions()->SetValue(OPT_UiFontSize, SizeAdj = 2); } // Resources and languages auto SetLanguage = [this]() { LVariant LangId; if (GetOptions()->GetValue(OPT_UiLanguage, LangId)) { // Set the language to load... LAppInst->SetConfig("Language", LangId.Str()); } LResources::SetLoadStyles(true); // Load the resources (with the current lang) if (!LgiGetResObj(true, "Scribe")) { LgiMsg(NULL, "The resource file 'Scribe.lr8' is missing.", AppName); ScribeState = ScribeExiting; LCloseApp(); } }; SetLanguage(); auto AppConstruct2 = [this]() { #if 1 auto CurRes = LgiGetResObj(false); LVariant Theme; if (CurRes && GetOptions()->GetValue(OPT_Theme, Theme)) { auto Paths = ScribeThemePaths(); auto NoTheme = LLoadString(IDS_DEFAULT); if (Theme.Str() && Stricmp(NoTheme, Theme.Str())) { for (auto p: Paths) { LFile::Path Inst(p); Inst += Theme.Str(); if (Inst.Exists()) { CurRes->SetThemeFolder(Inst); d->Static->OnSystemColourChange(); break; } } } } #endif LoadCalendarStringTable(); ZeroObj(DefaultFolderNames); DefaultFolderNames[FOLDER_INBOX] = LLoadString(IDS_FOLDER_INBOX, "Inbox"); DefaultFolderNames[FOLDER_OUTBOX] = LLoadString(IDS_FOLDER_OUTBOX, "Outbox"); DefaultFolderNames[FOLDER_SENT] = LLoadString(IDS_FOLDER_SENT, "Sent"); DefaultFolderNames[FOLDER_TRASH] = LLoadString(IDS_FOLDER_TRASH, "Trash"); DefaultFolderNames[FOLDER_CONTACTS] = LLoadString(IDS_FOLDER_CONTACTS, "Contacts"); DefaultFolderNames[FOLDER_TEMPLATES] = LLoadString(IDS_FOLDER_TEMPLATES, "Templates"); DefaultFolderNames[FOLDER_FILTERS] = LLoadString(IDS_FOLDER_FILTERS, "Filters"); DefaultFolderNames[FOLDER_CALENDAR] = LLoadString(IDS_FOLDER_CALENDAR, "Calendar"); DefaultFolderNames[FOLDER_GROUPS] = LLoadString(IDS_FOLDER_GROUPS, "Groups"); DefaultFolderNames[FOLDER_SPAM] = LLoadString(IDS_SPAM, "Spam"); LStringPipe RfXml; RfXml.Print(DefaultRfXml, LLoadString(IDS_ORIGINAL_MESSAGE), LLoadString(FIELD_TO), LLoadString(FIELD_FROM), LLoadString(FIELD_SUBJECT), LLoadString(IDS_DATE)); { LAutoString Xml(RfXml.NewStr()); UpgradeRfOption(this, OPT_TextReplyFormat, "ReplyXml", Xml); UpgradeRfOption(this, OPT_TextForwardFormat, "ForwardXml", Xml); } LFontType t; if (t.GetSystemFont("small")) { d->PreviewFont = t.Create(); if (d->PreviewFont) { #if defined WIN32 d->PreviewFont->PointSize(8); #endif } } MoveOnScreen(); // Load global graphics LoadImageResources(); // Load time threads // Window name Name(AppName); SetSnapToEdge(true); ClearTempPath(); #if WINNATIVE SetStyle(GetStyle() & ~WS_VISIBLE); SetExStyle(GetExStyle() & ~WS_EX_ACCEPTFILES); CreateClassW32(AppName, LoadIcon(LProcessInst(), MAKEINTRESOURCE(IDI_APP))); #endif #if defined LINUX SetIcon("About64px.png"); LFinishXWindowsStartup(this); #endif ScribeState = ScribeInitializing; #if LGI_VIEW_HANDLE if (Handle()) #endif OnCreate(); }; // If no language set... LVariant LangId; if (!GetOptions()->GetValue(OPT_UiLanguage, LangId)) { // Ask the user... auto Dlg = new LanguageDlg(this); if (!Dlg->Ok) { delete Dlg; LgiMsg(this, "Failed to create language selection dialog.", "Scribe Error"); ScribeState = ScribeExiting; LCloseApp(); } else { Dlg->DoModal([this, Dlg, SetLanguage, AppConstruct2](auto dlg, auto id) { if (id) { // Set the language in the options file LVariant v; GetOptions()->SetValue(OPT_UiLanguage, v = Dlg->Lang.Get()); // Reload the resource file... to get the new lang. LResources *Cur = LgiGetResObj(false); DeleteObj(Cur); SetLanguage(); AppConstruct2(); } else // User canceled { ScribeState = ScribeExiting; LCloseApp(); } delete dlg; }); } } else AppConstruct2(); }; auto Type = d->GetInstallMode(); if (Type == LOptionsFile::UnknownMode) { LoadOptions(); // This may make the mode more clear... Type = d->GetInstallMode(); } if (Type == LOptionsFile::UnknownMode) { d->AskUserForInstallMode([this, AppConstruct1](auto selectedMode) { d->SetInstallMode(selectedMode); AppConstruct1(); }); } else AppConstruct1(); } int StrSort(char *a, char *b, int d) { return _stricmp(a, b); } ScribeWnd::~ScribeWnd() { LAppInst->AppWnd = 0; SearchView = NULL; ScribeState = ScribeExiting; LScribeScript::Inst->ShowScriptingWindow(false); // Other cleanup... ClearTempPath(); ShutdownIpc(); SetPulse(); // Save anything thats still dirty in the folders... // just in case we crash during the shutdown phase. ScribeFolder *Cur = GetCurrentFolder(); if (Cur) Cur->SerializeFieldWidths(); SaveDirtyObjects(5000); // Tell the UI not to reference anything in the folders if (PreviewPanel) { PreviewPanel->OnThing(0, false); } Mail::NewMailLst.Empty(); // ~AccountStatusItem references the account list... must be before we // delete the accounts. DeleteObj(StatusPanel); // ~Accountlet needs to reference the root container... so // it has to go before unloading of folders. Accounts.DeleteObjects(); UnLoadFolders(); DeleteObj(PreviewPanel); SaveOptions(); DeleteObj(Commands); DeleteObj(d->PreviewFont); DeleteObj(d->SubSplit); DeleteObj(Splitter); MailList = NULL; CmdSend.ToolButton = NULL; CmdReceive.ToolButton = NULL; CmdPreview.ToolButton = NULL; CmdSend.MenuItem = NULL; CmdReceive.MenuItem = NULL; CmdPreview.MenuItem = NULL; // This could be using the OpenSSL library for HTTPS connections. So // close it before calling EndSSL. DeleteObj(d->ImageLoader); // This has to be after we close all the accounts... otherwise // they might still be using SSL functions, e.g. an IMAP/SSL connect. EndSSL(); DeleteObj(d); } LString ScribeWnd::GetResourceFile(SribeResourceType Type) { auto File = d->ResFiles.Find(Type); if (!File) LgiTrace("%s:%i - No file for resource type %i\n", _FL, Type); return File; } void ScribeWnd::LoadImageResources() { auto Res = LgiGetResObj(); LString::Array Folders; if (Res) { auto p = Res->GetThemeFolder(); if (p) Folders.Add(p); } Folders.Add(ScribeResourcePath()); for (auto p: Folders) { LDirectory Dir; LgiTrace("%s:%i - Loading resource folder '%s'\n", _FL, p.Get()); for (auto b = Dir.First(p); b; b = Dir.Next()) { if (Dir.IsDir()) continue; auto Name = Dir.GetName(); if (MatchStr("Toolbar-*.png", Name)) { if (!d->ResFiles.Find(ResToolbarFile)) d->ResFiles.Add(ResToolbarFile, Dir.FullPath()); } else if (MatchStr("xgate-icons-*.png", Name)) d->ResFiles.Add(ResToolbarFile, Dir.FullPath()); else if (MatchStr("Icons-*.png", Name)) d->ResFiles.Add(ResIconsFile, Dir.FullPath()); } } ToolbarImgs.Reset(LLoadImageList(GetResourceFile(ResToolbarFile))); ImageList.Reset(LLoadImageList(GetResourceFile(ResIconsFile))); if (!ImageList) LgiTrace("%s:%i - Failed to load toolbar image ('xgate-icons-32.png' or 'Toolbar-24.png')\n", _FL); } int ScribeWnd::GetEventHandle() { return d->AppWndHnd; } void ScribeWnd::OnCloseInstaller() { d->Bar = NULL; if (InThread()) { PourAll(); } else LAssert(0); } void ScribeWnd::OnInstall(CapsHash *Caps, bool Status) { } bool ScribeWnd::NeedsCapability(const char *Name, const char *Param) { #if DEBUG_CAPABILITIES LgiTrace("ScribeWnd::NeedsCapability(%s, %s)\n", Name, Param); #endif if (!InThread()) { #if DEBUG_CAPABILITIES LgiTrace("%s:%i - Posting M_NEEDS_CAP\n", _FL); #endif PostEvent(M_NEEDS_CAP, (LMessage::Param)NewStr(Name), (LMessage::Param)NewStr(Param)); } else { if (!Name) return false; if (d->MissingCaps.Find(Name)) { #if DEBUG_CAPABILITIES LgiTrace("%s:%i - Already in MissingCaps\n", _FL); #endif return true; } d->MissingCaps.Add(Name, true); LStringPipe MsgBuf(256); int i = 0; // const char *k; // for (bool b=d->MissingCaps.First(&k); b; b=d->MissingCaps.Next(&k), i++) for (auto k : d->MissingCaps) { MsgBuf.Print("%s%s", i?", ":"", k.key); } LVariant Actions; if (stristr(Name, "OpenSSL")) { MsgBuf.Print(LLoadString(IDS_ERROR_SERVER_CONNECT)); if (Param) MsgBuf.Print("\n%s", Param); Actions.Add(new LVariant(LLoadString(IDS_INSTALL))); } else if (stristr(Name, "Registry")) { MsgBuf.Print(LLoadString(IDS_ERROR_REG_WRITE)); Actions.Add(new LVariant(LLoadString(IDS_DONT_SHOW_AGAIN))); } else if (stristr(Name, "SpellingDictionary")) { MsgBuf.Print(LLoadString(IDS_ERROR_NEED_INSTALL), Param); Actions.Add(new LVariant(LLoadString(IDS_DOWNLOAD))); } Actions.Add(new LVariant(LLoadString(IDS_OK))); #if DEBUG_CAPABILITIES LgiTrace("%s:%i - Actions.Length()=%i, Bar=%p\n", _FL, Actions.Length(), d->Bar); #endif if (Actions.Length()) { LAutoString Msg(MsgBuf.NewStr()); // Check the script hook here... bool ShowInstallBar = true; LArray Callbacks; if (GetScriptCallbacks(LBeforeInstallBar, Callbacks)) { for (unsigned i=0; iCastInt32()) ShowInstallBar = false; else Msg.Reset(TheMsg.ReleaseStr()); } } } // Now create the capability install bar... if (!d->Bar && ShowInstallBar && Actions.Type == GV_LIST) { // FYI Capabilities are handled in ScribeWnd::StartAction. LArray Act; for (auto v : *Actions.Value.Lst) Act.Add(v->Str()); d->Bar = new MissingCapsBar(this, &d->MissingCaps, Msg, this, Act); AddView(d->Bar, 2); AttachChildren(); OnPosChange(); } } } return true; } LAutoString ScribeWnd::GetDataFolder() { LVariant v; GetOptions()->GetValue(OPT_IsPortableInstall, v); char p[MAX_PATH_LEN]; if (LGetSystemPath(v.CastInt32() ? LSP_APP_INSTALL : LSP_APP_ROOT, p, sizeof(p))) { if (!LDirExists(p)) FileDev->CreateFolder(p); return LAutoString(NewStr(p)); } else LgiTrace("%s:%i - LgiGetSystemPath failed (portable=%i).\n", _FL, v.CastInt32()); return LAutoString(); } LScriptEngine *ScribeWnd::GetScriptEngine() { return d->Engine; } LScriptCallback ScribeWnd::GetCallback(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() { 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; }); } ScribeAccount *ScribeWnd::GetAccountByEmail(const char *Email) { if (!Email) return NULL; for (auto a : *GetAccounts()) { LVariant e = a->Identity.Email(); if (e.Str() && !_stricmp(e.Str(), Email)) { return a; } } return 0; } ScribeAccount *ScribeWnd::GetAccountById(int Id) { for (auto a : *GetAccounts()) { if (a->Receive.Id() == Id) { return a; } } return 0; } const char *ScribeWnd::EditCtrlMimeType() { LVariant Html; GetOptions()->GetValue(OPT_EditControl, Html); return Html.CastInt32() ? sTextHtml : sTextPlain; } LAutoString ScribeWnd::GetReplyXml(const char *MimeType) { bool IsHtml = MimeType && !_stricmp(MimeType, sTextHtml); LVariant s; GetOptions()->GetValue(IsHtml ? OPT_HtmlReplyFormat : OPT_TextReplyFormat, s); return LAutoString(s.ReleaseStr()); } LAutoString ScribeWnd::GetForwardXml(const char *MimeType) { bool IsHtml = MimeType && !_stricmp(MimeType, sTextHtml); LVariant s; GetOptions()->GetValue(IsHtml ? OPT_HtmlForwardFormat : OPT_TextForwardFormat, s); return LAutoString(s.ReleaseStr()); } LVmDebuggerCallback *ScribeWnd::GetDebuggerCallback() { return d; } GpgConnector *ScribeWnd::GetGpgConnector() { if (!d->GpgInst) { if (!GpgConnector::IsInstalled()) return NULL; d->GpgInst.Reset(new GpgConnector()); } return d->GpgInst; } LFont *ScribeWnd::GetPreviewFont() { return d->PreviewFont; } bool ScribeWnd::IsValid() { #if 0 try { for (ScribeAccount *a = Accounts.First(); a; a = Accounts.Next()) { } } catch(...) { return false; } #endif return true; } bool ScribeWnd::ShutdownIpc() { // Remove our instance from the shared memory if (ScribeIpc && ScribeIpc->GetPtr()) { ScribeIpcInstance *InstLst = (ScribeIpcInstance*) ScribeIpc->GetPtr(); if (ThisInst) memset(ThisInst, 0, sizeof(*ThisInst)); int c = 0; for (int i=0; iDestroy(); } ThisInst = 0; DeleteObj(ScribeIpc); return true; } bool ScribeWnd::GetVariant(const char *Name, LVariant &Value, const char *Array) { ScribeDomType Fld = StrToDom(Name); switch (Fld) { case SdQuote: // Type: String { return GetOptions()->GetValue(OPT_QuoteReplyStr, Value); } case SdName: // Type: String { Value = AppName; break; } case SdHome: // Type: String { Value = LGetExePath().Get(); break; } case SdNow: // Type: String { LDateTime Now; Now.SetNow(); char s[64]; Now.Get(s, sizeof(s)); Value = s; break; } case SdFolder: // Type: ScribeFolder[] // Pass system folder index or string as array parameter. { ScribeFolder *f = 0; if (!Array) return false; if (IsDigit(*Array)) { f = GetFolder(atoi(Array)); } else { f = GetFolder(Array); } if (!f) return false; Value = (LDom*)f; break; } case SdAppName: // Type: String { Value = AppName; break; } case SdCalendarToday: // Type: String { return Calendar::SummaryOfToday(this, Value); } case SdInboxSummary: { LStringPipe p; // Iterate through the mail stores for (auto m: Folders) { if (!m.Store) continue; auto Inbox = m.Store->GetObj(FIELD_INBOX); if (!Inbox) continue; auto Unread = Inbox->GetInt(FIELD_UNREAD); auto Name = Inbox->GetStr(FIELD_FOLDER_NAME); LString Path; Path.Printf("/%s/%s", m.Name.Get(), Name); if (Unread) p.Print("%s: %i unread
", m.Name.Get(), Path.Get(), Unread); else p.Print("%s: 0 unread
", m.Name.Get()); } // And also the IMAP full folders for (auto a: Accounts) { ScribeProtocol Protocol = a->Receive.ProtocolType(); if (Protocol == ProtocolImapFull) { LDataStoreI *Store = a->Receive.GetDataStore(); if (Store) { auto Inbox = Store->GetObj(FIELD_INBOX); if (Inbox) { auto Unread = Inbox->GetInt(FIELD_UNREAD); auto Name = Inbox->GetStr(FIELD_FOLDER_NAME); auto m = a->Receive.Name(); if (m.Str()) { LString Path; Path.Printf("/%s/%s", m.Str(), Name); if (Unread) p.Print("%s: %i unread
", m.Str(), Path.Get(), Unread); else p.Print("%s: 0 unread
", m.Str()); } } } } } Value = p.NewGStr().Get(); break; } case SdExecute: // Type: String { if (!Array) return false; const char *s = Array; char *Exe = LTokStr(s); if (!Exe) return false; while (*s && *s == ' ') s++; LStringPipe Out; LSubProcess p(Exe, (char*)s); if (p.Start()) { p.Communicate(&Out); LAutoString o(Out.NewStr()); LAutoString t(TrimStr(o)); Value = t; } DeleteArray(Exe); break; } case SdBuildType: // Type: String { #ifdef _DEBUG Value = "Debug"; #else Value = "Release"; #endif break; } case SdPlatform: // Type: String { LArray Ver; LGetOs(&Ver); LString::Array Va; for (auto i: Ver) Va.New().Printf("%i", i); #if defined __GTK_H__ auto Api = "GTK3"; #elif LGI_SDL auto Api = "SDL"; #elif LGI_COCOA auto Api = "Cocoa"; #elif LGI_CARBON auto Api = "Carbon"; #elif defined WIN32 auto Api = "WinApi"; #else #error "Impl me." auto Api = "#err"; #endif LString s; s.Printf("%s, v%s, %s", LGetOsName(), LString(".").Join(Va).Get(), Api); Value = s.Get(); break; } case SdVersion: // Type: String { char Ver[32]; sprintf_s(Ver, sizeof(Ver), "v%s", ScribeVer); Value = Ver; break; } case SdBuild: // Type: String { char s[128]; sprintf_s(s, sizeof(s), "%s, %s, %ibit", __DATE__, __TIME__, (int)(sizeof(NativeInt)*8)); Value = s; break; } case SdLanguage: // Type: String { LLanguage *l = LGetLanguageId(); if (!l) return false; char s[256]; sprintf_s(s, sizeof(s), "%s \"%s\"", l->Name, l->Id); Value = s; break; } case SdString: // Type: String { if (!Array) { LAssert(!"Missing string ID"); return false; } int Id = atoi(Array); Value = LLoadString(Id); break; } case SdCurrentFolder: // Type: ScribeFolder { Value = (LDom*) GetCurrentFolder(); break; } case SdView: // Type: LView { Value = (LView*)this; break; } case SdNoContact: // Type: Contact { Value = (NoContactType*)d->NoContact; break; } case SdAccounts: { if (Array) { if (IsDigit(*Array)) { auto i = atoi(Array); if (i >= 0 && i < (ssize_t)Accounts.Length()) { Value = (LDom*)Accounts[i]; } else return false; } else { for (auto a : Accounts) { LVariant nm = a->Send.Name(); if (nm.Str() && !_stricmp(nm.Str(), Array)) { Value = (LDom*)a; break; } } } } else { Value = (int32)Accounts.Length(); } break; } case SdOptions: { Value = GetOptions(); break; } case SdMailStorePaths: { if (!Value.SetList()) return false; for (auto Ms : Folders) Value.Add(new LVariant(Ms.Path)); break; } case SdRootFolders: { if (!Value.SetList() || !Tree) return false; for (auto *i = Tree->GetChild(); i; i = i->GetNext()) { ScribeFolder *c = dynamic_cast(i); if (c) { auto p = c->GetPath(); Value.Add(new LVariant(p)); } } break; } default: { return false; } } return true; } bool ScribeWnd::CallMethod(const char *MethodName, LVariant *ReturnValue, LArray &Args) { ScribeDomType m = StrToDom(MethodName); switch (m) { case SdGrowlOnMail: // Type: (Mail Obj) { if (Args.Length() != 1) { LgiTrace("%s:%i - Wrong arg count: %i.\n", _FL, (int)Args.Length()); return false; } Mail *m = dynamic_cast(Args[0]->CastDom()); if (!m) { LgiTrace("%s:%i - Invalid object.\n", _FL); return false; } GrowlOnMail(m); break; } case SdGrowlInfo: { auto Title = Args.Length() > 0 ? Args[0] : NULL; auto Text = Args.Length() > 1 ? Args[1] : NULL; GrowlInfo(Title ? Title->Str() : NULL, Text ? Text->Str() : NULL); break; } case SdGetClipboardText: // Type: () { LClipBoard c(this); *ReturnValue = c.Text(); break; } case SdSetClipboardText: // Type: (String Text) { if (Args.Length() != 1) { LgiTrace("%s:%i - Wrong arg count: %i.\n", _FL, (int)Args.Length()); return false; } char *Str = Args[0]->CastString(); LClipBoard c(this); if (ValidStr(Str)) *ReturnValue = c.Text(Str); else *ReturnValue = c.Empty(); break; } case SdLookupContactGroup: // Type: (String GroupName) { if (Args.Length() != 1) { LgiTrace("%s:%i - Wrong arg count: %i.\n", _FL, (int)Args.Length()); return false; } ContactGroup *Grp = LookupContactGroup(this, Args[0]->Str()); *ReturnValue = dynamic_cast(Grp); break; } case SdGetUserString: // Type: (LView ParentView, String PromptMessage[, Bool ObsurePassword[, String DefaultValue]]) { if (Args.Length() < 2) return false; LView *View = Args[0]->CastView(); char *Prompt = Args[1]->CastString(); bool Pass = Args.Length() > 2 ? Args[2]->CastInt32() != 0 : false; char *Default = Args.Length() > 3 ? Args[3]->CastString() : NULL; 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); } 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.NewGStr(); LgiMsg(this, "Saving options failed:\n%s", AppName, MB_OK, a.Get()); } if (!WndStateSet) { LRect r(10, 10, 790, 590); SetPos(r); MoveToCenter(); } return Status; } // // Command Line Options: // // -m, -t : To recipient(s) // -f : The filename of the attachment // -b : Attach as a binary // -c : CC'd recipient(s) // -s : Subject for the email // -n : Send now... else UI is shown // -p : Print the file // -upgrade_folders : trigger a folder upgrade // -o : Load the following options file // -u : Load the following URL/file // void ScribeWnd::OnCommandLine() { // check command line args LString Str, File; bool CreateMail = false; CreateMail = LAppInst->GetOption("m", Str); if (!CreateMail) CreateMail = LAppInst->GetOption("t", Str); bool HasFile = LAppInst->GetOption("f", File); if (!CreateMail) CreateMail = HasFile; LString OpenArg; if (LAppInst->GetOption("u", OpenArg)) { LUri u(OpenArg); if (u.sProtocol) { OnUrl(OpenArg); } else if (LFileExists(OpenArg)) { LArray Files; Files.Add(OpenArg); OnReceiveFiles(Files); } } Mail *NewEmail = 0; if (CreateMail && Str) { // strip off quotes if needed char *In = Str, *Out = Str; for (; In && *In; In++) { if (!strchr("\'\"", *In)) { *Out++ = *In; } } *Out++ = 0; // create object NewEmail = dynamic_cast(CreateItem(MAGIC_MAIL, NULL, false)); if (NewEmail) { Mailto mt(this, Str); mt.Apply(NewEmail); // cc's? if (LAppInst->GetOption("c", Str)) { SetRecipients(this, Str, NewEmail->GetObject()->GetList(FIELD_TO), MAIL_ADDR_CC); } // attach a file? if (File) { if (LAppInst->GetOption("b")) { // attach as a binary file NewEmail->AttachFile(this, &File[0]); } else { // insert as the body LAutoString b(LReadTextFile(&File[0])); if (b) { NewEmail->SetBody(b); } } } // subject? if (LAppInst->GetOption("s", Str)) { NewEmail->SetSubject(Str); } // Send now or later? if (LAppInst->GetOption("n")) { // Check for exit after send option d->ExitAfterSend = LAppInst->GetOption("exit"); // now NewEmail->SetFlags(MAIL_CREATED | MAIL_READY_TO_SEND | NewEmail->GetFlags()); NewEmail->Save(); OnCommand(IDM_SEND_MAIL, 0, #ifndef __GTK_H__ Handle() #else NULL #endif ); } else { // later NewEmail->DoUI(); } } } // Pop3 on startup option LVariant n; if (GetOptions()->GetValue(OPT_Pop3OnStart, n) && n.CastInt32()) { OnCommand(IDM_RECEIVE_MAIL, 0, NULL); } } void ScribeWnd::SetCurrentIdentity(int i) { LVariant v = i; GetOptions()->SetValue(OPT_CurrentIdentity, v); if (DefaultIdentityItem) DefaultIdentityItem->Checked(i < 0); for (auto a: Accounts) { a->SetCheck(i == a->GetIndex()); } } ScribeAccount *ScribeWnd::GetCurrentAccount() { auto Idx = GetCurrentIdentity(); ScribeAccount *a = (Idx >= 0 && Idx < (ssize_t)Accounts.Length()) ? Accounts.ItemAt(Idx) : NULL; bool ValidId = a != NULL && a->IsValid(); if (!ValidId) { LAssert(!"No current identity?"); // Find a valid account to be the identity... for (auto a : Accounts) { if (!a->Send.Disabled() && a->Identity.IsValid()) { break; } } } return a; } int ScribeWnd::GetCurrentIdentity() { LVariant i; if (GetOptions()->GetValue(OPT_CurrentIdentity, i)) return i.CastInt32(); else if (ScribeState != ScribeInitializing) LgiTrace("%s:%i - No OPT_CurrentIdentity set.\n", _FL); return -1; } void ScribeWnd::SetupAccounts() { int i, CurrentIdentity = GetCurrentIdentity(); if (StatusPanel) { StatusPanel->Empty(); } #if !defined(COCOA) // FIXME LAssert(ReceiveMenu && PreviewMenu); #endif if (SendMenu) SendMenu->Empty(); if (ReceiveMenu) ReceiveMenu->Empty(); if (PreviewMenu) PreviewMenu->Empty(); if (IdentityMenu) { IdentityMenu->Empty(); } static bool Startup = true; bool ResetDefault = false; LArray Enabled; for (i=0; true; i++) { // char *s = 0; ScribeAccount *a = Startup ? new ScribeAccount(this, i) : Accounts[i]; if (a) { if (i == 0) { a->Create(); } a->Register(this); LVariant ReceiveName = a->Receive.Name(); LVariant ReceiveServer = a->Receive.Server(); LVariant SendServer = a->Send.Server(); if (i == 0 || ValidStr(ReceiveName.Str()) || ValidStr(ReceiveServer.Str()) || ValidStr(SendServer.Str()) ) { a->Send.SendItem = SendItem; a->Receive.ReceiveItem = ReceiveItem; a->Receive.PreviewItem = PreviewItem; if (!Accounts.HasItem(a)) { Accounts.Insert(a); } if (i) a->Create(); a->InitMenus(); // Identity Menu Item LVariant IdEmail = a->Identity.Email(); LVariant IdName = a->Identity.Name(); if (IdentityMenu && ValidStr(IdEmail.Str())) { char s[256]; if (IdName.Str()) sprintf_s(s, sizeof(s), "%s <%s>", IdName.Str(), IdEmail.Str()); else sprintf_s(s, sizeof(s), "<%s>", IdEmail.Str()); a->SetMenuItem(IdentityMenu->AppendItem(s, IDM_IDENTITY_BASE+i+1, !a->Send.Disabled())); if (a->Send.Disabled()) { a->SetCheck(false); if (i == CurrentIdentity) ResetDefault = true; } else { a->SetCheck(i == CurrentIdentity); Enabled[i] = a; } } } else { Accounts.Delete(a); DeleteObj(a); } } if (!a) break; } if ((ResetDefault || CurrentIdentity < 0) && Enabled.Length()) { for (unsigned i=0; iSetCheck(true); LVariant v; GetOptions()->SetValue(OPT_CurrentIdentity, v = (int)i); break; } } } Startup = false; if (ReceiveMenu && i == 0) { ReceiveMenu->AppendItem(LLoadString(IDS_NO_ITEMS), 0, false); } if (StatusPanel) { StatusPanel->OnAccountListChange(); } SetPulse(100); } ////////////////////////////////////////////////////////////////////////////// class LShutdown : public LDialog { LTextLabel *Msg; LButton *KillBtn; LButton *CancelBtn; bool Disconnected; public: ScribeAccount *Wait; List *Accounts; LShutdown(List *accounts) { Wait = 0; Disconnected = false; Accounts = accounts; LRect r( 0, 0, 320 + LAppInst->GetMetric(LGI_MET_DECOR_X), 70 + LAppInst->GetMetric(LGI_MET_DECOR_Y)); SetPos(r); MoveToCenter(); char Str[256]; sprintf_s(Str, sizeof(Str), "%s exiting...", AppName); LView::Name(Str); AddView(Msg = new LTextLabel(-1, 10, 10, 300, -1, "None")); AddView(KillBtn = new LButton(IDC_KILL, 70, 35, 60, 20, "Kill")); AddView(CancelBtn = new LButton(IDCANCEL, 140, 35, 60, 20, "Cancel")); if (KillBtn) { KillBtn->Enabled(false); } } void OnCreate() { SetPulse(100); } void OnPulse() { if (Accounts) { if (!Wait) { Wait = (*Accounts)[0]; if (Wait) { Disconnected = false; char s[256]; LVariant v = Wait->Receive.Name(); sprintf_s(s, sizeof(s), "Waiting for '%s'", v.Str() ? v.Str() : (char*)"Untitled..."); Msg->Name(s); Accounts->Delete(Wait); Wait->Stop(); KillBtn->Enabled(true); } else { SetPulse(); EndModal(true); } } if (Wait && !Wait->IsOnline()) { Wait = 0; Msg->Name("None"); KillBtn->Enabled(false); } } else { SetPulse(); EndModal(false); } } int OnNotify(LViewI *Ctrl, LNotification n) { switch (Ctrl->GetId()) { case IDC_KILL: { if (Wait) { if (!Disconnected) { Disconnected = true; Wait->Disconnect(); } else { Wait->Kill(); } } break; } case IDCANCEL: { EndModal(false); break; } } return 0; } }; bool ScribeWnd::OnRequestClose(bool OsShuttingDown) { if (FolderTasks.Length() > 0) { LgiTrace("%s:%i - %i folder tasks still busy...\n", _FL, FolderTasks.Length()); return false; } LString OnClose = LAppInst->GetConfig("Scribe.OnClose"); if (!d->IngoreOnClose && !OsShuttingDown && !Stricmp(OnClose.Get(), "minimize")) { SetZoom(LZoomMin); return false; } Visible(false); if (ScribeState != ScribeRunning) { // Inside a folder load/unload or initialization // Tell the loader to quit out... ScribeState = ScribeExiting; // Leave now, we can exit when we're ready return false; } else if (IsSending() || GetActiveThreads() > 0) { // whack up a shutdown window List Online; for (auto i: Accounts) { i->OnEndSession(); if (i->IsOnline()) { Online.Insert(i); } } auto Dlg = new LShutdown(&Online); Dlg->DoModal([this, Dlg](auto dlg, auto id) { if (id) { ScribeState = ScribeExiting; LCloseApp(); } else { ScribeState = ScribeRunning; Visible(true); } }); return false; // At the very minimum the app has to wait for the user to respond. } else { // End all sessions if any... for (auto i: Accounts) { i->OnEndSession(); } } // close all the other top level windows while (ThingUi::All.Length() > 0) { ThingUi *Ui = ThingUi::All.First(); if (!Ui->OnRequestClose(OsShuttingDown)) { ScribeState = ScribeRunning; Visible(true); return false; } size_t Start = ThingUi::All.Length(); Ui->Quit(); if (ThingUi::All.Length() >= Start) { LAssert(0); break; } } SerializeState(GetOptions(), OPT_ScribeWndPos, false); LCloseApp(); return LWindow::OnRequestClose(OsShuttingDown); } void ScribeWnd::DoOnTimer(LScriptCallback *c) { if (!c) return; auto Now = LCurrentTime(); if (c->PrevTs) { auto Since = Now - c->PrevTs; double Sec = (double)Since / 1000.0; if (Sec >= c->fParam) { // Call the function c->PrevTs = Now; LVirtualMachine Vm; LScriptArguments Args(&Vm); LVariant This((LDom*)this); Args.Add(&This); ExecuteScriptCallback(*c, Args); } } else { c->PrevTs = Now; } } void ScribeWnd::OnMinute() { if (Folders.Length() == 0) return; // Check for calendar event alarms... Calendar::CheckReminders(); // Check for any outgoing email that should be re-attempted... ScribeFolder *Outbox = GetFolder(FOLDER_OUTBOX); if (Outbox) { bool Resend = false; for (auto t : Outbox->Items) { Mail *m = t->IsMail(); if (m && !TestFlag(m->GetFlags(), MAIL_SENT) && TestFlag(m->GetFlags(), MAIL_READY_TO_SEND) && m->SendAttempts > 0) { Resend = true; break; } } if (Resend) Send(); } LArray Cb; if (GetScriptCallbacks(LOnTimer, Cb)) { for (auto c: Cb) { if (!c->Func) continue; if (c->fParam == 0.0) { // Work out the period from 'Data' char *s = c->Data.Str(); while (*s && IsWhiteSpace(*s)) s++; char *u = s; while (*u && !IsAlpha(*u)) u++; double v = atof(s); switch (*u) { case 's': case 'S': // seconds c->fParam = v; break; case 'm': case 'M': // mins c->fParam = v * LDateTime::MinuteLength; break; case 'h': case 'H': // hours c->fParam = v * LDateTime::HourLength; break; case 'd': case 'D': // days c->fParam = v * LDateTime::DayLength; break; default: { LgiTrace("%s:%i - Couldn't understand period '%s'\n", _FL, c->Data.Str()); c->Data.Empty(); break; } } if ((c->OnSecond = c->fParam < 60.0)) { d->OnSecondTimerCallbacks.Add(c); } } if (!c->OnSecond) DoOnTimer(c); } } } void ScribeWnd::OnHour() { // Force time zone update in case of daylight savings change. LDateTime::SystemTimeZone(true); // Check if we need should be doing a software update check static bool InSoftwareCheck = false; if (!InSoftwareCheck) { char s[64]; InSoftwareCheck = true; LVariant v; if (GetOptions()->GetValue(OPT_SoftwareUpdate, v) && v.CastInt32()) { LDateTime Now, Last; Now.SetFormat(GDTF_YEAR_MONTH_DAY); Last.SetFormat(Now.GetFormat()); Now.SetNow(); if (!GetOptions()->GetValue(OPT_SoftwareUpdateLast, v) || !Last.Set(v.Str())) { // Record now as the last check point Now.Get(s, sizeof(s)); GetOptions()->SetValue(OPT_SoftwareUpdateLast, v = s); } else if (GetOptions()->GetValue(OPT_SoftwareUpdateTime, v)) { // Valid last check date/time. switch (v.CastInt32()) { case 0: // Week Last.AddDays(7); break; case 1: // Month Last.AddMonths(1); break; case 2: // Year Last.AddMonths(12); break; default: LgiTrace("%s:%i - The option '%s' is not valid\n", _FL, OPT_SoftwareUpdateTime); return; } if (Last < Now) { // Save the last date for next time... Now.Get(s, sizeof(s)); GetOptions()->SetValue(OPT_SoftwareUpdateLast, v = s); // Check for update now... LSoftwareUpdate::UpdateInfo Info; GetOptions()->GetValue(OPT_SoftwareUpdateIncBeta, v); IsSoftwareUpToDate(Info, this, false, v.CastInt32() != 0, [Info, this](auto s) { if (s == SwOutOfDate) if (UpgradeSoftware(Info, this, true)) LCloseApp(); }); } } } InSoftwareCheck = false; } } bool ScribeWnd::SaveDirtyObjects(int TimeLimitMs) { bool Status = false; if (Thing::DirtyThings.Length() > 0) { static bool SavingObjects = false; if (!SavingObjects) { SavingObjects = true; LArray WriteTimes; // ssize_t StartDirty = Thing::DirtyThings.Length(); uint64 Start = LCurrentTime(); for (unsigned i=0; iSave(NULL)) { WriteTimes.Add((int) (LCurrentTime() - WriteStart)); LAssert(!ThingType::DirtyThings.HasItem(t)); Status = true; } else { LgiTrace("Failed to save thing type 0x%x\n", t->Type()); FailedWrites++; if (FailedWrites > 2) { while (ThingType::DirtyThings.Length()) ThingType::DirtyThings[0]->SetDirty(false); FailedWrites = 0; } } } } SavingObjects = false; /* if (Status) { LStringPipe p; p.Print("WriteTimes: "); for (unsigned i=0; iLastTs >= 1000) { d->LastTs = Now; OnPulseSecond(); } } } void ScribeWnd::OnPulseSecond() { #if PROFILE_ON_PULSE LProfile Prof("NewMailLst handling"); Prof.HideResultsIfBelow(50); #endif if (Mail::NewMailLst.Length() > 0) { LVariant Blink; if (GetOptions()->GetValue(OPT_BlinkNewMail, Blink) && Blink.CastInt32()) { TrayIcon.Value((TrayIcon.Value() == TRAY_ICON_MAIL) ? TRAY_ICON_NONE : TRAY_ICON_MAIL); } } else { bool Err = false; for (auto a: Accounts) { if (!a->Receive.GetStatus() || !a->Send.GetStatus()) { Err = true; } } TrayIcon.Value(Err ? TRAY_ICON_ERROR : TRAY_ICON_NORMAL); } #if PROFILE_ON_PULSE Prof.Add("StatusPanel handling"); #endif if (StatusPanel) { StatusPanel->OnPulse(); } #if PROFILE_ON_PULSE Prof.Add("OnXXXX handling"); #endif LDateTime Now; Now.SetNow(); if (d->LastMinute != Now.Minutes()) // Check every minute... { d->LastMinute = Now.Minutes(); OnMinute(); } if (d->LastHour != Now.Hours()) // Check every hour... { d->LastHour = Now.Hours(); OnHour(); } { // These timers need to be checked every second... for (auto c: d->OnSecondTimerCallbacks) DoOnTimer(c); } #if PROFILE_ON_PULSE Prof.Add("Instance handling"); #endif if (ThisInst && ValidStr(ThisInst->Args)) { LStringPipe p; p.Push(ThisInst->Args); if (ThisInst->Flags & SCRIBE_IPC_LONG_ARGS) { ThisInst->Flags |= SCRIBE_IPC_CONTINUE_ARGS; int64 Start = LCurrentTime(); while ( TestFlag(ThisInst->Flags, SCRIBE_IPC_LONG_ARGS) && LCurrentTime() - Start < 60000) { ZeroObj(ThisInst->Args); while ( TestFlag(ThisInst->Flags, SCRIBE_IPC_LONG_ARGS) && !ThisInst->Args[0] && LCurrentTime() - Start < 60000) { LSleep(10); } p.Push(ThisInst->Args); } } ZeroObj(ThisInst->Args); LAutoString Arg(p.NewStr()); if (Arg) { OsAppArguments AppArgs(0, 0); LgiTrace("Received cmd line: %s\n", Arg.Get()); AppArgs.Set(Arg); LAppInst->SetAppArgs(AppArgs); if (LAppInst->GetOption("m") && LAppInst->GetOption("f")) ; else LAppInst->OnCommandLine(); OnCommandLine(); if (GetZoom() == LZoomMin) SetZoom(LZoomNormal); Visible(true); } } #if PROFILE_ON_PULSE Prof.Add("PreviewPanel handling"); #endif if (PreviewPanel) { PreviewPanel->OnPulse(); } } void ScribeWnd::AddFolderToMru(char *FileName) { if (FileName) { // read MRU List Files; int i; for (i=0; i<10; i++) { char Key[32]; LVariant f; sprintf_s(Key, sizeof(Key), "FolderMru.%i", i); if (GetOptions()->GetValue(Key, f)) { Files.Insert(NewStr(f.Str())); GetOptions()->DeleteValue(Key); } } // remove FileName if present for (auto f: Files) { if (_stricmp(f, FileName) == 0) { Files.Delete(f); DeleteArray(f); break; } } // insert FileName at the start of the list Files.Insert(NewStr(FileName)); // write MRU for (i=0; i<10; i++) { char *n = Files.ItemAt(i); if (n) { char Key[32]; sprintf_s(Key, sizeof(Key), "FolderMru.%i", i); LVariant f; GetOptions()->SetValue(Key, f = n); } else break; } // Clean up Files.DeleteArrays(); } } bool ScribeWnd::CleanFolders(ScribeFolder *f) { if (!f) return false; if (f->Select()) { f->SerializeFieldWidths(); } for (ScribeFolder *c = f->GetChildFolder(); c; c = c->GetNextFolder()) { CleanFolders(c); } return true; } void ScribeWnd::OnFolderChanged(LDataFolderI *folder) { } bool ScribeWnd::OnFolderTask(LEventTargetI *Ptr, bool Add) { if (Add) { if (FolderTasks.HasItem(Ptr)) { LAssert(!"Can't add task twice."); return false; } FolderTasks.Add(Ptr); return true; } else { if (!FolderTasks.HasItem(Ptr)) { LAssert(!"Item not part of task list."); return false; } FolderTasks.Delete(Ptr); return true; } } LMailStore *ScribeWnd::GetDefaultMailStore() { LMailStore *Def = 0; for (unsigned i=0; i Def->Priority()) { Def = &Folders[i]; } } } } return Def; } bool HasMailStore(LXmlTag *MailStores, char *Name) { for (auto t : MailStores->Children) { char *StoreName = t->GetAttr(OPT_MailStoreName); if (StoreName && Name && !_stricmp(StoreName, Name)) return true; } return false; } LDataStoreI *ScribeWnd::CreateDataStore(char *Full, bool CreateIfMissing) { char *Ext = LGetExtension(Full); if (Ext) { if (!_stricmp(Ext, "mail2")) { LgiMsg(this, LLoadString(IDS_MAIL2_DEPRECATED), AppName, MB_OK, Full); } else if (!_stricmp(Ext, "mail3")) { return OpenMail3(Full, this, CreateIfMissing); } else if (!_stricmp(Ext, "sqlite")) { LTrimDir(Full); return OpenMail3(Full, this, CreateIfMissing); } else { LgiTrace("%s:%i - Not a valid mail store extension: %s\n", _FL, Full); LAssert(!"Not a valid mail store extension."); } } else LgiTrace("%s:%i - No extension for CreateDataStore: %s\n", _FL, Full); return NULL; } class MailStoreUpgrade : public LProgressDlg, public LDataPropI { public: ScribeWnd *App = NULL; LDataStoreI *Ds = NULL; int Status = -1; LString Error; MailStoreUpgrade(ScribeWnd *app, LDataStoreI *ds) { App = app; Ds = ds; SetCanCancel(false); SetDescription("Upgrading mail store..."); Ds->Upgrade(this, this, [this](auto status) { Status = status; }); } ~MailStoreUpgrade() { } void OnPulse() override { if (Status >= 0) { EndModal(0); return; } return LProgressDlg::OnPulse(); } LDataPropI &operator =(LDataPropI &p) { LAssert(0); return *this; } Store3Status SetStr(int id, const char *str) override { switch (id) { case Store3UiError: Error = str; break; default: LAssert(!"Impl me."); return Store3Error; break; } return Store3Success; } }; bool ScribeWnd::ProcessFolder(LDataStoreI *&Store, int StoreIdx, char *StoreName) { if (Store->GetInt(FIELD_VERSION) == 0) { // version error LgiMsg(this, LLoadString(IDS_ERROR_FOLDERS_VERSION), AppName, MB_OK, 0, Store->GetInt(FIELD_VERSION)); return false; } if (Store->GetInt(FIELD_READONLY)) { LgiMsg(this, LLoadString(IDS_ERROR_READONLY_FOLDERS), AppName); } // get root item LDataFolderI *Root = Store->GetRoot(); if (!Root) return false; ScribeFolder *&Mailbox = Folders[StoreIdx].Root; Mailbox = new ScribeFolder; if (Mailbox) { Mailbox->App = this; - Mailbox->SetObject(Root, _FL); + 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) { - MailStoreUpgrade Prog(this, Store); - Prog.DoModal(); + 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); StoreIdx++; } if (Status) { // Force load some folders... ScribeFolder *Folder = GetFolder(FOLDER_CALENDAR); if (Folder) Folder->LoadThings(); Folder = GetFolder(FOLDER_FILTERS); if (Folder) Folder->LoadThings(); for (auto ms: Folders) { if (!ms.Root) continue; for (auto c = ms.Root->GetChildFolder(); c; c = c->GetNextFolder()) { if (c->GetItemType() == MAGIC_CONTACT || c->GetItemType() == MAGIC_FILTER) c->LoadThings(); } } List c; GetContacts(c); // Set selected folder to inbox by default // if the user hasn't selected a folder already if (ScribeState != ScribeExiting && Tree && !Tree->Selection()) { LVariant StartInFolder; GetOptions()->GetValue(OPT_StartInFolder, StartInFolder); ScribeFolder *Start = 0; if (ValidStr(StartInFolder.Str())) { Start = GetFolder(StartInFolder.Str()); } if (!Start) { Start = GetFolder(FOLDER_INBOX); } if (Start && Tree) { Tree->Select(Start); } } } GetOptions()->DeleteValue(OPT_CreateFoldersIfMissing); if (OptionsDirty) SaveOptions(); GetOptions()->Unlock(); // Set system folders ScribeFolder *f = GetFolder(FOLDER_INBOX); if (f) f->SetSystemFolderType(Store3SystemInbox); f = GetFolder(FOLDER_OUTBOX); if (f) f->SetSystemFolderType(Store3SystemOutbox); f = GetFolder(FOLDER_SENT); if (f) f->SetSystemFolderType(Store3SystemSent); f = GetFolder(FOLDER_SPAM); if (f) f->SetSystemFolderType(Store3SystemSpam); return Status; } 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.NewGStr(); TrayIcon.Name(AppTitle); Name(AppTitle); TrayIcon.Value(TRAY_ICON_NORMAL); TrayIcon.Visible(true); auto Item = Menu->FindItem(IDM_SCRIPTING_CONSOLE); if (Item) { LVariant v; if (GetOptions()->GetValue(OPT_ShowScriptConsole, v)) { Item->Checked(v.CastInt32() != 0); LScribeScript::Inst->ShowScriptingWindow(Item->Checked()); } } if (Tree) { Tree->Focus(true); } } Thing *ScribeWnd::CreateItem(int Type, ScribeFolder *Folder, bool Ui) { auto FolderStore = Folder && Folder->GetObject() ? Folder->GetObject()->GetStore() : NULL; auto DefaultStore = GetDefaultMailStore(); auto Store = FolderStore ? FolderStore : (DefaultStore ? DefaultStore->Store : NULL); if (!Store) { LAssert(!"no store"); LgiTrace("%s:%i - No store for creating calendar object.\n", _FL); return NULL; } auto Obj = Store->Create(Type); if (!Obj) { LAssert(!"create failed"); LgiTrace("%s:%i - store failed to create object.\n", _FL); return NULL; } #define HANDLE_CREATE_ITEM(Magic, Type) \ case Magic: \ { \ auto o = new Type(this, Obj); \ if (!o) \ { \ LgiTrace("%s:%i - Alloc failed.\n", _FL); \ break; \ } \ if (Folder) o->SetParentFolder(Folder); \ if (Ui) o->DoUI(); \ return o; \ } switch ((uint32_t)Type) { case MAGIC_MAIL: { // create a new mail message Mail *m = new Mail(this, Obj); if (!m) { LgiTrace("%s:%i - Alloc failed.\n", _FL); break; } if (!m->GetObject()) { m->DecRef(); return 0; } m->OnCreate(); if (Folder) m->SetParentFolder(Folder); if (Ui) m->DoUI(); return m; } HANDLE_CREATE_ITEM(MAGIC_CONTACT, Contact) HANDLE_CREATE_ITEM(MAGIC_CALENDAR, Calendar) HANDLE_CREATE_ITEM(MAGIC_FILTER, Filter) HANDLE_CREATE_ITEM(MAGIC_GROUP, ContactGroup) default: LAssert(!"Unhandled object type."); break; } return NULL; } void ScribeWnd::OnPaint(LSurface *pDC) { #if 0 pDC->Colour(LColour::Red); pDC->Rectangle(); #else LCssTools Tools(this); auto c = GetClient(); // c.Offset(0, -26); Tools.PaintContent(pDC, c); #endif } int CompareContacts(Contact **a, Contact **b) { if (a && b) { auto A = (*a)->GetFirst(); auto B = (*b)->GetFirst(); if (A && B) return _stricmp(A, B); } return 0; } bool ScribeWnd::OpenAMail(ScribeFolder *Folder) { if (Folder && Tree) { Folder->LoadThings(); for (auto i: Folder->Items) { Mail *m = i->IsMail(); if (m && !(m->GetFlags() & MAIL_READ)) { Tree->Select(Folder); m->DoUI(); return true; } } for (auto *t=Folder->GetChild(); t; t=t->GetNext()) { ScribeFolder *f = dynamic_cast(t); if (OpenAMail(f)) { return true; } } } return false; } void AddContactToMenu(LSubMenu *Menu, Contact *c, ssize_t Index) { if (!c || Index < 0) return; auto Email = c->GetEmail(); if (!Email) return; // has an email, list it auto First = c->GetFirst(); auto Last = c->GetLast(); if (First || Last) { char Buf[256]; sprintf_s(Buf, sizeof(Buf), "%s %s", (First)?First:"", (Last)?Last:""); auto Item = Menu->AppendItem(Buf, TRAY_CONTACT_BASE + (int)Index, true); if (Item) Item->Icon(ICON_CONTACT); } } void ScribeWnd::AddContactsToMenu(LSubMenu *Menu) { if (!Menu) return; d->TrayMenuContacts.Sort(CompareContacts); if (((ssize_t)d->TrayMenuContacts.Length() << 4) > GdcD->Y() - 200) { // Group contacts by starting letter LArray Alpha[26]; LArray Other; for (auto c: d->TrayMenuContacts) { auto First = c->GetFirst(); auto Last = c->GetLast(); auto Email = c->GetEmail(); if (Email) { // has an email, list it if (First || Last) { auto Name = First?First:Last; if ( (*Name >= 'a' && *Name <= 'z') || (*Name >= 'A' && *Name <= 'Z') ) { int Ind = tolower(*Name) - 'a'; if (Ind >= 0 && Ind < CountOf(Alpha)) Alpha[Ind].Add(c); else Other.Add(c); } else Other.Add(c); } } } for (int i=0; i 0) { char Group[64]; sprintf_s(Group, sizeof(Group), "%c...", 'a' + i); auto Sub = Menu->AppendSub(Group); if (Sub) { for (auto c: Alpha[i]) AddContactToMenu(Sub, c, d->TrayMenuContacts.IndexOf(c)); } } } if (Other.Length()) { auto Sub = Menu->AppendSub("Other..."); if (Sub) { for (auto c: Other) AddContactToMenu(Sub, c, d->TrayMenuContacts.IndexOf(c)); } } } else { // Display all... for (size_t i=0; iTrayMenuContacts.Length(); i++) { AddContactToMenu(Menu, d->TrayMenuContacts[i], i); } } } void ScribeWnd::OnUrl(const char *Url) { LUri u(Url); if (u.sProtocol && !_stricmp(u.sProtocol, "mailto")) { CreateMail(0, Url, 0); } } void ScribeWnd::OnReceiveFiles(LArray &Files) { int UnknownFormats = 0; int64 Period = LCurrentTime() - LastDrop; if (Period > 500) // Lock out drops within 500ms of an LGI drop { LString sSend, sPages; bool HasSend = LAppInst->GetOption("send", sSend); bool HasPrint = LAppInst->GetOption("p", sPages); LArray NewMail; for (unsigned i=0; i str(new LFile); if (str->Open(f, O_READ)) { if (t->Import(t->AutoCast(str), MimeType)) { if (HasSend) { Mail *m = t->IsMail(); if (m) { NewMail.Add(m); m->SetFlags(m->GetFlags() | MAIL_CREATED | MAIL_READ); } } else { t->DoUI(); } } } } } else UnknownFormats++; } bool SendNow = sSend ? atoi(sSend) != 0 : false; for (unsigned i=0; iSetFlags(m->GetFlags() | MAIL_READY_TO_SEND); m->Save(); } else if (HasPrint) { ThingPrint(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: { extern void ExportScribe(ScribeWnd *App); ExportScribe(this); break; } case IDM_IMPORT_TEXT_MBOX: { Import_UnixMBox(this); break; } case IDM_IMP_EUDORA_ADDR: { Import_EudoraAddressBook(this); break; } case IDM_IMP_MOZILLA_ADDR: { Import_MozillaAddressBook(this); break; } case IDM_IMP_MOZILLA_MAIL: { Import_MozillaMail(this); break; } #if WINNATIVE case IDM_IMPORT_OUTLOOK_PAB: { Import_OutlookContacts(this); break; } case IDM_IMPORT_OUTLOOK_ITEMS: { Import_Outlook(this, IMP_OUTLOOK); break; } case IDM_EXPORT_OUTLOOK_ITEMS: { Export_Outlook(this); break; } #endif case IDM_IMP_MBX_EMAIL: { Import_OutlookExpress(this, false); // v4 break; } case IDM_IMP_DBX_EMAIL: { Import_OutlookExpress(this); // v5 break; } case IDM_IMPORT_NS_CONTACTS: { Import_NetscapeContacts(this); break; } case IDM_CHECK_UPDATE: { LVariant v; GetOptions()->GetValue(OPT_SoftwareUpdateIncBeta, v); SoftwareUpdate(this, true, v.CastInt32() != 0, [](auto goingToUpdate) { if (goingToUpdate) LCloseApp(); }); break; } case IDM_LOGOUT: { CurrentAuthLevel = PermRequireNone; auto i = Menu->FindItem(IDM_LOGOUT); if (i) i->Enabled(false); break; } case IDM_LAYOUT1: { LVariant v; int TwoThirds = GetClient().Y() >> 1; GetOptions()->SetValue(OPT_SplitterPos, v = 200); GetOptions()->SetValue(OPT_SubSplitPos, v = TwoThirds); SetLayout(FoldersListAndPreview); break; } case IDM_LAYOUT2: { LVariant v; int TwoThirds = GetClient().Y() >> 1; GetOptions()->SetValue(OPT_SplitterPos, v = TwoThirds); GetOptions()->SetValue(OPT_SubSplitPos, v = 200); SetLayout(PreviewOnBottom); break; } case IDM_LAYOUT3: { LVariant v; GetOptions()->SetValue(OPT_SplitterPos, v = 200); SetLayout(FoldersAndList); break; } case IDM_LAYOUT4: { LVariant v; GetOptions()->SetValue(OPT_SplitterPos, v = 200); GetOptions()->SetValue(OPT_SubSplitPos, v); SetLayout(ThreeColumn); break; } case IDM_CRASH: { int *Crash = 0; *Crash = true; break; } case IDM_DUMP_MEM: { LDumpMemoryStats(0); break; } case IDM_SCRIPT_DEBUG: { LVariant v; if (GetOptions()) GetOptions()->SetValue(OPT_ScriptDebugger, v = true); LVirtualMachine *vm = new LVirtualMachine(d); if (!vm) break; LVmDebugger *dbg = vm->OpenDebugger(); if (!dbg) break; dbg->OwnVm(true); break; } case IDM_SCRIPT_BREAK_ON_WARN: { auto mi = GetMenu()->FindItem(IDM_SCRIPT_BREAK_ON_WARN); if (!mi) break; LVirtualMachine::BreakOnWarning = !mi->Checked(); mi->Checked(LVirtualMachine::BreakOnWarning); break; } // Help menu case IDM_HELP: { LaunchHelp("index.html"); // LgiMsg(this, LLoadString(IDS_ERROR_NO_HELP), AppName, MB_OK); break; } case IDM_FEEDBACK: { LVariant e; if (GetOptions()->GetValue("author", e)) CreateMail(0, e.Str()); else CreateMail(0, AuthorEmailAddr); break; } case IDM_MEMECODE: { LExecute(AuthorHomepage); break; } case IDM_HOMEPAGE: { LVariant hp; if (GetOptions()->GetValue("homepage", hp)) LExecute(hp.Str()); else LExecute(ApplicationHomepage); break; } case IDM_VERSION_HISTORY: { LExecute("http://www.memecode.com/site/ver.php?id=445"); break; } case IDM_DEBUG_INFO: { char s[256]; sprintf_s(s, sizeof(s), "%s#debug", ApplicationHomepage); LExecute(s); break; } case IDM_TUTORIALS: { LExecute("http://www.memecode.com/scribe/tutorials"); break; } case IDM_INSCRIBE_LINK: { LExecute(CommercialHomepage); break; } case IDM_SCRIBE_FAQ: { LExecute(FaqHomepage); break; } case IDM_ABOUT: { extern void ScribeAbout(ScribeWnd *Parent); ScribeAbout(this); break; } default: { if (d->ScriptToolbar) d->ScriptToolbar->ExecuteCallbacks(this, 0, 0, Cmd); break; } } return 0; } void ScribeWnd::OnDelete() { LVariant ConfirmDelete; GetOptions()->GetValue(OPT_ConfirmDelete, ConfirmDelete); if (!ConfirmDelete.CastInt32() || LgiMsg(this, LLoadString(IDS_DELETE_ASK), AppName, MB_YESNO) == IDYES) { LArray Del; if (Tree && Tree->Focus()) { ScribeFolder *Item = dynamic_cast(Tree->Selection()); if (Item) { Tree->OnDelete(Item, false); } } else if (MailList #ifdef MAC && MailList->Focus() #endif ) { List Sel; MailList->GetSelection(Sel); for (auto i: Sel) { Thing *t = dynamic_cast(i); if (t) Del.Add(t->GetObject()); } if (Del.Length()) { auto Store = Del[0]->GetStore(); Store->Delete(Del, true); } else LgiTrace("%s:%i - Nothing to delete\n", _FL); #ifndef MAC MailList->Focus(true); #endif } } } int ScribeWnd::OnNotify(LViewI *Ctrl, LNotification n) { switch (Ctrl->GetId()) { case IDC_THING_LIST: { if (n.Type == LNotifyReturnKey) { LListItem *i = MailList ? MailList->GetSelected() : 0; Thing *t = dynamic_cast(i); if (t) { t->DoUI(); } } else if (n.Type == LNotifyDeleteKey) { /* This is now handled by the menu OnDelete(); return true; */ } if (SearchView && MailList) { SearchView->OnNotify(Ctrl, n); } break; } case IDC_TEXT: { if (PreviewPanel) { PreviewPanel->OnNotify(Ctrl, n); } break; } } return 0; } void ScribeWnd::AddThingSrc(ScribeFolder *src) { if (!d->ThingSources.HasItem(src)) d->ThingSources.Add(src); } void ScribeWnd::RemoveThingSrc(ScribeFolder *src) { d->ThingSources.Delete(src); } LArray ScribeWnd::GetThingSources(Store3ItemTypes Type) { LArray a; for (auto f: d->ThingSources) { if (f->GetItemType() == Type && !f->IsInTrash()) { a.Add(f); } } return a; } bool ScribeWnd::LogFilterActivity() { auto i = Menu->FindItem(IDM_DEBUG_FILTERS); return i ? i->Checked() : false; } bool ScribeWnd::CreateFolders(LAutoString &FileName) { bool Status = false; if (FileName) { char *Ext = LGetExtension(FileName); if (!Ext) { char File[300]; strcpy_s(File, sizeof(File), FileName); strcat(File, ".mail3"); FileName.Reset(NewStr(File)); } // Create objects, and then close the file.. it'll be reloaded later LAutoPtr m(CreateDataStore(FileName, true)); if (m) { m->GetRoot(true); Status = true; } else LgiTrace("%s:%i - CreateDataStore failed.\n", _FL); } else LgiTrace("%s:%i - No file name for CreateFolder.\n", _FL); return Status; } bool ScribeWnd::CompactFolders(LMailStore &Store, bool Interactive) { if (!Store.Store) return false; Store3Progress Dlg(this, Interactive); Dlg.SetDescription(LLoadString(IDS_CHECKING_OBJECTS)); bool Offline = false; if (WorkOffline) { Offline = WorkOffline->Checked(); WorkOffline->Checked(true); } Store.Store->Compact(this, &Dlg, [this, Offline](auto status) { if (WorkOffline) WorkOffline->Checked(Offline); }); return true; } CalendarSource *CalendarSource::Create(ScribeWnd *App, const char *ObjName, const char *Id) { if (!Stricmp(ObjName, "RemoteCalendarSource")) return new RemoteCalendarSource(App, Id); return new FolderCalendarSource(App, Id); } int ScribeWnd::GetCalendarSources(LArray &Out) { static bool Loaded = false; if (!Loaded) { Loaded = true; CalendarSource::SetCreateIn(NULL); LVariant Create; GetOptions()->GetValue(OPT_CalendarCreateIn, Create); // This should be a list of all calendar folders in ANY mail store... auto CalFlds = GetThingSources(MAGIC_CALENDAR); LXmlTag *t = GetOptions()->LockTag(OPT_CalendarSources, _FL); if (t) { bool AutoPopulate = t->Children.Length() == 0; for (auto c: t->Children) { auto s = CalendarSource::Create(this, c->GetAttr(CalendarSource::OptObject), c->GetTag()); if (s && s->Read()) { // Add known source... if (!Stricmp(Create.Str(), c->GetTag())) { CalendarSource::SetCreateIn(s); } // Remove from CalFlds FolderCalendarSource *Fcs = dynamic_cast(c); if (Fcs) { auto Path = Fcs->GetPath(); for (auto c: CalFlds) { if (c->GetPath().Equals(Path)) { CalFlds.Delete(c); break; } } } } } if (AutoPopulate) { // Now CalFlds should be a list of all calendar folders NOT in the source XML tag for (auto c: CalFlds) { FolderCalendarSource *s = new FolderCalendarSource(this); if (s) { // So add an entry to track it... auto Path = c->GetPath(); s->SetPath(Path); s->SetDisplay(true); s->SetColour(CalendarSource::FindUnusedColour()); s->Write(); } } } GetOptions()->Unlock(); } if (!CalendarSource::GetCreateIn() && CalendarSource::GetSources().Length()) { CalendarSource::SetCreateIn(CalendarSource::GetSources().ItemAt(0)); } } for (unsigned i=0; iPathOption; fi++) { bool Check = true; if (fi->HasOption) { LVariant v; if (GetOptions()->GetValue(fi->HasOption, v)) Check = v.CastInt32() != 0; } if (Check) { ScribeFolder *c = GetFolder(fi->Id); if (c == f) return fi->Id; } } return -1; } ScribeFolder *ScribeWnd::GetCurrentFolder() { if (Tree) { auto *Item = Tree->Selection(); if (Item) { return dynamic_cast(Item); } } return 0; } bool ScribeWnd::GetSystemPath(int Folder, LVariant &Path) { char KeyName[64]; sprintf_s(KeyName, sizeof(KeyName), "Folder-%i", Folder); return GetOptions()->GetValue(KeyName, Path); } LMailStore *ScribeWnd::GetMailStoreForIdentity(const char *IdEmail) { LVariant Tmp; if (!IdEmail) { // Get current identity ScribeAccount *Cur = GetCurrentAccount(); if (Cur) { Tmp = Cur->Identity.Email(); IdEmail = Tmp.Str(); } } if (!IdEmail) return NULL; ScribeAccount *a = NULL; for (auto Acc : Accounts) { LVariant e = Acc->Identity.Email(); if (e.Str() && !_stricmp(e.Str(), IdEmail)) { a = Acc; break; } } if (!a) return NULL; LVariant DestPath = a->Receive.DestinationFolder(); if (!DestPath.Str()) return NULL; return GetMailStoreForPath(DestPath.Str()); } ScribeFolder *ScribeWnd::GetFolder(int Id, LDataI *s) { if (s) { for (auto &f: Folders) if (s->GetStore() == f.Store) return GetFolder(Id, &f); } return GetFolder(Id); } ScribeFolder *ScribeWnd::GetFolder(int Id, LMailStore *Store, bool Quiet) { char KeyName[64]; sprintf_s(KeyName, sizeof(KeyName), "Folder-%i", Id); LVariant FolderName; bool NoOption = false; if (GetOptions()->GetValue(KeyName, FolderName)) { if (ValidStr(FolderName.Str()) && strlen(FolderName.Str()) > 0) { ScribeFolder *c = GetFolder(FolderName.Str(), Store); if (c) { return c; } else if (!Quiet) { LgiTrace("%s:%i - '%s' doesn't exist.\n", _FL, FolderName.Str()); } } } else if (!Quiet) { // LgiTrace("%s:%i - No option '%s'\n", _FL, KeyName); NoOption = true; } switch (Id) { case FOLDER_INBOX: case FOLDER_OUTBOX: case FOLDER_SENT: case FOLDER_TRASH: case FOLDER_CONTACTS: case FOLDER_TEMPLATES: case FOLDER_FILTERS: case FOLDER_CALENDAR: case FOLDER_GROUPS: case FOLDER_SPAM: { ScribeFolder *c = GetFolder(DefaultFolderNames[Id], Store); if (!c) { // if (!Quiet) // LgiTrace("%s:%i - Default folder '%s' doesn't exist.\n", _FL, DefaultFolderNames[Id]); } else if (NoOption) { auto p = c->GetPath(); GetOptions()->SetValue(KeyName, FolderName = p.Get()); } return c; } } return NULL; } bool ScribeWnd::OnMailStore(LMailStore **MailStore, bool Add) { if (!MailStore) { LAssert(!"No mail store pointer?"); return false; } if (Add) { *MailStore = &Folders.New(); if (*MailStore) return true; } else { ssize_t Idx = *MailStore - &Folders[0]; if (Idx >= 0 && Idx < (ssize_t)Folders.Length()) { Folders.DeleteAt(Idx, true); *MailStore = NULL; return true; } else { LAssert(!"Index out of range."); } } return false; } LMailStore *ScribeWnd::GetMailStoreForPath(const char *Path) { if (!Path) return NULL; LToken t(Path, "/"); if (t.Length() > 0) { const char *First = t[0]; // Find the mail store that that t[0] refers to for (unsigned i=0; iGetText(); if (RootStr && !_stricmp(RootStr, First)) { return &Folders[i]; } } } } return NULL; } ScribeFolder *ScribeWnd::GetFolder(const char *Name, LMailStore *s) { ScribeFolder *Folder = 0; if (ValidStr(Name)) { LString Sep("/"); auto t = LString(Name).Split(Sep); LMailStore tmp; LString TmpName; if (t.Length() > 0) { if (!s) { s = GetMailStoreForPath(Name); if (!s) { // IMAP folders? for (auto a: Accounts) { ScribeProtocol Proto = a->Receive.ProtocolType(); if (Proto == ProtocolImapFull) { ScribeFolder *Root = a->Receive.GetRootFolder(); if (Root) { const char *RootStr = Root->GetText(); if (RootStr && a->Receive.GetDataStore() && !_stricmp(RootStr, t[0])) { tmp.Root = Root; tmp.Store = a->Receive.GetDataStore(); s = &tmp; break; } } } } } if (s) { if (*Name == '/') Name++; Name = strchr(Name, '/'); if (!Name) Name = "/"; } } else if (s->Root) { // Check if the store name is on the start of the folder auto RootName = s->Root->GetName(true); if (RootName.Equals(t[0])) { LString::Array a; for (unsigned i=1; iRoot; Folder = s->Root ? s->Root->GetSubFolder(Name) : 0; } } return Folder; } void ScribeWnd::Update(int What) { if (What & UPDATE_TREE) { Tree->Invalidate(); return; } if (What & UPDATE_LIST) { if (MailList) MailList->Invalidate(); return; } } void ScribeWnd::DoDebug(char *s) { } Thing *ScribeWnd::CreateThingOfType(Store3ItemTypes Type, LDataI *obj) { Thing *t = NULL; switch (Type) { case MAGIC_CONTACT: { t = new Contact(this, obj); break; } case MAGIC_MAIL: { t = new Mail(this, obj); break; } case MAGIC_ATTACHMENT: { t = new Attachment(this, obj); break; } case MAGIC_FILTER: { t = new Filter(this, obj); break; } case MAGIC_CALENDAR: { t = new Calendar(this, obj); break; } case MAGIC_GROUP: { t = new ContactGroup(this, obj); break; } default: break; } if (t) { t->App = this; } return t; } void ScribeWnd::GetFilters(List &Filters, bool JustIn, bool JustOut, bool JustInternal) { auto Srcs = GetThingSources(MAGIC_FILTER); for (auto f: Srcs) { for (auto t: f->Items) { Filter *Ftr = t->IsFilter(); if (Ftr) { if (JustIn && !Ftr->GetIncoming()) continue; if (JustOut && !Ftr->GetOutgoing()) continue; if (JustInternal && !Ftr->GetInternal()) continue; Filters.Insert(Ftr); } } } extern int FilterCompare(Filter *a, Filter *b, NativeInt Data); Filters.Sort(FilterCompare); } bool ScribeWnd::ShowToolbarText() { LVariant i; if (GetOptions()->GetValue(OPT_ToolbarText, i)) { return i.CastInt32() != 0; } GetOptions()->SetValue(OPT_ToolbarText, i = true); return true; } void ScribeWnd::HashContacts(LHashTbl,Contact*> &Contacts, ScribeFolder *Folder, bool Deep) { if (!Folder) { // Default item is the contacts folder Folder = GetFolder(FOLDER_CONTACTS); } // recurse through each folder and make a list // of every contact object we find. if (Folder) { Folder->LoadThings(); for (auto t: Folder->Items) { Contact *c = t->IsContact(); if (c) { auto Emails = c->GetEmails(); for (auto e: Emails) if (e && !Contacts.Find(e)) Contacts.Add(e, c); } } for (ScribeFolder *f = Folder->GetChildFolder(); Deep && f; f = f->GetNextFolder()) { HashContacts(Contacts, f, Deep); } } } List *ScribeWnd::GetEveryone() { return &Contact::Everyone; } bool ScribeWnd::GetContacts(List &Contacts, ScribeFolder *Folder, bool Deep) { LArray Folders; if (!Folder) { Folders = GetThingSources(MAGIC_CONTACT); auto f = GetFolder(FOLDER_CONTACTS); if (f && !Folders.HasItem(f)) Folders.Add(f); } else Folders.Add(Folder); if (!Folders.Length()) return false; for (auto f: Folders) { // recurse through each folder and make a list // of every contact object we find. ScribePerm Perm = f->GetFolderPerms(ScribeReadAccess); bool Safe = CurrentAuthLevel >= Perm; if (Safe) { f->LoadThings(); for (auto t: f->Items) { Contact *c = t->IsContact(); if (c) Contacts.Insert(c); } for (ScribeFolder *c = f->GetChildFolder(); Deep && c; c = c->GetNextFolder()) GetContacts(Contacts, c, Deep); } } return true; } /* This function goes through the database and checks for some basic requirements and fixes things up if they aren't ok. */ bool ScribeWnd::ValidateFolder(LMailStore *s, int Id) { char OptName[32]; sprintf_s(OptName, sizeof(OptName), "Folder-%i", Id); LVariant Path; if (!GetOptions()->GetValue(OptName, Path)) { char Opt[256]; sprintf_s(Opt, sizeof(Opt), "/%s", DefaultFolderNames[Id]); GetOptions()->SetValue(OptName, Path = Opt); } // If the path name has the store name at the start, strip that off... LString Sep("/"); LString::Array Parts = LString(Path.Str()).Split(Sep); if (Parts.Length() > 1) { if (Parts[0].Equals(s->Name)) { Parts.DeleteAt(0, true); Path = Sep.Join(Parts); } else { LMailStore *ms = GetMailStoreForPath(Path.Str()); if (ms) { s = ms; } else { // Most likely the user has renamed something and broken the // path. Lets just error out instead of creating the wrong folder return false; } } } // Now resolve the path... ScribeFolder *Folder = GetFolder(Path.Str(), s); if (!Folder) { char *p = Path.Str(); if (_strnicmp(p, "/IMAP ", 6) != 0) { LAssert(DefaultFolderTypes[Id] != MAGIC_NONE); Folder = s->Root->CreateSubDirectory(*p=='/'?p+1:p, DefaultFolderTypes[Id]); } } if (!Folder) return false; Folder->SetDefaultFields(); return true; } void ScribeWnd::Validate(LMailStore *s) { // Check for all the basic folders int Errors = 0; for (SystemFolderInfo *fi = SystemFolders; fi->PathOption; fi++) { bool Check = true; if (fi->HasOption) { LVariant v; if (GetOptions()->GetValue(fi->HasOption, v)) Check = v.CastInt32() != 0; } if (Check) { if (!ValidateFolder(s, fi->Id)) Errors++; } } if (Errors && LgiMsg(this, "There were errors validating the system folders." "Would you like to review the mail store's system folder paths?", AppName, MB_YESNO) == IDYES) { PostEvent(M_COMMAND, IDM_MANAGE_MAIL_STORES); } } ThingFilter *ScribeWnd::GetThingFilter() { return SearchView; } ScribeAccount *ScribeWnd::GetSendAccount() { LVariant DefSendAcc = 0; if (!GetOptions()->GetValue(OPT_DefaultSendAccount, DefSendAcc)) { for (auto a : Accounts) if (a->Send.Server().Str()) return a; } ScribeAccount *i = Accounts.ItemAt(DefSendAcc.CastInt32()); if (i && i->Send.Server().Str()) return i; return NULL; } LPrinter *ScribeWnd::GetPrinter() { if (!d->PrintOptions) d->PrintOptions.Reset(new LPrinter); return d->PrintOptions; } int ScribeWnd::GetActiveThreads() { int Status = 0; for (auto i: Accounts) { if (i->IsOnline()) { Status++; } } return Status; } class DefaultClientDlg : public LDialog { public: bool DontWarn; DefaultClientDlg(LView *parent) { DontWarn = false; SetParent(parent); LoadFromResource(IDD_WARN_DEFAULT); MoveToCenter(); } int OnNotify(LViewI *Ctrl, LNotification n) { switch (Ctrl->GetId()) { case ID_YES: case ID_NO: { LCheckBox *DW; if (GetViewById(IDC_DONT_WARN, DW)) { DontWarn = DW->Value() != 0; } EndModal(Ctrl->GetId() == ID_YES); break; } } return 0; } }; #if WINNATIVE struct DefaultClient { char DefIcon[MAX_PATH_LEN]; char CmdLine[MAX_PATH_LEN]; char DllPath[MAX_PATH_LEN]; DefaultClient() { auto Exe = LGetExeFile(); sprintf_s(DefIcon, sizeof(DefIcon), "%s,1", Exe.Get()); sprintf_s(CmdLine, sizeof(CmdLine), "\"%s\" /m \"%%1\"", Exe.Get()); LMakePath(DllPath, sizeof(DllPath), Exe, "../ScribeMapi.dll"); } bool IsWindowsXp() { LArray Ver; int Os = LGetOs(&Ver); if ( ( Os == LGI_OS_WIN32 || Os == LGI_OS_WIN64 ) && Ver.Length() > 1 && Ver[0] == 5 && Ver[1] == 1 ) return true; return false; } bool InstallMailto(bool Write) { LAutoPtr mailto = CheckKey(Write, "HKCR\\mailto"); if (!mailto) return false; if (!CheckString(Write, mailto, NULL, "URL:MailTo Protocol")) return false; LAutoPtr deficon = CheckKey(Write, "HKCR\\mailto\\DefaultIcon"); if (!deficon) return false; if (!CheckString(Write, deficon, NULL, DefIcon)) return false; LAutoPtr shell = CheckKey(Write, "HKCR\\mailto\\shell"); if (!shell) return false; if (!CheckString(Write, shell, NULL, "open")) return false; LAutoPtr cmd = CheckKey(Write, "HKCR\\mailto\\shell\\open\\command"); if (!cmd) return false; if (!CheckString(Write, cmd, NULL, CmdLine)) return false; return true; } LAutoPtr CheckKey(bool Write, const char *Key, ...) 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.NewGStr(); } LAutoString ScribeWnd::ProcessSig(Mail *m, char *Xml, const char *MimeType) { LStringPipe p; if (!m || !Xml) return LAutoString(); if (MimeType && !_stricmp(MimeType, sTextHtml)) p.Write(Xml, strlen(Xml)); else { LMemStream mem(Xml, strlen(Xml)); LXmlTag x; LXmlTree t(GXT_KEEP_WHITESPACE|GXT_NO_DOM); if (t.Read(&x, &mem, 0)) { for (auto Tag: x.Children) { if (Tag->IsTag("random-line")) { char *FileName = 0; if ((FileName = Tag->GetAttr("Filename"))) { char *File = LReadTextFile(FileName); if (File) { LToken Lines(File, "\r\n"); DeleteArray(File); char *RandomLine = Lines[LRand((unsigned)Lines.Length())]; if (RandomLine) { p.Push(RandomLine); } } } } else if (Tag->IsTag("random-paragraph")) { char *FileName = 0; if ((FileName = Tag->GetAttr("Filename"))) { char *File = LReadTextFile(FileName); if (File) { List Para; for (char *f=File; f && *f; ) { // skip whitespace while (strchr(" \t\r\n", *f)) f++; if (*f) { char *Start = f; char *n; while ((n = strchr(f, '\n'))) { f = n + 1; if (f[1] == '\n' || (f[1] == '\r' && f[2] == '\n')) { break; } } if (f == Start) f += strlen(f); Para.Insert(NewStr(Start, f-Start)); } } DeleteArray(File); char *RandomPara = Para.ItemAt(LRand((int)Para.Length())); if (RandomPara) { p.Push(RandomPara); } Para.DeleteArrays(); } } } else if (Tag->IsTag("include-file")) { char *FileName = 0; if ((FileName = Tag->GetAttr("filename"))) { char *File = LReadTextFile(FileName); if (File) { p.Push(File); DeleteArray(File); } } } else if (Tag->IsTag("quote-file")) { char *FileName = 0; char *QuoteStr = 0; if ((FileName = Tag->GetAttr("filename")) && (QuoteStr = Tag->GetAttr("Quote"))) { } } else { p.Push(Tag->GetContent()); } } } } return LAutoString(p.NewStr()); } // 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)) { GDataIt To = m->GetObject()->GetList(FIELD_TO); if (To && To->Length()) { LAutoPtr Out(new ScribeEnvelope); if (Out) { if (m->OnBeforeSend(Out)) { SendAccountlet *Send = 0; for (auto a: Acc) { LVariant Ie = a->GetAccount()->Identity.Email(); if (ValidStr(Ie.Str()) && a->OnlySendThroughThisAccount()) { if (Ie.Str() && m->GetFromStr(FIELD_EMAIL) && _stricmp(Ie.Str(), m->GetFromStr(FIELD_EMAIL)) == 0) { Send = a; break; } } } if (!Send) { Send = Default; } if (Send) { LAssert(Out->To.Length() > 0); Out->SourceFolder = OutBox->GetPath(); Send->Outbox.Add(Out.Release()); MailToSend++; } } } } else { LgiMsg( this, LLoadString(IDS_ERROR_NO_RECIPIENTS), AppName, MB_OK, m->GetSubject() ? m->GetSubject() : (char*)"(none)"); } } } } if (MailToSend) { for (auto a: Acc) { if (a->Outbox.Length() > 0 && !a->IsOnline()) { if (a->IsConfigured()) { a->Connect(0, Quiet); } else { 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 GDataIt Sc = Src->GetList(FIELD_MIME_SEG); GDataIt Dc = Dst->GetList(FIELD_MIME_SEG); if (Dc && Sc) { for (unsigned i=0; iLength(); i++) { // Create new dest child, and merge the source child across LDataPropI *NewDestChild = Dc->Create(Dst->GetStore()); if (!MergeSegments(NewDestChild, (*Sc)[i], Dom)) return false; LDataI *NewDest = dynamic_cast(NewDestChild); if (NewDest) NewDest->Save(Dst); } } return true; } // Either 'FileName' or 'Source' will be valid void ScribeWnd::MailMerge(LArray &Contacts, const char *FileName, Mail *Source) { ScribeFolder *Outbox = GetFolder(FOLDER_OUTBOX); if (Outbox && Contacts.Length() && (FileName || Source)) { LAutoPtr ImportEmail; if (FileName) { LAutoPtr File(new LFile); if (File->Open(FileName, O_READ)) { Thing *t = CreateItem(MAGIC_MAIL, Outbox, false); ImportEmail.Reset(Source = t->IsMail()); if (!Source->Import(Source->AutoCast(File), sMimeMessage)) { Source = 0; } } } if (Source) { List Msgs; ScribeDom Dom(this); // Do the merging of the document with the database for (unsigned i=0; iGetContact() : 0; Contact *temp = new Contact(this); if (!c) { temp->SetFirst(la->sName); temp->SetEmail(la->sAddr); c = temp; } Dom.Con = c; Thing *t = CreateItem(MAGIC_MAIL, Outbox, false); if (t) { Dom.Email = t->IsMail(); LAutoString s; if (s.Reset(ScribeInsertFields(Source->GetSubject(), &Dom))) Dom.Email->SetSubject(s); LDataPropI *Recip = Dom.Email->GetTo()->Create(Dom.Email->GetObject()->GetStore()); if (Recip) { Recip->CopyProps(*Dom.Con->GetObject()); Recip->SetInt(FIELD_CC, 0); Dom.Email->GetTo()->Insert(Recip); } MergeSegments( Dom.Email->GetObject()->GetObj(FIELD_MIME_SEG), Source->GetObject()->GetObj(FIELD_MIME_SEG), &Dom); Msgs.Insert(Dom.Email); } temp->DecRef(); } // Ask user what to do if (Msgs[0]) { char Msg[256]; sprintf_s(Msg, sizeof(Msg), LLoadString(IDS_MAIL_MERGE_Q), Msgs.Length()); 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 = 0; for (unsigned i=0; iType() == MAGIC_FOLDER) { // Insert new folder into the right point on the tree... ScribeFolder *Sub = CastFolder(Item); if (!Sub) return true; LgiTrace("OnDelete folder %p (%i)\n", (ThingType*)Sub, ThingType::DirtyThings.HasItem(Sub)); // Remove the deleted object from the dirty queue... ThingType::DirtyThings.Delete(Sub); // And make sure it can't get dirty again... Sub->SetWillDirty(false); // Remove all the children LDataIterator &SubItems = Sub->GetFldObj()->Children(); LArray Children; for (LDataI *c = SubItems.First(); c; c = SubItems.Next()) { Children.Add(c); } OnDelete(Sub->GetFldObj(), Children); // Remove the UI element Sub->Remove(); // Free the memory for the object DeleteObj(Sub); } else { Fld = Parent ? CastFolder(Parent) : 0; if (!Fld) return false; Thing *t = CastThing(Item); if (!t) { // This happens when the item moves from one store to another // of a different type and a copy of the old object is made. The // 'Thing' is detached from 'Item' and attached to the new LDataI // object. // However if the object is an unread email... we should still decrement the // folder's unread count. if (Item->Type() == MAGIC_MAIL && !(Item->GetInt(FIELD_FLAGS) & MAIL_READ)) { Fld->OnUpdateUnRead(-1, false); } } else { LAssert(!Fld || Fld == t->GetFolder()); // LgiTrace("OnDelete thing %p (%i)\n", (ThingType*)t, ThingType::DirtyThings.HasItem(t)); // Remove the deleted object from the dirty queue... ThingType::DirtyThings.Delete(t); // And make sure it can't get dirty again... t->SetWillDirty(false); // Was the thing currently being previewed? if (PreviewPanel && PreviewPanel->GetCurrent() == t) PreviewPanel->OnThing(0, false); // Was the object selected in the thing list? if (Fld && Fld->Select()) { // If so, select an adjacent item. if (SelectIdx < 0 && t->Select()) SelectIdx = MailList->IndexOf(t); MailList->Remove(t); } // Was the object an unread email? Mail *m = t->IsMail(); if (m) { if (!TestFlag(m->GetFlags(), MAIL_READ) && Fld) UnreadAdjust--; Mail::NewMailLst.Delete(m); } if (Fld) { LAssert(Fld->Items.HasItem(t)); Fld->Items.Delete(t); Fld->Update(); } t->SetUI(); t->SetParentFolder(NULL); t->SetObject(NULL, false, _FL); if (!t->DecRef()) { t->SetDirty(false); t->SetWillDirty(false); } } } } if (Fld) Fld->OnUpdateUnRead(UnreadAdjust, false); if (SelectIdx >= 0) { LListItem *i = MailList->ItemAt(SelectIdx); if (!i && MailList->Length() > 0) i = MailList->ItemAt(MailList->Length()-1); if (i) i->Select(true); } return true; } bool ScribeWnd::AddStore3EventHandler(LDataEventsI *callback) { if (!d->Store3EventCallbacks.HasItem(callback)) { d->Store3EventCallbacks.Add(callback); } return true; } bool ScribeWnd::RemoveStore3EventHandler(LDataEventsI *callback) { d->Store3EventCallbacks.Delete(callback); return true; } bool ScribeWnd::OnMailTransferEvent(MailTransferEvent *t) { if (!Lock(_FL)) return false; LAssert(t); d->Transfers.Add(t); Unlock(); return true; } bool ScribeWnd::OnTransfer() { LVariant v; LArray Local; // Lock the transfer list if (Lock(_FL)) { // Take out a bunch of emails... for (int i=0; i<5 && d->Transfers.Length() > 0; i++) { auto t = d->Transfers[0]; LAssert(t); d->Transfers.DeleteAt(0, true); // Save them to a local array Local.Add(t); } Unlock(); if (Local.Length() == 0) // Didn't get any return false; } LArray Trans; for (unsigned s=0; sStartTransaction(); } for (auto Transfer: Local) { ReceiveStatus NewStatus = MailReceivedError; if (!Transfer) { LAssert(0); continue; } // We have to set Transfer->Status to something other than "waiting" // in all branches of this loop. Othewise the account thread will hang. Accountlet *Acc = Transfer->Account; #if DEBUG_NEW_MAIL LgiTrace( "%s:%i - NewMail.OnTransfer t=%p receive=%i act=%i\n", _FL, Transfer, Acc->IsReceive(), Transfer->Action); #endif if (Acc->IsReceive()) { switch (Transfer->Action) { default: break; case MailDownload: case MailDownloadAndDelete: { // Import newly received mail from file ReceiveAccountlet *Receive = dynamic_cast(Acc); if (Receive) { LVariant Path = Receive->DestinationFolder(); ScribeFolder *Inbox = Path.Str() ? GetFolder(Path.Str()) : NULL; if (!Inbox) Inbox = GetFolder(FOLDER_INBOX); if (Inbox) { Mail *m = new Mail(this, Inbox->GetObject()->GetStore()->Create(MAGIC_MAIL)); if (m) { // Just in case m->SetParentFolder(Inbox); // Set the new flag... m->SetFlags(m->GetFlags() | MAIL_NEW, true, false); // Set the account to, which is used to map the email back // to the incoming account in the case that the headers // don't have the correct "To" information. m->SetAccountId(Receive->Id()); // Decode the email m->OnAfterReceive(Transfer->Rfc822Msg); LVariant v; m->SetServerUid(v = Transfer->Uid); // Save to permanent storage if (m->Save(Inbox)) { m->SetDirty(false); NewStatus = MailReceivedOk; // Only after we've safely stored the email can we // actually mark it as downloaded. if (Transfer->Uid) { Receive->AddMsg(Transfer->Uid); } } else LgiTrace("%s:%i - Error: Couldn't save mail to folders.\n", _FL); } else LgiTrace("%s:%i - Error: Memory alloc failed.\n", _FL); } else LgiTrace("%s:%i - Error: No Inbox.\n", _FL); } else LgiTrace("%s:%i - Error: Bad ptr.\n", _FL); break; } case MailHeaders: { LList *Lst; if (Transfer->Msg && (Lst = Transfer->GetList())) { //LgiTrace("Using Lst=%p\n", Lst); Lst->Insert(Transfer->Msg); NewStatus = MailReceivedOk; } break; } } } else if (Transfer->Send) { ScribeFolder *Outbox = GetFolder(Transfer->Send->SourceFolder); if (!Outbox) Outbox = GetFolder(FOLDER_OUTBOX); - Outbox->GetMessageById(Transfer->Send->MsgId, [&](auto m) + 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()) { GDataIt To = m->GetObject()->GetList(FIELD_TO); if (To) { for (LDataPropI *a = To->First(); a; a = To->Next()) { if (a->GetStr(FIELD_EMAIL)) WhiteListIncrement(a->GetStr(FIELD_EMAIL)); } } } - NewStatus = MailReceivedOk; + // 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,4328 +1,4401 @@ /* ** FILE: ScribeFolder.cpp ** AUTHOR: Matthew Allen ** DATE: 17/1/2000 ** DESCRIPTION: Scribe folder's ** ** Copyright (C) 2000-2002, Matthew Allen ** fret@memecode.com */ // Includes #include "Scribe.h" #include "lgi/common/DropFiles.h" #include "lgi/common/ProgressDlg.h" #include "lgi/common/LgiQuickSort.h" #include "lgi/common/DisplayString.h" #include "lgi/common/TextFile.h" #include "lgi/common/LgiRes.h" #include "lgi/common/FileSelect.h" #include "lgi/common/ClipBoard.h" #include "Calendar.h" #include "resdefs.h" #include "ReplicateDlg.h" ////////////////////////////////////////////////////////////////////////////// #if WINNATIVE #include "lgi/common/Com.h" #endif class LDndFilePromise #if defined(WINDOWS) #elif defined(__GTK_H__) #elif defined(MAC) #endif { LStream *Src; #if WINNATIVE FILEGROUPDESCRIPTOR Gd; #elif defined(__GTK_H__) #elif defined(MAC) #endif public: LDndFilePromise(LDragData &dd, LStream *src, LString FileName) { Src = src; #if WINNATIVE ZeroObj(Gd); Gd.cItems = 1; // CLSID Fd.clsid; // SIZEL Fd.sizel; // POINTL Fd.pointl; // FILETIME Fd.ftCreationTime; // FILETIME Fd.ftLastAccessTime; // FILETIME Fd.ftLastWriteTime; FILEDESCRIPTOR &Fd = Gd.fgd[0]; Fd.dwFlags = FD_ATTRIBUTES | FD_PROGRESSUI; Fd.dwFileAttributes = FILE_ATTRIBUTE_COMPRESSED | FILE_ATTRIBUTE_NORMAL; int64 Sz = Src->GetSize(); if (Sz >= 0) { Fd.dwFlags |= FD_FILESIZE; Fd.nFileSizeHigh = Sz >> 32; Fd.nFileSizeLow = Sz & 0xffffffff; } auto Leaf = FileName.RFind(DIR_STR); LAutoWString FnW(Utf8ToWide(Leaf >= 0 ? FileName(Leaf+1,-1) : FileName)); Strcpy(Fd.cFileName, CountOf(Fd.cFileName), FnW.Get()); LVariant &v = dd.Data[0]; v.SetBinary(sizeof(Gd), &Gd); #elif defined(__GTK_H__) LAssert(!"Not impl."); #elif LGI_COCOA LAssert(!"Not impl."); #elif LGI_CARBON // See Apple: Technical Note TN1085 // https://web.archive.org/web/20080725134839/http://developer.apple.com/technotes/tn/tn1085.html // flavorTypePromiseHFS = 'phfs' PromiseHFSFlavor Promise; ZeroObj(Promise); Promise.fileType = 'mbox'; Promise.fileCreator = '****'; Promise.promisedFlavor = kDragPromisedFlavor; // 'fssP' int Sz = sizeof(Promise); LVariant &v1 = dd.Data[0]; v1.SetBinary(sizeof(Promise), &Promise, false); // The Dnd code will add the 2nd part of the promise // There isn't the visibility into that API at this level #else #error "Impl me." #endif } ~LDndFilePromise() { #if defined(WINDOWS) #elif defined(__GTK_H__) #elif defined(MAC) #endif } #if defined(WINDOWS) #elif defined(__GTK_H__) #elif defined(MAC) #endif int AddContents(LDragData &dd) { dd.Data[0] = Src; return 1; } }; ////////////////////////////////////////////////////////////////////////////// #define PROFILE_POPULATE 0 #define PROFILE_LOAD_THINGS 0 int FoldersCurrentlyLoading = 0; char ScribeFolderObject[] = "com.memecode.Folder"; class ThingContainerPriv { public: int8 IsInbox; bool InUpdateUnread = false; LAutoPtr DsBase; LAutoPtr DsUnread; LAutoPtr FilePromise; ThingContainerPriv() { IsInbox = -1; } }; ////////////////////////////////////////////////////////////////////////////// ScribeFolder::ScribeFolder() { d = new ThingContainerPriv; } ScribeFolder::~ScribeFolder() { bool IsRoot = !GetParent(); if (CurState != FldState_Idle) { // int Cur = FoldersCurrentlyLoading; #ifdef _DEBUG LAssert(!"Can't delete folder while it's busy."); #else LgiMsg( App, "Can't unload folder while busy. Please report what\n" "you were attempting to do to fret@memecode.com", "Scribe Error", MB_OK); #endif return; } switch (GetItemType()) { case MAGIC_CALENDAR: CalendarSource::FolderDelete(this); // fall through case MAGIC_CONTACT: case MAGIC_FILTER: case MAGIC_GROUP: App->RemoveThingSrc(this); break; default: break; } if (View()) { bool IsMe = View()->GetContainer() == this; // bool IsSelect = Select(); View()->DeletePlaceHolders(); if (IsMe) { View()->SetContainer(0); View()->RemoveAll(); } } Thing *t; int i = 0; while ((t = Items[i])) { if (!t->DecRef()) i++; } Update(); EmptyFieldList(); DeleteObj(d); ScribeFolder *f = GetChildFolder(); DeleteObj(f); if (!IsRoot) { f = GetNextFolder(); DeleteObj(f); } // Don't delete 'Object' here, it's owned by the backend storage object } bool ScribeFolder::SetObject(LDataI *o, bool InDestructor, const char *File, int Line) { if (CurState != FldState_Idle) { LAssert(!"Can't set object while folder is not idle."); return false; } return LDataUserI::SetObject(o, InDestructor, File, Line); } void ScribeFolder::UpdateOsUnread() { #ifdef WIN32 if (App->GetFolder(FOLDER_INBOX) == this) { LHashTbl, int> Un(0, -1); // Pre-populate 'Un' with our email accounts List *Acc = App->GetAccounts(); if (Acc) { for (auto a: *Acc) { LVariant e = a->Identity.Email(); if (e.Str()) Un.Add(e.Str(), 0); } } // Scan folder for unread email... for (auto t : Items) { Mail *m = t->IsMail(); if (m) { if (!TestFlag(m->GetFlags(), MAIL_READ)) { ScribeAccount *a = m->GetAccountSentTo(); if (a) { LVariant Email; Email = a->Identity.Email(); if (Email.Str()) { int Cur = Un.Find(Email.Str()); Un.Add(Email.Str(), Cur + 1); } } } } } #if 0 // Set system email status LArray Ver; int Os = LGetOs(&Ver); if ((Os == LGI_OS_WIN32 || Os == LGI_OS_WIN64) && (Ver[0] > 5 || (Ver[0] == 5 && Ver[1] > 0))) { char e[256]; LgiGetExeFile(e, sizeof(e)); typedef HRESULT (__stdcall *pSHSetUnreadMailCount)(LPCWSTR pszMailAddress, DWORD dwCount,LPCWSTR pszShellExecuteCommand); LLibrary Shell32("shell32"); pSHSetUnreadMailCount SHSetUnreadMailCount = (pSHSetUnreadMailCount) Shell32.GetAddress("SHSetUnreadMailCountW"); if (SHSetUnreadMailCount) { LVariant Exe; Exe = e; char *k; for (int u=Un.First(&k); u>=0; u=Un.Next(&k)) { LVariant Email = k; SHSetUnreadMailCount(Email.WStr(), u, Exe.WStr()); } } } #endif } #endif } void ScribeFolder::SetLoadOnDemand() { if ((LoadOnDemand = new LTreeItem)) { Expanded(false); LoadOnDemand->SetText((char*)LLoadString(IDS_LOADING)); Insert(LoadOnDemand); } } void ScribeFolder::GetMessageById(const char *Id, std::function Callback) { if (!Id) { if (Callback) Callback(NULL); return; } LoadThings(NULL, [&](auto s) { if (s <= Store3Loading) { if (Callback) Callback(NULL); return; } for (auto t : Items) { Mail *r = t->IsMail(); if (!r) continue; auto rid = r->GetMessageId(); if (!Stricmp(rid, Id)) { 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 Path = GetFolder()->GetPath(); auto OnAllow = [&]() { // Generic thing storage.. bool Create = !t->GetObject(); if (Create) t->SetObject(GetObject()->GetStore()->Create(t->Type()), false, _FL); if (!t->GetObject()) { LAssert(!"No object?"); LgiTrace("%s:%i - No object to save.\n", _FL); 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, [&](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 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, _FL); + 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()) { SerializeFieldWidths(); if (View()) { SetSort(View()->GetSortCol(), View()->GetSortAscending()); } if (OpenFolderProperties(this, Tab)) { SetDirty(); SerializeFieldWidths(true); Populate(View()); } } } ScribeFolder *ScribeFolder::CreateSubDirectory(const char *Name, int Type) { ScribeFolder *NewFolder = 0; auto ThisObj = dynamic_cast(GetObject()); if (Name && ThisObj && ThisObj->GetStore()) { LDataI *Fld = GetObject()->GetStore()->Create(MAGIC_FOLDER); if (Fld) { LDataFolderI *Obj = dynamic_cast(Fld); if (Obj) { NewFolder = new ScribeFolder; if (NewFolder) { NewFolder->App = App; NewFolder->SetObject(Obj, false, _FL); // Set name and type NewFolder->SetName(Name, true); NewFolder->GetObject()->SetInt(FIELD_FOLDER_TYPE, Type); ThisObj->SubFolders(); if (NewFolder->GetObject()->Save(ThisObj)) { Insert(NewFolder); NewFolder->SetDefaultFields(); NewFolder->OnItemType(); } else { DeleteObj(NewFolder); } } } } } return NewFolder; } bool ScribeFolder::Delete(LArray &Items, bool ToTrash) { if (!App) return false; List NotNew; LArray Del; LDataStoreI *Store = NULL; for (auto i: Items) { if (i->IsPlaceHolder()) { i->DecRef(); } else { Mail *m = i->IsMail(); if (m) NotNew.Insert(m); auto ObjStore = i->GetObject() ? i->GetObject()->GetStore() : NULL; if (!Store) Store = ObjStore; if (Store == ObjStore) Del.Add(i->GetObject()); else LAssert(!"All objects must have the same store."); } } if (NotNew.Length()) App->OnNewMail(&NotNew, false); if (Del.Length() == 0) return true; if (!Store) { LgiTrace("%s:%i - No Store?\n", _FL); return false; } if (!Store->Delete(Del, ToTrash)) { LgiTrace("%s:%i - Store.Delete failed.\n", _FL); return false; } return true; } #define MoveToStatus(idx, status) if (Status) { (*Status)[idx] = (status); } bool ScribeFolder::MoveTo(LArray &Items, bool CopyOnly, LArray *Status) { if (Items.Length() == 0) return false; if (!GetObject() || !App) return false; auto FolderItemType = GetItemType(); for (unsigned i=0; iGetObject()) { MoveToStatus(i, Store3Error); } else { auto ThingItemType = t->Type(); if (!(FolderItemType == ThingItemType || FolderItemType == MAGIC_ANY)) MoveToStatus(i, Store3Error); } } LVariant BayesInc; if (App) App->GetOptions()->GetValue(OPT_BayesIncremental, BayesInc); auto ThisFolderPath = GetPath(); auto NewBayesType = App->BayesTypeFromPath(ThisFolderPath); int NewFolderType = App->GetFolderType(this); ScribeFolder *TemplatesFolder = App->GetFolder(FOLDER_TEMPLATES); bool BuildDynMenus = this == TemplatesFolder; LArray InStoreMove; // Object in the same store... auto ThisStore = GetObject()->GetStore(); LHashTbl, bool> Map; for (unsigned i=0; iGetFolder(); LString Path; if (Old) { Path = Old->GetPath(); if (Old == TemplatesFolder) // Moving to or from the templates folder... update the menu BuildDynMenus = true; } ScribeMailType OldBayesType = BayesMailUnknown; if (BayesInc.CastInt32() && t->IsMail() && TestFlag(t->IsMail()->GetFlags(), MAIL_READ)) { OldBayesType = App->BayesTypeFromPath(t->IsMail()); } 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; int Hdrs; bool NewMsg; LArray Buf; int64 Pos; bool Eof; bool IsMessageHdr(char *c) { if (!c) return false; // check that it's a from line LToken T(c, " \r", true); if (T.Length() >= 7 && T.Length() <= 9 && !strcmp(T[0], "From")) { return true; } return false; } public: MboxParser(LStreamI *s) { Src = s; Hdrs = 0; Pos = 0; NewMsg = true; Eof = false; Buf.Length(128 << 10); } void SeekNext() { if (!NewMsg) { char b[256]; int r; while ( !NewMsg && (r = Pop(b, sizeof(b)) > 0) ) ; } } bool GetEof() { return Eof; } ssize_t Pop(char *Str, ssize_t BufSize) { while (true) { ssize_t r = LStringPipe::Pop(Str, BufSize); if (r <= 0 && Src) { r = Src ? Src->Read(&Buf[0], Buf.Length()) : 0; if (r > 0) { Push(&Buf[0], r); } else { Eof = true; return 0; } } else if (IsMessageHdr(Str)) { Pos += r; NewMsg = true; if (Hdrs++) return 0; } else { NewMsg = false; Pos += r; return r; } } return 0; } }; struct TempMsg : public LTempStream { TempMsg() : LTempStream(ScribeTempPath()) { } bool ReadMessage(LStringPipe *p) { LArray Buf(128<<10); ssize_t Bytes; bool Status = true; char *Ptr = &Buf[0]; ssize_t Len = Buf.Length(); while ( (Bytes = p->Pop(Ptr, Len)) > 0) { Status &= Write(&Buf[0], Bytes) == Bytes; } return Status; } }; class FolderTask : public LProgressDlg { protected: ScribeWnd *App = NULL; ScribeFolder *Folder = NULL; LString MimeType; LAutoPtr Stream; ThingType::IoProgress Status; ThingType::IoProgressCallback onComplete; public: // Minimum amount of time to do work. constexpr static int WORK_SLICE_MS = 130; // This should be larger then WORK_SLICE_MS to allow message loop to process constexpr static int PULSE_MS = 200; FolderTask( ScribeFolder *folder, LAutoPtr stream, LString mimeType, ThingType::IoProgressCallback cb) : LProgressDlg(folder->App), Folder(folder), Stream(stream), MimeType(mimeType), onComplete(cb), Status(Store3Success) { App = Folder->App; Ts = LCurrentTime(); SetParent(Folder->GetTree()); SetPulse(PULSE_MS); SetAlwaysOnTop(true); App->OnFolderTask(this, true); } virtual ~FolderTask() { Folder->App->OnFolderTask(this, false); if (onComplete) onComplete(&Status, Stream); } bool OnRequestClose(bool OsClose) { return true; } void OnPulse() { LProgressDlg::OnPulse(); auto StartTs = LCurrentTime(); while ( !IsCancelled() && (LCurrentTime() - StartTs) < WORK_SLICE_MS) { if (!TimeSlice()) { Quit(); break; } } } /// This should use around WORK_SLICE_MS of time and then /// \returns true if more work to do or false if finished. virtual bool TimeSlice() = 0; }; class ImportFolderTask : public FolderTask { LDataStoreI::StoreTrans trans; public: ImportFolderTask(ScribeFolder *fld, LAutoPtr in, LString mimeType, ThingType::IoProgressCallback cb) : FolderTask(fld, in, mimeType, cb) { SetDescription(LLoadString(IDS_MBOX_READING)); SetType("K"); SetScale(1.0/1024.0); SetRange(Stream->GetSize()); trans = fld->GetObject()->GetStore()->StartTransaction(); } bool TimeSlice() { auto Start = LCurrentTime(); MboxParser Parser(Stream); TempMsg *Tm; LAutoStreamI Msg(Tm = new TempMsg); while ( !Parser.GetEof() && LCurrentTime() - Start < WORK_SLICE_MS && !IsCancelled() && Tm->ReadMessage(&Parser)) { Mail *m = dynamic_cast(App->CreateItem(MAGIC_MAIL, Folder, false)); if (m) { m->OnAfterReceive(Msg); m->SetFlags(MAIL_RECEIVED|MAIL_READ); m->Save(); m->Update(); } Msg.Reset(Tm = new TempMsg); Parser.SeekNext(); Value(Stream->GetPos()); } return !Parser.GetEof(); } }; ThingType::IoProgress ScribeFolder::Import(IoProgressImplArgs) { if (Stricmp(mimeType, sMimeMbox) == 0 || Stricmp(mimeType, "text/x-mail") == 0) { // Mail box format... ThingType::IoProgress p(Store3Delayed); p.prog = new ImportFolderTask(this, stream, mimeType, cb); return p; } else if (Stricmp(mimeType, sMimeVCard) == 0) { VCard Io; Thing *t; bool Error = false; int Imported = 0; int Idx = 0; while ((t = App->CreateItem(GetItemType(), 0, false))) { Contact *c = t->IsContact(); if (!c) { t->DecRef(); Error = true; break; } if (Io.Import(c->GetObject(), stream)) { const char *First = 0, *Last = 0; c->GetField(FIELD_FIRST_NAME, First); c->GetField(FIELD_LAST_NAME, Last); LgiTrace("Import %i %s %s\n", Idx, First, Last); if (t->Save(this)) { Imported++; } else { Error = true; break; } } else { t->DecRef(); break; } Idx++; } if (Error) { LgiMsg( App, LLoadString(IDS_ERROR_IMPORT_COUNT), AppName, MB_OK, LLoadString(IDC_CONTACTS), Imported); IoProgressError("Contact import error."); } IoProgressSuccess(); } else if (Stricmp(mimeType, sMimeVCalendar) == 0) { VCal Io; Thing *t; while ((t = App->CreateItem(GetItemType(), this, false))) { if (Io.Import(t->GetObject(), stream)) { if (!t->Save(this)) IoProgressError("Contact save failed."); } else { t->DecRef(); break; } } IoProgressSuccess(); } else if (GetObject()) { Thing *t = App->CreateThingOfType(GetItemType(), GetObject()->GetStore()->Create(GetItemType())); if (!t) IoProgressError("Failed to create contact"); if (!t->Import(stream, mimeType)) { t->DecRef(); IoProgressError("Contact import failed."); } if (!t->Save(this)) { t->DecRef(); IoProgressError("Contact save failed."); } IoProgressSuccess(); } IoProgressNotImpl(); } class ExportFolderTask : public FolderTask { int Idx = 0; public: ExportFolderTask( ScribeFolder *folder, LAutoPtr out, LString mimeType, ThingType::IoProgressCallback cb) : FolderTask(folder, out, mimeType, cb) { bool Mbox = _stricmp(MimeType, sMimeMbox) == 0; // Clear the files contents Stream->SetSize(0); // Setup progress UI SetDescription(Mbox ? LLoadString(IDS_MBOX_WRITING) : (char*)"Writing..."); SetRange(Folder->Items.Length()); switch (Folder->GetItemType()) { case MAGIC_MAIL: SetType(LLoadString(IDS_EMAIL)); break; case MAGIC_CALENDAR: SetType(LLoadString(IDS_CALENDAR)); break; case MAGIC_CONTACT: SetType(LLoadString(IDS_CONTACT)); break; case MAGIC_GROUP: SetType("Groups"); break; default: SetType("Objects"); break; } SetPulse(PULSE_MS); SetAlwaysOnTop(true); } bool TimeSlice() { auto Start = LCurrentTime(); while ( LCurrentTime() - Start < WORK_SLICE_MS && !IsCancelled()) { if (Idx >= (ssize_t)Folder->Items.Length()) return false; // Process all the container's items Thing *t = Folder->Items[Idx++]; if (!t) return false; LAutoPtr wrapper(new LProxyStream(Stream)); if (!t->Export(wrapper, MimeType)) { Status.status = Store3Error; Status.errMsg = "Error exporting items."; return false; } Value(Idx); } return true; } /* void OnPulse() { LProgressDlg::OnPulse(); PostEvent(M_EXPORT_NEXT); } LMessage::Result OnEvent(LMessage *Msg) { if (Msg->Msg() == M_EXPORT_NEXT) { auto StartTs = LCurrentTime(); while ( !IsCancelled() && (LCurrentTime() - StartTs) < WORK_SLICE_MS) { if (Idx >= (ssize_t)Folder->Items.Length()) { Quit(); break; } // Process all the container's items Thing *t = Folder->Items[Idx++]; if (t) { LAutoPtr wrapper(new LProxyStream(Out)); if (t->Export(wrapper, MimeType)) { Value(Idx); } else { Status.status = Store3Error; Status.errMsg = "Error exporting items."; if (!onComplete) LgiMsg(this, "%s", AppName, MB_OK, Status.errMsg.Get()); Quit(); } } } return 0; } return LProgressDlg::OnEvent(Msg); } */ }; +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) - { - auto Task = new FolderExportTask(f, this, MimeType); - if (Callback) Callback(Task); - } - else if (Callback) - Callback(NULL); + Task = new FolderExportTask(f, this, MimeType); + if (Callback) + Callback(Task); }); } -void ScribeFolder::Export(LStreamI &f, const char *MimeType, std::function Callback) +ThingType::IoProgress ScribeFolder::Export(IoProgressImplArgs) { - if (!MimeType) + IoProgress ErrStatus(Store3Error); + if (!mimeType) { - if (Callback) Callback(Store3Error); - return; + ErrStatus.errMsg = "No mimetype."; + if (cb) cb(&ErrStatus, NULL); + return ErrStatus; } - - LoadThings(NULL, [&](auto Status) - { - if (Status != Store3Loaded) - { - if (Callback) Callback(Status); - return; - } - - bool Mbox = _stricmp(MimeType, sMimeMbox) == 0; - LProgressDlg Dlg(App); - App->OnFolderTask(&Dlg, true); - - // Clear the files contents - f.SetSize(0); - - // Setup progress UI - Dlg.SetDescription(Mbox ? LLoadString(IDS_MBOX_WRITING) : (char*)"Writing..."); - Dlg.Invalidate((LRect*)0, true); - Dlg.SetRange(Items.Length()); - Dlg.SetType(LLoadString(IDS_EMAIL)); - - // Process all the container's items - int Error = 0; - for (auto i: Items) - { - if (!i->Export(f, MimeType)) - Error++; - - Dlg.Value(Dlg.Value()+1); - Dlg.Invalidate((LRect*)0, true); - } - - // all done - App->OnFolderTask(&Dlg, false); - - if (Callback) Callback(Error ? Store3Error : Store3Success); - }); + 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; - } - - Export(f, Args[1]->Str(), [&](auto Status) - { - *ReturnValue = Status == Store3Success; - }); - - WaitForVariant(*ReturnValue); } break; } default: break; } return false; } void ScribeFolder::OnRethread() { if (GetThreaded()) { Thread(); } } diff --git a/Linux/Makefile.linux b/Linux/Makefile.linux --- a/Linux/Makefile.linux +++ b/Linux/Makefile.linux @@ -1,745 +1,747 @@ #!/usr/bin/make # # This makefile generated by LgiIde # http://www.memecode.com/lgi.php # .SILENT : CC = gcc CPP = g++ Target = ./scribe ifndef Build Build = Debug endif BuildDir = $(Build) Flags = -fPIC -w -fno-inline -fpermissive ifeq ($(Build),Debug) Flags += -g -std=c++14 Tag = d Defs = -D_DEBUG -DLINUX -D_REENTRANT -D_FILE_OFFSET_BITS=64 -DPOSIX -DSCRIBE_APP -DLIBPNG_SHARED=1 -DLIBJPEG_SHARED=1 -DLIBPNG_VERSION=\"1.5\" -DPOSIX Libs = \ -L../../libs/build-x64/libchardet \ -lchardet \ -static-libgcc \ -lpthread \ -ldl \ -lpng \ -ljpeg \ -lz \ `pkg-config --libs gtk+-3.0` \ -laspell-dist-060$(Tag) \ -L../../libs/aspell-0.60.6.1/linux/$(BuildDir) \ -lbzip2$(Tag) \ -L../../libs/bzip2-1.0.6/linux/$(BuildDir) \ - -llgi-dev$(Tag) \ + -llgi-gtk3$(Tag) \ -L../../../lgi/trunk/$(BuildDir) Inc = \ `pkg-config --cflags gtk+-3.0` \ -I../Utils/Tables \ -I../Resources \ -I../Code/Store3Webdav \ -I../Code/Sqlite \ -I../Code \ -I../../libs/libchardet/src \ -I../../libs/libchardet/include \ -I../../libs/aspell-0.60.6.1/interfaces/cc \ -I../../../lgi/trunk/private/common \ -I../../../lgi/trunk/include/lgi/linux/Gtk \ -I../../../lgi/trunk/include/lgi/linux \ -I../../../lgi/trunk/include \ -I../../../../codelib/openssl/include else Flags += -s -Os -std=c++14 Defs = -DLINUX -D_REENTRANT -D_FILE_OFFSET_BITS=64 -DPOSIX -DSCRIBE_APP -DLIBPNG_SHARED=1 -DLIBJPEG_SHARED=1 -DLIBPNG_VERSION=\"1.5\" -DPOSIX Libs = \ -L../../libs/build-x64/libchardet \ -lchardet \ -static-libgcc \ -lpthread \ -ldl \ -lpng \ -ljpeg \ -lz \ `pkg-config --libs gtk+-3.0` \ -laspell-dist-060$(Tag) \ -L../../libs/aspell-0.60.6.1/linux/$(BuildDir) \ -lbzip2$(Tag) \ -L../../libs/bzip2-1.0.6/linux/$(BuildDir) \ - -llgi-dev$(Tag) \ + -llgi-gtk3$(Tag) \ -L../../../lgi/trunk/$(BuildDir) Inc = \ `pkg-config --cflags gtk+-3.0` \ -I../Utils/Tables \ -I../Resources \ -I../Code/Store3Webdav \ -I../Code/Sqlite \ -I../Code \ -I../../libs/libchardet/src \ -I../../libs/libchardet/include \ -I../../libs/aspell-0.60.6.1/interfaces/cc \ -I../../../lgi/trunk/private/common \ -I../../../lgi/trunk/include/lgi/linux/Gtk \ -I../../../lgi/trunk/include/lgi/linux \ -I../../../lgi/trunk/include \ -I../../../../codelib/openssl/include endif # Dependencies Source = ../Utils/Tables/MailFlags.c \ ../Code/TitlePage.cpp \ ../Code/Store3Webdav/WebdavThread.cpp \ ../Code/Store3Webdav/WebdavStore.cpp \ ../Code/Store3Webdav/WebdavFolder.cpp \ ../Code/Store3Webdav/WebdavContact.cpp \ ../Code/Store3Webdav/WebdavCalendar.cpp \ ../Code/Store3Mail3/Mail3Store.cpp \ ../Code/Store3Mail3/Mail3Mail.cpp \ ../Code/Store3Mail3/Mail3Group.cpp \ ../Code/Store3Mail3/Mail3Folder.cpp \ ../Code/Store3Mail3/Mail3Filter.cpp \ ../Code/Store3Mail3/Mail3Contact.cpp \ ../Code/Store3Mail3/Mail3Calendar.cpp \ ../Code/Store3Mail3/Mail3Attachment.cpp \ ../Code/Store3Imap/ScribeImap_Thread.cpp \ ../Code/Store3Imap/ScribeImap_Store.cpp \ ../Code/Store3Imap/ScribeImap_Mail.cpp \ ../Code/Store3Imap/ScribeImap_Folder.cpp \ ../Code/Store3Imap/ScribeImap_Attachment.cpp \ ../Code/Store3Common.cpp \ ../Code/Sqlite/v3.6.14/sqlite3.c \ ../Code/SecurityDlg.cpp \ ../Code/SearchView.cpp \ ../Code/Scripting.cpp \ ../Code/ScribeUtils.cpp \ ../Code/ScribeUi.cpp \ ../Code/ScribeThing.cpp \ ../Code/ScribeStatusPanel.cpp \ ../Code/ScribeSockets.cpp \ ../Code/ScribeSendReceive.cpp \ ../Code/ScribeRepair.cpp \ ../Code/ScribePassword.cpp \ ../Code/ScribePageSetup.cpp \ ../Code/ScribeMain.cpp \ ../Code/ScribeMail.cpp \ ../Code/ScribeListAddr.cpp \ ../Code/ScribeLangDlg.cpp \ ../Code/ScribeItemList.cpp \ ../Code/ScribeGroup.cpp \ ../Code/ScribeFolderTree.cpp \ ../Code/ScribeFolderSelect.cpp \ ../Code/ScribeFolderProp.cpp \ ../Code/ScribeFolderDlg.cpp \ ../Code/ScribeFolder.cpp \ ../Code/ScribeFinder.cpp \ ../Code/ScribeFilter.cpp \ ../Code/ScribeContact.cpp \ ../Code/ScribeAttachment.cpp \ ../Code/ScribeApp.cpp \ ../Code/ScribeAccountUI.cpp \ ../Code/ScribeAccountPreview.cpp \ ../Code/ScribeAccount.cpp \ ../Code/ScribeAbout.cpp \ ../Code/ReplicateDlg.cpp \ ../Code/RemoteCalendarSource.cpp \ ../Code/PrintPreview.cpp \ ../Code/PrintContext.cpp \ ../Code/PreviewPanel.cpp \ ../Code/OptionsDlg.cpp \ ../Code/ObjectInspector.cpp \ ../Code/MContainer.cpp \ ../Code/ManageMailStores.cpp \ ../Code/ImpExp_Mbox.cpp \ ../Code/Imp_OutlookExpress.cpp \ ../Code/Imp_NetscapeContacts.cpp \ ../Code/Imp_Mozilla.cpp \ ../Code/Imp_Eudora.cpp \ ../Code/Imp_Eml.cpp \ ../Code/Imp_CsvContacts.cpp \ ../Code/HtmlToText.cpp \ ../Code/GNewMailDlg.cpp \ ../Code/GBTreeWordStore.cpp \ ../Code/FolderCalendarSource.cpp \ ../Code/Exp_Scribe.cpp \ ../Code/Encryption/GnuPG.cpp \ ../Code/Components.cpp \ ../Code/CalendarView.cpp \ ../Code/Calendar.cpp \ ../Code/BayesianFilter.cpp \ ../Code/BayesDlg.cpp \ ../Code/AddressSelect.cpp \ ../../libs/Btree/offsetlist.c \ ../../libs/Btree/hashtable.c \ ../../libs/Btree/gbtree.cpp \ ../../libs/Btree/db_utils.c \ ../../libs/Btree/db_lock.c \ ../../libs/Btree/db_header.c \ ../../libs/Btree/db_cache.c \ ../../libs/Btree/db_blocks.c \ ../../libs/Btree/db_blocklist.c \ ../../libs/Btree/db.c \ ../../libs/Btree/btree_traverse.c \ ../../libs/Btree/btree_search.c \ ../../libs/Btree/btree_node.c \ ../../libs/Btree/btree_lock.c \ ../../libs/Btree/btree_insert.c \ ../../libs/Btree/btree_header.c \ ../../libs/Btree/btree_delete.c \ ../../libs/Btree/btree.c \ ../../../lgi/trunk/src/common/Widgets/ZoomView.cpp \ ../../../lgi/trunk/src/common/Widgets/YearView.cpp \ ../../../lgi/trunk/src/common/Widgets/TimePopup.cpp \ ../../../lgi/trunk/src/common/Widgets/MonthView.cpp \ ../../../lgi/trunk/src/common/Widgets/FilterUi.cpp \ ../../../lgi/trunk/src/common/Widgets/Editor/TextBlock.cpp \ ../../../lgi/trunk/src/common/Widgets/Editor/RichTextEditPriv.cpp \ ../../../lgi/trunk/src/common/Widgets/Editor/RichTextEdit.cpp \ ../../../lgi/trunk/src/common/Widgets/Editor/ImageBlock.cpp \ ../../../lgi/trunk/src/common/Widgets/Editor/HorzRuleBlock.cpp \ ../../../lgi/trunk/src/common/Widgets/Editor/BlockCursor.cpp \ ../../../lgi/trunk/src/common/Widgets/DatePopup.cpp \ ../../../lgi/trunk/src/common/Widgets/ControlTree.cpp \ ../../../lgi/trunk/src/common/Widgets/ColourSelect.cpp \ ../../../lgi/trunk/src/common/Text/XmlTreeUi.cpp \ ../../../lgi/trunk/src/common/Text/vCard-vCal.cpp \ ../../../lgi/trunk/src/common/Text/TextView4.cpp \ ../../../lgi/trunk/src/common/Text/TextConvert.cpp \ ../../../lgi/trunk/src/common/Text/SpellCheckAspell.cpp \ ../../../lgi/trunk/src/common/Text/HtmlParser.cpp \ ../../../lgi/trunk/src/common/Text/HtmlCommon.cpp \ ../../../lgi/trunk/src/common/Text/Html.cpp \ ../../../lgi/trunk/src/common/Text/Homoglyphs/HomoglyphsTable.cpp \ ../../../lgi/trunk/src/common/Text/Homoglyphs/Homoglyphs.cpp \ ../../../lgi/trunk/src/common/Text/Emoji/EmojiTools.cpp \ ../../../lgi/trunk/src/common/Text/Emoji/EmojiMap.cpp \ ../../../lgi/trunk/src/common/Net/WebDav.cpp \ ../../../lgi/trunk/src/common/Net/OpenSSLSocket.cpp \ ../../../lgi/trunk/src/common/Net/OAuth2.cpp \ ../../../lgi/trunk/src/common/Net/Mime.cpp \ ../../../lgi/trunk/src/common/Net/MailImap.cpp \ ../../../lgi/trunk/src/common/Net/MailHttp.cpp \ ../../../lgi/trunk/src/common/Net/Mail.cpp \ ../../../lgi/trunk/src/common/Net/HttpTools.cpp \ ../../../lgi/trunk/src/common/Net/Http.cpp \ ../../../lgi/trunk/src/common/Net/Ftp.cpp \ ../../../lgi/trunk/src/common/Lgi/LgiMain.cpp \ ../../../lgi/trunk/src/common/Lgi/Browser.cpp \ ../../../lgi/trunk/src/common/General/Tnef.cpp \ ../../../lgi/trunk/src/common/General/TimeZone.cpp \ ../../../lgi/trunk/src/common/General/SoftwareUpdate.cpp \ ../../../lgi/trunk/src/common/General/SharedMemory.cpp \ ../../../lgi/trunk/src/common/General/SegmentTree.cpp \ ../../../lgi/trunk/src/common/General/Growl.cpp \ ../../../lgi/trunk/src/common/Gdc2/Font/EmojiFont.cpp \ ../../../lgi/trunk/src/common/Gdc2/Filters/Png.cpp \ ../../../lgi/trunk/src/common/Gdc2/Filters/Lzw.cpp \ ../../../lgi/trunk/src/common/Gdc2/Filters/Jpeg.cpp \ ../../../lgi/trunk/src/common/Gdc2/Filters/Gif.cpp \ ../../../lgi/trunk/src/common/Gdc2/DrawListSurface.cpp \ ../../../lgi/trunk/src/common/Db/Db-Csv.cpp \ ../../../lgi/trunk/src/common/Coding/ScriptVM.cpp \ ../../../lgi/trunk/src/common/Coding/ScriptLibrary.cpp \ ../../../lgi/trunk/src/common/Coding/ScriptCompiler.cpp \ ../../../lgi/trunk/src/common/Coding/LexCpp.cpp SourceLst := $(patsubst %.c,%.o,$(patsubst %.cpp,%.o,$(Source))) - Objects := $(addprefix $(BuildDir)/,$(SourceLst)) +Deps := $(patsubst %.o,%.d,$(Objects)) # Target # Executable target -$(Target) : ../../libs/aspell-0.60.6.1/linux/$(BuildDir)/libaspell-dist-060$(Tag).so ../../libs/bzip2-1.0.6/linux/$(BuildDir)/libbzip2$(Tag).a ../../../lgi/trunk/$(BuildDir)/liblgi-dev$(Tag).so $(Objects) +$(Target) : ../../libs/aspell-0.60.6.1/linux/$(BuildDir)/libaspell-dist-060$(Tag).so ../../libs/bzip2-1.0.6/linux/$(BuildDir)/libbzip2$(Tag).a ../../../lgi/trunk/$(BuildDir)/liblgi-gtk3$(Tag).so $(Objects) mkdir -p $(BuildDir) @echo Linking $(Target) [$(Build)]... $(CPP) -Wl,-export-dynamic,-R. -o \ $(Target) $(Objects) $(Libs) @echo Done. ../../libs/aspell-0.60.6.1/linux/$(BuildDir)/libaspell-dist-060$(Tag).so : ../../libs/aspell-0.60.6.1/common/asc_ctype.hpp \ ../../libs/aspell-0.60.6.1/common/basic_list.hpp \ ../../libs/aspell-0.60.6.1/common/block_slist-t.hpp \ ../../libs/aspell-0.60.6.1/common/block_slist.hpp \ ../../libs/aspell-0.60.6.1/common/cache-t.hpp \ ../../libs/aspell-0.60.6.1/common/cache.cpp \ ../../libs/aspell-0.60.6.1/common/cache.hpp \ ../../libs/aspell-0.60.6.1/common/can_have_error.cpp \ ../../libs/aspell-0.60.6.1/common/can_have_error.hpp \ ../../libs/aspell-0.60.6.1/common/char_vector.hpp \ ../../libs/aspell-0.60.6.1/common/clone_ptr-t.hpp \ ../../libs/aspell-0.60.6.1/common/clone_ptr.hpp \ ../../libs/aspell-0.60.6.1/common/config.cpp \ ../../libs/aspell-0.60.6.1/common/config.hpp \ ../../libs/aspell-0.60.6.1/common/convert.cpp \ ../../libs/aspell-0.60.6.1/common/convert.hpp \ ../../libs/aspell-0.60.6.1/common/copy_ptr.hpp \ ../../libs/aspell-0.60.6.1/common/document_checker.cpp \ ../../libs/aspell-0.60.6.1/common/document_checker.hpp \ ../../libs/aspell-0.60.6.1/common/enumeration.hpp \ ../../libs/aspell-0.60.6.1/common/error.cpp \ ../../libs/aspell-0.60.6.1/common/error.hpp \ ../../libs/aspell-0.60.6.1/common/errors.cpp \ ../../libs/aspell-0.60.6.1/common/errors.hpp \ ../../libs/aspell-0.60.6.1/common/file_data_util.cpp \ ../../libs/aspell-0.60.6.1/common/file_data_util.hpp \ ../../libs/aspell-0.60.6.1/common/file_util.cpp \ ../../libs/aspell-0.60.6.1/common/file_util.hpp \ ../../libs/aspell-0.60.6.1/common/filter.cpp \ ../../libs/aspell-0.60.6.1/common/filter.hpp \ ../../libs/aspell-0.60.6.1/common/filter_char.hpp \ ../../libs/aspell-0.60.6.1/common/filter_char_vector.hpp \ ../../libs/aspell-0.60.6.1/common/fstream.cpp \ ../../libs/aspell-0.60.6.1/common/fstream.hpp \ ../../libs/aspell-0.60.6.1/common/generic_copy_ptr-t.hpp \ ../../libs/aspell-0.60.6.1/common/generic_copy_ptr.hpp \ ../../libs/aspell-0.60.6.1/common/getdata.cpp \ ../../libs/aspell-0.60.6.1/common/getdata.hpp \ ../../libs/aspell-0.60.6.1/common/gettext.h \ ../../libs/aspell-0.60.6.1/common/gettext_init.cpp \ ../../libs/aspell-0.60.6.1/common/hash-t.hpp \ ../../libs/aspell-0.60.6.1/common/hash.hpp \ ../../libs/aspell-0.60.6.1/common/hash_fun.hpp \ ../../libs/aspell-0.60.6.1/common/indiv_filter.hpp \ ../../libs/aspell-0.60.6.1/common/info.cpp \ ../../libs/aspell-0.60.6.1/common/info.hpp \ ../../libs/aspell-0.60.6.1/common/iostream.cpp \ ../../libs/aspell-0.60.6.1/common/iostream.hpp \ ../../libs/aspell-0.60.6.1/common/istream.hpp \ ../../libs/aspell-0.60.6.1/common/itemize.cpp \ ../../libs/aspell-0.60.6.1/common/itemize.hpp \ ../../libs/aspell-0.60.6.1/common/key_info.hpp \ ../../libs/aspell-0.60.6.1/common/lock.hpp \ ../../libs/aspell-0.60.6.1/common/lsort.hpp \ ../../libs/aspell-0.60.6.1/common/mutable_container.hpp \ ../../libs/aspell-0.60.6.1/common/mutable_string.hpp \ ../../libs/aspell-0.60.6.1/common/ndebug.hpp \ ../../libs/aspell-0.60.6.1/common/objstack.cpp \ ../../libs/aspell-0.60.6.1/common/objstack.hpp \ ../../libs/aspell-0.60.6.1/common/ostream.hpp \ ../../libs/aspell-0.60.6.1/common/parm_string.hpp \ ../../libs/aspell-0.60.6.1/common/posib_err.cpp \ ../../libs/aspell-0.60.6.1/common/posib_err.hpp \ ../../libs/aspell-0.60.6.1/common/simple_string.hpp \ ../../libs/aspell-0.60.6.1/common/speller.cpp \ ../../libs/aspell-0.60.6.1/common/speller.hpp \ ../../libs/aspell-0.60.6.1/common/stack_ptr.hpp \ ../../libs/aspell-0.60.6.1/common/string.cpp \ ../../libs/aspell-0.60.6.1/common/string.hpp \ ../../libs/aspell-0.60.6.1/common/string_enumeration.hpp \ ../../libs/aspell-0.60.6.1/common/string_list.cpp \ ../../libs/aspell-0.60.6.1/common/string_list.hpp \ ../../libs/aspell-0.60.6.1/common/string_map.cpp \ ../../libs/aspell-0.60.6.1/common/string_map.hpp \ ../../libs/aspell-0.60.6.1/common/string_pair.hpp \ ../../libs/aspell-0.60.6.1/common/string_pair_enumeration.hpp \ ../../libs/aspell-0.60.6.1/common/strtonum.cpp \ ../../libs/aspell-0.60.6.1/common/strtonum.hpp \ ../../libs/aspell-0.60.6.1/common/tokenizer.cpp \ ../../libs/aspell-0.60.6.1/common/tokenizer.hpp \ ../../libs/aspell-0.60.6.1/common/type_id.hpp \ ../../libs/aspell-0.60.6.1/common/vararray.hpp \ ../../libs/aspell-0.60.6.1/common/vector.hpp \ ../../libs/aspell-0.60.6.1/common/word_list.hpp \ ../../libs/aspell-0.60.6.1/gen/static_filters.src.cpp \ ../../libs/aspell-0.60.6.1/interfaces/cc/aspell.h \ ../../libs/aspell-0.60.6.1/interfaces/cc/pspell.h \ ../../libs/aspell-0.60.6.1/lib/can_have_error-c.cpp \ ../../libs/aspell-0.60.6.1/lib/config-c.cpp \ ../../libs/aspell-0.60.6.1/lib/find_speller.cpp \ ../../libs/aspell-0.60.6.1/lib/info-c.cpp \ ../../libs/aspell-0.60.6.1/lib/new_checker.cpp \ ../../libs/aspell-0.60.6.1/lib/new_config.cpp \ ../../libs/aspell-0.60.6.1/lib/new_filter.cpp \ ../../libs/aspell-0.60.6.1/lib/new_fmode.cpp \ ../../libs/aspell-0.60.6.1/lib/speller-c.cpp \ ../../libs/aspell-0.60.6.1/lib/string_enumeration-c.cpp \ ../../libs/aspell-0.60.6.1/lib/word_list-c.cpp \ ../../libs/aspell-0.60.6.1/linux/dirs.h \ ../../libs/aspell-0.60.6.1/linux/Makefile.linux \ ../../libs/aspell-0.60.6.1/linux/settings.h \ ../../libs/aspell-0.60.6.1/modules/filter/context.cpp \ ../../libs/aspell-0.60.6.1/modules/filter/email.cpp \ ../../libs/aspell-0.60.6.1/modules/filter/nroff.cpp \ ../../libs/aspell-0.60.6.1/modules/filter/sgml.cpp \ ../../libs/aspell-0.60.6.1/modules/filter/tex.cpp \ ../../libs/aspell-0.60.6.1/modules/filter/texinfo.cpp \ ../../libs/aspell-0.60.6.1/modules/filter/url.cpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/affix.cpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/affix.hpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/asuggest.hpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/block_vector.hpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/check_list.hpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/data.cpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/data.hpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/data_id.hpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/data_util.hpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/editdist.cpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/editdist.hpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/editdist2.hpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/language.cpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/language.hpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/leditdist.cpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/leditdist.hpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/Makefile.in \ ../../libs/aspell-0.60.6.1/modules/speller/default/matrix.hpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/multi_ws.cpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/phonet.cpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/phonet.hpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/phonetic.cpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/phonetic.hpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/primes.cpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/primes.hpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/readonly_ws.cpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/speller_impl.cpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/speller_impl.hpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/suggest.cpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/suggest.hpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/typo_editdist.cpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/typo_editdist.hpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/vector_hash-t.hpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/vector_hash.hpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/weights.hpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/wordinfo.hpp \ ../../libs/aspell-0.60.6.1/modules/speller/default/writable.cpp \ ../../libs/aspell-0.60.6.1/modules/tokenizer/basic.cpp \ ../../libs/aspell-0.60.6.1/prog/prezip.c \ ../../libs/aspell-0.60.6.1/win32/api-glue.cpp \ ../../libs/aspell-0.60.6.1/win32/settings.h export Build=$(Build); \ $(MAKE) -C ../../libs/aspell-0.60.6.1/linux -f Makefile.linux ../../libs/bzip2-1.0.6/linux/$(BuildDir)/libbzip2$(Tag).a : ../../libs/bzip2-1.0.6/blocksort.c \ ../../libs/bzip2-1.0.6/bzlib.c \ ../../libs/bzip2-1.0.6/bzlib.h \ ../../libs/bzip2-1.0.6/bzlib_private.h \ ../../libs/bzip2-1.0.6/compress.c \ ../../libs/bzip2-1.0.6/crctable.c \ ../../libs/bzip2-1.0.6/decompress.c \ ../../libs/bzip2-1.0.6/huffman.c \ ../../libs/bzip2-1.0.6/randtable.c export Build=$(Build); \ $(MAKE) -C ../../libs/bzip2-1.0.6/linux -f Makefile.linux -../../../lgi/trunk/$(BuildDir)/liblgi-dev$(Tag).so : ../../../lgi/trunk/include/lgi/common/App.h \ +../../../lgi/trunk/$(BuildDir)/liblgi-gtk3$(Tag).so : ../../../lgi/trunk/include/lgi/common/App.h \ ../../../lgi/trunk/include/lgi/common/Array.h \ ../../../lgi/trunk/include/lgi/common/AutoPtr.h \ ../../../lgi/trunk/include/lgi/common/Base64.h \ ../../../lgi/trunk/include/lgi/common/Bitmap.h \ ../../../lgi/trunk/include/lgi/common/Box.h \ ../../../lgi/trunk/include/lgi/common/Button.h \ ../../../lgi/trunk/include/lgi/common/CairoSurface.h \ ../../../lgi/trunk/include/lgi/common/Cancel.h \ ../../../lgi/trunk/include/lgi/common/Capabilities.h \ ../../../lgi/trunk/include/lgi/common/Charset.h \ ../../../lgi/trunk/include/lgi/common/CheckBox.h \ ../../../lgi/trunk/include/lgi/common/ClipBoard.h \ ../../../lgi/trunk/include/lgi/common/Colour.h \ ../../../lgi/trunk/include/lgi/common/ColourSpace.h \ ../../../lgi/trunk/include/lgi/common/Com.h \ ../../../lgi/trunk/include/lgi/common/Combo.h \ ../../../lgi/trunk/include/lgi/common/Containers.h \ ../../../lgi/trunk/include/lgi/common/Core.h \ ../../../lgi/trunk/include/lgi/common/Css.h \ ../../../lgi/trunk/include/lgi/common/CssTools.h \ ../../../lgi/trunk/include/lgi/common/CurrentTime.h \ ../../../lgi/trunk/include/lgi/common/DataDlg.h \ ../../../lgi/trunk/include/lgi/common/DateTime.h \ ../../../lgi/trunk/include/lgi/common/Dialog.h \ ../../../lgi/trunk/include/lgi/common/DisplayString.h \ ../../../lgi/trunk/include/lgi/common/DocView.h \ ../../../lgi/trunk/include/lgi/common/Dom.h \ ../../../lgi/trunk/include/lgi/common/DomFields.h \ ../../../lgi/trunk/include/lgi/common/DragAndDrop.h \ ../../../lgi/trunk/include/lgi/common/DropFiles.h \ ../../../lgi/trunk/include/lgi/common/Edit.h \ ../../../lgi/trunk/include/lgi/common/Error.h \ ../../../lgi/trunk/include/lgi/common/EventTargetThread.h \ ../../../lgi/trunk/include/lgi/common/File.h \ ../../../lgi/trunk/include/lgi/common/FileSelect.h \ ../../../lgi/trunk/include/lgi/common/Filter.h \ ../../../lgi/trunk/include/lgi/common/FindReplaceDlg.h \ ../../../lgi/trunk/include/lgi/common/Font.h \ ../../../lgi/trunk/include/lgi/common/FontCache.h \ ../../../lgi/trunk/include/lgi/common/FontSelect.h \ ../../../lgi/trunk/include/lgi/common/Gdc2.h \ ../../../lgi/trunk/include/lgi/common/GdcTools.h \ ../../../lgi/trunk/include/lgi/common/GdiLeak.h \ ../../../lgi/trunk/include/lgi/common/HashTable.h \ ../../../lgi/trunk/include/lgi/common/ImageList.h \ ../../../lgi/trunk/include/lgi/common/Input.h \ ../../../lgi/trunk/include/lgi/common/ItemContainer.h \ ../../../lgi/trunk/include/lgi/common/Json.h \ ../../../lgi/trunk/include/lgi/common/Layout.h \ ../../../lgi/trunk/include/lgi/common/Lgi.h \ ../../../lgi/trunk/include/lgi/common/LgiClasses.h \ ../../../lgi/trunk/include/lgi/common/LgiCommon.h \ ../../../lgi/trunk/include/lgi/common/LgiDefs.h \ ../../../lgi/trunk/include/lgi/common/LgiInc.h \ ../../../lgi/trunk/include/lgi/common/LgiInterfaces.h \ ../../../lgi/trunk/include/lgi/common/LgiMsgs.h \ ../../../lgi/trunk/include/lgi/common/LgiNetInc.h \ ../../../lgi/trunk/include/lgi/common/LgiRes.h \ ../../../lgi/trunk/include/lgi/common/LgiString.h \ ../../../lgi/trunk/include/lgi/common/LgiUiBase.h \ ../../../lgi/trunk/include/lgi/common/Library.h \ ../../../lgi/trunk/include/lgi/common/LibraryUtils.h \ ../../../lgi/trunk/include/lgi/common/List.h \ ../../../lgi/trunk/include/lgi/common/ListItemCheckBox.h \ ../../../lgi/trunk/include/lgi/common/ListItemRadioBtn.h \ ../../../lgi/trunk/include/lgi/common/LMallocArray.h \ ../../../lgi/trunk/include/lgi/common/Mail.h \ ../../../lgi/trunk/include/lgi/common/Matrix.h \ ../../../lgi/trunk/include/lgi/common/Mem.h \ ../../../lgi/trunk/include/lgi/common/Menu.h \ ../../../lgi/trunk/include/lgi/common/Message.h \ ../../../lgi/trunk/include/lgi/common/Mime.h \ ../../../lgi/trunk/include/lgi/common/Mru.h \ ../../../lgi/trunk/include/lgi/common/Mutex.h \ ../../../lgi/trunk/include/lgi/common/Net.h \ ../../../lgi/trunk/include/lgi/common/NetTools.h \ ../../../lgi/trunk/include/lgi/common/Notifications.h \ ../../../lgi/trunk/include/lgi/common/OAuth2.h \ ../../../lgi/trunk/include/lgi/common/OptionsFile.h \ ../../../lgi/trunk/include/lgi/common/Palette.h \ ../../../lgi/trunk/include/lgi/common/Panel.h \ ../../../lgi/trunk/include/lgi/common/Password.h \ ../../../lgi/trunk/include/lgi/common/Path.h \ ../../../lgi/trunk/include/lgi/common/PixelRops.h \ ../../../lgi/trunk/include/lgi/common/Point.h \ ../../../lgi/trunk/include/lgi/common/Popup.h \ ../../../lgi/trunk/include/lgi/common/PopupList.h \ ../../../lgi/trunk/include/lgi/common/Printer.h \ ../../../lgi/trunk/include/lgi/common/Profile.h \ ../../../lgi/trunk/include/lgi/common/Progress.h \ ../../../lgi/trunk/include/lgi/common/ProgressDlg.h \ ../../../lgi/trunk/include/lgi/common/ProgressView.h \ ../../../lgi/trunk/include/lgi/common/Properties.h \ ../../../lgi/trunk/include/lgi/common/RadioGroup.h \ ../../../lgi/trunk/include/lgi/common/Range.h \ ../../../lgi/trunk/include/lgi/common/Rect.h \ ../../../lgi/trunk/include/lgi/common/RectF.h \ ../../../lgi/trunk/include/lgi/common/RefCount.h \ ../../../lgi/trunk/include/lgi/common/RegKey.h \ ../../../lgi/trunk/include/lgi/common/Res.h \ ../../../lgi/trunk/include/lgi/common/Rops.h \ ../../../lgi/trunk/include/lgi/common/ScrollBar.h \ ../../../lgi/trunk/include/lgi/common/SkinEngine.h \ ../../../lgi/trunk/include/lgi/common/Slider.h \ ../../../lgi/trunk/include/lgi/common/Splitter.h \ ../../../lgi/trunk/include/lgi/common/StatusBar.h \ ../../../lgi/trunk/include/lgi/common/Store3Defs.h \ ../../../lgi/trunk/include/lgi/common/Stream.h \ ../../../lgi/trunk/include/lgi/common/StringClass.h \ ../../../lgi/trunk/include/lgi/common/StringLayout.h \ ../../../lgi/trunk/include/lgi/common/StructuredIo.h \ ../../../lgi/trunk/include/lgi/common/StructuredLog.h \ ../../../lgi/trunk/include/lgi/common/SubProcess.h \ ../../../lgi/trunk/include/lgi/common/TableLayout.h \ ../../../lgi/trunk/include/lgi/common/TabView.h \ ../../../lgi/trunk/include/lgi/common/TextFile.h \ ../../../lgi/trunk/include/lgi/common/TextLabel.h \ ../../../lgi/trunk/include/lgi/common/TextLog.h \ ../../../lgi/trunk/include/lgi/common/TextView3.h \ ../../../lgi/trunk/include/lgi/common/Thread.h \ ../../../lgi/trunk/include/lgi/common/ThreadEvent.h \ ../../../lgi/trunk/include/lgi/common/Token.h \ ../../../lgi/trunk/include/lgi/common/ToolBar.h \ ../../../lgi/trunk/include/lgi/common/ToolTip.h \ ../../../lgi/trunk/include/lgi/common/TrayIcon.h \ ../../../lgi/trunk/include/lgi/common/Tree.h \ ../../../lgi/trunk/include/lgi/common/Undo.h \ ../../../lgi/trunk/include/lgi/common/Unicode.h \ ../../../lgi/trunk/include/lgi/common/UnicodeString.h \ ../../../lgi/trunk/include/lgi/common/UnrolledList.h \ ../../../lgi/trunk/include/lgi/common/Variant.h \ ../../../lgi/trunk/include/lgi/common/View.h \ ../../../lgi/trunk/include/lgi/common/Widgets.h \ ../../../lgi/trunk/include/lgi/common/Window.h \ ../../../lgi/trunk/include/lgi/common/XmlTree.h \ ../../../lgi/trunk/include/lgi/linux/Gtk/LgiOsClasses.h \ ../../../lgi/trunk/include/lgi/linux/Gtk/LgiOsDefs.h \ ../../../lgi/trunk/include/lgi/linux/Gtk/LgiWidget.h \ ../../../lgi/trunk/include/lgi/linux/Gtk/LgiWinManGlue.h \ ../../../lgi/trunk/include/lgi/linux/SymLookup.h \ ../../../lgi/trunk/include/lgi/mac/cocoa/LCocoaView.h \ ../../../lgi/trunk/include/lgi/mac/cocoa/LgiMac.h \ ../../../lgi/trunk/include/lgi/mac/cocoa/LgiOs.h \ ../../../lgi/trunk/include/lgi/mac/cocoa/LgiOsClasses.h \ ../../../lgi/trunk/include/lgi/mac/cocoa/LgiOsDefs.h \ ../../../lgi/trunk/include/lgi/mac/cocoa/ObjCWrapper.h \ ../../../lgi/trunk/include/lgi/mac/cocoa/SymLookup.h \ ../../../lgi/trunk/private/common/FontPriv.h \ ../../../lgi/trunk/private/common/ViewPriv.h \ ../../../lgi/trunk/private/linux/AppPriv.h \ ../../../lgi/trunk/src/common/Gdc2/15Bit.cpp \ ../../../lgi/trunk/src/common/Gdc2/16Bit.cpp \ ../../../lgi/trunk/src/common/Gdc2/24Bit.cpp \ ../../../lgi/trunk/src/common/Gdc2/32Bit.cpp \ ../../../lgi/trunk/src/common/Gdc2/8Bit.cpp \ ../../../lgi/trunk/src/common/Gdc2/Alpha.cpp \ ../../../lgi/trunk/src/common/Gdc2/Colour.cpp \ ../../../lgi/trunk/src/common/Gdc2/Filters/Filter.cpp \ ../../../lgi/trunk/src/common/Gdc2/Font/Charset.cpp \ ../../../lgi/trunk/src/common/Gdc2/Font/DisplayString.cpp \ ../../../lgi/trunk/src/common/Gdc2/Font/Font.cpp \ ../../../lgi/trunk/src/common/Gdc2/Font/FontSystem.cpp \ ../../../lgi/trunk/src/common/Gdc2/Font/FontType.cpp \ ../../../lgi/trunk/src/common/Gdc2/Font/StringLayout.cpp \ ../../../lgi/trunk/src/common/Gdc2/Font/TypeFace.cpp \ ../../../lgi/trunk/src/common/Gdc2/GdcCommon.cpp \ ../../../lgi/trunk/src/common/Gdc2/Path/Path.cpp \ ../../../lgi/trunk/src/common/Gdc2/Rect.cpp \ ../../../lgi/trunk/src/common/Gdc2/RopsCases.cpp \ ../../../lgi/trunk/src/common/Gdc2/Surface.cpp \ ../../../lgi/trunk/src/common/Gdc2/Tools/ColourReduce.cpp \ ../../../lgi/trunk/src/common/Gdc2/Tools/GdcTools.cpp \ ../../../lgi/trunk/src/common/General/Containers.cpp \ ../../../lgi/trunk/src/common/General/DateTime.cpp \ ../../../lgi/trunk/src/common/General/ExeCheck.cpp \ ../../../lgi/trunk/src/common/General/FileCommon.cpp \ ../../../lgi/trunk/src/common/General/Password.cpp \ ../../../lgi/trunk/src/common/General/Properties.cpp \ ../../../lgi/trunk/src/common/Hash/md5/md5.c \ ../../../lgi/trunk/src/common/Hash/md5/md5.h \ ../../../lgi/trunk/src/common/Hash/sha1/sha1.c \ ../../../lgi/trunk/src/common/Hash/sha1/sha1.h \ ../../../lgi/trunk/src/common/Lgi/Alert.cpp \ ../../../lgi/trunk/src/common/Lgi/AppCommon.cpp \ ../../../lgi/trunk/src/common/Lgi/Css.cpp \ ../../../lgi/trunk/src/common/Lgi/CssTools.cpp \ ../../../lgi/trunk/src/common/Lgi/DataDlg.cpp \ ../../../lgi/trunk/src/common/Lgi/DragAndDropCommon.cpp \ ../../../lgi/trunk/src/common/Lgi/FileSelect.cpp \ ../../../lgi/trunk/src/common/Lgi/FindReplace.cpp \ ../../../lgi/trunk/src/common/Lgi/FontSelect.cpp \ ../../../lgi/trunk/src/common/Lgi/GuiUtils.cpp \ ../../../lgi/trunk/src/common/Lgi/Input.cpp \ ../../../lgi/trunk/src/common/Lgi/LgiCommon.cpp \ ../../../lgi/trunk/src/common/Lgi/Library.cpp \ ../../../lgi/trunk/src/common/Lgi/LMsg.cpp \ ../../../lgi/trunk/src/common/Lgi/MemStream.cpp \ ../../../lgi/trunk/src/common/Lgi/MenuCommon.cpp \ ../../../lgi/trunk/src/common/Lgi/Mru.cpp \ ../../../lgi/trunk/src/common/Lgi/Mutex.cpp \ + ../../../lgi/trunk/src/common/Lgi/Object.cpp \ ../../../lgi/trunk/src/common/Lgi/OptionsFile.cpp \ ../../../lgi/trunk/src/common/Lgi/Rand.cpp \ ../../../lgi/trunk/src/common/Lgi/Stream.cpp \ ../../../lgi/trunk/src/common/Lgi/SubProcess.cpp \ ../../../lgi/trunk/src/common/Lgi/ThreadCommon.cpp \ ../../../lgi/trunk/src/common/Lgi/ThreadEvent.cpp \ + ../../../lgi/trunk/src/common/Lgi/ToolTip.cpp \ + ../../../lgi/trunk/src/common/Lgi/TrayIcon.cpp \ ../../../lgi/trunk/src/common/Lgi/Variant.cpp \ ../../../lgi/trunk/src/common/Lgi/ViewCommon.cpp \ ../../../lgi/trunk/src/common/Lgi/WindowCommon.cpp \ ../../../lgi/trunk/src/common/Net/Base64.cpp \ ../../../lgi/trunk/src/common/Net/MDStringToDigest.cpp \ ../../../lgi/trunk/src/common/Net/Net.cpp \ ../../../lgi/trunk/src/common/Net/NetTools.cpp \ ../../../lgi/trunk/src/common/Net/Uri.cpp \ ../../../lgi/trunk/src/common/Resource/LgiRes.cpp \ ../../../lgi/trunk/src/common/Resource/Res.cpp \ ../../../lgi/trunk/src/common/Skins/Gel/Gel.cpp \ ../../../lgi/trunk/src/common/Text/DocView.cpp \ ../../../lgi/trunk/src/common/Text/String.cpp \ ../../../lgi/trunk/src/common/Text/TextView3.cpp \ ../../../lgi/trunk/src/common/Text/Token.cpp \ ../../../lgi/trunk/src/common/Text/Unicode.cpp \ ../../../lgi/trunk/src/common/Text/Utf8.cpp \ ../../../lgi/trunk/src/common/Text/XmlTree.cpp \ ../../../lgi/trunk/src/common/Widgets/Bitmap.cpp \ ../../../lgi/trunk/src/common/Widgets/Box.cpp \ ../../../lgi/trunk/src/common/Widgets/Button.cpp \ ../../../lgi/trunk/src/common/Widgets/CheckBox.cpp \ ../../../lgi/trunk/src/common/Widgets/Combo.cpp \ ../../../lgi/trunk/src/common/Widgets/Edit.cpp \ ../../../lgi/trunk/src/common/Widgets/ItemContainer.cpp \ ../../../lgi/trunk/src/common/Widgets/List.cpp \ ../../../lgi/trunk/src/common/Widgets/Panel.cpp \ ../../../lgi/trunk/src/common/Widgets/Popup.cpp \ ../../../lgi/trunk/src/common/Widgets/Progress.cpp \ ../../../lgi/trunk/src/common/Widgets/ProgressDlg.cpp \ ../../../lgi/trunk/src/common/Widgets/RadioGroup.cpp \ ../../../lgi/trunk/src/common/Widgets/ScrollBar.cpp \ ../../../lgi/trunk/src/common/Widgets/Slider.cpp \ ../../../lgi/trunk/src/common/Widgets/Splitter.cpp \ ../../../lgi/trunk/src/common/Widgets/StatusBar.cpp \ ../../../lgi/trunk/src/common/Widgets/TableLayout.cpp \ ../../../lgi/trunk/src/common/Widgets/TabView.cpp \ ../../../lgi/trunk/src/common/Widgets/TextLabel.cpp \ ../../../lgi/trunk/src/common/Widgets/ToolBar.cpp \ ../../../lgi/trunk/src/common/Widgets/Tree.cpp \ - ../../../lgi/trunk/src/haiku/ShowFileProp_Haiku.cpp \ ../../../lgi/trunk/src/linux/General/File.cpp \ ../../../lgi/trunk/src/linux/General/Mem.cpp \ ../../../lgi/trunk/src/linux/General/ShowFileProp_Linux.cpp \ ../../../lgi/trunk/src/linux/Gtk/Gdc2.cpp \ ../../../lgi/trunk/src/linux/Gtk/LgiWidget.cpp \ ../../../lgi/trunk/src/linux/Gtk/MemDC.cpp \ ../../../lgi/trunk/src/linux/Gtk/PrintDC.cpp \ ../../../lgi/trunk/src/linux/Gtk/ScreenDC.cpp \ ../../../lgi/trunk/src/linux/Lgi/App.cpp \ ../../../lgi/trunk/src/linux/Lgi/ClipBoard.cpp \ ../../../lgi/trunk/src/linux/Lgi/DragAndDrop.cpp \ ../../../lgi/trunk/src/linux/Lgi/General.cpp \ ../../../lgi/trunk/src/linux/Lgi/Layout.cpp \ ../../../lgi/trunk/src/linux/Lgi/Menu.cpp \ ../../../lgi/trunk/src/linux/Lgi/Printer.cpp \ ../../../lgi/trunk/src/linux/Lgi/Thread.cpp \ ../../../lgi/trunk/src/linux/Lgi/View.cpp \ ../../../lgi/trunk/src/linux/Lgi/Widgets.cpp \ ../../../lgi/trunk/src/linux/Lgi/Window.cpp export Build=$(Build); \ $(MAKE) -C ../../../lgi/trunk -f Makefile.linux .SECONDEXPANSION: $(Objects): $(BuildDir)/%.o: $$(wildcard %.c*) mkdir -p $(@D) @echo $(