From 83b93e30f03796b5f89b461b2238b191bdd8ecc1 Mon Sep 17 00:00:00 2001 From: ketmar Date: Thu, 2 Dec 2021 09:34:56 +0000 Subject: [PATCH] some code to create and show reply window, and to build reply message (no sending yet) FossilOrigin-Name: fbed9492f05596698535fbe3ef083a92e61313541075fe06c849af1d30db0f66 --- chibackend/decode.d | 10 +- chibackend/mbuilder.d | 1 - chibackend/sqbase.d | 53 ++++- chiroptera.d | 217 +++++++++++++-------- dialogs.d | 521 ++++++++++++++++++++++++++++++++++---------------- 5 files changed, 544 insertions(+), 258 deletions(-) diff --git a/chibackend/decode.d b/chibackend/decode.d index e3fadf9..8694873 100644 --- a/chibackend/decode.d +++ b/chibackend/decode.d @@ -17,6 +17,7 @@ module chibackend.decode is aliced; import iv.cmdcon; +import iv.dynstring; import iv.encoding; import iv.strex; import iv.utfutil; @@ -736,7 +737,7 @@ public T extractName(T:const(char)[]) (T data) nothrow @trusted { // always returns new string, which is safe to `delete` // passed string must be in UTF-8 // can return `null` for empty string -public char[] strEncodeQ (const(char)[] s) nothrow @trusted { +public dynstring strEncodeQ (const(char)[] s) nothrow @trusted { static bool isSpecial (immutable char ch) pure nothrow @safe @nogc { return ch < ' ' || @@ -747,14 +748,13 @@ public char[] strEncodeQ (const(char)[] s) nothrow @trusted { ch == '\\' || ch == '@'; } - if (s.length == 0) return null; + dynstring res; + if (s.length == 0) return res; static immutable string hexd = "0123456789abcdef"; bool needWork = (s[0] == '=' || s[0] == '?'); if (!needWork) foreach (char ch; s) if (isSpecial(ch)) { needWork = true; break; } - char[] res; if (!needWork) { - res = new char[s.length]; - res[] = s[]; + res = s; } else { res.reserve(s.length*3+32); res ~= "=?UTF-8?Q?"; // quoted printable diff --git a/chibackend/mbuilder.d b/chibackend/mbuilder.d index 723dd5f..941b6c8 100644 --- a/chibackend/mbuilder.d +++ b/chibackend/mbuilder.d @@ -21,7 +21,6 @@ import iv.strex; import iv.vfs; import chibackend : DynStr, SysTimeToRFCString; -import chibackend.decode : strEncodeQ; import chibackend.parse : skipOneLine; diff --git a/chibackend/sqbase.d b/chibackend/sqbase.d index fdfe26f..af3d43f 100644 --- a/chibackend/sqbase.d +++ b/chibackend/sqbase.d @@ -3340,13 +3340,60 @@ public DynStr chiroGetAccountName (uint accid) { } +public struct AccountInfo { + uint accid; + DynStr name; + DynStr realname; + DynStr email; + DynStr nntpgroup; + + @property bool isValid () const pure nothrow @safe @nogc { return (accid != 0); } +} + +public bool chiroGetAccountInfo (uint accid, out AccountInfo nfo) { + static auto stat = LazyStatement!"Conf"(` + SELECT name AS name, realname AS realname, email AS email, nntpgroup AS nntpgroup + FROM accounts + WHERE accid=:accid LIMIT 1 + ;`); + if (accid == 0) return false; + foreach (auto row; stat.st.bind(":accid", accid).range) { + nfo.accid = accid; + nfo.name = row.name!SQ3Text; + nfo.realname = row.realname!SQ3Text; + nfo.email = row.email!SQ3Text; + nfo.nntpgroup = row.nntpgroup!SQ3Text; + return true; + } + return false; +} + +public bool chiroGetAccountInfo (const(char)[] accname, out AccountInfo nfo) { + static auto stat = LazyStatement!"Conf"(` + SELECT accid AS accid, name AS name, realname AS realname, email AS email, nntpgroup AS nntpgroup + FROM accounts + WHERE name=:name LIMIT 1 + ;`); + if (accname.length == 0) return false; + foreach (auto row; stat.st.bindConstText(":name", accname).range) { + nfo.accid = row.accid!uint; + nfo.name = row.name!SQ3Text; + nfo.realname = row.realname!SQ3Text; + nfo.email = row.email!SQ3Text; + nfo.nntpgroup = row.nntpgroup!SQ3Text; + return true; + } + return false; +} + + /** returns list of known tags, sorted by name. */ -public string[] chiroGetTagList () { +public DynStr[] chiroGetTagList () { static auto stat = LazyStatement!"View"(`SELECT tag AS tagname FROM tagnames WHERE hidden=0 ORDER BY tag;`); - string[] res; - foreach (auto row; stat.st.range) res ~= row.tagname!string; + DynStr[] res; + foreach (auto row; stat.st.range) res ~= DynStr(row.tagname!SQ3Text); return res; } diff --git a/chiroptera.d b/chiroptera.d index 5ab6eea..0df35f6 100644 --- a/chiroptera.d +++ b/chiroptera.d @@ -498,7 +498,7 @@ bool rescanFolders () { } } - // readd all epemerals + // readd all ephemerals for (;;) { bool again = false; foreach (FolderInfo fi; folderList) { @@ -1058,6 +1058,10 @@ void initConsole () { // //////////////////////////////////////////////////////////////////// // conRegFunc!(() { + auto pw = new PostWindow(); + pw.setFrom("goo boo!"); + pw.caption = "WRITE NEW MESSAGE"; + /*FIXME if (auto fld = getActiveFolder) { auto postDg = delegate (Account acc) { @@ -1096,94 +1100,145 @@ void initConsole () { // //////////////////////////////////////////////////////////////////// // - static string buildToStr (string name, string mail, ref string newfromname) { - if (auto mrp = mail in repmailreps) { - newfromname = mrp.newname; - return mrp.newname~" <"~mrp.newmail~">"; + static void doReplyXX (string repfld) { + if (vbwin is null || vbwin.closed) return; + if (mainPane is null) return; + if (!mainPane.msglistCurrUId) return; + + auto pw = new PostWindow(); + pw.ed.focus(); + + dynstring accname; + foreach (auto row; dbView.statement(` + SELECT DISTINCT(tagid) AS tagid, tt.tag AS name + FROM threads + INNER JOIN tagnames AS tt USING(tagid) + WHERE uid=:uid AND name LIKE 'account:%' + ;`).bind(":uid", mainPane.msglistCurrUId).range) + { + auto name = row.name!SQ3Text; + if (name.startsWith("account:")) { + accname = name[8..$]; + if (accname.length) { + pw.accname = accname; + break; + } + } + } + + if (accname.length == 0) { + conwriteln("ERROR: source account not found!"); + vbwin.beep(); } else { - return name~" <"~mail~">"; + conwriteln("account: ", accname); } - } - static void doReply (string repfld) { - /*FIXME - if (auto fld = getActiveFolder) { - auto postDg = delegate (Account acc) { - conwriteln("reply with account '", acc.name, "' (", acc.mail, ")"); - fld.withBaseReader((abase, cur, top, alist) { - if (cur < alist.length) { - auto aidx = alist.ptr[cur]; - abase.loadContent(aidx); - if (auto art = abase[aidx]) { - assert(art.contentLoaded); - auto atext = art.getTextContent; - auto pw = new PostWindow(); - pw.from.str = acc.realname~" <"~acc.mail~">"; - pw.from.readonly = true; - string from = art.fromname; - - if (auto nna = cast(NntpAccount)acc) { - pw.title = "Reply to NNTP "~nna.server~":"~nna.group; - pw.to.str = nna.group; - pw.to.readonly = true; - } else { - pw.title = "Reply from '"~acc.name~"' ("~acc.mail~")"; - //pw.to.str = art.fromname~" <"~art.frommail~">"; - string rs; - if (repfld != "From") { - auto rf = art.getHeaderValue(repfld); - if (rf) { - //rs = buildToStr(art.fromname, repfld, from); - if (art.getHeaderValue("List-Id").length) { - rs = rf.idup; - } else { - rs = art.fromname~" <"~rf.idup~">"; - } - } - } - if (!rs) rs = buildToStr(art.fromname, art.frommail, from); - pw.to.str = rs; - } - pw.subj.str = "Re: "~art.subj; - - { - auto vp = from.indexOf(" via Digitalmars-"); - if (vp > 0) { - from = from[0..vp].xstrip; - if (from.length == 0) from = "anonymous"; - } - } + foreach (auto row; dbStore.statement(` + SELECT ChiroHdr_Field(ChiroUnpack(data), :fldname) AS fldvalue + FROM messages + WHERE uid=:uid + ORDER BY uid + LIMIT 1 + `).bind(":uid", mainPane.msglistCurrUId).bindConstText(":fldname", repfld).range) + { + pw.to.str = row.fldvalue!SQ3Text.decodeSubj; + pw.caption = dynstring("Reply to: ")~pw.to.str; + } - pw.ed.addText(from); - pw.ed.addText(" wrote:\n"); - pw.ed.addText("\n"); - foreach (ConString s; LineIterator!false(atext)) { - if (s.length == 0 || s[0] == '>') pw.ed.addText(">"); else pw.ed.addText("> "); - pw.ed.addText(s); - pw.ed.addText("\n"); - } - pw.ed.addText("\n"); - pw.ed.reformat(); - pw.replyto = art.msgid; - pw.references = art.getHeaderValue("References").xstrip.idup; - pw.acc = acc; - pw.fld = fld; - pw.activeWidget = pw.ed; - } - } - }); - }; + dynstring fromname; + foreach (auto row; dbView.statement(` + SELECT subj AS subj, from_name AS fromname + FROM info + WHERE uid=:uid + LIMIT 1 + `).bind(":uid", mainPane.msglistCurrUId).range) + { + fromname = row.fromname!SQ3Text; + dynstring s = "Re: "; + s ~= row.subj!SQ3Text; + pw.subj.str = s; + } - auto acc = fld.findAccountToPost(fld.curidx); - if (acc is null) { - auto wacc = new SelectPopBoxWindow(defaultAcc); - wacc.onSelected = postDg; + foreach (auto row; dbView.statement(` + SELECT ChiroUnpack(content) AS content + FROM content_text + WHERE uid=:uid + LIMIT 1 + `).bind(":uid", mainPane.msglistCurrUId).range) + { + pw.ed.addText(fromname); + pw.ed.addText(" wrote:\n"); + pw.ed.addText("\n"); + dynstring text = row.content!SQ3Text; + foreach (SQ3Text s; text.byLine) { + if (s.length == 0 || s[0] == '>') pw.ed.addText(">"); else pw.ed.addText("> "); + pw.ed.addText(s); + pw.ed.addText("\n"); + } + pw.ed.addText("\n"); + pw.ed.reformat(); + } + + foreach (auto row; dbConf.statement(` + SELECT + accid AS accid + --, name AS name + , recvserver AS recvserver + , sendserver AS sendserver + , realname AS realname + , email AS email + , nntpgroup AS nntpgroup + FROM accounts + WHERE name=:accname --AND sendserver<>'' AND email<>'' + LIMIT 1 + `).bindConstText(":accname", accname.getData).range) + { + /* + conwriteln("GROUP: <", row.nntpgroup!SQ3Text, ">"); + conwriteln("NAME: <", row.realname!SQ3Text, ">"); + conwriteln("EMAIL: <", row.email!SQ3Text, ">"); + */ + dynstring frm; + if (row.realname!SQ3Text.length) { + frm = row.realname!SQ3Text; + frm ~= " <"; + frm ~= row.email!SQ3Text; + frm ~= ">"; } else { - postDg(acc); - acc = defaultAcc; + frm = row.email!SQ3Text; + } + pw.setFrom(frm); + + if (row.nntpgroup!SQ3Text.length) { + pw.to.str = dynstring("newsgroup: ")~row.nntpgroup!SQ3Text; + pw.to.readonly = true; + pw.to.disabled = true; + pw.allowAccountChange = false; + pw.nntp = true; } } - */ + + foreach (auto row; dbView.statement(` + SELECT msgid AS msgid + FROM refids + WHERE uid=:uid + ORDER BY idx + `).bind(":uid", mainPane.msglistCurrUId).range) + { + //conwriteln("REF: |", row.msgid!SQ3Text); + if (pw.references.length) pw.references ~= " "; + pw.references ~= row.msgid!SQ3Text; + } + } + + static void doReply (string repfld) { + try { + doReplyXX(repfld); + } catch (Exception e) { + conwriteln("EXCEPTION: ", e.msg); + auto s = e.toString(); + conwriteln(s); + } } conRegFunc!(() { doReply("Reply-To"); })("article_reply", "reply to the current article with \"reply-to\" field"); diff --git a/dialogs.d b/dialogs.d index 0392fd9..370110c 100644 --- a/dialogs.d +++ b/dialogs.d @@ -26,6 +26,7 @@ import iv.egra; import iv.alice; import iv.cmdcon; import iv.strex; +import iv.sq3; import iv.utfutil; import iv.vfs; @@ -222,49 +223,60 @@ public final class ProgressWindow : SubWindow { // ////////////////////////////////////////////////////////////////////////// // -/+ public class SelectPopBoxWindow : SubWindow { - SimpleListBoxWidget lb; - - void delegate (Account acc) onSelected; - - this (Account defacc) { - if (accounts.length > 0) { - int xhgt = 0; - int xwdt = 0; - - lb = new SimpleListBoxWidget(this); - bool accFound = false; - int defAccIdx = -1; - foreach (immutable aidx, Account acc; accounts) { - if (cast(Pop3Account)acc) { - import std.format : format; - string s = "%s <%s>".format(acc.realname, acc.mail); - lb.appendItem(s, acc); - int w = gxTextWidthUtf(s)+2; - if (xwdt < w) xwdt = w; - if (acc is defacc) { - lb.curidx = lb.length-1; - accFound = true; - } - if (acc is defaultAcc) defAccIdx = lb.length-1; - xhgt += gxTextHeightUtf; - } +public: + void delegate (uint accid) onSelected; + +public: + this (dynstring defacc) { + createRoot(); + + auto lb = new SimpleListBoxUDataWidget!uint(rootWidget); + lb.id = "listbox"; + + static auto stat = LazyStatement!"Conf"(` + SELECT accid AS accid, name AS name, realname AS realname, email AS email + FROM accounts + WHERE sendserver<>'' AND email<>'' and nntpgroup='' + ORDER BY accid + ;`); + + int xhgt = 0; + int xwdt = 0; + bool accFound = false; + //int defAccIdx = -1; + + foreach (auto row; stat.st.range) { + dynstring s = row.realname!SQ3Text; + s ~= " <"; + s ~= row.email!SQ3Text; + s ~= ">"; + //conwriteln(s); + lb.appendItemWithData(s, row.accid!uint); + int w = gxTextWidthUtf(s)+2; + if (xwdt < w) xwdt = w; + if (defacc == row.name!SQ3Text) { + lb.curidx = lb.length-1; + accFound = true; } + //if (acc is defaultAcc) defAccIdx = lb.length-1; + xhgt += gxTextHeightUtf; + } + if (lb.length > 0) { if (xhgt == 0) { super(); return; } if (xhgt > screenHeight) xhgt = screenHeight-decorationSizeY; - if (!accFound && defAccIdx >= 0) lb.curidx = defAccIdx; + //if (!accFound && defAccIdx >= 0) lb.curidx = defAccIdx; if (xwdt > screenWidth-decorationSizeX) xwdt = screenWidth-decorationSizeX; - super("Select Account", xwdt+decorationSizeX, xhgt+decorationSizeY); - lb.ww = clientWidth; - lb.wh = clientHeight; + super("Select Account", GxSize(xwdt+decorationSizeX, xhgt+decorationSizeY)); + lb.width = clientWidth; + lb.height = clientHeight; lb.onAction = delegate (self) { - if (auto acc = cast(Account)lb.itemData(lb.curidx)) { - self.parent.close(); + if (auto acc = lb.itemData(lb.curidx)) { + close(); if (onSelected !is null) onSelected(acc); else vbwin.beep(); } else { vbwin.beep(); @@ -274,148 +286,337 @@ public class SelectPopBoxWindow : SubWindow { addModal(); } else { super(); + close(); } } - override bool onKey (KeyEvent event) { + override bool onKeyBubble (KeyEvent event) { if (event.pressed) { - if (event == "Escape" || event == "C-Q") { close(); return true; } - if (event == "Enter") { lb.onAction(lb); return true; } + if (event == "Escape") { close(); return true; } + if (event == "Enter") { + if (auto lb = querySelector!Widget("#listbox")) { + lb.doAction(); + return true; + } + } } - return super.onKey(event); + return super.onKeyBubble(event); } } -+/ // ////////////////////////////////////////////////////////////////////////// // -/+ public class SelectAddressBookWindow : SubWindow { - SimpleListBoxWidget lb; + static struct Entry { + dynstring nick; + dynstring name; + dynstring email; + } - void delegate (AddressBookEntry acc) onSelected; + void delegate (dynstring nick, dynstring name, dynstring email) onSelected; + + SimpleListBoxUDataWidget!Entry lb; this (const(char)[] prefix) { - if (abook.length > 0) { - int xhgt = 0; - int xwdt = 0; - auto defae = abookFindByNickFirst(prefix); - if (defae is null && prefix.length) { - auto mstx = prefix.lastIndexOf('<'); - if (mstx >= 0) { - prefix = prefix[mstx+1..$]; - if (prefix.length && prefix[$-1] == '>') prefix = prefix[0..$-1]; - } - defae = abookFindByMailFirst(prefix); - } + static auto stat = LazyStatement!"Conf"(` + SELECT nick AS nick, name AS name, email AS email + FROM addressbook + WHERE nick<>'' AND email<>'' + ORDER BY nick + ;`); - lb = new SimpleListBoxWidget(this); - foreach (immutable aidx, AddressBookEntry ae; abook) { - import std.format : format; - string s; - if (ae.realname.length) s = ae.realname~" <"~ae.mail~">"; else s = ae.mail; - lb.appendItem(s, ae); - int w = gxTextWidthUtf(s)+2; - if (xwdt < w) xwdt = w; - if (ae is defae) lb.curidx = lb.length-1; - xhgt += gxTextHeightUtf; + createRoot(); + + lb = new SimpleListBoxUDataWidget!Entry(rootWidget); + lb.id = "listbox"; + + int xhgt = 0; + int xwdt = 0; + + foreach (auto row; stat.st.range) { + //conwriteln("! <", row.nick!SQ3Text, "> : <", row.name!SQ3Text, "> : <", row.email!SQ3Text, ">"); + if (prefix.length) { + if (!startsWithCI(row.nick!SQ3Text, prefix)) continue; + } + dynstring it; + if (row.name!SQ3Text.length) { + it ~= row.name!SQ3Text; + it ~= " <"; + it ~= row.email!SQ3Text; + it ~= ">"; + } else { + it = row.email!SQ3Text; } + Entry e; + e.nick = row.nick!SQ3Text; + e.name = row.name!SQ3Text; + e.email = row.email!SQ3Text; + lb.appendItemWithData(it, e); + + int w = gxTextWidthUtf(it)+2; + if (xwdt < w) xwdt = w; + //if (ae is defae) lb.curidx = lb.length-1; + xhgt += gxTextHeightUtf; + } - if (xhgt == 0) { super(); return; } - if (xhgt > screenHeight) xhgt = screenHeight-decorationSizeY; + if (xhgt == 0) { super(); return; } + if (xhgt > screenHeight) xhgt = screenHeight-decorationSizeY; - if (xwdt > screenWidth-decorationSizeX) xwdt = screenWidth-decorationSizeX; + if (xwdt > screenWidth-decorationSizeX) xwdt = screenWidth-decorationSizeX; - super("Select Recepient", xwdt+decorationSizeX, xhgt+decorationSizeY); - lb.ww = clientWidth; - lb.wh = clientHeight; + super("Select Recepient", GxSize(xwdt+decorationSizeX, xhgt+decorationSizeY)); + lb.width = clientWidth; + lb.height = clientHeight; - lb.onAction = delegate (self) { - if (auto ae = cast(AddressBookEntry)lb.itemData(lb.curidx)) { - close(); - if (onSelected !is null) onSelected(ae); else vbwin.beep(); - } else { - vbwin.beep(); - } - }; + lb.onAction = delegate (self) { + Entry ae = lb.itemData(lb.curidx); + if (ae.email.length) { + close(); + if (onSelected !is null) onSelected(ae.nick, ae.name, ae.email); else vbwin.beep(); + } else { + vbwin.beep(); + } + }; - addModal(); - } else { - super(); - } + addModal(); } - override bool onKey (KeyEvent event) { + override bool onKeyBubble (KeyEvent event) { if (event.pressed) { - if (event == "Escape" || event == "C-Q") { close(); return true; } + if (event == "Escape") { close(); return true; } if (event == "Enter") { lb.onAction(lb); return true; } } - return super.onKey(event); + return super.onKeyBubble(event); } } -+/ // ////////////////////////////////////////////////////////////////////////// // -/+ public class PostWindow : SubWindow { LineEditWidget from; LineEditWidget to; LineEditWidget subj; EditorWidget ed; - string replyto; - string references; // of replyto article - Account acc; - Folder fld; + //dynstring replyto; + dynstring references; // of replyto article + dynstring accname; + bool allowAccountChange = true; + bool nntp = false; + + void setFrom (const(char)[] s) { + from.readonly = false; + from.str = s; + from.readonly = true; + } - this () { - import std.algorithm : max; + bool trySend () { + static bool checkString (const(char)[] s) nothrow @trusted @nogc { + if (s.length == 0) return false; + if (s.utflen > 255) return false; + return true; + } - int wanthgt = screenHeight-42*2; - if (wanthgt < 80) wanthgt = 80; - int wantwdt = screenWidth-64*2; - if (wantwdt < 506) wantwdt = 506; - super("Compose Mail", /*506*/wantwdt, /*253*/wanthgt); - //if (hasWindowClass(this)) return; + if (!checkString(from.str)) { from.focus(); return false; } + if (!nntp && !checkString(to.str)) { to.focus(); return false; } + if (!checkString(subj.str)) { subj.focus(); return false; } + if (ed.editor[].length == 0) { ed.focus(); return false; } + + static const(char)[] extractMail (const(char)[] s) nothrow @trusted @nogc { + s = s.xstrip; + if (s.length == 0 || s[$-1] != '>') return s; + auto spp = s.lastIndexOf('<'); + if (spp < 0) return s; + s = s[spp+1..$-1].xstrip; + return s; + } + + static const(char)[] extractName (const(char)[] s) nothrow @trusted @nogc { + s = s.xstrip; + if (s.length == 0 || s[$-1] != '>') return null; + auto spp = s.lastIndexOf('<'); + if (spp < 0) return null; + s = s[0..spp]; + return s.xstrip; + } + + AccountInfo acc; + if (!chiroGetAccountInfo(accname.getData, out acc)) return false; + + if (nntp != (acc.nntpgroup.length != 0)) return false; + + /* + conwriteln("ACCOUNT ID: ", acc.accid); + conwriteln("ACCOUNT NAME: ", acc.name); + conwriteln("ACCOUNT REAL NAME: ", acc.realname); + conwriteln("ACCOUNT EMAIL: ", acc.email); + conwriteln("ACCOUNT NNTP GROUP: ", acc.nntpgroup); + */ + + dynstring fromname = extractName(from.str); + dynstring frommail = extractMail(from.str); + + dynstring toname = extractName(to.str); + dynstring tomail = extractMail(to.str); + + conwriteln("FROM: name=<", fromname, ">:<", strEncodeQ(fromname), "> : mail=<", frommail, ">"); + conwriteln("TO: name=<", toname, ">:<", strEncodeQ(toname), "> : mail=<", tomail, ">"); + + if (!isGoodEmail(frommail)) { from.focus(); return false; } + if (!nntp && !isGoodEmail(tomail)) { to.focus(); return false; } + + // build reply article and add it to send queue + // check attaches + ed.editor.clearAndDisableUndo(); // so removing attaches will not add 'em to undo, lol + dynstring[] attnames = ed.extractAttaches(); + if (attnames.length) foreach (dynstring ss; attnames) conwriteln("ATTACH: ", ss); + + MessageBuilder msg; + msg.setFromName(fromname); + msg.setFromMail(frommail); + msg.setToName(toname); + msg.setToMail(tomail); + if (acc.nntpgroup.length) msg.setNewsgroup(acc.nntpgroup); + msg.setSubj(subj.str); + msg.setBody(ed.getText); + + //if (replyto.length) msg.appendReference(replyto); + + const(char)[] refs = references.xstrip; + while (refs.length) { + usize spp = 0; + while (spp < refs.length && refs[spp] > ' ') ++spp; + msg.appendReference(refs[0..spp]); + refs = refs[spp..$].xstrip; + } - from = new LineEditWidget(this, "From:"); - to = new LineEditWidget(this, "To:"); - subj = new LineEditWidget(this, "Subj:"); - ed = new EditorWidget(this); + foreach (dynstring ss; attnames) { + try { + msg.attachFile(ss); + } catch (Exception e) { + conwriteln("ERROR: cannot attach file '", ss, "'!"); + } + } - setupClientClip(); + // clear editor, so it will free used memory + ed.editor.clearAndDisableUndo(); + ed.editor.clear(); - int tw = max(from.titwdt, to.titwdt, subj.titwdt); - from.titwdt = tw; - to.titwdt = tw; - subj.titwdt = tw; +/+ + -- this table holds all unsent messages + -- they are put in the storage and properly inserted, + -- but also put in this table, for the receiver to send them + -- also note that NNTP messages will be put in the storage without any tags (but with the contents) + -- (this is because we will receive them back from NNTP server later) + -- succesfully sent messages will be simply DELETEd + CREATE TABLE IF NOT EXISTS unsent ( + uid INTEGER PRIMARY KEY /* the same as in the storage, not automatic */ + , accid INTEGER /* account from which this message should be sent */ + , from_pop3 TEXT /* "from" for POP3 */ + , to_pop3 TEXT /* "to" for POP3 */ + , data TEXT /* PACKED data to send */ + ); ++/ - from.wy = 0; - to.wy = from.wy+from.wh+2; - subj.wy = to.wy+to.wh+2; + /+ + static auto stat = LazyStatement!"Store"(` + INSERT INTO messages + ;`); + +/ - ed.wx = 0; - ed.wy = subj.wy+subj.wh+2; - ed.ww = gxClipRect.width; - ed.wh = gxClipRect.height-ed.wy; + conwriteln("======================="); + conwrite(msg.getPrepared); + conwriteln("-----------------------"); - add(); + return true; + } + + override void createWidgets () { + (new VBoxWidget).enter{ + (new HBoxWidget).enter{ + with (new HotLabelWidget("&From:", LabelWidget.HAlign.Right)) { width = width+2; hsizeId = "editors"; } + new SpacerWidget(4); + from = new LineEditWidget(); + from.flex = 1; + from.readonly = true; + }.flex = 1; + + new SpacerWidget(1); + (new HBoxWidget).enter{ + with (new HotLabelWidget("&To:", LabelWidget.HAlign.Right)) { width = width+2; hsizeId = "editors"; } + new SpacerWidget(4); + to = new LineEditWidget(); + to.flex = 1; + }.flex = 1; + + new SpacerWidget(1); + (new HBoxWidget).enter{ + with (new HotLabelWidget("&Subj:", LabelWidget.HAlign.Right)) { width = width+2; hsizeId = "editors"; } + new SpacerWidget(4); + subj = new LineEditWidget(); + subj.flex = 1; + }.flex = 1; + }; + + (new VBoxWidget).enter{ + ed = new EditorWidget(); + ed.flex = 1; + }.flex = 1; + + new SpacerWidget(4); + (new HBoxWidget).enter{ + new SpacerWidget(12); + //new SpringWidget(1); + with (new ButtonWidget(" Send ")) { + hsizeId = "okcancel"; + //deftype = Default.Accept; + onAction = delegate (self) { + if (trySend) close(); else vbwin.beep(); + }; + flex = 1; + } + new SpacerWidget(12); + with (new ButtonWidget(" Cancel ")) { + hsizeId = "okcancel"; + //deftype = Default.Cancel; + onAction = delegate (self) { + close(); + }; + flex = 1; + } + //new SpringWidget(1); + new SpacerWidget(12); + }; + new SpacerWidget(4); + + relayout(); // don't resize window + centerWindow(); } - override bool onKey (KeyEvent event) { + this () { + int wanthgt = screenHeight-42*2; + if (wanthgt < 80) wanthgt = 80; + int wantwdt = screenWidth-64*2; + if (wantwdt < 506) wantwdt = 506; + super("Compose Mail", GxSize(wantwdt, wanthgt)); + //if (hasWindowClass(this)) return; + } + + override bool onKeySink (KeyEvent event) { if (event.pressed) { if (event == "Escape" && !ed.editor.textChanged) { close(); return true; } if (event == "C-G" || event == "C-C") { if (ed.editor.textChanged) { auto qww = new YesNoWindow("Close?", "Do you really want to close the editor?", true); qww.onYes = () { close(); }; - qww.addModal(); + //qww.addModal(); } else { close(); } return true; } - if (event == "M-Tab" && activeWidget is to) { + + /+ + if (event == "M-Tab" && focusedWidget is to) { auto ae = abookFindByNick(to.str); //if (ae is null) ae = abookFindByMail(to.str); if (ae !is null) { @@ -425,70 +626,54 @@ public class PostWindow : SubWindow { } return true; } - if (event == "C-Space" && activeWidget is to) { + +/ + + // select destination from the address book + if (event == "C-Space" && focusedWidget is to) { auto wae = new SelectAddressBookWindow(to.str); - wae.onSelected = delegate (AddressBookEntry ae) { - if (ae.realname.length) to.str = ae.realname~" <"~ae.mail~">"; else to.str = ae.mail; - }; + if (wae.lb.length != 0) { + wae.onSelected = delegate (dynstring nick, dynstring name, dynstring email) { + if (name.length) to.str = name~" <"~email~">"; else to.str = email; + }; + } else { + wae.close(); + } return true; } - if ((event == "C-Space") && activeWidget is from) { - if (cast(Pop3Account)acc) { - auto wacc = new SelectPopBoxWindow(acc); - wacc.onSelected = delegate (Account newacc) { - acc = newacc; - from.str = acc.realname~" <"~acc.mail~">"; + + // select from account from the address book + if (event == "C-Space" && focusedWidget is from) { + if (allowAccountChange) { + auto wacc = new SelectPopBoxWindow(accname); + wacc.onSelected = delegate (uint accid) { + AccountInfo acc; + if (chiroGetAccountInfo(accid, out acc)) { + accname = acc.name; + /* + conwriteln("ACCOUNT ID: ", acc.accid); + conwriteln("ACCOUNT NAME: ", acc.name); + conwriteln("ACCOUNT REAL NAME: ", acc.realname); + conwriteln("ACCOUNT EMAIL: ", acc.email); + conwriteln("ACCOUNT NNTP GROUP: ", acc.nntpgroup); + */ + setFrom(acc.realname~" <"~acc.email~">"); + } }; } return true; } + if (event == "C-Enter") { - static bool checkString (string s) nothrow @trusted @nogc { - if (s.length == 0) return false; - if (s.utflen > 255) return false; - return true; - } - if (!checkString(subj.str)) { vbwin.beep(); return true; } - if (!checkString(to.str)) { vbwin.beep(); return true; } - if (ed.editor[].length == 0) { vbwin.beep(); return true; } - auto senddg = delegate () { - // build reply article and add it to send queue - // check attaches - ed.editor.clearAndDisableUndo(); // so removing attaches will not add 'em to undo, lol - string[] attnames = ed.extractAttaches(); - if (attnames) conwriteln("ATTACHES: ", attnames); - // build article text - auto newart = new Article(); - newart.markAsStandalone(); - newart.attachedFileNames = attnames; - newart.startText(); - //foreach (immutable idx; 0..lcount) newart.appendTextLine(ed.editor[idx]); - { - auto mtr = ed.editor[]; - if (mtr.length) newart.appendTextRange(mtr); else newart.appendTextLine("no text"); - } - // clear editor, so it free used memory - ed.editor.clearAndDisableUndo(); - ed.editor.clear(); - // fix headers - newart.subj = subj.str; - //newart.inreplyto = replyto; - if (replyto.length) newart.replaceHeader("In-Reply-To", replyto); - if (references.length) newart.replaceHeader("References", references); - newart.replaceHeader("To", encodeq(to.str)); - if (!acc.addToSendQueue(fld, newart)) { vbwin.beep(); return; } - close(); - }; auto qww = new YesNoWindow("Send?", "Do you really want to send the message?", true); - qww.onYes = senddg; - qww.addModal(); + qww.onYes = { + if (trySend) close(); else vbwin.beep(); + }; return true; } } - return super.onKey(event); + return super.onKeyBubble(event); } } -+/ // ////////////////////////////////////////////////////////////////////////// // -- 2.11.4.GIT