moved egfx/egui to iv.egra
[chiroptera.git] / dialogs.d
blob23cf2133143f54d7c951e85c94c5f1147cfc35f8
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.utfutil;
30 import iv.vfs;
32 import chibackend /*: DynStr*/;
34 //import account;
35 //import addrbook;
36 //import hitwit;
39 // ////////////////////////////////////////////////////////////////////////// //
40 public final class HintWindow : SubWindow {
41 private DynStr mMessage;
43 this (const(char)[] amessage) {
44 mMessage = amessage;
46 int xwdt = gxTextWidthUtf(amessage)+6;
47 int xhgt = gxTextHeightUtf+4;
48 super(null, GxSize(xwdt, xhgt));
49 x0 = screenWidth-width;
50 y0 = 0;
51 mType = Type.OnTop;
52 add();
55 override @property int decorationSizeX () const nothrow @safe @nogc { return 3*2; }
56 override @property int decorationSizeY () const nothrow @safe @nogc { return 2*2; }
58 override @property int clientOffsetX () const nothrow @safe @nogc { return decorationSizeX/2; }
59 override @property int clientOffsetY () const nothrow @safe @nogc { return decorationSizeY/2; }
61 @property void message (DynStr v) {
62 if (mMessage != v) {
63 mMessage = v;
64 if (x0 == screenWidth-width) {
65 width = gxTextWidthUtf(v)+6;
66 x0 = screenWidth-width;
67 } else {
68 width = gxTextWidthUtf(v)+6;
69 if (x0+width > screenWidth) x0 = screenWidth-width;
71 postScreenRebuild();
75 override void onPaint () {
76 if (closed) return;
77 gxWithSavedClip {
78 if (!mMinimized) {
79 setupClip();
80 //gxDrawWindow(winrect, null, gxRGB!(255, 255, 255), gxRGB!(0, 0, 0), gxRGB!(255, 255, 255), gxRGB!(0, 0, 80));
81 gxDrawWindow(winrect, null,
82 getColor("frame"),
83 getColor("title-text"),
84 getColor("title-back"),
85 getColor("back"));
86 //getColor(gxRGB!(255, 255, 255), gxRGB!(0, 0, 0), gxRGB!(255, 255, 255), gxRGB!(0, 0, 80));
87 gxClipRect.shrinkBy(3, 2);
88 gxDrawTextUtf(x0+3, y0+2, mMessage, getColor("text")/*gxRGB!(155, 155, 155)*/);
89 } else {
90 drawWindowMinimized();
95 // prevent any default keyboard handling
96 override bool onKeySink (KeyEvent event) {
97 return true;
102 // ////////////////////////////////////////////////////////////////////////// //
103 public final class MessageWindow : SubWindow {
104 private DynStr mMessage;
105 private uint lastBarWidth;
107 this (const(char)[] amessage) {
108 mMessage = amessage;
110 int xwdt = gxTextWidthUtf(amessage)+decorationSizeX;
111 int xhgt = gxTextHeightUtf+decorationSizeY;
112 super(null, GxSize(xwdt, xhgt));
113 centerWindow();
114 mType = Type.OnTop;
115 add();
118 override @property int decorationSizeX () const nothrow @safe @nogc { return 24; }
119 override @property int decorationSizeY () const nothrow @safe @nogc { return 16; }
121 override @property int clientOffsetX () const nothrow @safe @nogc { return decorationSizeX/2; }
122 override @property int clientOffsetY () const nothrow @safe @nogc { return decorationSizeY/2; }
124 // returns `true` if need redraw
125 bool setProgress (const(char)[] v, uint curr, uint total) {
126 bool res = false;
127 // fix message
128 if (mMessage != v) {
129 mMessage = v;
130 int xwdt = gxTextWidthUtf(v)+decorationSizeX;
131 int xhgt = gxTextHeightUtf+decorationSizeY;
132 if (xwdt > width || xhgt > height) {
133 setSize(xwdt, xhgt);
134 centerWindow();
135 res = true;
138 // bix bar width
139 uint barWidth = 0;
140 if (total) barWidth = curr*cast(uint)(width-2)/total;
141 if (barWidth != lastBarWidth) res = true;
142 lastBarWidth = barWidth;
143 return res;
145 import core.stdc.stdio : snprintf;
146 char[128] buf = void;
147 if (total == 0) {
148 recalcHintWindow.message = msg;
149 } else {
150 auto len = snprintf(buf.ptr, buf.sizeof, "%.*s: %u", cast(uint)msg.length, msg.ptr, curr*100U/total);
151 recalcHintWindow.message = buf[0..len];
156 override void onPaint () {
157 if (closed) return;
158 gxWithSavedClip {
159 if (!mMinimized) {
160 setupClip();
161 //gxDrawWindow(winrect, null, gxRGB!(255, 255, 255), gxRGB!(0, 0, 0), gxRGB!(255, 255, 255), gxRGB!(0, 0, 80));
162 gxDrawWindow(winrect, null,
163 getColor("frame"),
164 getColor("title-text"),
165 getColor("title-back"),
166 getColor("back"));
167 if (lastBarWidth > 0) {
168 auto rc = GxRect(x0+1, y0+1, lastBarWidth, height-2);
169 gxFillRect(rc, getColor("bar-back")/*gxRGB!(90, 90, 180)*/);
171 setupClientClip();
172 //gxClipRect.shrinkBy(3, 2);
173 immutable tw = gxTextWidthUtf(mMessage);
174 immutable th = gxTextHeightUtf();
175 gxDrawTextUtf(x0+(width-tw)/2, y0+(height-th)/2, mMessage, getColor("text"));
176 } else {
177 drawWindowMinimized();
182 // prevent any default keyboard handling
183 override bool onKeySink (KeyEvent event) {
184 return true;
189 // ////////////////////////////////////////////////////////////////////////// //
190 public final class ProgressWindow : SubWindow {
191 ProgressBarWidget pbar;
192 private enum PadX = 24;
193 private enum PadY = 8;
195 this (string amessage) {
196 int xwdt = gxTextWidthUtf(amessage)+PadX*2;
197 int xhgt = gxTextHeightUtf+PadY*2;
198 super(null, GxSize(xwdt, xhgt));
199 centerWindow();
201 pbar = new ProgressBarWidget(rootWidget, amessage);
202 pbar.size = rootWidget.size;
204 mType = Type.OnTop;
205 add();
208 override @property int decorationSizeX () const nothrow @safe @nogc { return 0; }
209 override @property int decorationSizeY () const nothrow @safe @nogc { return 0; }
211 override @property int clientOffsetX () const nothrow @safe @nogc { return 0; }
212 override @property int clientOffsetY () const nothrow @safe @nogc { return 0; }
214 // returns `true` if need redraw
215 bool setProgress (const(char)[] v, uint curr, uint total) {
216 bool res = false;
217 // fix message
218 if (pbar.text != v) {
219 pbar.text = v;
220 int xwdt = gxTextWidthUtf(v)+PadX*2;
221 int xhgt = gxTextHeightUtf+PadY*2;
222 if (xwdt > width || xhgt > height) {
223 if (xwdt < width) xwdt = width;
224 if (xhgt < height) xhgt = height;
225 setSize(xwdt, xhgt);
226 rootWidget.size = GxSize(xwdt, xhgt);
227 pbar.size = rootWidget.size;
228 centerWindow();
229 res = true;
232 if (pbar.setCurrentTotal(cast(int)curr, cast(int)total)) res = true;
233 return res;
236 override void onPaint () {
237 if (closed) return;
238 gxWithSavedClip {
239 if (!mMinimized) {
240 drawWidgets();
241 } else {
242 drawWindowMinimized();
247 // prevent any default keyboard handling
248 override bool onKeySink (KeyEvent event) {
249 return true;
254 // ////////////////////////////////////////////////////////////////////////// //
256 public class SelectPopBoxWindow : SubWindow {
257 SimpleListBoxWidget lb;
259 void delegate (Account acc) onSelected;
261 this (Account defacc) {
262 if (accounts.length > 0) {
263 int xhgt = 0;
264 int xwdt = 0;
266 lb = new SimpleListBoxWidget(this);
267 bool accFound = false;
268 int defAccIdx = -1;
269 foreach (immutable aidx, Account acc; accounts) {
270 if (cast(Pop3Account)acc) {
271 import std.format : format;
272 string s = "%s <%s>".format(acc.realname, acc.mail);
273 lb.appendItem(s, acc);
274 int w = gxTextWidthUtf(s)+2;
275 if (xwdt < w) xwdt = w;
276 if (acc is defacc) {
277 lb.curidx = lb.length-1;
278 accFound = true;
280 if (acc is defaultAcc) defAccIdx = lb.length-1;
281 xhgt += gxTextHeightUtf;
285 if (xhgt == 0) { super(); return; }
286 if (xhgt > screenHeight) xhgt = screenHeight-decorationSizeY;
288 if (!accFound && defAccIdx >= 0) lb.curidx = defAccIdx;
289 if (xwdt > screenWidth-decorationSizeX) xwdt = screenWidth-decorationSizeX;
291 super("Select Account", xwdt+decorationSizeX, xhgt+decorationSizeY);
292 lb.ww = clientWidth;
293 lb.wh = clientHeight;
295 lb.onAction = delegate (self) {
296 if (auto acc = cast(Account)lb.itemData(lb.curidx)) {
297 self.parent.close();
298 if (onSelected !is null) onSelected(acc); else vbwin.beep();
299 } else {
300 vbwin.beep();
304 addModal();
305 } else {
306 super();
310 override bool onKey (KeyEvent event) {
311 if (event.pressed) {
312 if (event == "Escape" || event == "C-Q") { close(); return true; }
313 if (event == "Enter") { lb.onAction(lb); return true; }
315 return super.onKey(event);
321 // ////////////////////////////////////////////////////////////////////////// //
323 public class SelectAddressBookWindow : SubWindow {
324 SimpleListBoxWidget lb;
326 void delegate (AddressBookEntry acc) onSelected;
328 this (const(char)[] prefix) {
329 if (abook.length > 0) {
330 int xhgt = 0;
331 int xwdt = 0;
332 auto defae = abookFindByNickFirst(prefix);
333 if (defae is null && prefix.length) {
334 auto mstx = prefix.lastIndexOf('<');
335 if (mstx >= 0) {
336 prefix = prefix[mstx+1..$];
337 if (prefix.length && prefix[$-1] == '>') prefix = prefix[0..$-1];
339 defae = abookFindByMailFirst(prefix);
342 lb = new SimpleListBoxWidget(this);
343 foreach (immutable aidx, AddressBookEntry ae; abook) {
344 import std.format : format;
345 string s;
346 if (ae.realname.length) s = ae.realname~" <"~ae.mail~">"; else s = ae.mail;
347 lb.appendItem(s, ae);
348 int w = gxTextWidthUtf(s)+2;
349 if (xwdt < w) xwdt = w;
350 if (ae is defae) lb.curidx = lb.length-1;
351 xhgt += gxTextHeightUtf;
354 if (xhgt == 0) { super(); return; }
355 if (xhgt > screenHeight) xhgt = screenHeight-decorationSizeY;
357 if (xwdt > screenWidth-decorationSizeX) xwdt = screenWidth-decorationSizeX;
359 super("Select Recepient", xwdt+decorationSizeX, xhgt+decorationSizeY);
360 lb.ww = clientWidth;
361 lb.wh = clientHeight;
363 lb.onAction = delegate (self) {
364 if (auto ae = cast(AddressBookEntry)lb.itemData(lb.curidx)) {
365 close();
366 if (onSelected !is null) onSelected(ae); else vbwin.beep();
367 } else {
368 vbwin.beep();
372 addModal();
373 } else {
374 super();
378 override bool onKey (KeyEvent event) {
379 if (event.pressed) {
380 if (event == "Escape" || event == "C-Q") { close(); return true; }
381 if (event == "Enter") { lb.onAction(lb); return true; }
383 return super.onKey(event);
389 // ////////////////////////////////////////////////////////////////////////// //
391 public class PostWindow : SubWindow {
392 LineEditWidget from;
393 LineEditWidget to;
394 LineEditWidget subj;
395 EditorWidget ed;
396 string replyto;
397 string references; // of replyto article
398 Account acc;
399 Folder fld;
401 this () {
402 import std.algorithm : max;
404 int wanthgt = screenHeight-42*2;
405 if (wanthgt < 80) wanthgt = 80;
406 int wantwdt = screenWidth-64*2;
407 if (wantwdt < 506) wantwdt = 506;
408 super("Compose Mail", /*506*/wantwdt, /*253*/wanthgt);
409 //if (hasWindowClass(this)) return;
411 from = new LineEditWidget(this, "From:");
412 to = new LineEditWidget(this, "To:");
413 subj = new LineEditWidget(this, "Subj:");
414 ed = new EditorWidget(this);
416 setupClientClip();
418 int tw = max(from.titwdt, to.titwdt, subj.titwdt);
419 from.titwdt = tw;
420 to.titwdt = tw;
421 subj.titwdt = tw;
423 from.wy = 0;
424 to.wy = from.wy+from.wh+2;
425 subj.wy = to.wy+to.wh+2;
427 ed.wx = 0;
428 ed.wy = subj.wy+subj.wh+2;
429 ed.ww = gxClipRect.width;
430 ed.wh = gxClipRect.height-ed.wy;
432 add();
435 override bool onKey (KeyEvent event) {
436 if (event.pressed) {
437 if (event == "Escape" && !ed.editor.textChanged) { close(); return true; }
438 if (event == "C-G" || event == "C-C") {
439 if (ed.editor.textChanged) {
440 auto qww = new YesNoWindow("Close?", "Do you really want to close the editor?", true);
441 qww.onYes = () { close(); };
442 qww.addModal();
443 } else {
444 close();
446 return true;
448 if (event == "M-Tab" && activeWidget is to) {
449 auto ae = abookFindByNick(to.str);
450 //if (ae is null) ae = abookFindByMail(to.str);
451 if (ae !is null) {
452 if (ae.realname.length) to.str = ae.realname~" <"~ae.mail~">"; else to.str = ae.mail;
453 } else {
454 vbwin.beep();
456 return true;
458 if (event == "C-Space" && activeWidget is to) {
459 auto wae = new SelectAddressBookWindow(to.str);
460 wae.onSelected = delegate (AddressBookEntry ae) {
461 if (ae.realname.length) to.str = ae.realname~" <"~ae.mail~">"; else to.str = ae.mail;
463 return true;
465 if ((event == "C-Space") && activeWidget is from) {
466 if (cast(Pop3Account)acc) {
467 auto wacc = new SelectPopBoxWindow(acc);
468 wacc.onSelected = delegate (Account newacc) {
469 acc = newacc;
470 from.str = acc.realname~" <"~acc.mail~">";
473 return true;
475 if (event == "C-Enter") {
476 static bool checkString (string s) nothrow @trusted @nogc {
477 if (s.length == 0) return false;
478 if (s.utflen > 255) return false;
479 return true;
481 if (!checkString(subj.str)) { vbwin.beep(); return true; }
482 if (!checkString(to.str)) { vbwin.beep(); return true; }
483 if (ed.editor[].length == 0) { vbwin.beep(); return true; }
484 auto senddg = delegate () {
485 // build reply article and add it to send queue
486 // check attaches
487 ed.editor.clearAndDisableUndo(); // so removing attaches will not add 'em to undo, lol
488 string[] attnames = ed.extractAttaches();
489 if (attnames) conwriteln("ATTACHES: ", attnames);
490 // build article text
491 auto newart = new Article();
492 newart.markAsStandalone();
493 newart.attachedFileNames = attnames;
494 newart.startText();
495 //foreach (immutable idx; 0..lcount) newart.appendTextLine(ed.editor[idx]);
497 auto mtr = ed.editor[];
498 if (mtr.length) newart.appendTextRange(mtr); else newart.appendTextLine("no text");
500 // clear editor, so it free used memory
501 ed.editor.clearAndDisableUndo();
502 ed.editor.clear();
503 // fix headers
504 newart.subj = subj.str;
505 //newart.inreplyto = replyto;
506 if (replyto.length) newart.replaceHeader("In-Reply-To", replyto);
507 if (references.length) newart.replaceHeader("References", references);
508 newart.replaceHeader("To", encodeq(to.str));
509 if (!acc.addToSendQueue(fld, newart)) { vbwin.beep(); return; }
510 close();
512 auto qww = new YesNoWindow("Send?", "Do you really want to send the message?", true);
513 qww.onYes = senddg;
514 qww.addModal();
515 return true;
518 return super.onKey(event);
524 // ////////////////////////////////////////////////////////////////////////// //
525 public class TitlerWindow : SubWindow {
526 LineEditWidget edtTitle;
527 LineEditWidget fromName;
528 LineEditWidget fromMail;
529 //LineEditWidget fromTag;
530 //DynStr name;
531 //DynStr mail;
532 DynStr folder;
534 bool delegate (const(char)[] name, const(char)[] mail, const(char)[] folder, const(char)[] title) onSelected;
536 this (const(char)[] aname, const(char)[] amail, const(char)[] afolder, const(char)[] atitle) {
537 import std.algorithm : max;
538 int xhgt = gxTextHeightUtf+2;
540 DynStr caption = "Title for ";
541 caption ~= aname;
542 caption ~= " <";
543 caption ~= amail;
544 caption ~= ">";
545 super(caption.getData.idup/*, GxSize(506, xhgt+decorationSizeY)*/);
547 new SpacerWidget(rootWidget, 2);
548 fromName = new LineEditWidget(rootWidget, "Name:");
549 fromName.flex = 1;
551 new SpacerWidget(rootWidget, 2);
552 fromMail = new LineEditWidget(rootWidget, "Mail:");
553 fromMail.flex = 1;
555 new SpacerWidget(rootWidget, 2);
556 edtTitle = new LineEditWidget(rootWidget, "Title:");
557 edtTitle.flex = 1;
558 new SpacerWidget(rootWidget, 2);
560 fromName.str = aname;
561 fromMail.str = amail;
562 edtTitle.str = atitle;
563 folder = afolder;
565 int twdt = fromName.titwdt;
566 if (twdt < fromMail.titwdt) twdt = fromMail.titwdt;
567 if (twdt < edtTitle.titwdt) twdt = edtTitle.titwdt;
568 fromName.titwdt = twdt;
569 fromMail.titwdt = twdt;
570 edtTitle.titwdt = twdt;
572 minWinSize.w = 320;
574 relayoutResize();
575 centerWindow();
577 edtTitle.focus();
580 override bool onKeyBubble (KeyEvent event) {
581 if (event.pressed) {
582 if (event == "Escape" || event == "C-Q") { close(); return true; }
583 if (event == "Enter") {
584 if (onSelected !is null) {
585 if (!onSelected(fromName.str, fromMail.str, folder, edtTitle.str)) return true;
587 close();
588 return true;
591 return super.onKeyBubble(event);
596 // ////////////////////////////////////////////////////////////////////////// //
597 public class TagOptionsWindow : SubWindow {
598 LabelWidget optPath; // real path
599 LineEditWidget optMonthes;
600 CheckboxWidget optThreaded;
601 CheckboxWidget optAttaches;
602 DynStr tagname;
604 void delegate (const(char)[] tagname) onUpdated;
606 this (const(char)[] atagname) {
607 tagname = atagname;
609 DynStr caption = "options for '";
610 caption ~= atagname;
611 caption ~= "'";
612 super(caption.getData.idup/*, GxSize(706, 64)*/);
614 optPath = new LabelWidget(rootWidget, "", LabelWidget.HAlign.Center);
615 optPath.flex = 1;
617 optMonthes = new LineEditWidget(rootWidget, "Monthes:");
618 optMonthes.flex = 1;
619 optMonthes.width = optMonthes.titwdt+64;
621 optThreaded = new CheckboxWidget(rootWidget, "&Threaded");
622 optThreaded.flex = 1;
624 optAttaches = new CheckboxWidget(rootWidget, "&Attaches");
625 optAttaches.flex = 1;
627 optMonthes.focus();
630 optPath = new LabelWidget(this, "", LabelWidget.HAlign.Center);
631 optPath.wy = 1;
632 optPath.ww = clientWidth;
634 optMonthes = new LineEditWidget(this, "Monthes:");
635 optMonthes.wy = optPath.wy+optPath.wh+2;
636 optMonthes.ww = clientWidth;
638 optThreaded = new CheckboxWidget(this, "&Threaded");
639 optThreaded.ww = clientWidth;
640 optThreaded.wy = optMonthes.wy+optMonthes.wh+2;
642 optAttaches = new CheckboxWidget(this, "&Attaches");
643 optAttaches.ww = clientWidth;
644 optAttaches.wy = optThreaded.wy+optThreaded.wh+2;
646 activeWidget = optMonthes;
649 immutable bool goodTag = (chiroGetTagUid(tagname.getData) != 0);
651 import std.conv : to;
652 int val;
653 DynStr path = chiroGetTagMonthLimitEx(tagname.getData, out val, defval:6);
654 //conwriteln("TAGNAME=<", tagname.getData, ">; val=", val, "; path=<", path.getData, ">");
655 optPath.text = path.getData;
656 optMonthes.str = val.to!string;
657 optPath.width = gxTextWidthUtf(optPath.text)+4;
660 if (goodTag) {
661 foreach (auto row; dbView.statement(`
662 SELECT threading AS trd, noattaches AS noatt FROM tagnames WHERE tag=:tag LIMIT 1
663 ;`).bindConstText(":tag", atagname).range)
665 optThreaded.checked = (row.trd!int == 1);
666 optAttaches.checked = (row.noatt!int == 0);
668 } else {
669 optThreaded.enabled = false;
670 optAttaches.enabled = false;
673 optMonthes.killTextOnChar = true;
675 minWinSize.w = optPath.width;
676 relayoutResize();
678 centerWindow();
681 override bool onKeyBubble (KeyEvent event) {
682 if (event.pressed) {
683 if (event == "Escape" || event == "C-Q") { close(); return true; }
684 if (event == "Enter") {
685 int mv = -666;
686 try {
687 auto vv = optMonthes.str.xstrip;
688 if (vv.length == 0) {
689 mv = -2;
690 } else {
691 import std.conv : to;
692 mv = vv.to!int;
693 if (mv < -1) mv = -666; else if (mv == 0) mv = -1;
695 } catch (Exception) {
696 mv = -666;
698 if (mv < -2) return true;
699 import core.stdc.stdio : snprintf;
700 char[1024] xname = void;
701 const(char)[] tn = tagname.getData;
702 auto xlen = snprintf(xname.ptr, xname.sizeof, "/mainpane/msgview/monthlimit%s%.*s",
703 (tn.length && tn[0] != '/' ? "/".ptr : "".ptr), cast(uint)tn.length, tn.ptr);
704 if (mv == -2) {
705 // delete
706 if (xname[0..xlen] != "/mainpane/msgview/monthlimit") {
707 chiroDeleteOption(xname[0..xlen]);
709 } else {
710 // set
711 chiroSetOption(xname[0..xlen], mv);
713 // fix threading and attaches
714 if (optThreaded.enabled) {
715 dbView.statement(`
716 UPDATE tagnames
717 SET threading=:trd, noattaches=:noatt
718 WHERE tag=:name
720 .bindConstText(":name", tagname.getData())
721 .bind(":trd", (optThreaded.checked ? 1 : 0))
722 .bind(":noatt", (optAttaches.checked ? 0 : 1))
723 .doAll();
725 if (onUpdated !is null) onUpdated(tagname.getData);
726 close();
727 return true;
730 return super.onKeyBubble(event);