sq3: show SQLite error messages on stderr by default
[iv.d.git] / tuing / controls / editor.d
blob6c9878ca98e262407df3f46984a5f993e66d3f91
1 /* Invisible Vector Library
2 * simple FlexBox-based TUI engine
4 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
5 * Understanding is not required. Only obedience.
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, version 3 of the License ONLY.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 module iv.tuing.controls.editor /*is aliced*/;
21 import iv.alice;
22 import iv.eventbus;
23 import iv.flexlayout;
24 import iv.strex;
25 import iv.rawtty;
27 import iv.tuing.events;
28 import iv.tuing.tty;
29 import iv.tuing.tui;
30 import iv.tuing.types;
31 import iv.tuing.ttyeditor;
32 import iv.tuing.controls.box;
33 import iv.tuing.controls.button;
34 import iv.tuing.controls.editline;
35 import iv.tuing.controls.label;
36 import iv.tuing.controls.listbox;
37 import iv.tuing.controls.window;
40 // ////////////////////////////////////////////////////////////////////////// //
41 private class FuiEditorMessageWindow : FuiWindow {
42 alias onMyEvent = super.onMyEvent;
43 alias onBubbleEvent = super.onBubbleEvent;
45 this (FuiEditor aed, string msg) {
46 assert(aed !is null);
47 this.connectListeners();
48 super();
49 caption = "Editor Message";
50 minSize.w = cast(int)caption.length+6;
51 frame = Frame.Normal;
52 if (auto lb = new FuiLabel(this, "\x03"~msg)) {
53 lb.hotkeyed = false;
54 lb.aligning = Align.Stretch;
56 new FuiHLine(this);
57 if (auto box = new FuiHBox(this)) {
58 new FuiSpan(box);
59 with (new FuiButton(box, "&Close")) {
60 defctl = true;
61 escctl = true;
63 new FuiSpan(box);
67 static void create (FuiEditor aed, string msg) {
68 if (aed is null) return;
69 auto desk = aed.getDesk;
70 if (desk is null) return;
71 auto win = new FuiEditorMessageWindow(aed, msg);
72 fuiLayout(win);
73 win.positionCenterInControl(aed);
74 tuidesk.addPopup(win);
79 // ////////////////////////////////////////////////////////////////////////// //
80 private class FuiEditorNumInputWindow : FuiWindow {
81 alias onMyEvent = super.onMyEvent;
82 alias onBubbleEvent = super.onBubbleEvent;
84 FuiEditor edt;
85 FuiEditLine editline;
86 FuiButton btok;
88 int getNum () {
89 auto ed = editline.ed;
90 if (ed is null) return -1;
91 int num = 0;
92 auto rng = ed[];
93 while (!rng.empty && rng.front <= ' ') rng.popFront();
94 if (rng.empty || !rng.front.isdigit) return -1;
95 while (!rng.empty && rng.front.isdigit) {
96 num = num*10+rng.front-'0';
97 rng.popFront();
99 while (!rng.empty && rng.front <= ' ') rng.popFront();
100 return (rng.empty ? num : -1);
103 this (FuiEditor aed, string acaption, string alabel, int defval) {
104 import std.format : format;
105 assert(aed !is null);
106 this.connectListeners();
107 super();
108 edt = aed;
109 caption = acaption;
110 minSize.w = cast(int)caption.length+6;
111 frame = Frame.Normal;
112 if (auto box = new FuiHBox(this)) {
113 auto lb = new FuiLabel(box, alabel);
114 editline = new FuiEditLine(box, (defval > 0 ? "%s".format(defval) : ""));
115 editline.onAction = (FuiControl self) { btok.enabled = (getNum() > 0); };
117 defaultFocus = editline;
118 new FuiHLine(this);
119 if (auto box = new FuiHBox(this)) {
120 new FuiSpan(box);
121 btok = new FuiButton(box, "O&K");
122 with (btok) {
123 defctl = true;
124 enabled = (defval > 0);
126 with (new FuiButton(box, "&Cancel")) {
127 escctl = true;
129 new FuiSpan(box);
135 private void createTabSizeWindow (FuiEditor aed, int tabsize) {
136 if (aed is null) return;
137 auto desk = aed.getDesk;
138 if (desk is null) return;
139 auto win = new FuiEditorNumInputWindow(aed, "Editor Query", "&Tab size:", tabsize);
140 win.btok.onAction = (FuiControl self) {
141 if (auto w = cast(FuiEditorNumInputWindow)self.toplevel) {
142 int n = w.getNum();
143 if (n > 0) {
144 (new EventEditorReplyTabSize(w.edt.ed, n)).post;
145 (new FuiEventClose(w, self)).post;
149 fuiLayout(win);
150 win.positionCenterInControl(aed);
151 tuidesk.addModal(win);
155 private void createGotoLineWindow (FuiEditor aed) {
156 if (aed is null) return;
157 auto desk = aed.getDesk;
158 if (desk is null) return;
159 auto win = new FuiEditorNumInputWindow(aed, "Editor Query", "Line &number:", -1);
160 win.btok.onAction = (FuiControl self) {
161 if (auto w = cast(FuiEditorNumInputWindow)self.toplevel) {
162 int n = w.getNum();
163 if (n > 0) {
164 (new EventEditorReplyGotoLine(w.edt.ed, n)).post;
165 (new FuiEventClose(w, self)).post;
169 fuiLayout(win);
170 win.positionCenterInControl(aed);
171 tuidesk.addModal(win);
175 // ////////////////////////////////////////////////////////////////////////// //
176 private class FuiEditorCPWindow : FuiWindow {
177 alias onMyEvent = super.onMyEvent;
178 alias onBubbleEvent = super.onBubbleEvent;
180 FuiEditor ed;
181 FuiListBox lb;
183 this (FuiEditor aed, int ccp) {
184 assert(aed !is null);
185 this.connectListeners();
186 super();
187 ed = aed;
188 caption = "Codepage";
189 frame = Frame.Small;
190 //minSize.w = 14;
191 lb = new FuiListBox(this);
192 with (lb) {
193 aligning = Align.Stretch;
194 addItem("KOI8-U");
195 addItem("CP1251");
196 addItem("CP-866");
197 addItem("UTFUCK");
198 curitem = ccp;
199 defctl = true;
200 escctl = true;
201 maxSize.w = ttyw-8;
202 maxSize.h = ttyh-8;
206 override void onBubbleEvent (FuiEventKey evt) {
207 if (evt.key == "Enter" && lb !is null) {
208 (new EventEditorReplyCodePage(ed.ed, lb.curitem)).post;
210 super.onBubbleEvent(evt);
213 static void create (FuiEditor aed, int ccp) {
214 if (aed is null) return;
215 auto desk = aed.getDesk;
216 if (desk is null) return;
217 auto win = new FuiEditorCPWindow(aed, ccp);
218 fuiLayout(win);
219 win.positionCenterInControl(aed);
220 tuidesk.addPopup(win);
225 // ////////////////////////////////////////////////////////////////////////// //
226 private class FuiEditorACWindow : FuiWindow {
227 alias onMyEvent = super.onMyEvent;
228 alias onBubbleEvent = super.onBubbleEvent;
230 FuiEditor ed;
231 FuiListBox lb;
232 int pos, len;
234 this (FuiEditor aed, int apos, int alen, const(char)[][] list) {
235 assert(aed !is null);
236 this.connectListeners();
237 super();
238 ed = aed;
239 pos = apos;
240 len = alen;
241 frame = Frame.Small;
242 lb = new FuiListBox(this);
243 with (lb) {
244 aligning = Align.Stretch;
245 foreach (const(char)[] s; list) addItem(s);
246 defctl = true;
247 escctl = true;
248 maxSize.w = ttyw-8;
249 maxSize.h = 16;//ttyh-8;
251 // cancel autocompletion on close
252 addEventListener(this, (FuiEventClose evt) {
253 (new EventEditorReplyAutocompletion(ed.ed, pos, len, null)).post;
257 override void onBubbleEvent (FuiEventKey evt) {
258 if (evt.key == "Enter" && lb !is null) {
259 (new EventEditorReplyAutocompletion(ed.ed, pos, len, lb[lb.curitem])).post;
260 } else if (evt.key == "Escape") {
261 (new EventEditorReplyAutocompletion(ed.ed, pos, len, null)).post;
263 super.onBubbleEvent(evt);
266 static void create (FuiEditor aed, FuiPoint pt, int apos, int alen, const(char)[][] list) {
267 if (aed is null) return;
268 auto desk = aed.getDesk;
269 if (desk is null) return;
270 auto win = new FuiEditorACWindow(aed, apos, alen, list);
271 fuiLayout(win);
272 pt = aed.toGlobal(pt);
273 if (!aed.ed.hideSBar) pt.x = pt.x+1;
274 if (!aed.ed.hideStatus) pt.y = pt.y+1;
275 win.positionAtGlobal(pt);
276 tuidesk.addPopup(win);
281 // ////////////////////////////////////////////////////////////////////////// //
282 private class FuiEditorSRWindow : FuiWindow {
283 FuiEditor ed;
284 TtyEditor.SROptions* srr;
285 FuiEditLine els, elr;
286 FuiCheckBox cbinsel;
287 FuiCheckBox cbnocom;
288 FuiButton btok;
290 this (FuiEditor aed, TtyEditor.SROptions* asrr) {
291 assert(aed !is null);
292 this.connectListeners();
293 super();
294 ed = aed;
295 srr = asrr;
296 caption = "Search And Replace";
297 frame = Frame.Normal;
298 minSize.w = ttyw-(ttyw/3);
300 auto lbs = new FuiLabel(this, "&Search string:");
301 lbs.lineBreak = true;
302 els = new FuiEditLine(this, srr.search);
303 els.aligning = Align.Stretch;
304 els.lineBreak = true;
305 els.onAction = (FuiControl self) { btok.enabled = (els.ed.textsize > 0); };
306 lbs.dest = els;
308 auto lbr = new FuiLabel(this, "Re&placement string:");
309 lbr.lineBreak = true;
310 elr = new FuiEditLine(this, srr.replace);
311 elr.aligning = Align.Stretch;
312 elr.lineBreak = true;
313 lbr.dest = elr;
315 new FuiHLine(this);
317 if (auto box = new FuiHBox(this)) {
318 box.aligning = Align.Start;
319 radio("srrtype", cast(int)srr.type);
320 if (auto vb = new FuiVBox(box)) {
321 new FuiRadio(vb, "No&rmal", "srrtype", 0);
322 new FuiRadio(vb, "Re&gular expression", "srrtype", 1);
324 if (auto vb = new FuiVBox(box)) {
325 new FuiCheckBox(vb, "Cas&e sensitive", "casesens", srr.casesens);
326 new FuiCheckBox(vb, "&Backwards", "backwards", srr.backwards);
327 new FuiCheckBox(vb, "&Whole words", "wholeword", srr.wholeword);
328 cbinsel = new FuiCheckBox(vb, "In se&lection", "inselection", srr.inselection);
329 cbinsel.enabled = aed.ed.hasMarkedBlock;
330 cbnocom = new FuiCheckBox(vb, "S&kip comments", "nocomments", srr.nocomments);
334 new FuiHLine(this);
335 if (auto box = new FuiHBox(this)) {
336 new FuiSpan(box);
337 btok = new FuiButton(box, "O&K");
338 with (btok) {
339 defctl = true;
340 enabled = (srr.search.length > 0);
341 onAction = (FuiControl self) {
342 self.closetop;
343 auto rv = radio("srrtype");
344 if (rv < TtyEditor.SROptions.Type.min || rv > TtyEditor.SROptions.Type.max) return;
345 srr.type = cast(TtyEditor.SROptions.Type)rv;
346 srr.casesens = checkbox("casesens");
347 srr.backwards = checkbox("backwards");
348 srr.wholeword = checkbox("wholeword");
349 srr.inselection = checkbox("inselection");
350 srr.nocomments = checkbox("nocomments");
351 srr.search = els.getText!(const(char)[]);
352 srr.replace = elr.getText!(const(char)[]);
353 (new EventEditorReplySR(ed.ed, srr, true)).post;
356 with (new FuiButton(box, "&Cancel")) {
357 escctl = true;
358 onAction = (FuiControl self) {
359 self.closetop;
360 (new EventEditorReplySR(ed.ed, srr, false)).post;
363 new FuiSpan(box);
367 static void create (FuiEditor aed, TtyEditor.SROptions* srr) {
368 if (aed is null) return;
369 auto desk = aed.getDesk;
370 if (desk is null) return;
371 auto win = new FuiEditorSRWindow(aed, srr);
372 fuiLayout(win);
373 win.positionCenterInControl(aed);
374 tuidesk.addModal(win);
379 // ////////////////////////////////////////////////////////////////////////// //
380 private class FuiEditorSRConfirmWindow : FuiWindow {
381 FuiEditor ed;
382 TtyEditor.SROptions* srr;
384 this (FuiEditor aed, TtyEditor.SROptions* asrr) {
385 assert(aed !is null);
386 this.connectListeners();
387 super();
388 ed = aed;
389 srr = asrr;
390 caption = "Confirm replace";
391 frame = Frame.Normal;
393 with (new FuiLabel(this, "\x03Pattern found. Select your action!")) aligning = Align.Stretch;
395 new FuiHLine(this);
397 if (auto box = new FuiHBox(this)) {
398 (new FuiSpan(box)).minSize.w = 1;
399 with (new FuiButton(box, "&Replace")) {
400 onAction = (FuiControl self) {
401 self.closetop;
402 srr.cont = TtyEditor.SROptions.Cont.Yes;
403 (new EventEditorReplyReplacement(ed.ed, srr)).post;
406 with (new FuiButton(box, "A&ll")) {
407 onAction = (FuiControl self) {
408 self.closetop;
409 srr.cont = TtyEditor.SROptions.Cont.All;
410 (new EventEditorReplyReplacement(ed.ed, srr)).post;
413 with (new FuiButton(box, "&Skip")) {
414 onAction = (FuiControl self) {
415 self.closetop;
416 srr.cont = TtyEditor.SROptions.Cont.No;
417 (new EventEditorReplyReplacement(ed.ed, srr)).post;
420 with (new FuiButton(box, "&Cancel")) {
421 escctl = true;
422 onAction = (FuiControl self) {
423 self.closetop;
424 srr.cont = TtyEditor.SROptions.Cont.Cancel;
425 (new EventEditorReplyReplacement(ed.ed, srr)).post;
428 (new FuiSpan(box)).minSize.w = 1;
432 static void create (FuiEditor aed, TtyEditor.SROptions* srr) {
433 if (aed is null) return;
434 auto desk = aed.getDesk;
435 if (desk is null) return;
436 auto win = new FuiEditorSRConfirmWindow(aed, srr);
437 fuiLayout(win);
438 //auto pt = FuiPoint(aed.ed.curx, aed.ed.cury);
439 auto pt = FuiPoint(0, aed.ed.cury);
440 pt.y -= aed.ed.topline;
441 pt.y = pt.y+1;
442 if (!aed.ed.hideSBar) pt.x = pt.x+1;
443 if (!aed.ed.hideStatus) pt.y = pt.y+1;
444 pt = aed.toGlobal(pt);
445 win.positionAtGlobal(pt);
446 tuidesk.addModal(win);
451 // ////////////////////////////////////////////////////////////////////////// //
452 public class FuiEditor : FuiControl {
453 alias onMyEvent = super.onMyEvent;
455 TtyEditor ed;
457 this (FuiControl aparent, string atext=null) {
458 this.connectListeners();
459 super(aparent);
460 ed = new TtyEditor(0, 0, 10, 1, false); // size will be fixed later
461 //ed.hideStatus = true;
462 //ed.utfuck = utfuck;
463 ed.setNewText(atext);
464 minSize.w = 30;
465 minSize.h = 6;
466 horizontal = true;
467 aligning = Align.Stretch;
468 canBeFocused = true;
469 hotkeyed = false;
470 acceptClick(TtyEvent.MButton.Left);
472 addEventListener(ed, (EventEditorMessage evt) { FuiEditorMessageWindow.create(this, evt.msg); });
473 addEventListener(ed, (EventEditorQueryCodePage evt) { FuiEditorCPWindow.create(this, evt.cp); });
474 addEventListener(ed, (EventEditorQueryTabSize evt) { createTabSizeWindow(this, evt.tabsize); });
475 addEventListener(ed, (EventEditorQueryGotoLine evt) { createGotoLineWindow(this); });
476 addEventListener(ed, (EventEditorQuerySR evt) { FuiEditorSRWindow.create(this, cast(TtyEditor.SROptions*)evt.opt); });
477 addEventListener(ed, (EventEditorQueryReplacement evt) { FuiEditorSRConfirmWindow.create(this, cast(TtyEditor.SROptions*)evt.opt); });
478 addEventListener(ed, (EventEditorQueryAutocompletion evt) { FuiEditorACWindow.create(this, evt.pt, evt.pos, evt.len, evt.list); });
481 T getText(T:const(char)[]) () if (!is(T == typeof(null))) {
482 if (ed is null) return null;
483 char[] res;
484 auto rng = ed[];
485 res.reserve(rng.length);
486 foreach (char ch; rng) res ~= ch;
487 return cast(T)res;
490 // action called when editor processed any key
491 //override void doAction ()
493 protected override void drawSelf (XtWindow win) {
494 //ed.hideStatus = true;
495 ed.moveResize(win.x0+(ed.hideSBar ? 0 : 1), win.y0+(ed.hideStatus ? 0 : 1), win.width-(ed.hideSBar ? 0 : 1), win.height-(ed.hideStatus ? 0 : 1));
496 ed.fullDirty;
497 ed.dontSetCursor = !focused;
498 if (enabled) {
499 if (focused) {
500 ed.clrBlock = palColor!"inputmark"();
501 ed.clrText = palColor!"input"();
502 ed.clrTextUnchanged = palColor!"inputunchanged"();
503 } else {
504 ed.clrBlock = palColor!"inputmark"();
505 ed.clrText = palColor!"input"();
506 ed.clrTextUnchanged = palColor!"inputunchanged"();
508 } else {
509 ed.clrBlock = palColor!"disabled"();
510 ed.clrText = palColor!"disabled"();
511 ed.clrTextUnchanged = palColor!"disabled"();
513 ed.drawPage();
516 void onMyEvent (FuiEventClick evt) {
517 if (evt.left && ed.processClick(evt.bidx, evt.pt.x, evt.pt.y)) {
518 evt.eat();
519 doAction();
523 override void onMyEvent (FuiEventBlur evt) {
524 ed.resetPasteMode();
525 super.onMyEvent(evt);
528 void onMyEvent (FuiEventKey evt) {
529 if (disabled) return;
531 if (evt.key == "F1") {
532 import iv.vfs.io;
533 VFile("zhelp.log", "w").writeln(ed.buildHelpText);
534 evt.eat();
535 return;
538 if (ed.processKey(evt.key)) {
539 evt.eat();
540 doAction();
541 return;
543 if (!evt.key.mouse && ed.pasteMode) { evt.eat(); return; }