egfx: added more drawing primitives (circles, ellipses, non-filled rounded rect)
[chiroptera.git] / dialogs.d
blob127d0c5c7d5875e21b4126f3162f8bd5b299409f
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 egfx;
25 import egui;
27 import iv.alice;
28 import iv.cmdcon;
29 import iv.strex;
30 import iv.utfutil;
31 import iv.vfs;
33 import chibackend /*: DynStr*/;
35 //import account;
36 //import addrbook;
37 //import hitwit;
40 // ////////////////////////////////////////////////////////////////////////// //
41 public final class HintWindow : SubWindow {
42 private DynStr mMessage;
44 this (const(char)[] amessage) {
45 mMessage = amessage;
47 int xwdt = gxTextWidthUtf(amessage)+6;
48 int xhgt = gxTextHeightUtf+4;
49 super(null, 0, 0, xwdt, xhgt);
50 winx = screenWidth-winw;
51 winy = 0;
52 mType = Type.OnTop;
53 add();
56 override @property int decorationSizeX () const nothrow @safe @nogc { return 3*2; }
57 override @property int decorationSizeY () const nothrow @safe @nogc { return 2*2; }
59 override @property int clientOffsetX () const nothrow @safe @nogc { return decorationSizeX/2; }
60 override @property int clientOffsetY () const nothrow @safe @nogc { return decorationSizeY/2; }
62 @property void message (DynStr v) {
63 if (mMessage != v) {
64 mMessage = v;
65 if (winx == screenWidth-winw) {
66 winw = gxTextWidthUtf(v)+6;
67 winx = screenWidth-winw;
68 } else {
69 winw = gxTextWidthUtf(v)+6;
70 if (winx+winw > screenWidth) winx = screenWidth-winw;
72 postScreenRebuild();
76 override void onPaint () {
77 if (closed) return;
78 if (!mMinimized) {
79 setupClip();
80 gxDrawWindow(null, gxRGB!(255, 255, 255), gxRGB!(0, 0, 0), gxRGB!(255, 255, 255), gxRGB!(0, 0, 80));
81 gxClipRect.shrinkBy(3, 2);
82 gxDrawTextUtf(x0+3, y0+2, mMessage, gxRGB!(155, 155, 155));
83 } else {
84 drawWindowMinimized();
88 override bool onKey (KeyEvent event) {
89 return false;
92 override bool onMouse (MouseEvent event) {
93 if (closed) return false;
95 int mx, my;
96 event.mouse2xy(mx, my);
98 // start drag?
99 if (!isSubWinDragging && event.type == MouseEventType.buttonPressed && event.button == MouseButton.left) {
100 if (mx >= x0 && my >= y0 && mx < x0+width && my < (!mMinimized ? y0+10 : height)) {
101 startMouseDrag(event);
102 return true;
106 return false;
111 // ////////////////////////////////////////////////////////////////////////// //
112 public final class MessageWindow : SubWindow {
113 private DynStr mMessage;
114 private uint lastBarWidth;
116 this (const(char)[] amessage) {
117 mMessage = amessage;
119 int xwdt = gxTextWidthUtf(amessage)+decorationSizeX;
120 int xhgt = gxTextHeightUtf+decorationSizeY;
121 super(null, 0, 0, xwdt, xhgt);
122 centerWindow();
123 mType = Type.OnTop;
124 add();
127 override @property int decorationSizeX () const nothrow @safe @nogc { return 24; }
128 override @property int decorationSizeY () const nothrow @safe @nogc { return 16; }
130 override @property int clientOffsetX () const nothrow @safe @nogc { return decorationSizeX/2; }
131 override @property int clientOffsetY () const nothrow @safe @nogc { return decorationSizeY/2; }
133 // returns `true` if need redraw
134 bool setProgress (const(char)[] v, uint curr, uint total) {
135 bool res = false;
136 // fix message
137 if (mMessage != v) {
138 mMessage = v;
139 int xwdt = gxTextWidthUtf(v)+decorationSizeX;
140 int xhgt = gxTextHeightUtf+decorationSizeY;
141 if (xwdt > width || xhgt > height) {
142 setSize(xwdt, xhgt);
143 centerWindow();
144 res = true;
147 // bix bar width
148 uint barWidth = 0;
149 if (total) barWidth = curr*cast(uint)(width-2)/total;
150 if (barWidth != lastBarWidth) res = true;
151 lastBarWidth = barWidth;
152 return res;
154 import core.stdc.stdio : snprintf;
155 char[128] buf = void;
156 if (total == 0) {
157 recalcHintWindow.message = msg;
158 } else {
159 auto len = snprintf(buf.ptr, buf.sizeof, "%.*s: %u", cast(uint)msg.length, msg.ptr, curr*100U/total);
160 recalcHintWindow.message = buf[0..len];
165 override void onPaint () {
166 if (closed) return;
167 if (!mMinimized) {
168 setupClip();
169 gxDrawWindow(null, gxRGB!(255, 255, 255), gxRGB!(0, 0, 0), gxRGB!(255, 255, 255), gxRGB!(0, 0, 80));
170 if (lastBarWidth > 0) {
171 auto rc = GxRect(x0+1, y0+1, lastBarWidth, height-2);
172 gxFillRect(rc, gxRGB!(90, 90, 180));
174 setupClientClip();
175 //gxClipRect.shrinkBy(3, 2);
176 immutable tw = gxTextWidthUtf(mMessage);
177 immutable th = gxTextHeightUtf();
178 gxDrawTextUtf(x0+(width-tw)/2, y0+(height-th)/2, mMessage, gxRGB!(225, 225, 0/*225*/));
179 } else {
180 drawWindowMinimized();
184 override bool onKey (KeyEvent event) {
185 return false;
188 override bool onMouse (MouseEvent event) {
189 if (closed) return false;
191 int mx, my;
192 event.mouse2xy(mx, my);
194 // start drag?
195 if (!isSubWinDragging && event.type == MouseEventType.buttonPressed && event.button == MouseButton.left) {
196 if (mx >= x0 && my >= y0 && mx < x0+width && my < (!mMinimized ? y0+10 : height)) {
197 startMouseDrag(event);
198 return true;
202 return false;
207 // ////////////////////////////////////////////////////////////////////////// //
209 public class SelectPopBoxWindow : SubWindow {
210 SimpleListBoxWidget lb;
212 void delegate (Account acc) onSelected;
214 this (Account defacc) {
215 if (accounts.length > 0) {
216 int xhgt = 0;
217 int xwdt = 0;
219 lb = new SimpleListBoxWidget(this);
220 bool accFound = false;
221 int defAccIdx = -1;
222 foreach (immutable aidx, Account acc; accounts) {
223 if (cast(Pop3Account)acc) {
224 import std.format : format;
225 string s = "%s <%s>".format(acc.realname, acc.mail);
226 lb.appendItem(s, acc);
227 int w = gxTextWidthUtf(s)+2;
228 if (xwdt < w) xwdt = w;
229 if (acc is defacc) {
230 lb.curidx = lb.length-1;
231 accFound = true;
233 if (acc is defaultAcc) defAccIdx = lb.length-1;
234 xhgt += gxTextHeightUtf;
238 if (xhgt == 0) { super(); return; }
239 if (xhgt > screenHeight) xhgt = screenHeight-decorationSizeY;
241 if (!accFound && defAccIdx >= 0) lb.curidx = defAccIdx;
242 if (xwdt > screenWidth-decorationSizeX) xwdt = screenWidth-decorationSizeX;
244 super("Select Account", xwdt+decorationSizeX, xhgt+decorationSizeY);
245 lb.ww = clientWidth;
246 lb.wh = clientHeight;
248 lb.onAction = delegate (self) {
249 if (auto acc = cast(Account)lb.itemData(lb.curidx)) {
250 self.parent.close();
251 if (onSelected !is null) onSelected(acc); else vbwin.beep();
252 } else {
253 vbwin.beep();
257 addModal();
258 } else {
259 super();
263 override bool onKey (KeyEvent event) {
264 if (event.pressed) {
265 if (event == "Escape" || event == "C-Q") { close(); return true; }
266 if (event == "Enter") { lb.onAction(lb); return true; }
268 return super.onKey(event);
274 // ////////////////////////////////////////////////////////////////////////// //
276 public class SelectAddressBookWindow : SubWindow {
277 SimpleListBoxWidget lb;
279 void delegate (AddressBookEntry acc) onSelected;
281 this (const(char)[] prefix) {
282 if (abook.length > 0) {
283 int xhgt = 0;
284 int xwdt = 0;
285 auto defae = abookFindByNickFirst(prefix);
286 if (defae is null && prefix.length) {
287 auto mstx = prefix.lastIndexOf('<');
288 if (mstx >= 0) {
289 prefix = prefix[mstx+1..$];
290 if (prefix.length && prefix[$-1] == '>') prefix = prefix[0..$-1];
292 defae = abookFindByMailFirst(prefix);
295 lb = new SimpleListBoxWidget(this);
296 foreach (immutable aidx, AddressBookEntry ae; abook) {
297 import std.format : format;
298 string s;
299 if (ae.realname.length) s = ae.realname~" <"~ae.mail~">"; else s = ae.mail;
300 lb.appendItem(s, ae);
301 int w = gxTextWidthUtf(s)+2;
302 if (xwdt < w) xwdt = w;
303 if (ae is defae) lb.curidx = lb.length-1;
304 xhgt += gxTextHeightUtf;
307 if (xhgt == 0) { super(); return; }
308 if (xhgt > screenHeight) xhgt = screenHeight-decorationSizeY;
310 if (xwdt > screenWidth-decorationSizeX) xwdt = screenWidth-decorationSizeX;
312 super("Select Recepient", xwdt+decorationSizeX, xhgt+decorationSizeY);
313 lb.ww = clientWidth;
314 lb.wh = clientHeight;
316 lb.onAction = delegate (self) {
317 if (auto ae = cast(AddressBookEntry)lb.itemData(lb.curidx)) {
318 close();
319 if (onSelected !is null) onSelected(ae); else vbwin.beep();
320 } else {
321 vbwin.beep();
325 addModal();
326 } else {
327 super();
331 override bool onKey (KeyEvent event) {
332 if (event.pressed) {
333 if (event == "Escape" || event == "C-Q") { close(); return true; }
334 if (event == "Enter") { lb.onAction(lb); return true; }
336 return super.onKey(event);
342 // ////////////////////////////////////////////////////////////////////////// //
344 public class PostWindow : SubWindow {
345 LineEditWidget from;
346 LineEditWidget to;
347 LineEditWidget subj;
348 EditorWidget ed;
349 string replyto;
350 string references; // of replyto article
351 Account acc;
352 Folder fld;
354 this () {
355 import std.algorithm : max;
357 int wanthgt = screenHeight-42*2;
358 if (wanthgt < 80) wanthgt = 80;
359 int wantwdt = screenWidth-64*2;
360 if (wantwdt < 506) wantwdt = 506;
361 super("Compose Mail", /*506*/wantwdt, /*253*/wanthgt);
362 //if (hasWindowClass(this)) return;
364 from = new LineEditWidget(this, "From:");
365 to = new LineEditWidget(this, "To:");
366 subj = new LineEditWidget(this, "Subj:");
367 ed = new EditorWidget(this);
369 setupClientClip();
371 int tw = max(from.titwdt, to.titwdt, subj.titwdt);
372 from.titwdt = tw;
373 to.titwdt = tw;
374 subj.titwdt = tw;
376 from.wy = 0;
377 to.wy = from.wy+from.wh+2;
378 subj.wy = to.wy+to.wh+2;
380 ed.wx = 0;
381 ed.wy = subj.wy+subj.wh+2;
382 ed.ww = gxClipRect.width;
383 ed.wh = gxClipRect.height-ed.wy;
385 add();
388 override bool onKey (KeyEvent event) {
389 if (event.pressed) {
390 if (event == "Escape" && !ed.editor.textChanged) { close(); return true; }
391 if (event == "C-G" || event == "C-C") {
392 if (ed.editor.textChanged) {
393 auto qww = new YesNoWindow("Close?", "Do you really want to close the editor?", true);
394 qww.onYes = () { close(); };
395 qww.addModal();
396 } else {
397 close();
399 return true;
401 if (event == "M-Tab" && activeWidget is to) {
402 auto ae = abookFindByNick(to.str);
403 //if (ae is null) ae = abookFindByMail(to.str);
404 if (ae !is null) {
405 if (ae.realname.length) to.str = ae.realname~" <"~ae.mail~">"; else to.str = ae.mail;
406 } else {
407 vbwin.beep();
409 return true;
411 if (event == "C-Space" && activeWidget is to) {
412 auto wae = new SelectAddressBookWindow(to.str);
413 wae.onSelected = delegate (AddressBookEntry ae) {
414 if (ae.realname.length) to.str = ae.realname~" <"~ae.mail~">"; else to.str = ae.mail;
416 return true;
418 if ((event == "C-Space") && activeWidget is from) {
419 if (cast(Pop3Account)acc) {
420 auto wacc = new SelectPopBoxWindow(acc);
421 wacc.onSelected = delegate (Account newacc) {
422 acc = newacc;
423 from.str = acc.realname~" <"~acc.mail~">";
426 return true;
428 if (event == "C-Enter") {
429 static bool checkString (string s) nothrow @trusted @nogc {
430 if (s.length == 0) return false;
431 if (s.utflen > 255) return false;
432 return true;
434 if (!checkString(subj.str)) { vbwin.beep(); return true; }
435 if (!checkString(to.str)) { vbwin.beep(); return true; }
436 if (ed.editor[].length == 0) { vbwin.beep(); return true; }
437 auto senddg = delegate () {
438 // build reply article and add it to send queue
439 // check attaches
440 ed.editor.clearAndDisableUndo(); // so removing attaches will not add 'em to undo, lol
441 string[] attnames = ed.extractAttaches();
442 if (attnames) conwriteln("ATTACHES: ", attnames);
443 // build article text
444 auto newart = new Article();
445 newart.markAsStandalone();
446 newart.attachedFileNames = attnames;
447 newart.startText();
448 //foreach (immutable idx; 0..lcount) newart.appendTextLine(ed.editor[idx]);
450 auto mtr = ed.editor[];
451 if (mtr.length) newart.appendTextRange(mtr); else newart.appendTextLine("no text");
453 // clear editor, so it free used memory
454 ed.editor.clearAndDisableUndo();
455 ed.editor.clear();
456 // fix headers
457 newart.subj = subj.str;
458 //newart.inreplyto = replyto;
459 if (replyto.length) newart.replaceHeader("In-Reply-To", replyto);
460 if (references.length) newart.replaceHeader("References", references);
461 newart.replaceHeader("To", encodeq(to.str));
462 if (!acc.addToSendQueue(fld, newart)) { vbwin.beep(); return; }
463 close();
465 auto qww = new YesNoWindow("Send?", "Do you really want to send the message?", true);
466 qww.onYes = senddg;
467 qww.addModal();
468 return true;
471 return super.onKey(event);
477 // ////////////////////////////////////////////////////////////////////////// //
478 public class TitlerWindow : SubWindow {
479 LineEditWidget title;
480 LineEditWidget fromName;
481 LineEditWidget fromMail;
482 //LineEditWidget fromTag;
483 //DynStr name;
484 //DynStr mail;
485 DynStr folder;
487 bool delegate (const(char)[] name, const(char)[] mail, const(char)[] folder, const(char)[] title) onSelected;
489 this (const(char)[] aname, const(char)[] amail, const(char)[] afolder, const(char)[] atitle) {
490 import std.algorithm : max;
491 int xhgt = gxTextHeightUtf+2;
493 DynStr caption = "Title for ";
494 caption ~= aname;
495 caption ~= " <";
496 caption ~= amail;
497 caption ~= ">";
498 super(caption.getData.idup, 506, xhgt+decorationSizeY);
500 fromName = new LineEditWidget(this, "Name:");
501 //fromName.wh = clientWidth;
503 fromMail = new LineEditWidget(this, "Mail:");
504 //fromMail.wh = clientWidth;
506 title = new LineEditWidget(this, "Title:");
508 setClientSize(-1, fromName.wh+2+fromMail.wh+2+title.wh+2);
509 centerWindow();
511 setupClientClip();
513 int tw = max(fromName.titwdt, fromMail.titwdt, title.titwdt);
514 fromName.titwdt = tw;
515 fromMail.titwdt = tw;
516 title.titwdt = tw;
518 fromName.wy = 0;
519 fromMail.wy = fromName.wy+fromName.wh+2;
520 title.wy = fromMail.wy+fromMail.wh+2;
522 activeWidget = title;
524 folder = afolder;
525 fromName.str = aname;
526 fromMail.str = amail;
527 title.str = atitle;
528 add();
531 override bool onKey (KeyEvent event) {
532 if (event.pressed) {
533 if (event == "Escape" || event == "C-Q") { close(); return true; }
534 if (event == "Enter") {
535 if (onSelected !is null) {
536 if (!onSelected(fromName.str, fromMail.str, folder, title.str)) return true;
538 close();
539 return true;
542 return super.onKey(event);
547 // ////////////////////////////////////////////////////////////////////////// //
548 public class TagOptionsWindow : SubWindow {
549 LabelWidget optPath; // real path
550 LineEditWidget optMonthes;
551 CheckboxWidget optThreaded;
552 CheckboxWidget optAttaches;
553 DynStr tagname;
555 void delegate (const(char)[] tagname) onUpdated;
557 this (const(char)[] atagname) {
558 tagname = atagname;
560 DynStr caption = "options for '";
561 caption ~= atagname;
562 caption ~= "'";
563 super(caption.getData.idup, 706, 64);
565 optPath = new LabelWidget(this, "", LabelWidget.HAlign.Center);
566 optPath.wy = 1;
567 optPath.ww = clientWidth;
569 optMonthes = new LineEditWidget(this, "Monthes:");
570 optMonthes.wy = optPath.wy+optPath.wh+2;
571 optMonthes.ww = clientWidth;
573 optThreaded = new CheckboxWidget(this, "&Threaded");
574 optThreaded.ww = clientWidth;
575 optThreaded.wy = optMonthes.wy+optMonthes.wh+2;
577 optAttaches = new CheckboxWidget(this, "&Attaches");
578 optAttaches.ww = clientWidth;
579 optAttaches.wy = optThreaded.wy+optThreaded.wh+2;
581 activeWidget = optMonthes;
583 bool goodTag = (chiroGetTagUid(tagname.getData) != 0);
584 version(none) {
586 import core.stdc.stdio : snprintf;
587 char[1024] xname = void;
588 bool exists;
589 const(char)[] tn = tagname.getData;
590 bool found = false;
591 for (;;) {
592 auto len = snprintf(xname.ptr, xname.sizeof, "/mainpane/msgview/monthlimit%.*s", cast(uint)tn.length, tn.ptr);
593 int v = chiroGetOptionEx!int(xname[0..len], out exists);
594 if (exists) {
595 import std.conv : to;
596 found = true;
597 optPath.text = xname[0..len].idup;
598 optMonthes.str = v.to!string;
599 //conwriteln("tn=[", tn, "]");
600 //goodTag = (tn.length && chiroGetTagUid(tn) != 0);
601 break;
603 auto slp = tn.lastIndexOf('/', 1);
604 if (slp <= 0) break;
605 tn = tn[0..slp];
607 if (!found) {
608 import std.conv : to;
609 int v = chiroGetOption!int("/mainpane/msgview/monthlimit", 6);
610 optPath.text = "/mainpane/msgview/monthlimit";
611 optMonthes.str = v.to!string;
612 //goodTag = false;
615 } else {
616 import std.conv : to;
617 int val;
618 DynStr path = chiroGetTagMonthLimitEx(tagname.getData, out val, defval:6);
619 //conwriteln("TAGNAME=<", tagname.getData, ">; val=", val, "; path=<", path.getData, ">");
620 optPath.text = path.getData;
621 optMonthes.str = val.to!string;
624 if (goodTag) {
625 foreach (auto row; dbView.statement(`
626 SELECT threading AS trd, noattaches AS noatt FROM tagnames WHERE tag=:tag LIMIT 1
627 ;`).bindConstText(":tag", atagname).range)
629 optThreaded.checked = (row.trd!int == 1);
630 optAttaches.checked = (row.noatt!int == 0);
632 } else {
633 optThreaded.enabled = false;
634 optAttaches.enabled = false;
637 optMonthes.killTextOnChar = true;
639 setClientSize(-1, widgets[$-1].wy+widgets[$-1].wh+1);
640 centerWindow();
642 setupClientClip();
644 add();
647 override bool onKey (KeyEvent event) {
648 if (event.pressed) {
649 if (event == "Escape" || event == "C-Q") { close(); return true; }
650 if (event == "Enter") {
651 int mv = -666;
652 try {
653 auto vv = optMonthes.str.xstrip;
654 if (vv.length == 0) {
655 mv = -2;
656 } else {
657 import std.conv : to;
658 mv = vv.to!int;
659 if (mv < -1) mv = -666; else if (mv == 0) mv = -1;
661 } catch (Exception) {
662 mv = -666;
664 if (mv < -2) return true;
665 import core.stdc.stdio : snprintf;
666 char[1024] xname = void;
667 const(char)[] tn = tagname.getData;
668 auto xlen = snprintf(xname.ptr, xname.sizeof, "/mainpane/msgview/monthlimit%s%.*s",
669 (tn.length && tn[0] != '/' ? "/".ptr : "".ptr), cast(uint)tn.length, tn.ptr);
670 if (mv == -2) {
671 // delete
672 if (xname[0..xlen] != "/mainpane/msgview/monthlimit") {
673 chiroDeleteOption(xname[0..xlen]);
675 } else {
676 // set
677 chiroSetOption(xname[0..xlen], mv);
679 // fix threading and attaches
680 if (optThreaded.enabled) {
681 dbView.statement(`
682 UPDATE tagnames
683 SET threading=:trd, noattaches=:noatt
684 WHERE tag=:name
686 .bindConstText(":name", tagname.getData())
687 .bind(":trd", (optThreaded.checked ? 1 : 0))
688 .bind(":noatt", (optAttaches.checked ? 0 : 1))
689 .doAll();
691 if (onUpdated !is null) onUpdated(tagname.getData);
692 close();
693 return true;
696 return super.onKey(event);