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*/;
27 import iv
.tuing
.events
;
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
) {
47 this.connectListeners();
49 caption
= "Editor Message";
50 minSize
.w
= cast(int)caption
.length
+6;
52 if (auto lb
= new FuiLabel(this, "\x03"~msg
)) {
54 lb
.aligning
= Align
.Stretch
;
57 if (auto box
= new FuiHBox(this)) {
59 with (new FuiButton(box
, "&Close")) {
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
);
73 win
.positionCenterInControl(aed
);
74 tuidesk
.addPopup(win
);
79 // ////////////////////////////////////////////////////////////////////////// //
80 private class FuiEditorNumInputWindow
: FuiWindow
{
81 alias onMyEvent
= super.onMyEvent
;
82 alias onBubbleEvent
= super.onBubbleEvent
;
89 auto ed
= editline
.ed
;
90 if (ed
is null) return -1;
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';
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();
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
;
119 if (auto box
= new FuiHBox(this)) {
121 btok
= new FuiButton(box
, "O&K");
124 enabled
= (defval
> 0);
126 with (new FuiButton(box
, "&Cancel")) {
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
) {
144 (new EventEditorReplyTabSize(w
.edt
.ed
, n
)).post
;
145 (new FuiEventClose(w
, self
)).post
;
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
) {
164 (new EventEditorReplyGotoLine(w
.edt
.ed
, n
)).post
;
165 (new FuiEventClose(w
, self
)).post
;
170 win
.positionCenterInControl(aed
);
171 tuidesk
.addModal(win
);
175 // ////////////////////////////////////////////////////////////////////////// //
176 private class FuiEditorCPWindow
: FuiWindow
{
177 alias onMyEvent
= super.onMyEvent
;
178 alias onBubbleEvent
= super.onBubbleEvent
;
183 this (FuiEditor aed
, int ccp
) {
184 assert(aed
!is null);
185 this.connectListeners();
188 caption
= "Codepage";
191 lb
= new FuiListBox(this);
193 aligning
= Align
.Stretch
;
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
);
219 win
.positionCenterInControl(aed
);
220 tuidesk
.addPopup(win
);
225 // ////////////////////////////////////////////////////////////////////////// //
226 private class FuiEditorACWindow
: FuiWindow
{
227 alias onMyEvent
= super.onMyEvent
;
228 alias onBubbleEvent
= super.onBubbleEvent
;
234 this (FuiEditor aed
, int apos
, int alen
, const(char)[][] list
) {
235 assert(aed
!is null);
236 this.connectListeners();
242 lb
= new FuiListBox(this);
244 aligning
= Align
.Stretch
;
245 foreach (const(char)[] s
; list
) addItem(s
);
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
);
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
{
284 TtyEditor
.SROptions
* srr
;
285 FuiEditLine els
, elr
;
290 this (FuiEditor aed
, TtyEditor
.SROptions
* asrr
) {
291 assert(aed
!is null);
292 this.connectListeners();
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); };
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;
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
);
335 if (auto box
= new FuiHBox(this)) {
337 btok
= new FuiButton(box
, "O&K");
340 enabled
= (srr
.search
.length
> 0);
341 onAction
= (FuiControl self
) {
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")) {
358 onAction
= (FuiControl self
) {
360 (new EventEditorReplySR(ed
.ed
, srr
, false)).post
;
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
);
373 win
.positionCenterInControl(aed
);
374 tuidesk
.addModal(win
);
379 // ////////////////////////////////////////////////////////////////////////// //
380 private class FuiEditorSRConfirmWindow
: FuiWindow
{
382 TtyEditor
.SROptions
* srr
;
384 this (FuiEditor aed
, TtyEditor
.SROptions
* asrr
) {
385 assert(aed
!is null);
386 this.connectListeners();
390 caption
= "Confirm replace";
391 frame
= Frame
.Normal
;
393 with (new FuiLabel(this, "\x03Pattern found. Select your action!")) aligning
= Align
.Stretch
;
397 if (auto box
= new FuiHBox(this)) {
398 (new FuiSpan(box
)).minSize
.w
= 1;
399 with (new FuiButton(box
, "&Replace")) {
400 onAction
= (FuiControl self
) {
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
) {
409 srr
.cont
= TtyEditor
.SROptions
.Cont
.All
;
410 (new EventEditorReplyReplacement(ed
.ed
, srr
)).post
;
413 with (new FuiButton(box
, "&Skip")) {
414 onAction
= (FuiControl self
) {
416 srr
.cont
= TtyEditor
.SROptions
.Cont
.No
;
417 (new EventEditorReplyReplacement(ed
.ed
, srr
)).post
;
420 with (new FuiButton(box
, "&Cancel")) {
422 onAction
= (FuiControl self
) {
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
);
438 //auto pt = FuiPoint(aed.ed.curx, aed.ed.cury);
439 auto pt
= FuiPoint(0, aed
.ed
.cury
);
440 pt
.y
-= aed
.ed
.topline
;
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
;
457 this (FuiControl aparent
, string atext
=null) {
458 this.connectListeners();
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
);
467 aligning
= Align
.Stretch
;
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;
485 res
.reserve(rng
.length
);
486 foreach (char ch
; rng
) res
~= ch
;
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));
497 ed
.dontSetCursor
= !focused
;
500 ed
.clrBlock
= palColor
!"inputmark"();
501 ed
.clrText
= palColor
!"input"();
502 ed
.clrTextUnchanged
= palColor
!"inputunchanged"();
504 ed
.clrBlock
= palColor
!"inputmark"();
505 ed
.clrText
= palColor
!"input"();
506 ed
.clrTextUnchanged
= palColor
!"inputunchanged"();
509 ed
.clrBlock
= palColor
!"disabled"();
510 ed
.clrText
= palColor
!"disabled"();
511 ed
.clrTextUnchanged
= palColor
!"disabled"();
516 void onMyEvent (FuiEventClick evt
) {
517 if (evt
.left
&& ed
.processClick(evt
.bidx
, evt
.pt
.x
, evt
.pt
.y
)) {
523 override void onMyEvent (FuiEventBlur evt
) {
525 super.onMyEvent(evt
);
528 void onMyEvent (FuiEventKey evt
) {
529 if (disabled
) return;
531 if (evt.key == "F1") {
533 VFile("zhelp.log", "w").writeln(ed.buildHelpText);
538 if (ed
.processKey(evt
.key
)) {
543 if (!evt
.key
.mouse
&& ed
.pasteMode
) { evt
.eat(); return; }