backend/net: try to prevent hangup on conntection/read failure
[chiroptera.git] / dialogs.d
blobfd7f95fc23de83c9f87af0b4976ca25988d0d447
1 /* E-Mail Client
2 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
3 * Understanding is not required. Only obedience.
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, version 3 of the License ONLY.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 module dialogs /*is aliced*/;
18 private:
20 import core.time;
22 import arsd.simpledisplay;
24 import iv.egra;
26 import iv.alice;
27 import iv.cmdcon;
28 import iv.strex;
29 import iv.sq3;
30 import iv.utfutil;
31 import iv.vfs;
33 import chibackend /*: DynStr*/;
34 import receiver /*: DynStr*/;
37 // ////////////////////////////////////////////////////////////////////////// //
38 public final class HintWindow : SubWindow {
39 private DynStr mMessage;
41 this (const(char)[] amessage) {
42 mMessage = amessage;
44 int xwdt = gxTextWidthUtf(amessage)+6;
45 int xhgt = gxTextHeightUtf+4;
46 super(null, GxSize(xwdt, xhgt));
47 x0 = screenWidth-width;
48 y0 = 0;
49 mType = Type.OnTop;
50 add();
53 override @property int decorationSizeX () const nothrow @safe @nogc { return 3*2; }
54 override @property int decorationSizeY () const nothrow @safe @nogc { return 2*2; }
56 override @property int clientOffsetX () const nothrow @safe @nogc { return decorationSizeX/2; }
57 override @property int clientOffsetY () const nothrow @safe @nogc { return decorationSizeY/2; }
59 @property void message (DynStr v) {
60 if (mMessage != v) {
61 mMessage = v;
62 if (x0 == screenWidth-width) {
63 width = gxTextWidthUtf(v)+6;
64 x0 = screenWidth-width;
65 } else {
66 width = gxTextWidthUtf(v)+6;
67 if (x0+width > screenWidth) x0 = screenWidth-width;
69 postScreenRebuild();
73 override void onPaint () {
74 if (closed) return;
76 immutable bool oldNoTitle = mNoTitle;
77 scope(exit) mNoTitle = oldNoTitle;
78 if (!minimised) mNoTitle = true;
79 super.onPaint();
81 if (!minimised && mMessage.length) {
82 gxWithSavedClip {
83 setupClip();
84 gxClipRect.shrinkBy(3, 2);
85 gxDrawTextUtf(x0+3, y0+2, mMessage, getColor("text"));
90 // prevent any default keyboard handling
91 override bool onKeySink (KeyEvent event) {
92 return true;
97 // ////////////////////////////////////////////////////////////////////////// //
98 public final class MessageWindow : SubWindow {
99 private DynStr mMessage;
100 private uint lastBarWidth;
102 this (const(char)[] amessage) {
103 mMessage = amessage;
105 int xwdt = gxTextWidthUtf(amessage)+decorationSizeX;
106 int xhgt = gxTextHeightUtf+decorationSizeY;
107 super(null, GxSize(xwdt, xhgt));
108 centerWindow();
109 mType = Type.OnTop;
110 add();
113 override @property int decorationSizeX () const nothrow @safe @nogc { return 24; }
114 override @property int decorationSizeY () const nothrow @safe @nogc { return 16; }
116 override @property int clientOffsetX () const nothrow @safe @nogc { return decorationSizeX/2; }
117 override @property int clientOffsetY () const nothrow @safe @nogc { return decorationSizeY/2; }
119 // returns `true` if need redraw
120 bool setProgress (const(char)[] v, uint curr, uint total) {
121 bool res = false;
122 // fix message
123 if (mMessage != v) {
124 mMessage = v;
125 int xwdt = gxTextWidthUtf(v)+decorationSizeX;
126 int xhgt = gxTextHeightUtf+decorationSizeY;
127 if (xwdt > width || xhgt > height) {
128 setSize(xwdt, xhgt);
129 centerWindow();
130 res = true;
133 // bix bar width
134 uint barWidth = 0;
135 if (total) barWidth = curr*cast(uint)(width-2)/total;
136 if (barWidth != lastBarWidth) res = true;
137 lastBarWidth = barWidth;
138 return res;
141 override void onPaint () {
142 if (closed) return;
144 immutable bool oldNoTitle = mNoTitle;
145 scope(exit) mNoTitle = oldNoTitle;
146 if (!minimised) mNoTitle = true;
147 super.onPaint();
149 if (!minimised && mMessage.length) {
150 gxWithSavedClip {
151 setupClip();
152 if (lastBarWidth > 0) {
153 auto rc = GxRect(x0+1, y0+1, lastBarWidth, height-2);
154 gxFillRect(rc, getColor("bar-back"));
156 setupClientClip();
157 //gxClipRect.shrinkBy(3, 2);
158 immutable tw = gxTextWidthUtf(mMessage);
159 immutable th = gxTextHeightUtf();
160 gxDrawTextUtf(x0+(width-tw)/2, y0+(height-th)/2, mMessage, getColor("text"));
165 // prevent any default keyboard handling
166 override bool onKeySink (KeyEvent event) {
167 return true;
172 // ////////////////////////////////////////////////////////////////////////// //
173 public final class ProgressWindow : SubWindow {
174 ProgressBarWidget pbar;
175 private enum PadX = 24;
176 private enum PadY = 8;
178 this (const(char)[] amessage) {
179 int xwdt = gxTextWidthUtf(amessage)+PadX*2;
180 int xhgt = gxTextHeightUtf+PadY*2;
181 super(null, GxSize(xwdt, xhgt));
182 centerWindow();
184 pbar = new ProgressBarWidget(rootWidget, amessage);
185 pbar.size = rootWidget.size;
187 mType = Type.OnTop;
188 add();
191 override @property int decorationSizeX () const nothrow @safe @nogc { return 0; }
192 override @property int decorationSizeY () const nothrow @safe @nogc { return 0; }
194 override @property int clientOffsetX () const nothrow @safe @nogc { return 0; }
195 override @property int clientOffsetY () const nothrow @safe @nogc { return 0; }
197 // returns `true` if need redraw
198 bool setProgress (const(char)[] v, uint curr, uint total) {
199 bool res = false;
200 // fix message
201 if (pbar.text != v) {
202 pbar.text = v;
203 int xwdt = gxTextWidthUtf(v)+PadX*2;
204 int xhgt = gxTextHeightUtf+PadY*2;
205 if (xwdt > width || xhgt > height) {
206 if (xwdt < width) xwdt = width;
207 if (xhgt < height) xhgt = height;
208 setSize(xwdt, xhgt);
209 rootWidget.size = GxSize(xwdt, xhgt);
210 pbar.size = rootWidget.size;
211 centerWindow();
212 res = true;
215 if (pbar.setCurrentTotal(cast(int)curr, cast(int)total)) res = true;
216 return res;
219 // prevent any default keyboard handling
220 override bool onKeySink (KeyEvent event) {
221 return true;
226 // ////////////////////////////////////////////////////////////////////////// //
227 public class SelectPopBoxWindow : SubWindow {
228 public:
229 void delegate (uint accid) onSelected;
231 public:
232 this (dynstring defacc) {
233 createRoot();
235 auto lb = new SimpleListBoxUDataWidget!uint(rootWidget);
236 lb.id = "listbox";
238 static auto stat = LazyStatement!"Conf"(`
239 SELECT accid AS accid, name AS name, realname AS realname, email AS email
240 FROM accounts
241 WHERE sendserver<>'' AND email<>'' and nntpgroup=''
242 ORDER BY accid
243 ;`);
245 int xhgt = 0;
246 int xwdt = 0;
247 bool accFound = false;
248 //int defAccIdx = -1;
250 foreach (auto row; stat.st.range) {
251 dynstring s = row.realname!SQ3Text;
252 s ~= " <";
253 s ~= row.email!SQ3Text;
254 s ~= ">";
255 //conwriteln(s);
256 lb.appendItemWithData(s, row.accid!uint);
257 int w = gxTextWidthUtf(s)+2;
258 if (xwdt < w) xwdt = w;
259 if (defacc == row.name!SQ3Text) {
260 lb.curidx = lb.length-1;
261 accFound = true;
263 //if (acc is defaultAcc) defAccIdx = lb.length-1;
264 xhgt += gxTextHeightUtf;
267 if (lb.length > 0) {
268 if (xhgt == 0) { super(); return; }
269 if (xhgt > screenHeight) xhgt = screenHeight-decorationSizeY;
271 //if (!accFound && defAccIdx >= 0) lb.curidx = defAccIdx;
272 if (xwdt > screenWidth-decorationSizeX) xwdt = screenWidth-decorationSizeX;
274 super("Select Account", GxSize(xwdt+decorationSizeX, xhgt+decorationSizeY));
275 lb.width = clientWidth;
276 lb.height = clientHeight;
278 lb.onAction = delegate (self) {
279 if (auto acc = lb.itemData(lb.curidx)) {
280 close();
281 if (onSelected !is null) onSelected(acc); else vbwin.beep();
282 } else {
283 vbwin.beep();
287 addModal();
288 } else {
289 super();
290 close();
294 override bool onKeyBubble (KeyEvent event) {
295 if (event.pressed) {
296 if (event == "Escape") { close(); return true; }
297 if (event == "Enter") {
298 if (auto lb = querySelector!Widget("#listbox")) {
299 lb.doAction();
300 return true;
304 return super.onKeyBubble(event);
309 // ////////////////////////////////////////////////////////////////////////// //
310 public class SelectAddressBookWindow : SubWindow {
311 static struct Entry {
312 dynstring nick;
313 dynstring name;
314 dynstring email;
317 void delegate (dynstring nick, dynstring name, dynstring email) onSelected;
319 SimpleListBoxUDataWidget!Entry lb;
321 this (const(char)[] prefix) {
322 static auto stat = LazyStatement!"Conf"(`
323 SELECT nick AS nick, name AS name, email AS email
324 FROM addressbook
325 WHERE nick<>'' AND email<>''
326 ORDER BY nick
327 ;`);
329 createRoot();
331 lb = new SimpleListBoxUDataWidget!Entry(rootWidget);
332 lb.id = "listbox";
334 int xhgt = 0;
335 int xwdt = 0;
337 foreach (auto row; stat.st.range) {
338 //conwriteln("! <", row.nick!SQ3Text, "> : <", row.name!SQ3Text, "> : <", row.email!SQ3Text, ">");
339 if (prefix.length) {
340 if (!startsWithCI(row.nick!SQ3Text, prefix)) continue;
342 dynstring it;
343 if (row.name!SQ3Text.length) {
344 it ~= row.name!SQ3Text;
345 it ~= " <";
346 it ~= row.email!SQ3Text;
347 it ~= ">";
348 } else {
349 it = row.email!SQ3Text;
351 Entry e;
352 e.nick = row.nick!SQ3Text;
353 e.name = row.name!SQ3Text;
354 e.email = row.email!SQ3Text;
355 lb.appendItemWithData(it, e);
357 int w = gxTextWidthUtf(it)+2;
358 if (xwdt < w) xwdt = w;
359 //if (ae is defae) lb.curidx = lb.length-1;
360 xhgt += gxTextHeightUtf;
363 if (xhgt == 0) { super(); return; }
364 if (xhgt > screenHeight) xhgt = screenHeight-decorationSizeY;
366 if (xwdt > screenWidth-decorationSizeX) xwdt = screenWidth-decorationSizeX;
368 super("Select Recepient", GxSize(xwdt+decorationSizeX, xhgt+decorationSizeY));
369 lb.width = clientWidth;
370 lb.height = clientHeight;
372 lb.onAction = delegate (self) {
373 Entry ae = lb.itemData(lb.curidx);
374 if (ae.email.length) {
375 close();
376 if (onSelected !is null) onSelected(ae.nick, ae.name, ae.email); else vbwin.beep();
377 } else {
378 vbwin.beep();
382 addModal();
385 override bool onKeyBubble (KeyEvent event) {
386 if (event.pressed) {
387 if (event == "Escape") { close(); return true; }
388 if (event == "Enter") { lb.onAction(lb); return true; }
390 return super.onKeyBubble(event);
395 // ////////////////////////////////////////////////////////////////////////// //
396 public class PostWindow : SubWindow {
397 LineEditWidget from;
398 LineEditWidget to;
399 LineEditWidget subj;
400 EditorWidget ed;
401 //dynstring replyto;
402 dynstring references; // of replyto article
403 dynstring accname;
404 dynstring desttag;
405 bool allowAccountChange = true;
406 bool nntp = false;
408 void setFrom (const(char)[] s) {
409 from.readonly = false;
410 from.str = s;
411 from.readonly = true;
414 void setFrom (const(char)[] name, const(char)[] email) {
415 dynstring s;
416 if (name.length) {
417 s = name;
418 s ~= " <";
419 s ~= email;
420 s ~= ">";
421 } else {
422 s = email;
424 setFrom(s.getData);
427 bool trySend () {
428 static bool checkString (const(char)[] s) nothrow @trusted @nogc {
429 if (s.length == 0) return false;
430 if (s.utflen > 255) return false;
431 return true;
434 if (!checkString(from.str)) { from.focus(); return false; }
435 if (!nntp && !checkString(to.str)) { to.focus(); return false; }
436 if (!checkString(subj.str)) { subj.focus(); return false; }
437 if (ed.editor[].length == 0) { ed.focus(); return false; }
439 static const(char)[] extractMail (const(char)[] s) nothrow @trusted @nogc {
440 s = s.xstrip;
441 if (s.length == 0 || s[$-1] != '>') return s;
442 auto spp = s.lastIndexOf('<');
443 if (spp < 0) return s;
444 s = s[spp+1..$-1].xstrip;
445 return s;
448 static const(char)[] extractName (const(char)[] s) nothrow @trusted @nogc {
449 s = s.xstrip;
450 if (s.length == 0 || s[$-1] != '>') return null;
451 auto spp = s.lastIndexOf('<');
452 if (spp < 0) return null;
453 s = s[0..spp];
454 return s.xstrip;
457 AccountInfo acc;
458 if (!chiroGetAccountInfo(accname.getData, out acc)) return false;
460 if (nntp != (acc.nntpgroup.length != 0)) return false;
463 conwriteln("ACCOUNT ID: ", acc.accid);
464 conwriteln("ACCOUNT NAME: ", acc.name);
465 conwriteln("ACCOUNT REAL NAME: ", acc.realname);
466 conwriteln("ACCOUNT EMAIL: ", acc.email);
467 conwriteln("ACCOUNT NNTP GROUP: ", acc.nntpgroup);
470 dynstring fromname = extractName(from.str);
471 dynstring frommail = extractMail(from.str);
473 dynstring toname = extractName(to.str);
474 dynstring tomail = extractMail(to.str);
476 conwriteln("FROM: name=<", fromname, ">:<", strEncodeQ(fromname), "> : mail=<", frommail, ">");
477 conwriteln("TO: name=<", toname, ">:<", strEncodeQ(toname), "> : mail=<", tomail, ">");
479 if (!isGoodEmail(frommail)) { from.focus(); return false; }
480 if (!nntp && !isGoodEmail(tomail)) { to.focus(); return false; }
482 // build reply article and add it to send queue
483 // check attaches
484 ed.editor.clearAndDisableUndo(); // so removing attaches will not add 'em to undo, lol
485 dynstring[] attnames = ed.extractAttaches();
486 if (attnames.length) foreach (dynstring ss; attnames) conwriteln("ATTACH: ", ss);
488 MessageBuilder msg;
489 msg.setFromName(fromname);
490 msg.setFromMail(frommail);
491 msg.setToName(toname);
492 msg.setToMail(tomail);
493 if (acc.nntpgroup.length) msg.setNewsgroup(acc.nntpgroup);
494 msg.setSubj(subj.str);
495 msg.setBody(ed.getText);
497 //if (replyto.length) msg.appendReference(replyto);
499 const(char)[] refs = references.xstrip;
500 while (refs.length) {
501 usize spp = 0;
502 while (spp < refs.length && refs[spp] > ' ') ++spp;
503 msg.appendReference(refs[0..spp]);
504 refs = refs[spp..$].xstrip;
507 foreach (dynstring ss; attnames) {
508 try {
509 msg.attachFile(ss);
510 } catch (Exception e) {
511 conwriteln("ERROR: cannot attach file '", ss, "'!");
515 // clear editor, so it will free used memory
516 ed.editor.clearAndDisableUndo();
517 ed.editor.clear();
519 bool skipFilters = true;
520 // build tags
521 dynstring tags;
522 // account
523 if (!nntp) {
524 tags ~= "account:";
525 tags ~= acc.name;
526 if (desttag.length) {
527 tags ~= "|";
528 tags ~= desttag;
529 } else if (acc.inbox.length) {
530 // put to inbox
531 tags ~= "|";
532 tags ~= acc.inbox;
533 } else {
534 skipFilters = false;
538 //conwriteln("TAGS: ", tags);
539 //assert(0);
541 dynstring mdata = msg.getPrepared;
543 // for NNTP, we will do the trick: insert deleted message into the storage
544 // this is because next NNTP check will receive it
546 uid INTEGER PRIMARY KEY /* the same as in the storage, not automatic */
547 , accid INTEGER /* account from which this message should be sent */
548 , from_pop3 TEXT /* "from" for POP3 */
549 , to_pop3 TEXT /* "to" for POP3 */
550 , data TEXT /* PACKED data to send */
551 , sendtime INTEGER DEFAULT 0 /* 0: not yet; unixtime */
552 , lastsendtime INTEGER DEFAULT 0 /* when we last tried to send it? 0 means "not yet" */
555 // insert into main store
556 uint uid;
557 transacted!"Store"{
558 foreach (auto row; dbStore.statement(`
559 INSERT INTO messages(tags, data) VALUES(:tags, ChiroPack(:data)) RETURNING uid;`)
560 .bindConstText(":tags", tags.getData)
561 .bindConstBlob(":data", mdata.getData)
562 .range)
564 uid = row.uid!uint;
568 if (nntp) tomail.clear();
570 // insert into unsent queue
571 transacted!"View"{
572 dbView.statement(`
573 INSERT INTO unsent
574 ( uid, accid, from_pop3, to_pop3, data)
575 VALUES(:uid,:accid,:from_pop3,:to_pop3,ChiroPack(:data))
577 .bind(":uid", uid)
578 .bind(":accid", acc.accid)
579 .bind(":accid", acc.accid)
580 .bindConstText(":from_pop3", frommail.getData)
581 .bindConstText(":to_pop3", tomail.getData)
582 .bindConstBlob(":data", mdata.getData)
583 .doAll();
586 conwriteln("message inserted");
587 if (!nntp) {
588 updateViewDB(skipFilters:skipFilters);
592 conwriteln("=======================");
593 conwrite(msg.getPrepared);
594 conwriteln("-----------------------");
597 return true;
600 override void createWidgets () {
601 (new VBoxWidget).enter{
602 (new HBoxWidget).enter{
603 with (new HotLabelWidget("&From:", LabelWidget.HAlign.Right)) { width = width+2; hsizeId = "editors"; }
604 new SpacerWidget(4);
605 from = new LineEditWidget();
606 from.flex = 1;
607 from.readonly = true;
608 }.flex = 1;
610 new SpacerWidget(1);
611 (new HBoxWidget).enter{
612 with (new HotLabelWidget("&To:", LabelWidget.HAlign.Right)) { width = width+2; hsizeId = "editors"; }
613 new SpacerWidget(4);
614 to = new LineEditWidget();
615 to.flex = 1;
616 }.flex = 1;
618 new SpacerWidget(1);
619 (new HBoxWidget).enter{
620 with (new HotLabelWidget("&Subj:", LabelWidget.HAlign.Right)) { width = width+2; hsizeId = "editors"; }
621 new SpacerWidget(4);
622 subj = new LineEditWidget();
623 subj.flex = 1;
624 }.flex = 1;
627 (new VBoxWidget).enter{
628 ed = new EditorWidget();
629 ed.flex = 1;
630 }.flex = 1;
632 new SpacerWidget(4);
633 (new HBoxWidget).enter{
634 new SpacerWidget(12);
635 //new SpringWidget(1);
636 with (new ButtonWidget(" Send ")) {
637 hsizeId = "okcancel";
638 //deftype = Default.Accept;
639 onAction = delegate (self) {
640 if (trySend) close(); else vbwin.beep();
642 flex = 1;
644 new SpacerWidget(12);
645 with (new ButtonWidget(" Cancel ")) {
646 hsizeId = "okcancel";
647 //deftype = Default.Cancel;
648 onAction = delegate (self) {
649 close();
651 flex = 1;
653 //new SpringWidget(1);
654 new SpacerWidget(12);
656 new SpacerWidget(4);
658 relayout(); // don't resize window
659 centerWindow();
662 this () {
663 int wanthgt = screenHeight-42*2;
664 if (wanthgt < 80) wanthgt = 80;
665 int wantwdt = screenWidth-64*2;
666 if (wantwdt < 506) wantwdt = 506;
667 super("Compose Mail", GxSize(wantwdt, wanthgt));
668 //if (hasWindowClass(this)) return;
671 override bool onKeySink (KeyEvent event) {
672 if (event.pressed) {
673 if (event == "Escape" && !ed.editor.textChanged) { close(); return true; }
674 if (event == "C-G" || event == "C-C") {
675 if (ed.editor.textChanged) {
676 auto qww = new YesNoWindow("Close?", "Do you really want to close the editor?", true);
677 qww.onYes = () { close(); };
678 //qww.addModal();
679 } else {
680 close();
682 return true;
686 if (event == "M-Tab" && focusedWidget is to) {
687 auto ae = abookFindByNick(to.str);
688 //if (ae is null) ae = abookFindByMail(to.str);
689 if (ae !is null) {
690 if (ae.realname.length) to.str = ae.realname~" <"~ae.mail~">"; else to.str = ae.mail;
691 } else {
692 vbwin.beep();
694 return true;
698 // select destination from the address book
699 if (event == "C-Space" && focusedWidget is to) {
700 auto wae = new SelectAddressBookWindow(to.str);
701 if (wae.lb.length != 0) {
702 wae.onSelected = delegate (dynstring nick, dynstring name, dynstring email) {
703 if (name.length) to.str = name~" <"~email~">"; else to.str = email;
705 } else {
706 wae.close();
708 return true;
711 // select from account from the address book
712 if (event == "C-Space" && focusedWidget is from) {
713 if (allowAccountChange) {
714 auto wacc = new SelectPopBoxWindow(accname);
715 wacc.onSelected = delegate (uint accid) {
716 AccountInfo acc;
717 if (chiroGetAccountInfo(accid, out acc)) {
718 accname = acc.name;
720 conwriteln("ACCOUNT ID: ", acc.accid);
721 conwriteln("ACCOUNT NAME: ", acc.name);
722 conwriteln("ACCOUNT REAL NAME: ", acc.realname);
723 conwriteln("ACCOUNT EMAIL: ", acc.email);
724 conwriteln("ACCOUNT NNTP GROUP: ", acc.nntpgroup);
726 setFrom(acc.realname~" <"~acc.email~">");
730 return true;
733 if (event == "C-Enter") {
734 auto qww = new YesNoWindow("Send?", "Do you really want to send the message?", true);
735 qww.onYes = {
736 if (trySend) close(); else vbwin.beep();
738 return true;
741 return super.onKeyBubble(event);
746 // ////////////////////////////////////////////////////////////////////////// //
747 public class TitlerWindow : SubWindow {
748 LineEditWidget edtTitle;
749 LineEditWidget fromName;
750 LineEditWidget fromMail;
751 //LineEditWidget fromTag;
752 DynStr name;
753 DynStr mail;
754 DynStr folder;
755 DynStr title;
757 bool delegate (const(char)[] name, const(char)[] mail, const(char)[] folder, const(char)[] title) onSelected;
759 override void createWidgets () {
760 new SpacerWidget(rootWidget, 2);
762 version(none) {
763 fromName = new LineEditWidget(rootWidget, "Name:");
764 fromName.flex = 1;
766 new SpacerWidget(rootWidget, 2);
767 fromMail = new LineEditWidget(rootWidget, "Mail:");
768 fromMail.flex = 1;
770 new SpacerWidget(rootWidget, 2);
771 edtTitle = new LineEditWidget(rootWidget, "Title:");
772 edtTitle.flex = 1;
773 } else {
774 (new HBoxWidget).enter{
775 with (new HotLabelWidget("&Name:", LabelWidget.HAlign.Right)) { width = width+2; hsizeId = "editors"; }
776 new SpacerWidget(4);
777 fromName = new LineEditWidget();
778 fromName.flex = 1;
779 }.flex = 1;
781 new SpacerWidget(1);
782 (new HBoxWidget).enter{
783 with (new HotLabelWidget("&Mail:", LabelWidget.HAlign.Right)) { width = width+2; hsizeId = "editors"; }
784 new SpacerWidget(4);
785 fromMail = new LineEditWidget();
786 fromMail.flex = 1;
787 }.flex = 1;
789 new SpacerWidget(1);
790 (new HBoxWidget).enter{
791 with (new HotLabelWidget("&Title:", LabelWidget.HAlign.Right)) { width = width+2; hsizeId = "editors"; }
792 new SpacerWidget(4);
793 edtTitle = new LineEditWidget();
794 edtTitle.flex = 1;
795 }.flex = 1;
798 new SpacerWidget(4);
799 (new HBoxWidget).enter{
800 new SpacerWidget(2);
801 new SpringWidget(1);
802 with (new ButtonWidget(" O&k ")) {
803 hsizeId = "okcancel";
804 deftype = Default.Accept;
805 onAction = delegate (self) {
806 if (onSelected !is null) {
807 if (!onSelected(fromName.str, fromMail.str, folder, edtTitle.str)) return;
809 close();
812 new SpacerWidget(2);
813 with (new ButtonWidget(" Cancel ")) {
814 hsizeId = "okcancel";
815 deftype = Default.Cancel;
816 onAction = delegate (self) {
817 close();
820 new SpringWidget(1);
821 new SpacerWidget(2);
823 new SpacerWidget(4);
825 fromName.str = name;
826 fromMail.str = mail;
827 edtTitle.str = title;
829 minWinSize.w = 320;
831 edtTitle.focus();
833 relayoutResize();
834 centerWindow();
837 this (const(char)[] aname, const(char)[] amail, const(char)[] afolder, const(char)[] atitle) {
838 name = aname;
839 mail = amail;
840 title = atitle;
841 folder = afolder;
843 DynStr caption = "Title for ";
844 caption ~= aname;
845 caption ~= " <";
846 caption ~= amail;
847 caption ~= ">";
848 super(caption.getData);
853 // ////////////////////////////////////////////////////////////////////////// //
854 public class TagOptionsWindow : SubWindow {
855 LabelWidget optPath; // real path
856 LineEditWidget optMonthes;
857 CheckboxWidget optThreaded;
858 CheckboxWidget optAttaches;
859 DynStr tagname;
861 void delegate (const(char)[] tagname) onUpdated;
863 bool onAccept () {
864 int mv = -666;
865 try {
866 auto vv = optMonthes.str.xstrip;
867 if (vv.length == 0) {
868 mv = -2;
869 } else {
870 mv = vv.toInt;
871 if (mv < -1) mv = -666; else if (mv == 0) mv = -1;
873 } catch (Exception) {
874 mv = -666;
876 if (mv < -2) return false;
877 import core.stdc.stdio : snprintf;
878 char[1024] xname = void;
879 const(char)[] tn = tagname.getData;
880 auto xlen = snprintf(xname.ptr, xname.sizeof, "/mainpane/msgview/monthlimit%s%.*s",
881 (tn.length && tn[0] != '/' ? "/".ptr : "".ptr), cast(uint)tn.length, tn.ptr);
882 if (mv == -2) {
883 // delete
884 if (xname[0..xlen] != "/mainpane/msgview/monthlimit") {
885 chiroDeleteOption(xname[0..xlen]);
887 } else {
888 // set
889 chiroSetOption(xname[0..xlen], mv);
891 // fix threading and attaches
892 if (optThreaded.enabled) {
893 dbView.statement(`
894 UPDATE tagnames
895 SET threading=:trd, noattaches=:noatt
896 WHERE tag=:name
898 .bindConstText(":name", tagname.getData())
899 .bind(":trd", (optThreaded.checked ? 1 : 0))
900 .bind(":noatt", (optAttaches.checked ? 0 : 1))
901 .doAll();
903 if (onUpdated !is null) onUpdated(tagname.getData);
904 return true;
907 override void createWidgets () {
908 optPath = new LabelWidget(rootWidget, "", LabelWidget.HAlign.Center);
909 optPath.flex = 1;
911 version(none) {
912 optMonthes = new LineEditWidget(rootWidget, "Monthes:");
913 optMonthes.flex = 1;
914 optMonthes.width = optMonthes.titwdt+64;
915 } else {
916 (new HBoxWidget).enter{
917 with (new HotLabelWidget("&Monthes:", LabelWidget.HAlign.Right)) { width = width+2; /*hsizeId = "editors";*/ }
918 new SpacerWidget(4);
919 optMonthes = new LineEditWidget();
920 //optMonthes.flex = 1;
921 optMonthes.width = gxTextWidthUtf("96669");
922 //new SpringWidget(1);
923 }.flex = 1;
926 optThreaded = new CheckboxWidget(rootWidget, "&Threaded");
927 optThreaded.flex = 1;
929 optAttaches = new CheckboxWidget(rootWidget, "&Attaches");
930 optAttaches.flex = 1;
932 new SpacerWidget(4);
933 (new HBoxWidget).enter{
934 new SpacerWidget(2);
935 new SpringWidget(1);
936 with (new ButtonWidget(" O&k ")) {
937 hsizeId = "okcancel";
938 deftype = Default.Accept;
939 onAction = delegate (self) {
940 if (onAccept()) close();
943 new SpacerWidget(2);
944 with (new ButtonWidget(" Cancel ")) {
945 hsizeId = "okcancel";
946 deftype = Default.Cancel;
947 onAction = delegate (self) {
948 close();
951 new SpringWidget(1);
952 new SpacerWidget(2);
954 new SpacerWidget(4);
956 optMonthes.focus();
958 immutable bool goodTag = (chiroGetTagUid(tagname.getData) != 0);
960 import std.conv : to;
961 int val;
962 DynStr path = chiroGetTagMonthLimitEx(tagname.getData, out val, defval:6);
963 //conwriteln("TAGNAME=<", tagname.getData, ">; val=", val, "; path=<", path.getData, ">");
964 optPath.text = path.getData;
965 optMonthes.str = val.to!string;
966 optPath.width = gxTextWidthUtf(optPath.text)+4;
969 if (goodTag) {
970 foreach (auto row; dbView.statement(`
971 SELECT threading AS trd, noattaches AS noatt FROM tagnames WHERE tag=:tag LIMIT 1
972 ;`).bindConstText(":tag", tagname.getData).range)
974 optThreaded.checked = (row.trd!int == 1);
975 optAttaches.checked = (row.noatt!int == 0);
977 } else {
978 optThreaded.enabled = false;
979 optAttaches.enabled = false;
982 optMonthes.killTextOnChar = true;
984 minWinSize.w = optPath.width;
985 relayoutResize();
987 centerWindow();
990 this (const(char)[] atagname) {
991 tagname = atagname;
993 DynStr caption = "options for '";
994 caption ~= atagname;
995 caption ~= "'";
996 super(caption.getData);