1 /*
2 * IceWM
3 *
4 * Copyright (C) 1997-2001 Marko Macek
5 */
6 #include "config.h"
7 #include "globit.h"
8 #include "yinputline.h"
9 #include "ymenu.h"
10 #include "yxapp.h"
11 #include "prefs.h"
12 #include "intl.h"
13 #include <X11/keysym.h>
14
15 class YInputMenu: public YMenu {
16 public:
YInputMenu()17 YInputMenu() {
18 addItem(_("_Copy"), -2, _("Ctrl+C"), actionCopy);
19 addItem(_("Cu_t"), -2, _("Ctrl+X"), actionCut);
20 addItem(_("_Paste"), -2, _("Ctrl+V"), actionPaste);
21 addItem(_("Paste _Selection"), -2, _("Ctrl+P"), actionPasteSelection);
22 addSeparator();
23 addItem(_("Select _All"), -2, _("Ctrl+A"), actionSelectAll);
24 }
~YInputMenu()25 ~YInputMenu() {
26 if (xapp->popup() == this) {
27 xapp->popdown(this);
28 }
29 }
30 };
31
YInputLine(YWindow * parent,YInputListener * listener)32 YInputLine::YInputLine(YWindow *parent, YInputListener *listener):
33 YWindow(parent),
34 markPos(0),
35 curPos(0),
36 leftOfs(0),
37 fAutoScrollDelta(0),
38 fHasFocus(false),
39 fCursorVisible(true),
40 fSelecting(false),
41 fBlinkTime(333),
42 fListener(listener),
43 inputFont(inputFontName),
44 inputBg(&clrInput),
45 inputFg(&clrInputText),
46 inputSelectionBg(&clrInputSelection),
47 inputSelectionFg(&clrInputSelectionText)
48 {
49 addStyle(wsNoExpose);
50 if (inputFont != null)
51 setSize(width(), inputFont->height() + 2);
52 }
53
~YInputLine()54 YInputLine::~YInputLine() {
55 }
56
setText(mstring text,bool asMarked)57 void YInputLine::setText(mstring text, bool asMarked) {
58 fText = text;
59 leftOfs = 0;
60 curPos = fText.length();
61 markPos = asMarked ? 0 : curPos;
62 limit();
63 repaint();
64 }
65
getText()66 mstring YInputLine::getText() {
67 return fText;
68 }
69
repaint()70 void YInputLine::repaint() {
71 if (width() > 1) {
72 GraphicsBuffer(this).paint();
73 }
74 }
75
configure(const YRect2 & r)76 void YInputLine::configure(const YRect2& r) {
77 if (r.resized()) {
78 repaint();
79 }
80 }
81
paint(Graphics & g,const YRect &)82 void YInputLine::paint(Graphics &g, const YRect &/*r*/) {
83 YFont font = inputFont;
84 int min, max, minOfs = 0, maxOfs = 0;
85 int textLen = fText.length();
86
87 if (curPos > markPos) {
88 min = markPos;
89 max = curPos;
90 } else {
91 min = curPos;
92 max = markPos;
93 }
94
95 if (curPos == markPos || fText == null || font == null || !fHasFocus) {
96 g.setColor(inputBg);
97 g.fillRect(0, 0, width(), height());
98 } else {
99 minOfs = font->textWidth(fText.data(), min) - leftOfs;
100 maxOfs = font->textWidth(fText.data(), max) - leftOfs;
101
102 if (minOfs > 0) {
103 g.setColor(inputBg);
104 g.fillRect(0, 0, minOfs, height());
105 }
106 /// !!! optimize (0, width)
107 if (minOfs < maxOfs) {
108 g.setColor(inputSelectionBg);
109 g.fillRect(minOfs, 0, maxOfs - minOfs, height());
110 }
111 if (maxOfs < int(width())) {
112 g.setColor(inputBg);
113 g.fillRect(maxOfs, 0, width() - maxOfs, height());
114 }
115 }
116
117 if (font != null) {
118 int yo = ::max(0, (int(height()) - int(font->height())) / 2);
119 int yp = font->ascent() + yo;
120 int curOfs = font->textWidth(fText.data(), curPos);
121 int cx = ::max(1, curOfs - leftOfs);
122
123 g.setFont(font);
124
125 if (curPos == markPos || !fHasFocus || fText == null) {
126 g.setColor(inputFg);
127 if (fText != null)
128 g.drawChars(fText.data(), 0, textLen, -leftOfs, yp);
129 if (fHasFocus && fCursorVisible)
130 g.drawLine(cx, yo, cx, font->height() + 2);
131 } else {
132 if (min > 0) {
133 g.setColor(inputFg);
134 g.drawChars(fText.data(), 0, min, -leftOfs, yp);
135 }
136 /// !!! same here
137 if (min < max) {
138 g.setColor(inputSelectionFg);
139 g.drawChars(fText.data(), min, max - min, minOfs, yp);
140 }
141 if (max < textLen) {
142 g.setColor(inputFg);
143 g.drawChars(fText.data(), max, textLen - max, maxOfs, yp);
144 }
145 }
146 }
147 }
148
handleKey(const XKeyEvent & key)149 bool YInputLine::handleKey(const XKeyEvent &key) {
150 if (key.type == KeyPress) {
151 KeySym k = keyCodeToKeySym(key.keycode);
152
153 switch (k) {
154 case XK_KP_Home:
155 case XK_KP_Up:
156 case XK_KP_Prior:
157 case XK_KP_Left:
158 case XK_KP_Begin:
159 case XK_KP_Right:
160 case XK_KP_End:
161 case XK_KP_Down:
162 case XK_KP_Next:
163 case XK_KP_Insert:
164 case XK_KP_Delete:
165 if (key.state & xapp->NumLockMask) {
166 k = keyCodeToKeySym(key.keycode, 1);
167 }
168 break;
169 }
170
171 int m = KEY_MODMASK(key.state);
172 bool extend = (m & ShiftMask) ? true : false;
173 unsigned textLen = fText.length();
174
175 if (m & ControlMask) {
176 switch(k) {
177 case 'A':
178 case 'a':
179 case '/':
180 selectAll();
181 return true;
182
183 case '\\':
184 unselectAll();
185 return true;
186 case 'u':
187 case 'U':
188 if ( !deleteSelection())
189 deleteToBegin();
190 return true;
191 case 'v':
192 case 'V':
193 requestSelection(false);
194 return true;
195 case 'w':
196 case 'W':
197 if ( !deleteSelection())
198 deletePreviousWord();
199 return true;
200 case 'X':
201 case 'x':
202 cutSelection();
203 return true;
204 case 'c':
205 case 'C':
206 case XK_Insert:
207 case XK_KP_Insert:
208 copySelection();
209 return true;
210 case 'i':
211 case 'I':
212 complete();
213 return true;
214 }
215 }
216 if (m & ShiftMask) {
217 switch (k) {
218 case XK_Insert:
219 case XK_KP_Insert:
220 requestSelection(false);
221 return true;
222 case XK_Delete:
223 case XK_KP_Delete:
224 cutSelection();
225 break;
226 }
227 }
228 switch (k) {
229 case XK_Left:
230 case XK_KP_Left:
231 if (m & ControlMask) {
232 unsigned p = prevWord(curPos, false);
233 if (p != curPos) {
234 if (move(p, extend))
235 return true;
236 }
237 } else {
238 if (curPos > 0) {
239 if (move(curPos - 1, extend))
240 return true;
241 }
242 }
243 break;
244 case XK_Right:
245 case XK_KP_Right:
246 if (m & ControlMask) {
247 unsigned p = nextWord(curPos, false);
248 if (p != curPos) {
249 if (move(p, extend))
250 return true;
251 }
252 } else {
253 if (curPos < textLen) {
254 if (move(curPos + 1, extend))
255 return true;
256 }
257 }
258 break;
259 case XK_Home:
260 case XK_KP_Home:
261 move(0, extend);
262 return true;
263 case XK_End:
264 case XK_KP_End:
265 move(textLen, extend);
266 return true;
267 case XK_Delete:
268 case XK_KP_Delete:
269 case XK_BackSpace:
270 if (hasSelection()) {
271 if (deleteSelection())
272 return true;
273 } else {
274 switch (k) {
275 case XK_Delete:
276 case XK_KP_Delete:
277 if (m & ControlMask) {
278 if (m & ShiftMask) {
279 if (deleteToEnd())
280 return true;
281 } else {
282 if (deleteNextWord())
283 return true;
284 }
285 } else {
286 if (deleteNextChar())
287 return true;
288 }
289 break;
290 case XK_BackSpace:
291 if (m & ControlMask) {
292 if (m & ShiftMask) {
293 if (deleteToBegin())
294 return true;
295 } else {
296 if (deletePreviousWord())
297 return true;
298 }
299 } else {
300 if (deletePreviousChar())
301 return true;
302 }
303 break;
304 }
305 }
306 break;
307 case XK_Tab:
308 complete();
309 break;
310 default:
311 if (fListener &&
312 ((k == XK_Return && m == 0) ||
313 (k == XK_KP_Enter && m == 0) ||
314 (k == XK_j && m == ControlMask) ||
315 (k == XK_m && m == ControlMask)))
316 {
317 fListener->inputReturn(this);
318 return true;
319 }
320 else if (fListener && (k == XK_Escape && m == 0))
321 {
322 fListener->inputEscape(this);
323 return true;
324 }
325 else
326 {
327 char s[16];
328
329 if (getCharFromEvent(key, s, sizeof(s))) {
330 replaceSelection(s, strlen(s));
331 return true;
332 }
333 }
334 }
335 }
336 return YWindow::handleKey(key);
337 }
338
handleButton(const XButtonEvent & button)339 void YInputLine::handleButton(const XButtonEvent &button) {
340 if (button.type == ButtonPress) {
341 if (button.button == 1) {
342 if (fHasFocus == false) {
343 setWindowFocus();
344 requestFocus(false);
345 } else {
346 fSelecting = true;
347 curPos = markPos = offsetToPos(button.x + leftOfs);
348 limit();
349 repaint();
350 }
351 }
352 } else if (button.type == ButtonRelease) {
353 autoScroll(0, nullptr);
354 if (fSelecting && button.button == 1) {
355 fSelecting = false;
356 //curPos = offsetToPos(button.x + leftOfs);
357 //limit();
358 repaint();
359 }
360 }
361 YWindow::handleButton(button);
362 }
363
handleMotion(const XMotionEvent & motion)364 void YInputLine::handleMotion(const XMotionEvent &motion) {
365 if (fSelecting && (motion.state & Button1Mask)) {
366 if (motion.x < 0)
367 autoScroll(-8, &motion); // fix
368 else if (motion.x >= int(width()))
369 autoScroll(8, &motion); // fix
370 else {
371 autoScroll(0, &motion);
372 unsigned c = offsetToPos(motion.x + leftOfs);
373 if (getClickCount() == 2) {
374 if (c >= markPos) {
375 if (markPos > curPos)
376 markPos = curPos;
377 c = nextWord(c, true);
378 } else if (c < markPos) {
379 if (markPos < curPos)
380 markPos = curPos;
381 c = prevWord(c, true);
382 }
383 }
384 if (curPos != c) {
385 curPos = c;
386 limit();
387 repaint();
388 }
389 }
390 }
391 YWindow::handleMotion(motion);
392 }
393
handleClickDown(const XButtonEvent & down,int count)394 void YInputLine::handleClickDown(const XButtonEvent &down, int count) {
395 if (down.button == 1) {
396 if ((count % 4) == 2) {
397 unsigned l = prevWord(curPos, true);
398 unsigned r = nextWord(curPos, true);
399 if (l != markPos || r != curPos) {
400 markPos = l;
401 curPos = r;
402 limit();
403 repaint();
404 }
405 } else if ((count % 4) == 3) {
406 markPos = curPos = 0;
407 curPos = fText.length();
408 fSelecting = false;
409 limit();
410 repaint();
411 }
412 }
413 }
414
handleClick(const XButtonEvent & up,int)415 void YInputLine::handleClick(const XButtonEvent &up, int /*count*/) {
416 if (up.button == 3 && xapp->isButton(up.state, Button3Mask)) {
417 inputMenu->setActionListener(this);
418 inputMenu->popup(this, nullptr, this, up.x_root, up.y_root,
419 YPopupWindow::pfCanFlipVertical |
420 YPopupWindow::pfCanFlipHorizontal);
421 inputMenu->setPopDownListener(this);
422 } else if (up.button == 2 && xapp->isButton(up.state, Button2Mask)) {
423 requestSelection(true);
424 }
425 }
426
handleSelection(const XSelectionEvent & selection)427 void YInputLine::handleSelection(const XSelectionEvent &selection) {
428 if (selection.property != None) {
429 YProperty prop(selection.requestor, selection.property,
430 F8, 32 * 1024, selection.target, True);
431 if (prop) {
432 replaceSelection(prop.data<char>(), prop.size());
433 }
434 }
435 }
436
offsetToPos(int offset)437 unsigned YInputLine::offsetToPos(int offset) {
438 YFont font = inputFont;
439 int ofs = 0, pos = 0;
440 int textLen = fText.length();
441
442 if (font != null) {
443 while (pos < textLen) {
444 ofs += font->textWidth(fText.data() + pos, 1);
445 if (ofs < offset)
446 pos++;
447 else
448 break;
449 }
450 }
451 return pos;
452 }
453
handleFocus(const XFocusChangeEvent & focus)454 void YInputLine::handleFocus(const XFocusChangeEvent &focus) {
455 if (focus.type == FocusIn /* && fHasFocus == false*/
456 && focus.detail != NotifyPointer
457 && focus.detail != NotifyPointerRoot)
458 {
459 fHasFocus = true;
460 selectAll();
461 cursorBlinkTimer->setTimer(fBlinkTime, this, true);
462 }
463 else if (focus.type == FocusOut/* && fHasFocus == true*/) {
464 fHasFocus = false;
465 repaint();
466 cursorBlinkTimer = null;
467 if (inputMenu && inputMenu == xapp->popup()) {
468 }
469 else if (fListener) {
470 fListener->inputLostFocus(this);
471 }
472 }
473 }
474
handlePopDown(YPopupWindow * popup)475 void YInputLine::handlePopDown(YPopupWindow *popup) {
476 inputMenu = null;
477 }
478
handleAutoScroll(const XMotionEvent &)479 bool YInputLine::handleAutoScroll(const XMotionEvent & /*mouse*/) {
480 curPos += fAutoScrollDelta;
481 leftOfs += fAutoScrollDelta;
482 int c = curPos;
483
484 if (fAutoScrollDelta < 0)
485 c = offsetToPos(leftOfs);
486 else if (fAutoScrollDelta > 0)
487 c = offsetToPos(leftOfs + width());
488 curPos = c;
489 limit();
490 repaint();
491 return true;
492 }
493
handleTimer(YTimer * t)494 bool YInputLine::handleTimer(YTimer *t) {
495 if (t == cursorBlinkTimer) {
496 fCursorVisible ^= true;
497 repaint();
498 return fHasFocus;
499 }
500 return false;
501 }
502
move(unsigned pos,bool extend)503 bool YInputLine::move(unsigned pos, bool extend) {
504 unsigned textLen = fText.length();
505
506 if (curPos > textLen)
507 return false;
508
509 if (curPos != pos || (!extend && curPos != markPos)) {
510
511 curPos = pos;
512 if (!extend)
513 markPos = curPos;
514
515 limit();
516 repaint();
517 }
518 return true;
519 }
520
limit()521 void YInputLine::limit() {
522 unsigned textLen = fText.length();
523
524 if (curPos > textLen)
525 curPos = textLen;
526 if (markPos > textLen)
527 markPos = textLen;
528
529 YFont font = inputFont;
530 if (font != null) {
531 int curOfs = font->textWidth(fText.data(), curPos);
532 int curLen = font->textWidth(fText.data(), textLen);
533
534 if (curOfs >= leftOfs + int(width()) + 1)
535 leftOfs = curOfs - width() + 2;
536 if (curOfs < leftOfs)
537 leftOfs = curOfs;
538 if (leftOfs + int(width()) + 1 > curLen)
539 leftOfs = curLen - width() + 1;
540 if (leftOfs < 0)
541 leftOfs = 0;
542 }
543 }
544
replaceSelection(const char * insert,int amount)545 void YInputLine::replaceSelection(const char* insert, int amount) {
546 unsigned from = min(curPos, markPos);
547 unsigned to = max(curPos, markPos);
548 YWideString wide(insert, amount);
549 fText.replace(from, to - from, wide);
550 curPos = markPos = from + wide.length();
551 limit();
552 repaint();
553 }
554
deleteSelection()555 bool YInputLine::deleteSelection() {
556 if (hasSelection()) {
557 replaceSelection("", 0);
558 return true;
559 }
560 return false;
561 }
562
deleteNextChar()563 bool YInputLine::deleteNextChar() {
564 unsigned textLen = fText.length();
565
566 if (curPos < textLen) {
567 markPos = curPos + (curPos < UINT_MAX);
568 deleteSelection();
569 return true;
570 }
571 return false;
572 }
573
deletePreviousChar()574 bool YInputLine::deletePreviousChar() {
575 if (curPos > 0) {
576 markPos = curPos - 1;
577 deleteSelection();
578 return true;
579 }
580 return false;
581 }
582
583 #define CHCLASS(c) ((c) == ' ')
584
nextWord(unsigned p,bool sep)585 unsigned YInputLine::nextWord(unsigned p, bool sep) {
586 unsigned textLen = fText.length();
587
588 while (p < textLen &&
589 (CHCLASS(fText.charAt(p)) == CHCLASS(fText.charAt(p + 1)) ||
590 (!sep && CHCLASS(fText.charAt(p)))))
591 {
592 p++;
593 }
594 if (p < textLen)
595 p++;
596 return p;
597 }
598
prevWord(unsigned p,bool sep)599 unsigned YInputLine::prevWord(unsigned p, bool sep) {
600 if (p > 0 && !sep)
601 p--;
602 while (p > 0 &&
603 (CHCLASS(fText.charAt(p)) == CHCLASS(fText.charAt(p - 1)) ||
604 (!sep && CHCLASS(fText.charAt(p)))))
605 {
606 p--;
607 }
608 return p;
609 }
610
deleteNextWord()611 bool YInputLine::deleteNextWord() {
612 unsigned p = nextWord(curPos, false);
613 if (p != curPos) {
614 markPos = p;
615 return deleteSelection();
616 }
617 return false;
618 }
619
deletePreviousWord()620 bool YInputLine::deletePreviousWord() {
621 unsigned p = prevWord(curPos, false);
622 if (p != curPos) {
623 markPos = p;
624 return deleteSelection();
625 }
626 return false;
627 }
628
deleteToEnd()629 bool YInputLine::deleteToEnd() {
630 unsigned textLen = fText.length();
631
632 if (curPos < textLen) {
633 markPos = textLen;
634 return deleteSelection();
635 }
636 return false;
637 }
638
deleteToBegin()639 bool YInputLine::deleteToBegin() {
640 if (curPos > 0) {
641 markPos = 0;
642 return deleteSelection();
643 }
644 return false;
645 }
646
selectAll()647 void YInputLine::selectAll() {
648 markPos = curPos = 0;
649 curPos = fText.length();
650 fSelecting = false;
651 limit();
652 repaint();
653 }
654
unselectAll()655 void YInputLine::unselectAll() {
656 fSelecting = false;
657 if (markPos != curPos) {
658 markPos = curPos;
659 repaint();
660 }
661 }
662
cutSelection()663 bool YInputLine::cutSelection() {
664 return copySelection() && deleteSelection();
665 }
666
copySelection()667 bool YInputLine::copySelection() {
668 unsigned min = ::min(curPos, markPos), max = ::max(curPos, markPos);
669 if (min < max && fText.length() <= max) {
670 YWideString copy(fText.copy(min, max - min));
671 xapp->setClipboardText(copy);
672 return true;
673 } else {
674 return false;
675 }
676 }
677
actionPerformed(YAction action,unsigned int)678 void YInputLine::actionPerformed(YAction action, unsigned int /*modifiers*/) {
679 if (action == actionSelectAll)
680 selectAll();
681 else if (action == actionPaste)
682 requestSelection(false);
683 else if (action == actionPasteSelection)
684 requestSelection(true);
685 else if (action == actionCopy)
686 copySelection();
687 else if (action == actionCut)
688 cutSelection();
689 }
690
autoScroll(int delta,const XMotionEvent * motion)691 void YInputLine::autoScroll(int delta, const XMotionEvent *motion) {
692 fAutoScrollDelta = delta;
693 beginAutoScroll(delta ? true : false, motion);
694 }
695
complete()696 void YInputLine::complete() {
697 char* res = nullptr;
698 mstring mstr(fText);
699 int res_count = globit_best(mstr, &res, nullptr, nullptr);
700 // directory is not a final match
701 if (res_count == 1 && upath(res).dirExists())
702 res_count++;
703 if (1 <= res_count)
704 setText(res, res_count == 1);
705 free(res);
706 }
707
isFocusTraversable()708 bool YInputLine::isFocusTraversable() {
709 return true;
710 }
711
gotFocus()712 void YInputLine::gotFocus() {
713 if (fHasFocus == false) {
714 fHasFocus = true;
715 fCursorVisible = true;
716 cursorBlinkTimer->setTimer(fBlinkTime, this, true);
717 repaint();
718 }
719 }
720
lostFocus()721 void YInputLine::lostFocus() {
722 if (cursorBlinkTimer) {
723 cursorBlinkTimer = null;
724 fHasFocus = false;
725 repaint();
726 }
727 }
728
729 // vim: set sw=4 ts=4 et:
730