egui: style lookup optimisations
[chiroptera.git] / dialogs.d
blob6ac1443249ba124da0dbf323bd7b28e16d65b7f5
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, GxSize(xwdt, xhgt));
50 x0 = screenWidth-width;
51 y0 = 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 (x0 == screenWidth-width) {
66 width = gxTextWidthUtf(v)+6;
67 x0 = screenWidth-width;
68 } else {
69 width = gxTextWidthUtf(v)+6;
70 if (x0+width > screenWidth) x0 = screenWidth-width;
72 postScreenRebuild();
76 override void onPaint () {
77 if (closed) return;
78 gxWithSavedClip {
79 if (!mMinimized) {
80 setupClip();
81 //gxDrawWindow(winrect, null, gxRGB!(255, 255, 255), gxRGB!(0, 0, 0), gxRGB!(255, 255, 255), gxRGB!(0, 0, 80));
82 gxDrawWindow(winrect, null,
83 getColor("frame"),
84 getColor("title-text"),
85 getColor("title-back"),
86 getColor("back"));
87 //getColor(gxRGB!(255, 255, 255), gxRGB!(0, 0, 0), gxRGB!(255, 255, 255), gxRGB!(0, 0, 80));
88 gxClipRect.shrinkBy(3, 2);
89 gxDrawTextUtf(x0+3, y0+2, mMessage, getColor("text")/*gxRGB!(155, 155, 155)*/);
90 } else {
91 drawWindowMinimized();
96 // prevent any default keyboard handling
97 override bool onKeySink (KeyEvent event) {
98 return true;
103 // ////////////////////////////////////////////////////////////////////////// //
104 public final class MessageWindow : SubWindow {
105 private DynStr mMessage;
106 private uint lastBarWidth;
108 this (const(char)[] amessage) {
109 mMessage = amessage;
111 int xwdt = gxTextWidthUtf(amessage)+decorationSizeX;
112 int xhgt = gxTextHeightUtf+decorationSizeY;
113 super(null, GxSize(xwdt, xhgt));
114 centerWindow();
115 mType = Type.OnTop;
116 add();
119 override @property int decorationSizeX () const nothrow @safe @nogc { return 24; }
120 override @property int decorationSizeY () const nothrow @safe @nogc { return 16; }
122 override @property int clientOffsetX () const nothrow @safe @nogc { return decorationSizeX/2; }
123 override @property int clientOffsetY () const nothrow @safe @nogc { return decorationSizeY/2; }
125 // returns `true` if need redraw
126 bool setProgress (const(char)[] v, uint curr, uint total) {
127 bool res = false;
128 // fix message
129 if (mMessage != v) {
130 mMessage = v;
131 int xwdt = gxTextWidthUtf(v)+decorationSizeX;
132 int xhgt = gxTextHeightUtf+decorationSizeY;
133 if (xwdt > width || xhgt > height) {
134 setSize(xwdt, xhgt);
135 centerWindow();
136 res = true;
139 // bix bar width
140 uint barWidth = 0;
141 if (total) barWidth = curr*cast(uint)(width-2)/total;
142 if (barWidth != lastBarWidth) res = true;
143 lastBarWidth = barWidth;
144 return res;
146 import core.stdc.stdio : snprintf;
147 char[128] buf = void;
148 if (total == 0) {
149 recalcHintWindow.message = msg;
150 } else {
151 auto len = snprintf(buf.ptr, buf.sizeof, "%.*s: %u", cast(uint)msg.length, msg.ptr, curr*100U/total);
152 recalcHintWindow.message = buf[0..len];
157 override void onPaint () {
158 if (closed) return;
159 gxWithSavedClip {
160 if (!mMinimized) {
161 setupClip();
162 //gxDrawWindow(winrect, null, gxRGB!(255, 255, 255), gxRGB!(0, 0, 0), gxRGB!(255, 255, 255), gxRGB!(0, 0, 80));
163 gxDrawWindow(winrect, null,
164 getColor("frame"),
165 getColor("title-text"),
166 getColor("title-back"),
167 getColor("back"));
168 if (lastBarWidth > 0) {
169 auto rc = GxRect(x0+1, y0+1, lastBarWidth, height-2);
170 gxFillRect(rc, getColor("bar-back")/*gxRGB!(90, 90, 180)*/);
172 setupClientClip();
173 //gxClipRect.shrinkBy(3, 2);
174 immutable tw = gxTextWidthUtf(mMessage);
175 immutable th = gxTextHeightUtf();
176 gxDrawTextUtf(x0+(width-tw)/2, y0+(height-th)/2, mMessage, getColor("text"));
177 } else {
178 drawWindowMinimized();
183 // prevent any default keyboard handling
184 override bool onKeySink (KeyEvent event) {
185 return true;
190 // ////////////////////////////////////////////////////////////////////////// //
192 public class SelectPopBoxWindow : SubWindow {
193 SimpleListBoxWidget lb;
195 void delegate (Account acc) onSelected;
197 this (Account defacc) {
198 if (accounts.length > 0) {
199 int xhgt = 0;
200 int xwdt = 0;
202 lb = new SimpleListBoxWidget(this);
203 bool accFound = false;
204 int defAccIdx = -1;
205 foreach (immutable aidx, Account acc; accounts) {
206 if (cast(Pop3Account)acc) {
207 import std.format : format;
208 string s = "%s <%s>".format(acc.realname, acc.mail);
209 lb.appendItem(s, acc);
210 int w = gxTextWidthUtf(s)+2;
211 if (xwdt < w) xwdt = w;
212 if (acc is defacc) {
213 lb.curidx = lb.length-1;
214 accFound = true;
216 if (acc is defaultAcc) defAccIdx = lb.length-1;
217 xhgt += gxTextHeightUtf;
221 if (xhgt == 0) { super(); return; }
222 if (xhgt > screenHeight) xhgt = screenHeight-decorationSizeY;
224 if (!accFound && defAccIdx >= 0) lb.curidx = defAccIdx;
225 if (xwdt > screenWidth-decorationSizeX) xwdt = screenWidth-decorationSizeX;
227 super("Select Account", xwdt+decorationSizeX, xhgt+decorationSizeY);
228 lb.ww = clientWidth;
229 lb.wh = clientHeight;
231 lb.onAction = delegate (self) {
232 if (auto acc = cast(Account)lb.itemData(lb.curidx)) {
233 self.parent.close();
234 if (onSelected !is null) onSelected(acc); else vbwin.beep();
235 } else {
236 vbwin.beep();
240 addModal();
241 } else {
242 super();
246 override bool onKey (KeyEvent event) {
247 if (event.pressed) {
248 if (event == "Escape" || event == "C-Q") { close(); return true; }
249 if (event == "Enter") { lb.onAction(lb); return true; }
251 return super.onKey(event);
257 // ////////////////////////////////////////////////////////////////////////// //
259 public class SelectAddressBookWindow : SubWindow {
260 SimpleListBoxWidget lb;
262 void delegate (AddressBookEntry acc) onSelected;
264 this (const(char)[] prefix) {
265 if (abook.length > 0) {
266 int xhgt = 0;
267 int xwdt = 0;
268 auto defae = abookFindByNickFirst(prefix);
269 if (defae is null && prefix.length) {
270 auto mstx = prefix.lastIndexOf('<');
271 if (mstx >= 0) {
272 prefix = prefix[mstx+1..$];
273 if (prefix.length && prefix[$-1] == '>') prefix = prefix[0..$-1];
275 defae = abookFindByMailFirst(prefix);
278 lb = new SimpleListBoxWidget(this);
279 foreach (immutable aidx, AddressBookEntry ae; abook) {
280 import std.format : format;
281 string s;
282 if (ae.realname.length) s = ae.realname~" <"~ae.mail~">"; else s = ae.mail;
283 lb.appendItem(s, ae);
284 int w = gxTextWidthUtf(s)+2;
285 if (xwdt < w) xwdt = w;
286 if (ae is defae) lb.curidx = lb.length-1;
287 xhgt += gxTextHeightUtf;
290 if (xhgt == 0) { super(); return; }
291 if (xhgt > screenHeight) xhgt = screenHeight-decorationSizeY;
293 if (xwdt > screenWidth-decorationSizeX) xwdt = screenWidth-decorationSizeX;
295 super("Select Recepient", xwdt+decorationSizeX, xhgt+decorationSizeY);
296 lb.ww = clientWidth;
297 lb.wh = clientHeight;
299 lb.onAction = delegate (self) {
300 if (auto ae = cast(AddressBookEntry)lb.itemData(lb.curidx)) {
301 close();
302 if (onSelected !is null) onSelected(ae); else vbwin.beep();
303 } else {
304 vbwin.beep();
308 addModal();
309 } else {
310 super();
314 override bool onKey (KeyEvent event) {
315 if (event.pressed) {
316 if (event == "Escape" || event == "C-Q") { close(); return true; }
317 if (event == "Enter") { lb.onAction(lb); return true; }
319 return super.onKey(event);
325 // ////////////////////////////////////////////////////////////////////////// //
327 public class PostWindow : SubWindow {
328 LineEditWidget from;
329 LineEditWidget to;
330 LineEditWidget subj;
331 EditorWidget ed;
332 string replyto;
333 string references; // of replyto article
334 Account acc;
335 Folder fld;
337 this () {
338 import std.algorithm : max;
340 int wanthgt = screenHeight-42*2;
341 if (wanthgt < 80) wanthgt = 80;
342 int wantwdt = screenWidth-64*2;
343 if (wantwdt < 506) wantwdt = 506;
344 super("Compose Mail", /*506*/wantwdt, /*253*/wanthgt);
345 //if (hasWindowClass(this)) return;
347 from = new LineEditWidget(this, "From:");
348 to = new LineEditWidget(this, "To:");
349 subj = new LineEditWidget(this, "Subj:");
350 ed = new EditorWidget(this);
352 setupClientClip();
354 int tw = max(from.titwdt, to.titwdt, subj.titwdt);
355 from.titwdt = tw;
356 to.titwdt = tw;
357 subj.titwdt = tw;
359 from.wy = 0;
360 to.wy = from.wy+from.wh+2;
361 subj.wy = to.wy+to.wh+2;
363 ed.wx = 0;
364 ed.wy = subj.wy+subj.wh+2;
365 ed.ww = gxClipRect.width;
366 ed.wh = gxClipRect.height-ed.wy;
368 add();
371 override bool onKey (KeyEvent event) {
372 if (event.pressed) {
373 if (event == "Escape" && !ed.editor.textChanged) { close(); return true; }
374 if (event == "C-G" || event == "C-C") {
375 if (ed.editor.textChanged) {
376 auto qww = new YesNoWindow("Close?", "Do you really want to close the editor?", true);
377 qww.onYes = () { close(); };
378 qww.addModal();
379 } else {
380 close();
382 return true;
384 if (event == "M-Tab" && activeWidget is to) {
385 auto ae = abookFindByNick(to.str);
386 //if (ae is null) ae = abookFindByMail(to.str);
387 if (ae !is null) {
388 if (ae.realname.length) to.str = ae.realname~" <"~ae.mail~">"; else to.str = ae.mail;
389 } else {
390 vbwin.beep();
392 return true;
394 if (event == "C-Space" && activeWidget is to) {
395 auto wae = new SelectAddressBookWindow(to.str);
396 wae.onSelected = delegate (AddressBookEntry ae) {
397 if (ae.realname.length) to.str = ae.realname~" <"~ae.mail~">"; else to.str = ae.mail;
399 return true;
401 if ((event == "C-Space") && activeWidget is from) {
402 if (cast(Pop3Account)acc) {
403 auto wacc = new SelectPopBoxWindow(acc);
404 wacc.onSelected = delegate (Account newacc) {
405 acc = newacc;
406 from.str = acc.realname~" <"~acc.mail~">";
409 return true;
411 if (event == "C-Enter") {
412 static bool checkString (string s) nothrow @trusted @nogc {
413 if (s.length == 0) return false;
414 if (s.utflen > 255) return false;
415 return true;
417 if (!checkString(subj.str)) { vbwin.beep(); return true; }
418 if (!checkString(to.str)) { vbwin.beep(); return true; }
419 if (ed.editor[].length == 0) { vbwin.beep(); return true; }
420 auto senddg = delegate () {
421 // build reply article and add it to send queue
422 // check attaches
423 ed.editor.clearAndDisableUndo(); // so removing attaches will not add 'em to undo, lol
424 string[] attnames = ed.extractAttaches();
425 if (attnames) conwriteln("ATTACHES: ", attnames);
426 // build article text
427 auto newart = new Article();
428 newart.markAsStandalone();
429 newart.attachedFileNames = attnames;
430 newart.startText();
431 //foreach (immutable idx; 0..lcount) newart.appendTextLine(ed.editor[idx]);
433 auto mtr = ed.editor[];
434 if (mtr.length) newart.appendTextRange(mtr); else newart.appendTextLine("no text");
436 // clear editor, so it free used memory
437 ed.editor.clearAndDisableUndo();
438 ed.editor.clear();
439 // fix headers
440 newart.subj = subj.str;
441 //newart.inreplyto = replyto;
442 if (replyto.length) newart.replaceHeader("In-Reply-To", replyto);
443 if (references.length) newart.replaceHeader("References", references);
444 newart.replaceHeader("To", encodeq(to.str));
445 if (!acc.addToSendQueue(fld, newart)) { vbwin.beep(); return; }
446 close();
448 auto qww = new YesNoWindow("Send?", "Do you really want to send the message?", true);
449 qww.onYes = senddg;
450 qww.addModal();
451 return true;
454 return super.onKey(event);
460 // ////////////////////////////////////////////////////////////////////////// //
461 public class TitlerWindow : SubWindow {
462 LineEditWidget edtTitle;
463 LineEditWidget fromName;
464 LineEditWidget fromMail;
465 //LineEditWidget fromTag;
466 //DynStr name;
467 //DynStr mail;
468 DynStr folder;
470 bool delegate (const(char)[] name, const(char)[] mail, const(char)[] folder, const(char)[] title) onSelected;
472 this (const(char)[] aname, const(char)[] amail, const(char)[] afolder, const(char)[] atitle) {
473 import std.algorithm : max;
474 int xhgt = gxTextHeightUtf+2;
476 DynStr caption = "Title for ";
477 caption ~= aname;
478 caption ~= " <";
479 caption ~= amail;
480 caption ~= ">";
481 super(caption.getData.idup/*, GxSize(506, xhgt+decorationSizeY)*/);
483 new SpacerWidget(rootWidget, 2);
484 fromName = new LineEditWidget(rootWidget, "Name:");
485 fromName.flex = 1;
487 new SpacerWidget(rootWidget, 2);
488 fromMail = new LineEditWidget(rootWidget, "Mail:");
489 fromMail.flex = 1;
491 new SpacerWidget(rootWidget, 2);
492 edtTitle = new LineEditWidget(rootWidget, "Title:");
493 edtTitle.flex = 1;
494 new SpacerWidget(rootWidget, 2);
496 fromName.str = aname;
497 fromMail.str = amail;
498 edtTitle.str = atitle;
499 folder = afolder;
501 int twdt = fromName.titwdt;
502 if (twdt < fromMail.titwdt) twdt = fromMail.titwdt;
503 if (twdt < edtTitle.titwdt) twdt = edtTitle.titwdt;
504 fromName.titwdt = twdt;
505 fromMail.titwdt = twdt;
506 edtTitle.titwdt = twdt;
508 minWinSize.w = 320;
510 relayoutResize();
511 centerWindow();
513 edtTitle.focus();
516 override bool onKeyBubble (KeyEvent event) {
517 if (event.pressed) {
518 if (event == "Escape" || event == "C-Q") { close(); return true; }
519 if (event == "Enter") {
520 if (onSelected !is null) {
521 if (!onSelected(fromName.str, fromMail.str, folder, edtTitle.str)) return true;
523 close();
524 return true;
527 return super.onKeyBubble(event);
532 // ////////////////////////////////////////////////////////////////////////// //
533 public class TagOptionsWindow : SubWindow {
534 LabelWidget optPath; // real path
535 LineEditWidget optMonthes;
536 CheckboxWidget optThreaded;
537 CheckboxWidget optAttaches;
538 DynStr tagname;
540 void delegate (const(char)[] tagname) onUpdated;
542 this (const(char)[] atagname) {
543 tagname = atagname;
545 DynStr caption = "options for '";
546 caption ~= atagname;
547 caption ~= "'";
548 super(caption.getData.idup/*, GxSize(706, 64)*/);
550 optPath = new LabelWidget(rootWidget, "", LabelWidget.HAlign.Center);
551 optPath.flex = 1;
553 optMonthes = new LineEditWidget(rootWidget, "Monthes:");
554 optMonthes.flex = 1;
555 optMonthes.width = optMonthes.titwdt+64;
557 optThreaded = new CheckboxWidget(rootWidget, "&Threaded");
558 optThreaded.flex = 1;
560 optAttaches = new CheckboxWidget(rootWidget, "&Attaches");
561 optAttaches.flex = 1;
563 optMonthes.focus();
566 optPath = new LabelWidget(this, "", LabelWidget.HAlign.Center);
567 optPath.wy = 1;
568 optPath.ww = clientWidth;
570 optMonthes = new LineEditWidget(this, "Monthes:");
571 optMonthes.wy = optPath.wy+optPath.wh+2;
572 optMonthes.ww = clientWidth;
574 optThreaded = new CheckboxWidget(this, "&Threaded");
575 optThreaded.ww = clientWidth;
576 optThreaded.wy = optMonthes.wy+optMonthes.wh+2;
578 optAttaches = new CheckboxWidget(this, "&Attaches");
579 optAttaches.ww = clientWidth;
580 optAttaches.wy = optThreaded.wy+optThreaded.wh+2;
582 activeWidget = optMonthes;
585 immutable bool goodTag = (chiroGetTagUid(tagname.getData) != 0);
587 import std.conv : to;
588 int val;
589 DynStr path = chiroGetTagMonthLimitEx(tagname.getData, out val, defval:6);
590 //conwriteln("TAGNAME=<", tagname.getData, ">; val=", val, "; path=<", path.getData, ">");
591 optPath.text = path.getData;
592 optMonthes.str = val.to!string;
593 optPath.width = gxTextWidthUtf(optPath.text)+4;
596 if (goodTag) {
597 foreach (auto row; dbView.statement(`
598 SELECT threading AS trd, noattaches AS noatt FROM tagnames WHERE tag=:tag LIMIT 1
599 ;`).bindConstText(":tag", atagname).range)
601 optThreaded.checked = (row.trd!int == 1);
602 optAttaches.checked = (row.noatt!int == 0);
604 } else {
605 optThreaded.enabled = false;
606 optAttaches.enabled = false;
609 optMonthes.killTextOnChar = true;
611 minWinSize.w = optPath.width;
612 relayoutResize();
614 centerWindow();
617 override bool onKeyBubble (KeyEvent event) {
618 if (event.pressed) {
619 if (event == "Escape" || event == "C-Q") { close(); return true; }
620 if (event == "Enter") {
621 int mv = -666;
622 try {
623 auto vv = optMonthes.str.xstrip;
624 if (vv.length == 0) {
625 mv = -2;
626 } else {
627 import std.conv : to;
628 mv = vv.to!int;
629 if (mv < -1) mv = -666; else if (mv == 0) mv = -1;
631 } catch (Exception) {
632 mv = -666;
634 if (mv < -2) return true;
635 import core.stdc.stdio : snprintf;
636 char[1024] xname = void;
637 const(char)[] tn = tagname.getData;
638 auto xlen = snprintf(xname.ptr, xname.sizeof, "/mainpane/msgview/monthlimit%s%.*s",
639 (tn.length && tn[0] != '/' ? "/".ptr : "".ptr), cast(uint)tn.length, tn.ptr);
640 if (mv == -2) {
641 // delete
642 if (xname[0..xlen] != "/mainpane/msgview/monthlimit") {
643 chiroDeleteOption(xname[0..xlen]);
645 } else {
646 // set
647 chiroSetOption(xname[0..xlen], mv);
649 // fix threading and attaches
650 if (optThreaded.enabled) {
651 dbView.statement(`
652 UPDATE tagnames
653 SET threading=:trd, noattaches=:noatt
654 WHERE tag=:name
656 .bindConstText(":name", tagname.getData())
657 .bind(":trd", (optThreaded.checked ? 1 : 0))
658 .bind(":noatt", (optAttaches.checked ? 0 : 1))
659 .doAll();
661 if (onUpdated !is null) onUpdated(tagname.getData);
662 close();
663 return true;
666 return super.onKeyBubble(event);