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*/;
22 import arsd
.simpledisplay
;
33 import chibackend
/*: DynStr*/;
40 // ////////////////////////////////////////////////////////////////////////// //
41 public final class HintWindow
: SubWindow
{
42 private DynStr mMessage
;
44 this (const(char)[] amessage
) {
47 int xwdt
= gxTextWidthUtf(amessage
)+6;
48 int xhgt
= gxTextHeightUtf
+4;
49 super(null, 0, 0, xwdt
, xhgt
);
50 winx
= screenWidth
-winw
;
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
) {
65 if (winx
== screenWidth
-winw
) {
66 winw
= gxTextWidthUtf(v
)+6;
67 winx
= screenWidth
-winw
;
69 winw
= gxTextWidthUtf(v
)+6;
70 if (winx
+winw
> screenWidth
) winx
= screenWidth
-winw
;
76 override void onPaint () {
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));
84 drawWindowMinimized();
88 override bool onKey (KeyEvent event
) {
92 override bool onMouse (MouseEvent event
) {
93 if (closed
) return false;
96 event
.mouse2xy(mx
, my
);
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
);
111 // ////////////////////////////////////////////////////////////////////////// //
112 public final class MessageWindow
: SubWindow
{
113 private DynStr mMessage
;
114 private uint lastBarWidth
;
116 this (const(char)[] amessage
) {
119 int xwdt
= gxTextWidthUtf(amessage
)+decorationSizeX
;
120 int xhgt
= gxTextHeightUtf
+decorationSizeY
;
121 super(null, 0, 0, xwdt
, xhgt
);
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
) {
139 int xwdt
= gxTextWidthUtf(v
)+decorationSizeX
;
140 int xhgt
= gxTextHeightUtf
+decorationSizeY
;
141 if (xwdt
> width || xhgt
> height
) {
149 if (total
) barWidth
= curr
*cast(uint)(width
-2)/total
;
150 if (barWidth
!= lastBarWidth
) res
= true;
151 lastBarWidth
= barWidth
;
154 import core.stdc.stdio : snprintf;
155 char[128] buf = void;
157 recalcHintWindow.message = msg;
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 () {
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));
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*/));
180 drawWindowMinimized();
184 override bool onKey (KeyEvent event
) {
188 override bool onMouse (MouseEvent event
) {
189 if (closed
) return false;
192 event
.mouse2xy(mx
, my
);
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
);
207 // ////////////////////////////////////////////////////////////////////////// //
209 public class SelectPopBoxWindow : SubWindow {
210 SimpleListBoxWidget lb;
212 void delegate (Account acc) onSelected;
214 this (Account defacc) {
215 if (accounts.length > 0) {
219 lb = new SimpleListBoxWidget(this);
220 bool accFound = false;
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;
230 lb.curidx = lb.length-1;
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);
246 lb.wh = clientHeight;
248 lb.onAction = delegate (self) {
249 if (auto acc = cast(Account)lb.itemData(lb.curidx)) {
251 if (onSelected !is null) onSelected(acc); else vbwin.beep();
263 override bool onKey (KeyEvent event) {
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) {
285 auto defae = abookFindByNickFirst(prefix);
286 if (defae is null && prefix.length) {
287 auto mstx = prefix.lastIndexOf('<');
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;
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);
314 lb.wh = clientHeight;
316 lb.onAction = delegate (self) {
317 if (auto ae = cast(AddressBookEntry)lb.itemData(lb.curidx)) {
319 if (onSelected !is null) onSelected(ae); else vbwin.beep();
331 override bool onKey (KeyEvent event) {
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 {
350 string references; // of replyto article
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);
371 int tw = max(from.titwdt, to.titwdt, subj.titwdt);
377 to.wy = from.wy+from.wh+2;
378 subj.wy = to.wy+to.wh+2;
381 ed.wy = subj.wy+subj.wh+2;
382 ed.ww = gxClipRect.width;
383 ed.wh = gxClipRect.height-ed.wy;
388 override bool onKey (KeyEvent event) {
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(); };
401 if (event == "M-Tab" && activeWidget is to) {
402 auto ae = abookFindByNick(to.str);
403 //if (ae is null) ae = abookFindByMail(to.str);
405 if (ae.realname.length) to.str = ae.realname~" <"~ae.mail~">"; else to.str = ae.mail;
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;
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) {
423 from.str = acc.realname~" <"~acc.mail~">";
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;
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
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;
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();
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; }
465 auto qww = new YesNoWindow("Send?", "Do you really want to send the message?", true);
471 return super.onKey(event);
477 // ////////////////////////////////////////////////////////////////////////// //
478 public class TitlerWindow
: SubWindow
{
479 LineEditWidget title
;
480 LineEditWidget fromName
;
481 LineEditWidget fromMail
;
482 //LineEditWidget fromTag;
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 ";
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);
513 int tw
= max(fromName
.titwdt
, fromMail
.titwdt
, title
.titwdt
);
514 fromName
.titwdt
= tw
;
515 fromMail
.titwdt
= tw
;
519 fromMail
.wy
= fromName
.wy
+fromName
.wh
+2;
520 title
.wy
= fromMail
.wy
+fromMail
.wh
+2;
522 activeWidget
= title
;
525 fromName
.str = aname
;
526 fromMail
.str = amail
;
531 override bool onKey (KeyEvent event
) {
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;
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
;
555 void delegate (const(char)[] tagname
) onUpdated
;
557 this (const(char)[] atagname
) {
560 DynStr caption
= "options for '";
563 super(caption
.getData
.idup
, 706, 64);
565 optPath
= new LabelWidget(this, "", LabelWidget
.HAlign
.Center
);
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);
586 import core
.stdc
.stdio
: snprintf
;
587 char[1024] xname
= void;
589 const(char)[] tn
= tagname
.getData
;
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
);
595 import std
.conv
: to
;
597 optPath
.text
= xname
[0..len
].idup
;
598 optMonthes
.str = v
.to
!string
;
599 //conwriteln("tn=[", tn, "]");
600 //goodTag = (tn.length && chiroGetTagUid(tn) != 0);
603 auto slp
= tn
.lastIndexOf('/', 1);
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
;
616 import std
.conv
: to
;
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
;
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);
633 optThreaded
.enabled
= false;
634 optAttaches
.enabled
= false;
637 optMonthes
.killTextOnChar
= true;
639 setClientSize(-1, widgets
[$-1].wy
+widgets
[$-1].wh
+1);
647 override bool onKey (KeyEvent event
) {
649 if (event
== "Escape" || event
== "C-Q") { close(); return true; }
650 if (event
== "Enter") {
653 auto vv
= optMonthes
.str.xstrip
;
654 if (vv
.length
== 0) {
657 import std
.conv
: to
;
659 if (mv
< -1) mv
= -666; else if (mv
== 0) mv
= -1;
661 } catch (Exception
) {
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
);
672 if (xname
[0..xlen
] != "/mainpane/msgview/monthlimit") {
673 chiroDeleteOption(xname
[0..xlen
]);
677 chiroSetOption(xname
[0..xlen
], mv
);
679 // fix threading and attaches
680 if (optThreaded
.enabled
) {
683 SET threading=:trd, noattaches=:noatt
686 .bindConstText(":name", tagname
.getData())
687 .bind(":trd", (optThreaded
.checked ?
1 : 0))
688 .bind(":noatt", (optAttaches
.checked ?
0 : 1))
691 if (onUpdated
!is null) onUpdated(tagname
.getData
);
696 return super.onKey(event
);