use `gxDashRect()` to shade twitted messages instead of doing it pixel by pixel
[chiroptera.git] / dialogs.d
blob35ca711ec9d377ee8efb42c2950d26c2335616fe
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*/;
35 // ////////////////////////////////////////////////////////////////////////// //
36 public final class HintWindow : SubWindow {
37 private DynStr mMessage;
39 this (const(char)[] amessage) {
40 mMessage = amessage;
42 int xwdt = gxTextWidthUtf(amessage)+6;
43 int xhgt = gxTextHeightUtf+4;
44 super(null, GxSize(xwdt, xhgt));
45 x0 = screenWidth-width;
46 y0 = 0;
47 mType = Type.OnTop;
48 add();
51 override @property int decorationSizeX () const nothrow @safe @nogc { return 3*2; }
52 override @property int decorationSizeY () const nothrow @safe @nogc { return 2*2; }
54 override @property int clientOffsetX () const nothrow @safe @nogc { return decorationSizeX/2; }
55 override @property int clientOffsetY () const nothrow @safe @nogc { return decorationSizeY/2; }
57 @property void message (DynStr v) {
58 if (mMessage != v) {
59 mMessage = v;
60 if (x0 == screenWidth-width) {
61 width = gxTextWidthUtf(v)+6;
62 x0 = screenWidth-width;
63 } else {
64 width = gxTextWidthUtf(v)+6;
65 if (x0+width > screenWidth) x0 = screenWidth-width;
67 postScreenRebuild();
71 override void onPaint () {
72 if (closed) return;
74 immutable string oldtitle = mWinTitle;
75 scope(exit) mWinTitle = oldtitle;
76 if (!minimised) mWinTitle = null;
77 super.onPaint();
79 if (!minimised && mMessage.length) {
80 gxWithSavedClip {
81 setupClip();
82 gxClipRect.shrinkBy(3, 2);
83 gxDrawTextUtf(x0+3, y0+2, mMessage, getColor("text"));
88 // prevent any default keyboard handling
89 override bool onKeySink (KeyEvent event) {
90 return true;
95 // ////////////////////////////////////////////////////////////////////////// //
96 public final class MessageWindow : SubWindow {
97 private DynStr mMessage;
98 private uint lastBarWidth;
100 this (const(char)[] amessage) {
101 mMessage = amessage;
103 int xwdt = gxTextWidthUtf(amessage)+decorationSizeX;
104 int xhgt = gxTextHeightUtf+decorationSizeY;
105 super(null, GxSize(xwdt, xhgt));
106 centerWindow();
107 mType = Type.OnTop;
108 add();
111 override @property int decorationSizeX () const nothrow @safe @nogc { return 24; }
112 override @property int decorationSizeY () const nothrow @safe @nogc { return 16; }
114 override @property int clientOffsetX () const nothrow @safe @nogc { return decorationSizeX/2; }
115 override @property int clientOffsetY () const nothrow @safe @nogc { return decorationSizeY/2; }
117 // returns `true` if need redraw
118 bool setProgress (const(char)[] v, uint curr, uint total) {
119 bool res = false;
120 // fix message
121 if (mMessage != v) {
122 mMessage = v;
123 int xwdt = gxTextWidthUtf(v)+decorationSizeX;
124 int xhgt = gxTextHeightUtf+decorationSizeY;
125 if (xwdt > width || xhgt > height) {
126 setSize(xwdt, xhgt);
127 centerWindow();
128 res = true;
131 // bix bar width
132 uint barWidth = 0;
133 if (total) barWidth = curr*cast(uint)(width-2)/total;
134 if (barWidth != lastBarWidth) res = true;
135 lastBarWidth = barWidth;
136 return res;
139 override void onPaint () {
140 if (closed) return;
142 immutable string oldtitle = mWinTitle;
143 scope(exit) mWinTitle = oldtitle;
144 if (!minimised) mWinTitle = null;
145 super.onPaint();
147 if (!minimised && mMessage.length) {
148 gxWithSavedClip {
149 setupClip();
150 if (lastBarWidth > 0) {
151 auto rc = GxRect(x0+1, y0+1, lastBarWidth, height-2);
152 gxFillRect(rc, getColor("bar-back"));
154 setupClientClip();
155 //gxClipRect.shrinkBy(3, 2);
156 immutable tw = gxTextWidthUtf(mMessage);
157 immutable th = gxTextHeightUtf();
158 gxDrawTextUtf(x0+(width-tw)/2, y0+(height-th)/2, mMessage, getColor("text"));
163 // prevent any default keyboard handling
164 override bool onKeySink (KeyEvent event) {
165 return true;
170 // ////////////////////////////////////////////////////////////////////////// //
171 public final class ProgressWindow : SubWindow {
172 ProgressBarWidget pbar;
173 private enum PadX = 24;
174 private enum PadY = 8;
176 this (string amessage) {
177 int xwdt = gxTextWidthUtf(amessage)+PadX*2;
178 int xhgt = gxTextHeightUtf+PadY*2;
179 super(null, GxSize(xwdt, xhgt));
180 centerWindow();
182 pbar = new ProgressBarWidget(rootWidget, amessage);
183 pbar.size = rootWidget.size;
185 mType = Type.OnTop;
186 add();
189 override @property int decorationSizeX () const nothrow @safe @nogc { return 0; }
190 override @property int decorationSizeY () const nothrow @safe @nogc { return 0; }
192 override @property int clientOffsetX () const nothrow @safe @nogc { return 0; }
193 override @property int clientOffsetY () const nothrow @safe @nogc { return 0; }
195 // returns `true` if need redraw
196 bool setProgress (const(char)[] v, uint curr, uint total) {
197 bool res = false;
198 // fix message
199 if (pbar.text != v) {
200 pbar.text = v;
201 int xwdt = gxTextWidthUtf(v)+PadX*2;
202 int xhgt = gxTextHeightUtf+PadY*2;
203 if (xwdt > width || xhgt > height) {
204 if (xwdt < width) xwdt = width;
205 if (xhgt < height) xhgt = height;
206 setSize(xwdt, xhgt);
207 rootWidget.size = GxSize(xwdt, xhgt);
208 pbar.size = rootWidget.size;
209 centerWindow();
210 res = true;
213 if (pbar.setCurrentTotal(cast(int)curr, cast(int)total)) res = true;
214 return res;
217 // prevent any default keyboard handling
218 override bool onKeySink (KeyEvent event) {
219 return true;
224 // ////////////////////////////////////////////////////////////////////////// //
226 public class SelectPopBoxWindow : SubWindow {
227 SimpleListBoxWidget lb;
229 void delegate (Account acc) onSelected;
231 this (Account defacc) {
232 if (accounts.length > 0) {
233 int xhgt = 0;
234 int xwdt = 0;
236 lb = new SimpleListBoxWidget(this);
237 bool accFound = false;
238 int defAccIdx = -1;
239 foreach (immutable aidx, Account acc; accounts) {
240 if (cast(Pop3Account)acc) {
241 import std.format : format;
242 string s = "%s <%s>".format(acc.realname, acc.mail);
243 lb.appendItem(s, acc);
244 int w = gxTextWidthUtf(s)+2;
245 if (xwdt < w) xwdt = w;
246 if (acc is defacc) {
247 lb.curidx = lb.length-1;
248 accFound = true;
250 if (acc is defaultAcc) defAccIdx = lb.length-1;
251 xhgt += gxTextHeightUtf;
255 if (xhgt == 0) { super(); return; }
256 if (xhgt > screenHeight) xhgt = screenHeight-decorationSizeY;
258 if (!accFound && defAccIdx >= 0) lb.curidx = defAccIdx;
259 if (xwdt > screenWidth-decorationSizeX) xwdt = screenWidth-decorationSizeX;
261 super("Select Account", xwdt+decorationSizeX, xhgt+decorationSizeY);
262 lb.ww = clientWidth;
263 lb.wh = clientHeight;
265 lb.onAction = delegate (self) {
266 if (auto acc = cast(Account)lb.itemData(lb.curidx)) {
267 self.parent.close();
268 if (onSelected !is null) onSelected(acc); else vbwin.beep();
269 } else {
270 vbwin.beep();
274 addModal();
275 } else {
276 super();
280 override bool onKey (KeyEvent event) {
281 if (event.pressed) {
282 if (event == "Escape" || event == "C-Q") { close(); return true; }
283 if (event == "Enter") { lb.onAction(lb); return true; }
285 return super.onKey(event);
291 // ////////////////////////////////////////////////////////////////////////// //
293 public class SelectAddressBookWindow : SubWindow {
294 SimpleListBoxWidget lb;
296 void delegate (AddressBookEntry acc) onSelected;
298 this (const(char)[] prefix) {
299 if (abook.length > 0) {
300 int xhgt = 0;
301 int xwdt = 0;
302 auto defae = abookFindByNickFirst(prefix);
303 if (defae is null && prefix.length) {
304 auto mstx = prefix.lastIndexOf('<');
305 if (mstx >= 0) {
306 prefix = prefix[mstx+1..$];
307 if (prefix.length && prefix[$-1] == '>') prefix = prefix[0..$-1];
309 defae = abookFindByMailFirst(prefix);
312 lb = new SimpleListBoxWidget(this);
313 foreach (immutable aidx, AddressBookEntry ae; abook) {
314 import std.format : format;
315 string s;
316 if (ae.realname.length) s = ae.realname~" <"~ae.mail~">"; else s = ae.mail;
317 lb.appendItem(s, ae);
318 int w = gxTextWidthUtf(s)+2;
319 if (xwdt < w) xwdt = w;
320 if (ae is defae) lb.curidx = lb.length-1;
321 xhgt += gxTextHeightUtf;
324 if (xhgt == 0) { super(); return; }
325 if (xhgt > screenHeight) xhgt = screenHeight-decorationSizeY;
327 if (xwdt > screenWidth-decorationSizeX) xwdt = screenWidth-decorationSizeX;
329 super("Select Recepient", xwdt+decorationSizeX, xhgt+decorationSizeY);
330 lb.ww = clientWidth;
331 lb.wh = clientHeight;
333 lb.onAction = delegate (self) {
334 if (auto ae = cast(AddressBookEntry)lb.itemData(lb.curidx)) {
335 close();
336 if (onSelected !is null) onSelected(ae); else vbwin.beep();
337 } else {
338 vbwin.beep();
342 addModal();
343 } else {
344 super();
348 override bool onKey (KeyEvent event) {
349 if (event.pressed) {
350 if (event == "Escape" || event == "C-Q") { close(); return true; }
351 if (event == "Enter") { lb.onAction(lb); return true; }
353 return super.onKey(event);
359 // ////////////////////////////////////////////////////////////////////////// //
361 public class PostWindow : SubWindow {
362 LineEditWidget from;
363 LineEditWidget to;
364 LineEditWidget subj;
365 EditorWidget ed;
366 string replyto;
367 string references; // of replyto article
368 Account acc;
369 Folder fld;
371 this () {
372 import std.algorithm : max;
374 int wanthgt = screenHeight-42*2;
375 if (wanthgt < 80) wanthgt = 80;
376 int wantwdt = screenWidth-64*2;
377 if (wantwdt < 506) wantwdt = 506;
378 super("Compose Mail", /*506*/wantwdt, /*253*/wanthgt);
379 //if (hasWindowClass(this)) return;
381 from = new LineEditWidget(this, "From:");
382 to = new LineEditWidget(this, "To:");
383 subj = new LineEditWidget(this, "Subj:");
384 ed = new EditorWidget(this);
386 setupClientClip();
388 int tw = max(from.titwdt, to.titwdt, subj.titwdt);
389 from.titwdt = tw;
390 to.titwdt = tw;
391 subj.titwdt = tw;
393 from.wy = 0;
394 to.wy = from.wy+from.wh+2;
395 subj.wy = to.wy+to.wh+2;
397 ed.wx = 0;
398 ed.wy = subj.wy+subj.wh+2;
399 ed.ww = gxClipRect.width;
400 ed.wh = gxClipRect.height-ed.wy;
402 add();
405 override bool onKey (KeyEvent event) {
406 if (event.pressed) {
407 if (event == "Escape" && !ed.editor.textChanged) { close(); return true; }
408 if (event == "C-G" || event == "C-C") {
409 if (ed.editor.textChanged) {
410 auto qww = new YesNoWindow("Close?", "Do you really want to close the editor?", true);
411 qww.onYes = () { close(); };
412 qww.addModal();
413 } else {
414 close();
416 return true;
418 if (event == "M-Tab" && activeWidget is to) {
419 auto ae = abookFindByNick(to.str);
420 //if (ae is null) ae = abookFindByMail(to.str);
421 if (ae !is null) {
422 if (ae.realname.length) to.str = ae.realname~" <"~ae.mail~">"; else to.str = ae.mail;
423 } else {
424 vbwin.beep();
426 return true;
428 if (event == "C-Space" && activeWidget is to) {
429 auto wae = new SelectAddressBookWindow(to.str);
430 wae.onSelected = delegate (AddressBookEntry ae) {
431 if (ae.realname.length) to.str = ae.realname~" <"~ae.mail~">"; else to.str = ae.mail;
433 return true;
435 if ((event == "C-Space") && activeWidget is from) {
436 if (cast(Pop3Account)acc) {
437 auto wacc = new SelectPopBoxWindow(acc);
438 wacc.onSelected = delegate (Account newacc) {
439 acc = newacc;
440 from.str = acc.realname~" <"~acc.mail~">";
443 return true;
445 if (event == "C-Enter") {
446 static bool checkString (string s) nothrow @trusted @nogc {
447 if (s.length == 0) return false;
448 if (s.utflen > 255) return false;
449 return true;
451 if (!checkString(subj.str)) { vbwin.beep(); return true; }
452 if (!checkString(to.str)) { vbwin.beep(); return true; }
453 if (ed.editor[].length == 0) { vbwin.beep(); return true; }
454 auto senddg = delegate () {
455 // build reply article and add it to send queue
456 // check attaches
457 ed.editor.clearAndDisableUndo(); // so removing attaches will not add 'em to undo, lol
458 string[] attnames = ed.extractAttaches();
459 if (attnames) conwriteln("ATTACHES: ", attnames);
460 // build article text
461 auto newart = new Article();
462 newart.markAsStandalone();
463 newart.attachedFileNames = attnames;
464 newart.startText();
465 //foreach (immutable idx; 0..lcount) newart.appendTextLine(ed.editor[idx]);
467 auto mtr = ed.editor[];
468 if (mtr.length) newart.appendTextRange(mtr); else newart.appendTextLine("no text");
470 // clear editor, so it free used memory
471 ed.editor.clearAndDisableUndo();
472 ed.editor.clear();
473 // fix headers
474 newart.subj = subj.str;
475 //newart.inreplyto = replyto;
476 if (replyto.length) newart.replaceHeader("In-Reply-To", replyto);
477 if (references.length) newart.replaceHeader("References", references);
478 newart.replaceHeader("To", encodeq(to.str));
479 if (!acc.addToSendQueue(fld, newart)) { vbwin.beep(); return; }
480 close();
482 auto qww = new YesNoWindow("Send?", "Do you really want to send the message?", true);
483 qww.onYes = senddg;
484 qww.addModal();
485 return true;
488 return super.onKey(event);
494 // ////////////////////////////////////////////////////////////////////////// //
495 public class TitlerWindow : SubWindow {
496 LineEditWidget edtTitle;
497 LineEditWidget fromName;
498 LineEditWidget fromMail;
499 //LineEditWidget fromTag;
500 DynStr name;
501 DynStr mail;
502 DynStr folder;
503 DynStr title;
505 bool delegate (const(char)[] name, const(char)[] mail, const(char)[] folder, const(char)[] title) onSelected;
507 override void createWidgets () {
508 new SpacerWidget(rootWidget, 2);
510 version(none) {
511 fromName = new LineEditWidget(rootWidget, "Name:");
512 fromName.flex = 1;
514 new SpacerWidget(rootWidget, 2);
515 fromMail = new LineEditWidget(rootWidget, "Mail:");
516 fromMail.flex = 1;
518 new SpacerWidget(rootWidget, 2);
519 edtTitle = new LineEditWidget(rootWidget, "Title:");
520 edtTitle.flex = 1;
521 } else {
522 (new HBoxWidget).enter{
523 with (new HotLabelWidget("&Name:", LabelWidget.HAlign.Right)) { width = width+2; hsizeId = "editors"; }
524 new SpacerWidget(4);
525 fromName = new LineEditWidget();
526 fromName.flex = 1;
527 }.flex = 1;
529 new SpacerWidget(1);
530 (new HBoxWidget).enter{
531 with (new HotLabelWidget("&Mail:", LabelWidget.HAlign.Right)) { width = width+2; hsizeId = "editors"; }
532 new SpacerWidget(4);
533 fromMail = new LineEditWidget();
534 fromMail.flex = 1;
535 }.flex = 1;
537 new SpacerWidget(1);
538 (new HBoxWidget).enter{
539 with (new HotLabelWidget("&Title:", LabelWidget.HAlign.Right)) { width = width+2; hsizeId = "editors"; }
540 new SpacerWidget(4);
541 edtTitle = new LineEditWidget();
542 edtTitle.flex = 1;
543 }.flex = 1;
546 new SpacerWidget(rootWidget, 2);
548 fromName.str = name;
549 fromMail.str = mail;
550 edtTitle.str = title;
552 minWinSize.w = 320;
554 relayoutResize();
555 centerWindow();
557 edtTitle.focus();
560 this (const(char)[] aname, const(char)[] amail, const(char)[] afolder, const(char)[] atitle) {
561 name = aname;
562 mail = amail;
563 title = atitle;
564 folder = afolder;
566 DynStr caption = "Title for ";
567 caption ~= aname;
568 caption ~= " <";
569 caption ~= amail;
570 caption ~= ">";
571 super(caption.getData);
574 override bool onKeyBubble (KeyEvent event) {
575 if (event.pressed) {
576 if (event == "Escape" || event == "C-Q") { close(); return true; }
577 if (event == "Enter") {
578 if (onSelected !is null) {
579 if (!onSelected(fromName.str, fromMail.str, folder, edtTitle.str)) return true;
581 close();
582 return true;
585 return super.onKeyBubble(event);
590 // ////////////////////////////////////////////////////////////////////////// //
591 public class TagOptionsWindow : SubWindow {
592 LabelWidget optPath; // real path
593 LineEditWidget optMonthes;
594 CheckboxWidget optThreaded;
595 CheckboxWidget optAttaches;
596 DynStr tagname;
598 void delegate (const(char)[] tagname) onUpdated;
600 override void createWidgets () {
601 optPath = new LabelWidget(rootWidget, "", LabelWidget.HAlign.Center);
602 optPath.flex = 1;
604 version(none) {
605 optMonthes = new LineEditWidget(rootWidget, "Monthes:");
606 optMonthes.flex = 1;
607 optMonthes.width = optMonthes.titwdt+64;
608 } else {
609 (new HBoxWidget).enter{
610 with (new HotLabelWidget("&Monthes:", LabelWidget.HAlign.Right)) { width = width+2; /*hsizeId = "editors";*/ }
611 new SpacerWidget(4);
612 optMonthes = new LineEditWidget();
613 //optMonthes.flex = 1;
614 optMonthes.width = gxTextWidthUtf("96669");
615 //new SpringWidget(1);
616 }.flex = 1;
619 optThreaded = new CheckboxWidget(rootWidget, "&Threaded");
620 optThreaded.flex = 1;
622 optAttaches = new CheckboxWidget(rootWidget, "&Attaches");
623 optAttaches.flex = 1;
625 optMonthes.focus();
627 immutable bool goodTag = (chiroGetTagUid(tagname.getData) != 0);
629 import std.conv : to;
630 int val;
631 DynStr path = chiroGetTagMonthLimitEx(tagname.getData, out val, defval:6);
632 //conwriteln("TAGNAME=<", tagname.getData, ">; val=", val, "; path=<", path.getData, ">");
633 optPath.text = path.getData;
634 optMonthes.str = val.to!string;
635 optPath.width = gxTextWidthUtf(optPath.text)+4;
638 if (goodTag) {
639 foreach (auto row; dbView.statement(`
640 SELECT threading AS trd, noattaches AS noatt FROM tagnames WHERE tag=:tag LIMIT 1
641 ;`).bindConstText(":tag", tagname.getData).range)
643 optThreaded.checked = (row.trd!int == 1);
644 optAttaches.checked = (row.noatt!int == 0);
646 } else {
647 optThreaded.enabled = false;
648 optAttaches.enabled = false;
651 optMonthes.killTextOnChar = true;
653 minWinSize.w = optPath.width;
654 relayoutResize();
656 centerWindow();
659 this (const(char)[] atagname) {
660 tagname = atagname;
662 DynStr caption = "options for '";
663 caption ~= atagname;
664 caption ~= "'";
665 super(caption.getData);
668 override bool onKeyBubble (KeyEvent event) {
669 if (event.pressed) {
670 if (event == "Escape" || event == "C-Q") { close(); return true; }
671 if (event == "Enter") {
672 int mv = -666;
673 try {
674 auto vv = optMonthes.str.xstrip;
675 if (vv.length == 0) {
676 mv = -2;
677 } else {
678 import std.conv : to;
679 mv = vv.to!int;
680 if (mv < -1) mv = -666; else if (mv == 0) mv = -1;
682 } catch (Exception) {
683 mv = -666;
685 if (mv < -2) return true;
686 import core.stdc.stdio : snprintf;
687 char[1024] xname = void;
688 const(char)[] tn = tagname.getData;
689 auto xlen = snprintf(xname.ptr, xname.sizeof, "/mainpane/msgview/monthlimit%s%.*s",
690 (tn.length && tn[0] != '/' ? "/".ptr : "".ptr), cast(uint)tn.length, tn.ptr);
691 if (mv == -2) {
692 // delete
693 if (xname[0..xlen] != "/mainpane/msgview/monthlimit") {
694 chiroDeleteOption(xname[0..xlen]);
696 } else {
697 // set
698 chiroSetOption(xname[0..xlen], mv);
700 // fix threading and attaches
701 if (optThreaded.enabled) {
702 dbView.statement(`
703 UPDATE tagnames
704 SET threading=:trd, noattaches=:noatt
705 WHERE tag=:name
707 .bindConstText(":name", tagname.getData())
708 .bind(":trd", (optThreaded.checked ? 1 : 0))
709 .bind(":noatt", (optAttaches.checked ? 0 : 1))
710 .doAll();
712 if (onUpdated !is null) onUpdated(tagname.getData);
713 close();
714 return true;
717 return super.onKeyBubble(event);