1 /*
2  * This file is part of the Colobot: Gold Edition source code
3  * Copyright (C) 2001-2020, Daniel Roux, EPSITEC SA & TerranovaTeam
4  * http://epsitec.ch; http://colobot.info; http://github.com/colobot
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
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.
14  * See the GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program. If not, see http://gnu.org/licenses
18  */
19 
20 
21 #include "ui/controls/edit.h"
22 
23 #include "common/config.h"
24 
25 #include "app/app.h"
26 #include "app/input.h"
27 
28 #include "common/logger.h"
29 #include "common/make_unique.h"
30 
31 #include "common/resources/inputstream.h"
32 #include "common/resources/outputstream.h"
33 
34 #include "graphics/engine/engine.h"
35 
36 #include "level/parser/parser.h"
37 
38 #include "script/script.h"
39 
40 #include "ui/controls/scroll.h"
41 
42 #include <SDL.h>
43 #include <boost/algorithm/string.hpp>
44 
45 #include <cstring>
46 
47 namespace Ui
48 {
49 namespace
50 {
51 const float MARGX           = (3.75f/640.0f);
52 const float MARGY           = (3.75f/480.0f);
53 const float MARGYS          = (2.75f/480.0f);
54 const float MARGY1          = (1.0f/480.0f);
55 //! time limit for double-click
56 const float DELAY_DBCLICK   = 0.75f;
57 //! time limit for scroll
58 const float DELAY_SCROLL    = 0.1f;
59 //! expansion for \b;
60 const float BIG_FONT        = 1.6f;
61 
62 
63 
64 //! Indicates whether a character is a space.
65 
IsSpace(int character)66 bool IsSpace(int character)
67 {
68     return ( character == ' '  ||
69              character == '\t' ||
70              character == '\n' );
71 }
72 
73 //! Indicates whether a character is part of a word.
74 
IsWord(char c)75 bool IsWord(char c)
76 {
77     return ( isalnum(c) || c == '_');
78 }
79 
80 //! Indicates whether a character is a word separator.
81 
IsSep(int character)82 bool IsSep(int character)
83 {
84     if ( IsSpace(character) )  return false;
85     return !IsWord(character);
86 }
87 
IsBreaker(char c)88 bool IsBreaker(char c)
89 {
90     return ( c == '.'  || c == '{' || c == '}' ||
91              c == ';' || c == ':' || c == '[' || c == ']' ||
92              c == '(' || c == ')' || c == '=' || c == '"' || c == '\'' );
93 }
94 
IsDelimiter(char c)95 bool IsDelimiter(char c)
96 {
97     return IsSpace( c ) || IsBreaker( c );
98 }
99 
100 } // namespace
101 
102 //! Object's constructor.
CEdit()103 CEdit::CEdit()
104     : CControl(),
105       m_maxChar( std::numeric_limits<int>::max() ),
106       m_text(),
107       m_lineOffset(),
108       m_lineIndent()
109 {
110     m_len = 0;
111 
112     m_fontType = Gfx::FONT_STUDIO;
113     m_bEdit         = true;
114     m_bHilite       = true;
115     m_bInsideScroll = true;
116     m_bCapture      = false;
117     m_bDisplaySpec  = false;
118     m_bSoluce       = false;
119     m_bGeneric      = false;
120     m_bAutoIndent   = false;
121     m_cursor1       = 0;
122     m_cursor2       = 0;
123     m_column        = 0;
124 
125     m_timeLastScroll = 0.0f;
126     m_timeBlink = 0.0f;
127     m_time = 0.0f;
128     m_historyCurrent = 0;
129     m_bMulti = false;
130     m_lineDescent = 0.0f;
131     m_timeLastClick = 0.0f;
132     m_bMultiFont = false;
133     m_lineAscent = 0.0f;
134     m_historyTotal = 0;
135     m_lineTotal = 0;
136     m_lineHeight = 0.0f;
137     m_lineVisible = 0;
138     m_lineFirst = 0;
139 
140     HyperFlush();
141 
142     m_bUndoForce = true;
143     m_undoOper = OPERUNDO_SPEC;
144 }
145 
146 // Object's destructor.
147 
~CEdit()148 CEdit::~CEdit()
149 {
150     FreeImage();
151 
152     if (m_bFocus)
153     {
154         CApplication::GetInstancePointer()->SetTextInput(false, m_eventType);
155     }
156 }
157 
158 
159 // Creates a new editable line.
160 
Create(Math::Point pos,Math::Point dim,int icon,EventType eventType)161 bool CEdit::Create(Math::Point pos, Math::Point dim, int icon, EventType eventType)
162 {
163     if ( eventType == EVENT_NULL )  eventType = GetUniqueEventType();
164     CControl::Create(pos, dim, icon, eventType);
165 
166     m_len = 0;
167     m_lineFirst = 0;
168     m_time = 0.0f;
169     m_timeBlink = 0.0f;
170     m_timeLastClick = 0.0f;
171     m_timeLastScroll = 0.0f;
172 
173     m_bMulti = false;
174     MoveAdjust();
175     if ( m_lineVisible <= 1 )
176     {
177         m_bMulti = false;
178     }
179     else
180     {
181         m_bMulti = true;
182         MoveAdjust();  // readjusts multi-line mode
183         m_scroll = MakeUnique<Ui::CScroll>();
184         m_scroll->Create(pos, dim, -1, EVENT_NULL);
185         MoveAdjust();
186     }
187 
188     return true;
189 }
190 
191 
SetPos(Math::Point pos)192 void CEdit::SetPos(Math::Point pos)
193 {
194     CControl::SetPos(pos);
195     MoveAdjust();
196 }
197 
SetDim(Math::Point dim)198 void CEdit::SetDim(Math::Point dim)
199 {
200     CControl::SetDim(dim);
201     MoveAdjust();
202 }
203 
MoveAdjust()204 void CEdit::MoveAdjust()
205 {
206     Math::Point     pos, dim;
207     float       height;
208 
209     m_lineDescent = m_engine->GetText()->GetDescent(m_fontType, m_fontSize);
210     m_lineAscent  = m_engine->GetText()->GetAscent(m_fontType, m_fontSize);
211     m_lineHeight  = m_engine->GetText()->GetHeight(m_fontType, m_fontSize);
212 
213     height = m_dim.y-(m_bMulti?MARGY*2.0f:MARGY1);
214     m_lineVisible = static_cast<int>((height/m_lineHeight));
215 
216     if (m_scroll != nullptr)
217     {
218         if ( m_bInsideScroll )
219         {
220             pos.x = m_pos.x + m_dim.x - MARGX-SCROLL_WIDTH;
221             pos.y = m_pos.y + MARGYS;
222             dim.x = SCROLL_WIDTH;
223             dim.y = m_dim.y - MARGYS*2.0f;
224         }
225         else
226         {
227             pos.x = m_pos.x + m_dim.x - SCROLL_WIDTH;
228             pos.y = m_pos.y;
229             dim.x = SCROLL_WIDTH;
230             dim.y = m_dim.y;
231         }
232         m_scroll->SetPos(pos);
233         m_scroll->SetDim(dim);
234     }
235 
236     Justif();
237 
238     if ( m_lineFirst > m_lineTotal-m_lineVisible )
239     {
240         m_lineFirst = m_lineTotal-m_lineVisible;
241         if ( m_lineFirst < 0 )  m_lineFirst = 0;
242     }
243 
244     pos.x = m_pos.x+m_dim.x-(m_bMulti?SCROLL_WIDTH:0.0f);
245     pos.y = m_pos.y;
246     GlintCreate(pos, false, false);
247 }
248 
249 
250 // Management of an event.
251 
EventProcess(const Event & event)252 bool CEdit::EventProcess(const Event &event)
253 {
254     bool    bShift = false, bControl = false;
255 
256     if ( (m_state & STATE_VISIBLE) == 0 )  return true;
257 
258     if (event.type == EVENT_MOUSE_WHEEL &&
259         Detect(event.mousePos))
260     {
261         auto data = event.GetData<MouseWheelEventData>();
262         Scroll(m_lineFirst - data->y, true);
263         return false;
264     }
265 
266     CControl::EventProcess(event);
267 
268     if ( event.type == EVENT_FRAME )
269     {
270         m_time += event.rTime;
271         m_timeBlink += event.rTime;
272     }
273 
274     if ( event.type == EVENT_MOUSE_MOVE || event.type == EVENT_MOUSE_BUTTON_DOWN || event.type == EVENT_MOUSE_BUTTON_UP )
275     {
276         if ( Detect(event.mousePos) &&
277              event.mousePos.x < m_pos.x+m_dim.x-(m_bMulti?MARGX+SCROLL_WIDTH:0.0f) )
278         {
279             if ( m_bEdit )
280             {
281                 m_engine->SetMouseType(Gfx::ENG_MOUSE_EDIT);
282             }
283             else
284             {
285                 if ( IsLinkPos(event.mousePos) )
286                 {
287                     m_engine->SetMouseType(Gfx::ENG_MOUSE_HAND);
288                 }
289                 else
290                 {
291                     m_engine->SetMouseType(Gfx::ENG_MOUSE_NORM);
292                 }
293             }
294         }
295     }
296 
297     if (m_scroll != nullptr && !m_bGeneric)
298     {
299         m_scroll->EventProcess(event);
300 
301         if (event.type == m_scroll->GetEventType())
302         {
303             Scroll();
304             return true;
305         }
306     }
307 
308     if (event.type == EVENT_KEY_DOWN)
309     {
310         bShift   = ( (event.kmodState & KEY_MOD(SHIFT) ) != 0 );
311         #if PLATFORM_MACOSX
312         bControl = ( (event.kmodState & KEY_MOD(GUI) ) != 0);
313         #else
314         bControl = ( (event.kmodState & KEY_MOD(CTRL) ) != 0);
315         #endif
316     }
317 
318     if ( event.type == EVENT_KEY_DOWN && m_bFocus )
319     {
320         auto data = event.GetData<KeyEventData>();
321 
322         if ( (data->key == KEY(x)      && !bShift &&  bControl) ||
323              (data->key == KEY(DELETE) &&  bShift && !bControl) )
324         {
325             Cut();
326             return true;
327         }
328         if ( (data->key == KEY(c)      && !bShift &&  bControl) ||
329              (data->key == KEY(INSERT) && !bShift &&  bControl) )
330         {
331             Copy();
332             return true;
333         }
334         if ( (data->key == KEY(v)      && !bShift &&  bControl) ||
335              (data->key == KEY(INSERT) &&  bShift && !bControl) )
336         {
337             Paste();
338             return true;
339         }
340 
341         if ( data->key == KEY(a) && !bShift && bControl )
342         {
343             SetCursor(999999, 0);
344             return true;
345         }
346 
347         if ( data->key == KEY(o) && !bShift && bControl )
348         {
349             m_event->AddEvent(Event(EVENT_STUDIO_OPEN));
350         }
351         if ( data->key == KEY(s) && !bShift && bControl )
352         {
353             m_event->AddEvent(Event(EVENT_STUDIO_SAVE));
354         }
355 
356         if ( data->key == KEY(z) && !bShift && bControl )
357         {
358             Undo();
359             return true;
360         }
361 
362         if ( data->key == KEY(u) && !bShift && bControl )
363         {
364             if ( MinMaj(false) )  return true;
365         }
366         if ( data->key == KEY(u) && bShift && bControl )
367         {
368             if ( MinMaj(true) )  return true;
369         }
370 
371         if ( data->key == KEY(TAB) && !bShift && !bControl && !m_bAutoIndent )
372         {
373             if ( Shift(false) )  return true;
374         }
375         if ( data->key == KEY(TAB) && bShift && !bControl && !m_bAutoIndent )
376         {
377             if ( Shift(true) )  return true;
378         }
379 
380         if ( m_bEdit )
381         {
382             if ( data->key == KEY(LEFT) )
383             {
384                 MoveChar(-1, bControl, bShift);
385                 return true;
386             }
387             if ( data->key == KEY(RIGHT) )
388             {
389                 MoveChar(1, bControl, bShift);
390                 return true;
391             }
392             if ( data->key == KEY(UP) && m_bMulti )
393             {
394                 MoveLine(-1, bControl, bShift);
395                 return true;
396             }
397             if ( data->key == KEY(DOWN) && m_bMulti )
398             {
399                 MoveLine(1, bControl, bShift);
400                 return true;
401             }
402 
403             if ( data->key == KEY(PAGEUP) && m_bMulti )  // PageUp ?
404             {
405                 MoveLine(-(m_lineVisible-1), bControl, bShift);
406                 return true;
407             }
408             if ( data->key == KEY(PAGEDOWN) && m_bMulti )  // PageDown ?
409             {
410                 MoveLine(m_lineVisible-1, bControl, bShift);
411                 return true;
412             }
413         }
414         else
415         {
416             if ( data->key == KEY(LEFT) ||
417                  data->key == KEY(UP)   )
418             {
419                 Scroll(m_lineFirst-1, true);
420                 return true;
421             }
422             if ( data->key == KEY(RIGHT) ||
423                  data->key == KEY(DOWN)  )
424             {
425                 Scroll(m_lineFirst+1, true);
426                 return true;
427             }
428 
429             if ( data->key == KEY(PAGEUP) )  // PageUp ?
430             {
431                 Scroll(m_lineFirst-(m_lineVisible-1), true);
432                 return true;
433             }
434             if ( data->key == KEY(PAGEDOWN) )  // PageDown ?
435             {
436                 Scroll(m_lineFirst+(m_lineVisible-1), true);
437                 return true;
438             }
439         }
440 
441         if ( data->key == KEY(HOME) )
442         {
443             MoveHome(bControl, bShift);
444             return true;
445         }
446         if ( data->key == KEY(END) )
447         {
448             MoveEnd(bControl, bShift);
449             return true;
450         }
451 
452         if ( data->key == KEY(BACKSPACE) && !bControl )  // backspace ( <- ) ?
453         {
454             Delete(-1);
455             SendModifEvent();
456             return true;
457         }
458         if ( data->key == KEY(DELETE) && !bControl )
459         {
460             Delete(1);
461             SendModifEvent();
462             return true;
463         }
464 
465         if ( data->key == KEY(BACKSPACE) && bControl )
466         {
467             DeleteWord(-1);
468             SendModifEvent();
469             return true;
470         }
471         if ( data->key == KEY(DELETE) && bControl )
472         {
473             DeleteWord(1);
474             SendModifEvent();
475             return true;
476         }
477 
478         if ( data->key == KEY(RETURN) && !bControl )
479         {
480             Insert('\n');
481             SendModifEvent();
482             return true;
483         }
484         if ( data->key == KEY(TAB) && !bControl )
485         {
486             Insert('\t');
487             SendModifEvent();
488             return true;
489         }
490     }
491 
492     if ( event.type == EVENT_TEXT_INPUT && !bControl && m_bFocus )
493     {
494         auto data = event.GetData<TextInputData>();
495         Insert(data->text[0]); // TODO: insert utf-8 char
496         SendModifEvent();
497         return true;
498     }
499 
500     if ( event.type == EVENT_FOCUS )
501     {
502         if ( event.customParam == m_eventType )
503         {
504             m_bFocus = true;
505             UpdateFocus();
506         }
507         else
508         {
509             m_bFocus = false;
510             UpdateFocus();
511         }
512     }
513 
514     if (event.type == EVENT_MOUSE_BUTTON_DOWN &&
515         event.GetData<MouseButtonEventData>()->button == MOUSE_BUTTON_LEFT)
516     {
517         m_mouseFirstPos = event.mousePos;
518         m_mouseLastPos  = event.mousePos;
519         if ( Detect(event.mousePos) )
520         {
521             if ( event.mousePos.x < m_pos.x+m_dim.x-(m_bMulti?MARGX+SCROLL_WIDTH:0.0f) )
522             {
523                 MouseClick(event.mousePos);
524                 if ( m_bEdit || m_bHilite )  m_bCapture = true;
525             }
526 
527             if (!m_bFocus)
528             {
529                 m_bFocus = true;
530                 UpdateFocus();
531             }
532         }
533         else
534         {
535             if (m_bFocus)
536             {
537                 m_bFocus = false;
538                 UpdateFocus();
539             }
540         }
541     }
542 
543     if ( event.type == EVENT_MOUSE_MOVE && m_bCapture )
544     {
545         m_mouseLastPos = event.mousePos;
546         MouseMove(event.mousePos);
547     }
548 
549     if ( event.type == EVENT_FRAME && m_bCapture )
550     {
551         MouseMove(m_mouseLastPos);
552     }
553 
554     if (event.type == EVENT_MOUSE_BUTTON_UP &&
555         event.GetData<MouseButtonEventData>()->button == MOUSE_BUTTON_LEFT)
556     {
557         if ( Detect(event.mousePos) )
558         {
559             if ( event.mousePos.x < m_pos.x+m_dim.x-(m_bMulti?MARGX+SCROLL_WIDTH:0.0f) )
560             {
561                 MouseRelease(m_mouseFirstPos);
562             }
563         }
564         if ( m_bCapture )
565         {
566             if ( m_timeLastClick+DELAY_DBCLICK > m_time )  // double-click ?
567             {
568                 MouseDoubleClick(event.mousePos);
569             }
570             m_timeLastClick = m_time;
571             m_bCapture = false;
572         }
573     }
574 
575     return true;
576 }
577 
578 
579 // Sends an event to indicate that the text was modified.
580 
SendModifEvent()581 void CEdit::SendModifEvent()
582 {
583     m_event->AddEvent(Event(m_eventType));
584 }
585 
586 
587 // Detects whether the mouse is over a hyperlink character.
588 
IsLinkPos(Math::Point pos)589 bool CEdit::IsLinkPos(Math::Point pos)
590 {
591     int     i;
592 
593     if ( m_format.empty() )  return false;
594 
595     i = MouseDetect(pos);
596     if ( i == -1 )  return false;
597     if ( i >= m_len )  return false;
598 
599     if ( m_format.size() > static_cast<unsigned int>(i) && ((m_format[i] & Gfx::FONT_MASK_LINK) != 0))  return true; // TODO
600     return false;
601 }
602 
603 
604 // Positions the cursor after a double click.
605 
MouseDoubleClick(Math::Point mouse)606 void CEdit::MouseDoubleClick(Math::Point mouse)
607 {
608     int     i, character;
609 
610     if ( m_bMulti )  // Multi-line?
611     {
612         i = MouseDetect(mouse);
613         if ( i == -1 )  return;
614 
615         while ( i > 0 )
616         {
617             character = static_cast<unsigned char>(m_text[i-1]);
618             if ( !IsWord(character) )  break;
619             i --;
620         }
621         m_cursor2 = i;
622 
623         while ( i < m_len )
624         {
625             character = static_cast<unsigned char>(m_text[i]);
626             if ( !IsWord(character) )  break;
627             i ++;
628         }
629         m_cursor1 = i;
630     }
631     else    // single-line?
632     {
633         m_cursor2 = 0;
634         m_cursor1 = m_len;  // selects all
635     }
636 
637     m_bUndoForce = true;
638 
639     Justif();
640     ColumnFix();
641 }
642 
643 // Positions the cursor when clicked.
644 
MouseClick(Math::Point mouse)645 void CEdit::MouseClick(Math::Point mouse)
646 {
647     int     i;
648 
649     i = MouseDetect(mouse);
650     if ( i == -1 )  return;
651 
652     if ( m_bEdit || m_bHilite )
653     {
654         m_cursor1 = i;
655         m_cursor2 = i;
656         m_bUndoForce = true;
657         m_timeBlink = 0.0f;  // lights the cursor immediately
658         ColumnFix();
659     }
660 }
661 
662 // Positions the cursor when clicked released.
663 
MouseRelease(Math::Point mouse)664 void CEdit::MouseRelease(Math::Point mouse)
665 {
666     int i = MouseDetect(mouse);
667     if ( i == -1 )  return;
668 
669     if ( !m_bEdit )
670     {
671         if ( m_format.size() > 0 && i < m_len && m_cursor1 == m_cursor2 &&
672             (m_format[i]&Gfx::FONT_MASK_LINK) != 0) //TODO
673         {
674             int rank = -1;
675             for ( int j=0 ; j<=i ; j++ )
676             {
677                 if ( (j == 0 || (m_format[j-1]&Gfx::FONT_MASK_LINK) == 0) && // TODO check if good
678                      (m_format[j+0]&Gfx::FONT_MASK_LINK) != 0) // TODO
679                 {
680                     rank ++;
681                 }
682             }
683             assert(static_cast<unsigned int>(rank) < m_link.size());
684             HyperJump(m_link[rank].name, m_link[rank].marker);
685         }
686     }
687 }
688 
689 // Positions the cursor after movement.
690 
MouseMove(Math::Point mouse)691 void CEdit::MouseMove(Math::Point mouse)
692 {
693     int     i;
694 
695     if ( m_bMulti &&
696          m_timeLastScroll+DELAY_SCROLL <= m_time )
697     {
698         if ( mouse.y > m_pos.y+m_dim.y )  // above?
699         {
700             Scroll(m_lineFirst-1, false);
701             mouse.y = m_pos.y+m_dim.y-MARGY-m_lineHeight/2.0f;
702         }
703         if ( mouse.y < m_pos.y )  // lower?
704         {
705             Scroll(m_lineFirst+1, false);
706             mouse.y = m_pos.y+m_dim.y-MARGY-m_lineVisible*m_lineHeight+m_lineHeight/2.0f;
707         }
708         m_timeLastScroll = m_time;
709     }
710 
711     i = MouseDetect(mouse);
712     if ( i != -1 )
713     {
714         m_cursor1 = i;
715         m_bUndoForce = true;
716         m_timeBlink = 0.0f;  // lights the cursor immediately
717         ColumnFix();
718     }
719 }
720 
721 // Positions the cursor when clicked.
722 
MouseDetect(Math::Point mouse)723 int CEdit::MouseDetect(Math::Point mouse)
724 {
725     Math::Point pos;
726     float   indentLength = 0.0f, offset, size;
727     int     i, len, c;
728     bool    bTitle;
729 
730     if ( m_bAutoIndent )
731     {
732         indentLength = m_engine->GetText()->GetCharWidth(static_cast<Gfx::UTF8Char>(' '), m_fontType, m_fontSize, 0.0f)
733                         * m_engine->GetEditIndentValue();
734     }
735 
736     pos.y = m_pos.y+m_dim.y-m_lineHeight-(m_bMulti?MARGY:MARGY1);
737     for ( i=m_lineFirst ; i<m_lineTotal ; i++ )
738     {
739         bTitle = ( m_format.size() > 0 && (m_format[m_lineOffset[i]]&Gfx::FONT_MASK_TITLE) == Gfx::FONT_TITLE_BIG );
740 
741         if ( i >= m_lineFirst+m_lineVisible )  break;
742 
743         pos.x = m_pos.x+(7.5f/640.0f)*(m_fontSize/Gfx::FONT_SIZE_SMALL);
744         if ( m_bAutoIndent )
745         {
746             pos.x += indentLength*m_lineIndent[i];
747         }
748         offset = mouse.x-pos.x;
749 
750         if ( bTitle )  pos.y -= m_lineHeight;
751 
752         if ( mouse.y > pos.y )
753         {
754             len = m_lineOffset[i+1] - m_lineOffset[i];
755 
756             if ( m_format.empty() )
757             {
758 //                c = m_engine->GetText()->Detect(m_text.data()+m_lineOffset[i],
759 //                                                len, offset, m_fontSize,
760 //                                                m_fontStretch, m_fontType);
761                 c = m_engine->GetText()->Detect(std::string(m_text.data()+m_lineOffset[i]).substr(0, len), m_fontType, m_fontSize, offset); // TODO check if good
762             }
763             else
764             {
765                 size = m_fontSize;
766                 if ( bTitle )  size *= Gfx::FONT_SIZE_BIG;
767 
768 //                c = m_engine->GetText()->Detect(m_text.data()+m_lineOffset[i],
769 //                                                m_format+m_lineOffset[i],
770 //                                                len, offset, size,
771 //                                                m_fontStretch);
772                 c = m_engine->GetText()->Detect(std::string(m_text.data()+m_lineOffset[i]).substr(0, len),
773                                                 m_format.begin() + m_lineOffset[i],
774                                                 m_format.end(),
775                                                 size,
776                                                 offset); // TODO check if good
777             }
778             return m_lineOffset[i]+c;
779         }
780 
781         if ( bTitle )  i ++;
782         pos.y -= m_lineHeight;
783     }
784     return -1;
785 }
786 
787 
788 // Clears all history.
789 
HyperFlush()790 void CEdit::HyperFlush()
791 {
792     m_historyTotal = 0;
793     m_historyCurrent = -1;
794 }
795 
796 // Indicates which is the home page.
797 
HyperHome(std::string filename)798 void CEdit::HyperHome(std::string filename)
799 {
800     HyperFlush();
801     HyperAdd(filename, 0);
802 }
803 
804 // Performs a hyper jump through a link.
805 
HyperJump(std::string name,std::string marker)806 void CEdit::HyperJump(std::string name, std::string marker)
807 {
808     if ( m_historyCurrent >= 0 )
809     {
810         m_history[m_historyCurrent].firstLine = m_lineFirst;
811     }
812 
813     std::string filename = name + std::string(".txt");
814     filename = InjectLevelPathsForCurrentLevel(filename, "help/%lng%");
815     boost::replace_all(filename, "\\", "/"); //TODO: Fix this in files
816 
817     if ( ReadText(filename) )
818     {
819         Justif();
820 
821         int line = 0;
822         auto it = std::find_if(m_marker.begin(), m_marker.end(), [&marker](HyperMarker hyperMarker) { return hyperMarker.name == marker; });
823         if(it != m_marker.end())
824         {
825             int pos = it->pos;
826             for ( int i=0 ; i<m_lineTotal ; i++ )
827             {
828                 if ( pos >= m_lineOffset[i] )
829                 {
830                     line = i;
831                 }
832             }
833         }
834 
835         SetFirstLine(line);
836         HyperAdd(filename, line);
837     }
838 }
839 
840 // Adds text to the history of visited.
841 
HyperAdd(std::string filename,int firstLine)842 bool CEdit::HyperAdd(std::string filename, int firstLine)
843 {
844     if ( m_historyCurrent >= EDITHISTORYMAX-1 )  return false;
845 
846     m_historyCurrent ++;
847     m_history[m_historyCurrent].filename = filename;
848     m_history[m_historyCurrent].firstLine = firstLine;
849 
850     m_historyTotal = m_historyCurrent+1;
851     return true;
852 }
853 
854 // Indicates whether a button EVENT_HYPER_ * is active or not.
855 
HyperTest(EventType event)856 bool CEdit::HyperTest(EventType event)
857 {
858     if ( event == EVENT_HYPER_HOME )
859     {
860         return ( m_historyCurrent > 0 );
861     }
862 
863     if ( event == EVENT_HYPER_PREV )
864     {
865         return ( m_historyCurrent > 0 );
866     }
867 
868     if ( event == EVENT_HYPER_NEXT )
869     {
870         return ( m_historyCurrent < m_historyTotal-1 );
871     }
872 
873     return false;
874 }
875 
876 // Performs the action corresponding to a button EVENT_HYPER_ *.
877 
HyperGo(EventType event)878 bool CEdit::HyperGo(EventType event)
879 {
880     if ( !HyperTest(event) )  return false;
881 
882     m_history[m_historyCurrent].firstLine = m_lineFirst;
883 
884     if ( event == EVENT_HYPER_HOME )
885     {
886         m_historyCurrent = 0;
887     }
888 
889     if ( event == EVENT_HYPER_PREV )
890     {
891         m_historyCurrent --;
892     }
893 
894     if ( event == EVENT_HYPER_NEXT )
895     {
896         m_historyCurrent ++;
897     }
898 
899     ReadText(m_history[m_historyCurrent].filename);
900     Justif();
901     SetFirstLine(m_history[m_historyCurrent].firstLine);
902     return true;
903 }
904 
905 
906 // Draw the editable line.
907 
Draw()908 void CEdit::Draw()
909 {
910     Math::Point     pos, ppos, dim, start, end;
911     float       size = 0.0f, indentLength = 0.0f;
912     int         i, j, beg, len, c1, c2, o1, o2, eol, line;
913 
914     if ( (m_state & STATE_VISIBLE) == 0 )  return;
915 
916     if ( m_state & STATE_SHADOW )
917     {
918         DrawShadow(m_pos, m_dim);
919     }
920 
921     pos.x = m_pos.x;
922     pos.y = m_pos.y;
923     dim.x = m_dim.x;
924     if ( !m_bInsideScroll )  dim.x -= m_bMulti?SCROLL_WIDTH:0.0f;
925     dim.y = m_dim.y;
926     DrawBack(pos, dim);  // background
927 
928     if ( (m_state & STATE_ENABLE) == 0 ) return;
929 
930     // Displays all lines.
931     c1 = m_cursor1;
932     c2 = m_cursor2;
933     if ( c1 > c2 )  Math::Swap(c1, c2);  // always c1 <= c2
934 
935     if ( m_bInsideScroll )
936     {
937         dim.x -= m_bMulti?SCROLL_WIDTH:0.0f + (1.0f/640.0f);
938     }
939 
940     if ( m_bAutoIndent )
941     {
942         indentLength = m_engine->GetText()->GetCharWidth(static_cast<Gfx::UTF8Char>(' '), m_fontType, m_fontSize, 0.0f)
943                         * m_engine->GetEditIndentValue();
944     }
945 
946     pos.y = m_pos.y+m_dim.y-m_lineHeight-(m_bMulti?MARGY:MARGY1);
947     for ( i=m_lineFirst ; i<m_lineTotal ; i++ )
948     {
949         if ( i == m_lineFirst && i < m_lineTotal-1 &&
950              m_lineOffset[i] == m_lineOffset[i+1] )
951         {
952             pos.y -= m_lineHeight;  // Double jump line \b;
953             i ++;
954         }
955 
956         if ( i >= m_lineFirst+m_lineVisible )  break;
957 
958         pos.x = m_pos.x+(7.5f/640.0f)*(m_fontSize/Gfx::FONT_SIZE_SMALL);
959         if ( m_bAutoIndent )
960         {
961             const char *s = "\t";  // line | dotted
962             for ( j=0 ; j<m_lineIndent[i] ; j++ )
963             {
964                 m_engine->GetText()->DrawText(s, m_fontType, m_fontSize, pos, 1.0f, Gfx::TEXT_ALIGN_LEFT, 0);
965                 pos.x += indentLength;
966             }
967         }
968 
969         beg = m_lineOffset[i];
970         len = m_lineOffset[i+1] - m_lineOffset[i];
971 
972         ppos = pos;
973         size = m_fontSize;
974 
975         // Headline \b;?
976         if ( beg+len < m_len && m_format.size() > static_cast<unsigned int>(beg) &&
977              (m_format[beg]&Gfx::FONT_MASK_TITLE) == Gfx::FONT_TITLE_BIG )
978         {
979             start.x = ppos.x-MARGX;
980             end.x   = dim.x-MARGX*2.0f;
981             start.y = ppos.y-(m_bMulti?0.0f:MARGY1)-m_lineHeight*(BIG_FONT-1.0f);
982             end.y   = m_lineHeight*BIG_FONT;
983             DrawHorizontalGradient(start, end, Gfx::Color(0.549f, 0.514f, 0.376f, 1.0f), Gfx::Color(0.733f, 0.706f, 0.600f, 1.0f));  // blue gradient background
984 
985             size *= BIG_FONT;
986             ppos.y -= m_lineHeight*(BIG_FONT-1.0f);
987         }
988 
989         // As \t;?
990         if ( beg+len < m_len && m_format.size() > static_cast<unsigned int>(beg) &&
991              (m_format[beg]&Gfx::FONT_MASK_TITLE) == Gfx::FONT_TITLE_NORM )
992         {
993             start.x = ppos.x-MARGX;
994             end.x   = dim.x-MARGX*2.0f;
995             start.y = ppos.y-(m_bMulti?0.0f:MARGY1);
996             end.y   = m_lineHeight;
997             DrawHorizontalGradient(start, end, Gfx::Color(0.549f, 0.514f, 0.376f, 1.0f), Gfx::Color(0.733f, 0.706f, 0.600f, 1.0f));  // blue gradient background
998         }
999 
1000         // Subtitle \s;?
1001         if ( beg+len < m_len && m_format.size() > static_cast<unsigned int>(beg) &&
1002              (m_format[beg]&Gfx::FONT_MASK_TITLE) == Gfx::FONT_TITLE_LITTLE )
1003         {
1004             start.x = ppos.x-MARGX;
1005             end.x   = dim.x-MARGX*2.0f;
1006             start.y = ppos.y-(m_bMulti?0.0f:MARGY1);
1007             end.y   = m_lineHeight;
1008             DrawHorizontalGradient(start, end, Gfx::Color(0.847f, 0.847f, 0.847f, 1.0f), Gfx::Color(0.996f, 0.953f, 0.792f, 1.0f));  // yellow background gradient
1009         }
1010 
1011         // Table \tab;?
1012         if ( beg+len < m_len && m_format.size() > static_cast<unsigned int>(beg) &&
1013              (m_format[beg]&Gfx::FONT_MASK_HIGHLIGHT) == Gfx::FONT_HIGHLIGHT_TABLE )
1014         {
1015             start.x = ppos.x-MARGX;
1016             end.x   = dim.x-MARGX*2.0f;
1017             start.y = ppos.y-(m_bMulti?0.0f:MARGY1);
1018             end.y   = m_lineHeight;
1019             DrawHorizontalGradient(start, end, Gfx::Color(0.996f, 0.675f, 0.329f, 1.0f), Gfx::Color(1.000f, 0.898f, 0.788f, 1.0f));  // fond orange d�grad� ->
1020         }
1021 
1022         // Image \image; ?
1023         if ( beg+len < m_len && m_format.size() > static_cast<unsigned int>(beg) &&
1024              (m_format[beg]&Gfx::FONT_MASK_IMAGE) != 0 )
1025         {
1026             line = 1;
1027             while ( true )  // includes the image slices
1028             {
1029                 if ( i+line >= m_lineTotal                ||
1030                      i+line >= m_lineFirst+m_lineVisible  ||
1031                      (m_format.size() > static_cast<unsigned int>(beg+line) && m_format[beg+line]&Gfx::FONT_MASK_IMAGE) == 0 )  break;
1032                 line ++;
1033             }
1034 
1035             unsigned int iIndex = m_text[beg];  // character = index in m_image
1036             assert(iIndex < m_image.size());
1037             pos.y -= m_lineHeight*(line-1);
1038             DrawImage(pos, m_image[iIndex].name,
1039                       m_image[iIndex].width*(m_fontSize/Gfx::FONT_SIZE_SMALL),
1040                       m_image[iIndex].offset, m_image[iIndex].height*line, line);
1041             pos.y -= m_lineHeight;
1042             i += line-1;
1043             continue;
1044         }
1045 
1046         if ( ((m_bEdit && m_bFocus && m_bHilite) ||
1047               (!m_bEdit && m_bHilite)            ) &&
1048              c1 != c2 && beg <= c2 && beg+len >= c1 )  // selected area?
1049         {
1050             o1 = c1;  if ( o1 < beg     )  o1 = beg;
1051             o2 = c2;  if ( o2 > beg+len )  o2 = beg+len;
1052 
1053             if ( m_format.empty() )
1054             {
1055                 start.x = ppos.x+m_engine->GetText()->GetStringWidth(std::string(m_text.data()+beg).substr(0, o1-beg), m_fontType, size);
1056                 end.x   = m_engine->GetText()->GetStringWidth(std::string(m_text.data()+o1).substr(0, o2-o1), m_fontType, size);
1057             }
1058             else
1059             {
1060                 start.x = ppos.x+m_engine->GetText()->GetStringWidth(std::string(m_text.data()+beg).substr(0, o1-beg),
1061                                                                      m_format.begin() + beg,
1062                                                                      m_format.end(),
1063                                                                      size);
1064                 end.x   = m_engine->GetText()->GetStringWidth(std::string(m_text.data()+o1).substr(0, o2-o1),
1065                                                               m_format.begin() + o1,
1066                                                               m_format.end(),
1067                                                               size);
1068             }
1069 
1070             start.y = ppos.y-(m_bMulti?0.0f:MARGY1);
1071             end.y   = m_lineHeight;
1072             if ( m_format.size() > static_cast<unsigned int>(beg) && (m_format[beg]&Gfx::FONT_MASK_TITLE) == Gfx::FONT_TITLE_BIG)  end.y *= BIG_FONT;
1073             DrawColor(start, end, Gfx::Color(1.000f, 0.620f, 0.075f, 1.0f));  // plain yellow background
1074         }
1075 
1076         eol = 16;  // >
1077         if ( len > 0 && m_text[beg+len-1] == '\n' )
1078         {
1079             len --;  // does not display the '\ n'
1080             eol = 0;  // nothing
1081         }
1082         if ( beg+len >= m_len )
1083         {
1084             eol = 2;  // square (eot)
1085         }
1086         if ( !m_bMulti || !m_bDisplaySpec )  eol = 0;
1087         if ( m_format.empty() )
1088         {
1089             m_engine->GetText()->DrawText(std::string(m_text.data()+beg).substr(0, len), m_fontType, size, ppos, m_dim.x, Gfx::TEXT_ALIGN_LEFT, eol);
1090         }
1091         else
1092         {
1093             m_engine->GetText()->DrawText(std::string(m_text.data()+beg).substr(0, len),
1094                                           m_format.begin() + beg,
1095                                           m_format.end(),
1096                                           size,
1097                                           ppos,
1098                                           m_dim.x,
1099                                           Gfx::TEXT_ALIGN_LEFT,
1100                                           eol);
1101         }
1102 
1103         pos.y -= m_lineHeight;
1104 
1105         if ( i < m_lineTotal-2 && m_lineOffset[i+1] == m_lineOffset[i+2] )
1106         {
1107             pos.y -= m_lineHeight;  // double jump line \b;
1108             i ++;
1109         }
1110     }
1111 
1112     // Shows the cursor.
1113     if ( (m_bEdit && m_bFocus && m_bHilite && Math::Mod(m_timeBlink, 1.0f) <= 0.5f) )  // it blinks
1114     {
1115         pos.y = m_pos.y+m_dim.y-m_lineHeight-(m_bMulti?MARGY:MARGY1*2.0f);
1116         for ( i=m_lineFirst ; i<m_lineTotal ; i++ )
1117         {
1118             if ( i == m_lineTotal-1 || m_cursor1 < m_lineOffset[i+1] )
1119             {
1120                 pos.x = m_pos.x+(7.5f/640.0f)*(m_fontSize/Gfx::FONT_SIZE_SMALL);
1121                 if ( m_bAutoIndent )
1122                 {
1123                     pos.x += indentLength*m_lineIndent[i];
1124                 }
1125 
1126                 len = m_cursor1 - m_lineOffset[i];
1127 
1128                 if ( m_format.empty() )
1129                 {
1130                     m_engine->GetText()->SizeText(std::string(m_text.data()+m_lineOffset[i]).substr(0, len), m_fontType,
1131                                                   size, pos, Gfx::TEXT_ALIGN_LEFT,
1132                                                   start, end);
1133                 }
1134                 else
1135                 {
1136                     m_engine->GetText()->SizeText(std::string(m_text.data()+m_lineOffset[i]).substr(0, len),
1137                                                   m_format.begin() + m_lineOffset[i],
1138                                                   m_format.end(),
1139                                                   size, pos, Gfx::TEXT_ALIGN_LEFT,
1140                                                   start, end);
1141                 }
1142 
1143                 pos.x = end.x;
1144                 break;
1145             }
1146             pos.y -= m_lineHeight;
1147         }
1148         pos.x -= 1.0f / 640.0f;
1149         dim.x = 2.0f / 640.0f;
1150         dim.y = m_lineHeight;
1151         DrawColor(pos, dim, Gfx::Color(1.0f, 0.0f, 0.0f, 1.0f));  // red
1152     }
1153 
1154     if (m_scroll != nullptr && !m_bGeneric)
1155     {
1156         m_scroll->Draw();
1157     }
1158 }
1159 
1160 // Draw an image part.
1161 
PrepareImageFilename(std::string name)1162 static std::string PrepareImageFilename(std::string name)
1163 {
1164     std::string filename;
1165     filename = name + ".png";
1166     filename = InjectLevelPathsForCurrentLevel(filename, "icons");
1167     boost::replace_all(filename, "\\", "/"); // TODO: Fix this in files
1168     return filename;
1169 }
1170 
DrawImage(Math::Point pos,std::string name,float width,float offset,float height,int nbLine)1171 void CEdit::DrawImage(Math::Point pos, std::string name, float width,
1172                       float offset, float height, int nbLine)
1173 {
1174     Math::Point uv1, uv2, dim;
1175     float dp;
1176 
1177     m_engine->SetState(Gfx::ENG_RSTATE_NORMAL);
1178 
1179     Gfx::TextureCreateParams params;
1180     params.format = Gfx::TEX_IMG_AUTO;
1181     params.filter = Gfx::TEX_FILTER_BILINEAR;
1182     params.padToNearestPowerOfTwo = true;
1183     Gfx::Texture tex = m_engine->LoadTexture(PrepareImageFilename(name), params);
1184 
1185     m_engine->SetTexture(tex);
1186 
1187     uv1.x = 0.0f;
1188     uv2.x = 1.0f;
1189     uv1.y = offset;
1190     uv2.y = offset+height;
1191 
1192     uv1.x *= static_cast<float>(tex.originalSize.x) / static_cast<float>(tex.size.x);
1193     uv2.x *= static_cast<float>(tex.originalSize.x) / static_cast<float>(tex.size.x);
1194     uv1.y *= static_cast<float>(tex.originalSize.y) / static_cast<float>(tex.size.y);
1195     uv2.y *= static_cast<float>(tex.originalSize.y) / static_cast<float>(tex.size.y);
1196 
1197     dp = 0.5f/256.0f;
1198     uv1.x += dp;
1199     uv1.y += dp;
1200     uv2.x -= dp;
1201     uv2.y -= dp;
1202 
1203     dim.x = width;
1204     dim.y = m_lineHeight*nbLine;
1205     DrawIcon(pos, dim, uv1, uv2);
1206 }
1207 
1208 // Draw the background.
1209 
DrawBack(Math::Point pos,Math::Point dim)1210 void CEdit::DrawBack(Math::Point pos, Math::Point dim)
1211 {
1212     Math::Point     uv1,uv2, corner;
1213     float       dp;
1214 
1215     if ( m_bGeneric )  return;
1216 
1217     m_engine->SetTexture("textures/interface/button2.png");
1218     m_engine->SetState(Gfx::ENG_RSTATE_NORMAL);
1219 
1220     if ( m_bMulti )
1221     {
1222         uv1.x = 128.0f/256.0f;  // light blue
1223         uv1.y =  64.0f/256.0f;
1224         uv2.x = 160.0f/256.0f;
1225         uv2.y =  96.0f/256.0f;
1226     }
1227     else
1228     {
1229         uv1.x = 160.0f/256.0f;  // medium blue
1230         uv1.y = 192.0f/256.0f;
1231         uv2.x = 192.0f/256.0f;
1232         uv2.y = 224.0f/256.0f;
1233     }
1234     if ( m_icon == 1 )
1235     {
1236         uv1.x = 192.0f/256.0f;  // orange
1237         uv1.y =  96.0f/256.0f;
1238         uv2.x = 224.0f/256.0f;
1239         uv2.y = 128.0f/256.0f;
1240     }
1241 
1242     dp = 0.5f/256.0f;
1243     uv1.x += dp;
1244     uv1.y += dp;
1245     uv2.x -= dp;
1246     uv2.y -= dp;
1247 
1248     if ( m_bMulti )
1249     {
1250         corner.x = 10.0f/640.0f;
1251         corner.y = 10.0f/480.0f;
1252         DrawIcon(pos, dim, uv1, uv2, corner, 8.0f/256.0f);
1253     }
1254     else
1255     {
1256         DrawIcon(pos, dim, uv1, uv2, 8.0f/256.0f);
1257     }
1258 }
1259 
DrawHorizontalGradient(Math::Point pos,Math::Point dim,Gfx::Color color1,Gfx::Color color2)1260 void CEdit::DrawHorizontalGradient(Math::Point pos, Math::Point dim, Gfx::Color color1, Gfx::Color color2)
1261 {
1262     m_engine->SetState(Gfx::ENG_RSTATE_OPAQUE_COLOR);
1263 
1264     Math::Point p1, p2;
1265     p1.x = pos.x;
1266     p1.y = pos.y;
1267     p2.x = pos.x + dim.x;
1268     p2.y = pos.y + dim.y;
1269 
1270     Gfx::VertexCol quad[] =
1271     {
1272         Gfx::VertexCol(Math::Vector(p1.x, p1.y, 0.0f), color1),
1273         Gfx::VertexCol(Math::Vector(p1.x, p2.y, 0.0f), color1),
1274         Gfx::VertexCol(Math::Vector(p2.x, p1.y, 0.0f), color2),
1275         Gfx::VertexCol(Math::Vector(p2.x, p2.y, 0.0f), color2)
1276     };
1277 
1278     m_engine->GetDevice()->DrawPrimitive(Gfx::PRIMITIVE_TRIANGLE_STRIP, quad, 4);
1279     m_engine->AddStatisticTriangle(2);
1280 }
1281 
DrawColor(Math::Point pos,Math::Point dim,Gfx::Color color)1282 void CEdit::DrawColor(Math::Point pos, Math::Point dim, Gfx::Color color)
1283 {
1284     DrawHorizontalGradient(pos, dim, color, color);
1285 }
1286 
1287 
1288 // Give the text to edit.
1289 
SetText(const std::string & text,bool bNew)1290 void CEdit::SetText(const std::string& text, bool bNew)
1291 {
1292     int     i, j, font;
1293     bool    bBOL;
1294 
1295     if ( !bNew )  UndoMemorize(OPERUNDO_SPEC);
1296 
1297     m_len = text.size();
1298 
1299     if( m_len >= GetMaxChar() ) m_len = GetMaxChar();
1300 
1301     m_text.resize( m_len + 1, '\0' );
1302     m_text[m_len] = '\0';
1303     m_format.resize( m_len + 1, m_fontType );
1304     m_format[m_len] = m_fontType;
1305 
1306     font = m_fontType;
1307     j = 0;
1308     bBOL = true;
1309     for ( i=0 ; i<m_len ; i++ )
1310     {
1311         if ( m_bAutoIndent )
1312         {
1313             if ( text[i] == '\t' )
1314             {
1315                 if ( !bBOL )
1316                 {
1317                     m_text[j] = ' ';
1318                     m_format[j] = font;
1319                     j ++;
1320                 }
1321                 continue;  // removes tabs
1322             }
1323             bBOL = ( text[i] == '\n' );
1324         }
1325 
1326         if ( text[i] == '\\' && text[i+2] == ';' )
1327         {
1328             if ( text[i+1] == 'n' )  // normal ?
1329             {
1330                 font &= ~Gfx::FONT_MASK_FONT;
1331                 font |= Gfx::FONT_COMMON;
1332                 i += 2;
1333             }
1334             else if ( text[i+1] == 'c' )  // cbot ?
1335             {
1336                 font &= ~Gfx::FONT_MASK_FONT;
1337                 font |= Gfx::FONT_STUDIO;
1338                 i += 2;
1339             }
1340             else if ( text[i+1] == 'b' )  // big title ?
1341             {
1342                 font &= ~Gfx::FONT_MASK_TITLE;
1343                 font |= Gfx::FONT_TITLE_BIG;
1344                 i += 2;
1345             }
1346             else if ( text[i+1] == 't' )  // title ?
1347             {
1348                 font &= ~Gfx::FONT_MASK_TITLE;
1349                 font |= Gfx::FONT_TITLE_NORM;
1350                 i += 2;
1351             }
1352             else if ( text[i+1] == 's' )  // subtitle ?
1353             {
1354                 font &= ~Gfx::FONT_MASK_TITLE;
1355                 font |= Gfx::FONT_TITLE_LITTLE;
1356                 i += 2;
1357             }
1358         }
1359         else
1360         {
1361             m_text[j] = text[i];
1362             m_format[j] = font;
1363             j ++;
1364             font &= ~Gfx::FONT_MASK_TITLE;  // reset title
1365         }
1366     }
1367     m_len = j;
1368 
1369     if ( bNew )  UndoFlush();
1370 
1371     m_cursor1 = 0;
1372     m_cursor2 = 0;  // cursor to the beginning
1373     Justif();
1374     ColumnFix();
1375 }
1376 
1377 // Returns a const reference to the edited text.
1378 
GetText()1379 const std::string& CEdit::GetText()
1380 {
1381     return m_text;
1382 }
1383 
1384 // Returns the edited text.
1385 
GetText(int max)1386 std::string CEdit::GetText(int max)
1387 {
1388     if ( m_len < max )  max = m_len;
1389     if ( m_len > max )  max = max-1;
1390 
1391     return std::string( m_text, 0, max );
1392 }
1393 
1394 // Returns the length of the text.
1395 
GetTextLength()1396 int CEdit::GetTextLength()
1397 {
1398     return m_len;
1399 }
1400 
1401 
1402 
1403 // Returns a name in a command.
1404 // \x nom1 nom2 nom3;
1405 
GetNameParam(std::string cmd,int rank)1406 static std::string GetNameParam(std::string cmd, int rank)
1407 {
1408     std::vector<std::string> results;
1409     boost::split(results, cmd, boost::is_any_of(" ;"));
1410 
1411     if (results.size() > static_cast<unsigned int>(rank))
1412     {
1413         return results.at(rank);
1414     }
1415 
1416     return "";
1417 }
1418 
1419 // Returns a number of a command.
1420 // \x nom n1 n2;
1421 
GetValueParam(std::string cmd,int rank)1422 static int GetValueParam(std::string cmd, int rank)
1423 {
1424     std::vector<std::string> results;
1425     boost::split(results, cmd, boost::is_any_of(" ;"));
1426     int return_value = 0;
1427 
1428     if (results.size() > static_cast<unsigned int>(rank))
1429     {
1430         return_value = atoi(results.at(rank).c_str());
1431     }
1432 
1433     return return_value;
1434 }
1435 
1436 // Frees all images.
1437 
FreeImage()1438 void CEdit::FreeImage()
1439 {
1440     for (auto& image : m_image)
1441     {
1442         m_engine->DeleteTexture(PrepareImageFilename(image.name));
1443     }
1444 }
1445 
1446 // Read from a text file.
1447 
ReadText(std::string filename)1448 bool CEdit::ReadText(std::string filename)
1449 {
1450     int         len, len2, i, j, n, font, iLines, iCount;
1451     char        iName[50];
1452     float       iWidth;
1453     InputSlot   slot;
1454     bool        bInSoluce, bBOL;
1455 
1456     if ( filename.empty() )  return false;
1457 
1458     CInputStream stream;
1459     stream.open(filename);
1460 
1461     if (!stream.is_open())
1462     {
1463         GetLogger()->Error("Failed to load text file %s\n", filename.c_str());
1464         return false;
1465     }
1466 
1467     len = stream.size();
1468     len2 = len + 1;
1469 
1470     m_len = len;
1471     m_cursor1 = 0;
1472     m_cursor2 = 0;
1473 
1474     FreeImage();
1475 
1476     m_text = std::string(len2+1, '\0');
1477 
1478     std::vector<char> buffer(len2+1, '\0');
1479 
1480     stream.read(buffer.data(), len);
1481 
1482     m_format.clear();
1483     m_format.resize(len2+1, m_fontType);
1484 
1485     stream.close();
1486 
1487     bInSoluce = false;
1488     font = m_fontType;
1489     m_image.clear();
1490     m_marker.clear();
1491     m_link.clear();
1492     i = j = 0;
1493     bBOL = true;
1494     int cbotStart = 0;
1495     bool inCbotBackground = false;
1496     bool inCbot = false;
1497     while ( i < m_len )
1498     {
1499         if ( m_bAutoIndent )
1500         {
1501             if ( buffer[i] == '\t' )
1502             {
1503                 if ( !bBOL )
1504                 {
1505                     m_text[j] = buffer[i];
1506                     //if ( m_format.size() > 0 )
1507                     m_format[j] = font;
1508                     j ++;
1509                 }
1510                 i ++;
1511                 continue;  // removes the tabs
1512             }
1513             bBOL = ( buffer[i] == '\n' || buffer[i] == '\r' );
1514         }
1515 
1516         if ( buffer[i] == '\r' )  // removes \ r
1517         {
1518             i ++;
1519         }
1520         else if ( buffer[i] == '\\' && buffer[i+2] == ';' )
1521         {
1522             if ( buffer[i+1] == 'n' )  // normal ?
1523             {
1524                 if ( m_bSoluce || !bInSoluce )
1525                 {
1526                     font &= ~Gfx::FONT_MASK_FONT;
1527                     font |= Gfx::FONT_COMMON;
1528                     inCbot = false;
1529                 }
1530                 i += 3;
1531             }
1532             else if ( buffer[i+1] == 'c' )  // cbot ?
1533             {
1534                 if ( m_bSoluce || !bInSoluce )
1535                 {
1536                     font &= ~Gfx::FONT_MASK_FONT;
1537                     font |= Gfx::FONT_STUDIO;
1538                     if (!inCbot)
1539                     {
1540                         if (inCbotBackground)
1541                         {
1542                             cbotStart = j;
1543                         }
1544                         inCbot = true;
1545                     }
1546                 }
1547                 i += 3;
1548             }
1549             else if ( buffer[i+1] == 'b' )  // big title ?
1550             {
1551                 if ( m_bSoluce || !bInSoluce )
1552                 {
1553                     font &= ~Gfx::FONT_MASK_TITLE;
1554                     font |= Gfx::FONT_TITLE_BIG;
1555                 }
1556                 i += 3;
1557             }
1558             else if ( buffer[i+1] == 't' )  // title ?
1559             {
1560                 if ( m_bSoluce || !bInSoluce )
1561                 {
1562                     font &= ~Gfx::FONT_MASK_TITLE;
1563                     font |= Gfx::FONT_TITLE_NORM;
1564                 }
1565                 i += 3;
1566             }
1567             else if ( buffer[i+1] == 's' )  // subtitle ?
1568             {
1569                 if ( m_bSoluce || !bInSoluce )
1570                 {
1571                     font &= ~Gfx::FONT_MASK_TITLE;
1572                     font |= Gfx::FONT_TITLE_LITTLE;
1573                     if (!inCbotBackground)
1574                     {
1575                         if (inCbot)
1576                         {
1577                             cbotStart = j;
1578                         }
1579                         inCbotBackground = true;
1580                     }
1581                 }
1582                 i += 3;
1583             }
1584             else if ( buffer[i+1] == 'l' )  // link ?
1585             {
1586                 if ( m_bSoluce || !bInSoluce )
1587                 {
1588                     font |= Gfx::FONT_MASK_LINK;
1589                 }
1590                 i += 3;
1591             }
1592             else
1593             {
1594                 i += 3;
1595             }
1596         }
1597         else if ( //m_format.size() > 0       &&
1598                   buffer[i+0] == '\\' &&  // \u marker name; ?
1599                   buffer[i+1] == 'u'  &&
1600                   buffer[i+2] == ' '  )
1601         {
1602             if ( m_bSoluce || !bInSoluce )
1603             {
1604                 HyperLink link;
1605                 link.name = GetNameParam(buffer.data()+i+3, 0);
1606                 link.marker = GetNameParam(buffer.data()+i+3, 1);
1607                 m_link.push_back(link);
1608                 font &= ~Gfx::FONT_MASK_LINK;
1609             }
1610             i += strchr(buffer.data()+i, ';')-(buffer.data()+i)+1;
1611         }
1612         else if (// m_format.size() > 0       &&
1613                   buffer[i+0] == '\\' &&  // \m marker; ?
1614                   buffer[i+1] == 'm'  &&
1615                   buffer[i+2] == ' '  )
1616         {
1617             if ( m_bSoluce || !bInSoluce )
1618             {
1619                 HyperMarker marker;
1620                 marker.name = GetNameParam(buffer.data()+i+3, 0);
1621                 marker.pos = j;
1622                 m_marker.push_back(marker);
1623             }
1624             i += strchr(buffer.data()+i, ';')-(buffer.data()+i)+1;
1625         }
1626         else if ( //m_format.size() > 0       &&
1627                   buffer[i+0] == '\\' &&  // \image name lx ly; ?
1628                   buffer[i+1] == 'i'  &&
1629                   buffer[i+2] == 'm'  &&
1630                   buffer[i+3] == 'a'  &&
1631                   buffer[i+4] == 'g'  &&
1632                   buffer[i+5] == 'e'  &&
1633                   buffer[i+6] == ' '  )
1634         {
1635             if ( m_bSoluce || !bInSoluce )
1636             {
1637                 strcpy(iName, GetNameParam(buffer.data()+i+7, 0).c_str());
1638 
1639 //?             iWidth = m_lineHeight*RetValueParam(buffer.data()+i+7, 1);
1640                 iWidth = static_cast<float>(GetValueParam(buffer.data()+i+7, 1));
1641                 iWidth *= m_engine->GetText()->GetHeight(Gfx::FONT_COMMON, Gfx::FONT_SIZE_SMALL);
1642                 iLines = GetValueParam(buffer.data()+i+7, 2);
1643 
1644                 // A part of image per line of text.
1645                 for ( iCount=0 ; iCount<iLines ; iCount++ )
1646                 {
1647                     ImageLine image;
1648                     image.name = iName;
1649                     image.offset = static_cast<float>(iCount) / static_cast<float>(iLines);
1650                     image.height = 1.0f/iLines;
1651                     image.width = iWidth*0.75f;
1652 
1653                     m_image.push_back(image);
1654                     m_text[j] = static_cast<char>(m_image.size()-1);  // as an index into m_image
1655                     m_format[j] = Gfx::FONT_MASK_IMAGE;
1656                     j ++;
1657                 }
1658             }
1659             i += strchr(buffer.data()+i, ';')-(buffer.data()+i)+1;
1660         }
1661         else if ( //m_format.size() > 0       &&
1662                   buffer[i+0] == '\\' &&  // \button; ?
1663                   buffer[i+1] == 'b'  &&
1664                   buffer[i+2] == 'u'  &&
1665                   buffer[i+3] == 't'  &&
1666                   buffer[i+4] == 't'  &&
1667                   buffer[i+5] == 'o'  &&
1668                   buffer[i+6] == 'n'  &&
1669                   buffer[i+7] == ' '  )
1670         {
1671             if ( m_bSoluce || !bInSoluce )
1672             {
1673                 m_text[j] = GetValueParam(buffer.data()+i+8, 0);
1674                 m_format[j] = font|Gfx::FONT_BUTTON;
1675                 j ++;
1676             }
1677             i += strchr(buffer.data()+i, ';')-(buffer.data()+i)+1;
1678         }
1679         else if ( //m_format.size() > 0       &&
1680                   buffer[i+0] == '\\' &&  // \token; ?
1681                   buffer[i+1] == 't'  &&
1682                   buffer[i+2] == 'o'  &&
1683                   buffer[i+3] == 'k'  &&
1684                   buffer[i+4] == 'e'  &&
1685                   buffer[i+5] == 'n'  &&
1686                   buffer[i+6] == ';'  )
1687         {
1688             if ( m_bSoluce || !bInSoluce )
1689             {
1690                 font &= ~Gfx::FONT_MASK_HIGHLIGHT;
1691                 font |= Gfx::FONT_HIGHLIGHT_TOKEN;
1692             }
1693             i += 7;
1694         }
1695         else if ( //m_format.size() > 0       &&
1696                   buffer[i+0] == '\\' &&  // \type; ?
1697                   buffer[i+1] == 't'  &&
1698                   buffer[i+2] == 'y'  &&
1699                   buffer[i+3] == 'p'  &&
1700                   buffer[i+4] == 'e'  &&
1701                   buffer[i+5] == ';'  )
1702         {
1703             if ( m_bSoluce || !bInSoluce )
1704             {
1705                 font &= ~Gfx::FONT_MASK_HIGHLIGHT;
1706                 font |= Gfx::FONT_HIGHLIGHT_TYPE;
1707             }
1708             i += 6;
1709         }
1710         else if ( //m_format.size() > 0       &&
1711                   buffer[i+0] == '\\' &&  // \const; ?
1712                   buffer[i+1] == 'c'  &&
1713                   buffer[i+2] == 'o'  &&
1714                   buffer[i+3] == 'n'  &&
1715                   buffer[i+4] == 's'  &&
1716                   buffer[i+5] == 't'  &&
1717                   buffer[i+6] == ';'  )
1718         {
1719             if ( m_bSoluce || !bInSoluce )
1720             {
1721                 font &= ~Gfx::FONT_MASK_HIGHLIGHT;
1722                 font |= Gfx::FONT_HIGHLIGHT_CONST;
1723             }
1724             i += 7;
1725         }
1726         else if ( //m_format.size() > 0       &&
1727                   buffer[i+0] == '\\' &&  // \key; ?
1728                   buffer[i+1] == 'k'  &&
1729                   buffer[i+2] == 'e'  &&
1730                   buffer[i+3] == 'y'  &&
1731                   buffer[i+4] == ';'  )
1732         {
1733             if ( m_bSoluce || !bInSoluce )
1734             {
1735                 font &= ~Gfx::FONT_MASK_HIGHLIGHT;
1736                 font |= Gfx::FONT_HIGHLIGHT_KEY;
1737             }
1738             i += 5;
1739         }
1740         else if ( //m_format.size() > 0       &&
1741                   buffer[i+0] == '\\' &&  // \tab; ?
1742                   buffer[i+1] == 't'  &&
1743                   buffer[i+2] == 'a'  &&
1744                   buffer[i+3] == 'b'  &&
1745                   buffer[i+4] == ';'  )
1746         {
1747             if ( m_bSoluce || !bInSoluce )
1748             {
1749                 font |= Gfx::FONT_HIGHLIGHT_TABLE;
1750             }
1751             i += 5;
1752         }
1753         else if (// m_format.size() > 0       &&
1754                   buffer[i+0] == '\\' &&  // \norm; ?
1755                   buffer[i+1] == 'n'  &&
1756                   buffer[i+2] == 'o'  &&
1757                   buffer[i+3] == 'r'  &&
1758                   buffer[i+4] == 'm'  &&
1759                   buffer[i+5] == ';'  )
1760         {
1761             if ( m_bSoluce || !bInSoluce )
1762             {
1763                 font &= ~Gfx::FONT_MASK_HIGHLIGHT;
1764             }
1765             i += 6;
1766         }
1767         else if ( //m_format.size() > 0       &&
1768                   buffer[i+0] == '\\' &&  // \begin soluce; ?
1769                   buffer[i+1] == 'b'  &&
1770                   buffer[i+2] == 's'  &&
1771                   buffer[i+3] == ';'  )
1772         {
1773             bInSoluce = true;
1774             i += 4;
1775         }
1776         else if ( //m_format.size() > 0       &&
1777                   buffer[i+0] == '\\' &&  // \end soluce; ?
1778                   buffer[i+1] == 'e'  &&
1779                   buffer[i+2] == 's'  &&
1780                   buffer[i+3] == ';'  )
1781         {
1782             bInSoluce = false;
1783             i += 4;
1784         }
1785         else if ( //m_format.size() > 0       &&
1786                   buffer[i+0] == '\\' &&  // \key name; ?
1787                   buffer[i+1] == 'k'  &&
1788                   buffer[i+2] == 'e'  &&
1789                   buffer[i+3] == 'y'  &&
1790                   buffer[i+4] == ' '  )
1791         {
1792             int count;
1793             for (count = 0; buffer[i+5+count] != ';'; count++);
1794             if ( m_bSoluce || !bInSoluce ) //TODO: ???
1795             {
1796                 CInput* input = CInput::GetInstancePointer();
1797                 slot = input->SearchKeyById(std::string(&buffer[i+5], count));
1798                 if ( slot != INPUT_SLOT_MAX )
1799                 {
1800                     std::string iNameStr = input->GetKeysString(slot);
1801                     strcpy(iName, iNameStr.c_str());
1802                     m_text[j] = ' ';
1803                     m_format[j] = font;
1804                     j ++;
1805                     n = 0;
1806                     while ( iName[n] != 0 )
1807                     {
1808                         m_text[j] = iName[n++];
1809                         m_format[j] = font;
1810                         j ++;
1811                     }
1812                     m_text[j] = ' ';
1813                     m_format[j] = font;
1814                     j ++;
1815                 }
1816                 else
1817                 {
1818                     m_text[j] = '?';
1819                     m_format[j] = font;
1820                     j ++;
1821                 }
1822             }
1823             i = i+5+count+1;
1824         }
1825         else
1826         {
1827             if (buffer[i] == '\n' && inCbotBackground)
1828             {
1829                 CScript::ColorizeScript(this, cbotStart, j);
1830                 inCbotBackground = false;
1831             }
1832 
1833             if ( m_bSoluce || !bInSoluce )
1834             {
1835                 m_text[j] = buffer[i];
1836                 //if ( m_format.size() > 0 )
1837                 m_format[j] = font;
1838                 j ++;
1839             }
1840             i ++;
1841 
1842             font &= ~Gfx::FONT_MASK_TITLE;  // reset title
1843 
1844             if ( (font&Gfx::FONT_MASK_HIGHLIGHT) == Gfx::FONT_HIGHLIGHT_TABLE )
1845             {
1846                 font &= ~Gfx::FONT_HIGHLIGHT_TABLE;
1847             }
1848         }
1849     }
1850     m_len = j;
1851 
1852     Justif();
1853     ColumnFix();
1854     return true;
1855 }
1856 
1857 // Writes all the text in a file.
1858 
WriteText(std::string filename)1859 bool CEdit::WriteText(std::string filename)
1860 {
1861     if (filename.empty())  return false;
1862 
1863     COutputStream stream;
1864     stream.open(filename);
1865 
1866     if (!stream.is_open())
1867     {
1868         GetLogger()->Error("Failed to open output file: '%s'", filename.c_str());
1869         return false;
1870     }
1871 
1872     GetIndentedText(stream, 0, m_len);
1873 
1874     stream.close();
1875 
1876     return true;
1877 }
1878 
GetIndentedText(std::ostream & stream,unsigned int start,unsigned int end)1879 void CEdit::GetIndentedText(std::ostream& stream, unsigned int start, unsigned int end)
1880 {
1881     float iDim = 0.0f;
1882 
1883     if ( m_bAutoIndent )
1884     {
1885         iDim = m_dim.x;
1886         m_dim.x = 1000.0f;  // puts an infinite width!
1887         Justif();
1888     }
1889 
1890     unsigned int i = 0, line = 0;
1891     while ( m_text[i] != 0 && i < end && i < static_cast<unsigned int>(m_len) ) // TODO: fix this (un)signed comparation
1892     {
1893         if ( m_bAutoIndent && i == static_cast<unsigned int>(m_lineOffset[line]) ) // TODO: fix this (un)signed comparation
1894         {
1895             for (int n = 0; n < m_lineIndent[line]; n++)
1896             {
1897                 if (i > start)
1898                 {
1899                     stream << '\t';
1900                 }
1901             }
1902             line++;
1903         }
1904 
1905         if (i >= start)
1906         {
1907             stream << m_text[i];
1908         }
1909 
1910         i ++;
1911     }
1912 
1913     if ( m_bAutoIndent )
1914     {
1915         m_dim.x = iDim;  // presents the initial width
1916         Justif();
1917     }
1918 }
1919 
1920 
1921 // Manage the number of max characters editable.
1922 
SetMaxChar(int max)1923 void CEdit::SetMaxChar(int max)
1924 {
1925     FreeImage();
1926 
1927     m_maxChar = max;
1928 
1929     m_text.resize( m_maxChar + 1, '\0' );
1930 
1931     m_format.clear();
1932     m_format.resize(m_maxChar + 1, m_fontType);
1933 
1934     m_len = 0;
1935     m_cursor1 = 0;
1936     m_cursor2 = 0;
1937     Justif();
1938     UndoFlush();
1939 }
1940 
GetMaxChar()1941 int CEdit::GetMaxChar()
1942 {
1943     return m_maxChar;
1944 }
1945 
1946 
1947 // Mode management "editable".
1948 
SetEditCap(bool bMode)1949 void CEdit::SetEditCap(bool bMode)
1950 {
1951     m_bEdit = bMode;
1952 }
1953 
GetEditCap()1954 bool CEdit::GetEditCap()
1955 {
1956     return m_bEdit;
1957 }
1958 
1959 // Mode management "hilitable" (that's the franch).
1960 
SetHighlightCap(bool bEnable)1961 void CEdit::SetHighlightCap(bool bEnable)
1962 {
1963     m_bHilite = bEnable;
1964 }
1965 
GetHighlightCap()1966 bool CEdit::GetHighlightCap()
1967 {
1968     return m_bHilite;
1969 }
1970 
1971 // Lift in / out connection.
1972 
SetInsideScroll(bool bInside)1973 void CEdit::SetInsideScroll(bool bInside)
1974 {
1975     m_bInsideScroll = bInside;
1976 }
1977 
GetInsideScroll()1978 bool CEdit::GetInsideScroll()
1979 {
1980     return m_bInsideScroll;
1981 }
1982 
1983 // Specifies whether to display the links showing the solution.
1984 
SetSoluceMode(bool bSoluce)1985 void CEdit::SetSoluceMode(bool bSoluce)
1986 {
1987     m_bSoluce = bSoluce;
1988 }
1989 
GetSoluceMode()1990 bool CEdit::GetSoluceMode()
1991 {
1992     return m_bSoluce;
1993 }
1994 
1995 // Indicates whether the text is a defile that generic.
1996 
SetGenericMode(bool bGeneric)1997 void CEdit::SetGenericMode(bool bGeneric)
1998 {
1999     m_bGeneric = bGeneric;
2000 }
2001 
GetGenericMode()2002 bool CEdit::GetGenericMode()
2003 {
2004     return m_bGeneric;
2005 }
2006 
2007 
2008 // Management of automatic indentation mode with {}.
2009 
SetAutoIndent(bool bMode)2010 void CEdit::SetAutoIndent(bool bMode)
2011 {
2012     m_bAutoIndent = bMode;
2013 }
2014 
GetAutoIndent()2015 bool CEdit::GetAutoIndent()
2016 {
2017     return m_bAutoIndent;
2018 }
2019 
2020 
2021 
2022 // Moves the cursors.
2023 
SetCursor(int cursor1,int cursor2)2024 void CEdit::SetCursor(int cursor1, int cursor2)
2025 {
2026     if ( cursor1 > m_len )  cursor1 = m_len;
2027     if ( cursor2 > m_len )  cursor2 = m_len;
2028 
2029     m_cursor1 = cursor1;
2030     m_cursor2 = cursor2;
2031     m_bUndoForce = true;
2032     ColumnFix();
2033 }
2034 
2035 // Returns the sliders.
2036 
GetCursor(int & cursor1,int & cursor2)2037 void CEdit::GetCursor(int &cursor1, int &cursor2)
2038 {
2039     cursor1 = m_cursor1;
2040     cursor2 = m_cursor2;
2041 }
2042 
2043 
2044 // Displayed line modifies the first.
2045 
SetFirstLine(int rank)2046 void CEdit::SetFirstLine(int rank)
2047 {
2048     Scroll(rank, true);
2049 }
2050 
2051 // Returns the first displayed line.
2052 
GetFirstLine()2053 int CEdit::GetFirstLine()
2054 {
2055     if ( m_historyTotal > 0 )
2056     {
2057         if ( m_historyCurrent == 0 )
2058         {
2059             return m_lineFirst;
2060         }
2061         else
2062         {
2063             return m_history[0].firstLine;
2064         }
2065     }
2066     return m_lineFirst;
2067 }
2068 
2069 
2070 // Shows the selected area.
2071 
ShowSelect()2072 void CEdit::ShowSelect()
2073 {
2074     int     cursor1, cursor2, line;
2075 
2076     if ( m_cursor1 < m_cursor2 )
2077     {
2078         cursor1 = m_cursor1;
2079         cursor2 = m_cursor2;
2080     }
2081     else
2082     {
2083         cursor1 = m_cursor2;
2084         cursor2 = m_cursor1;
2085     }
2086 
2087     line = GetCursorLine(cursor2);
2088     if ( line >= m_lineFirst+m_lineVisible )
2089     {
2090         line -= m_lineVisible-1;
2091         if ( line < 0 )  line = 0;
2092         Scroll(line, false);
2093     }
2094 
2095     line = GetCursorLine(cursor1);
2096     if ( line < m_lineFirst )
2097     {
2098         Scroll(line, false);
2099     }
2100 }
2101 
2102 
2103 // Management of the display mode of special characters.
2104 
SetDisplaySpec(bool bDisplay)2105 void CEdit::SetDisplaySpec(bool bDisplay)
2106 {
2107     m_bDisplaySpec = bDisplay;
2108 }
2109 
GetDisplaySpec()2110 bool CEdit::GetDisplaySpec()
2111 {
2112     return m_bDisplaySpec;
2113 }
2114 
2115 
2116 // Multi-fonts mode management.
2117 
SetMultiFont(bool bMulti)2118 void CEdit::SetMultiFont(bool bMulti)
2119 {
2120     m_format.clear();
2121 
2122     if (bMulti)
2123     {
2124         m_format.resize( m_text.size() + 1, m_fontType );
2125     }
2126 }
2127 
2128 // TODO check if it works correctly; was checking if variable is null
GetMultiFont()2129 bool CEdit::GetMultiFont()
2130 {
2131     return ( m_format.size() > 0 );
2132 }
2133 
2134 
2135 // Management of the character size.
2136 
SetFontSize(float size)2137 void CEdit::SetFontSize(float size)
2138 {
2139     CControl::SetFontSize(size);
2140 
2141     MoveAdjust();
2142 }
2143 
2144 
2145 // Moves according to the visible lift.
2146 
Scroll()2147 void CEdit::Scroll()
2148 {
2149     if (m_scroll != nullptr)
2150     {
2151         float value = m_scroll->GetVisibleValue();
2152         value *= m_lineTotal - m_lineVisible;
2153         Scroll(static_cast<int>(value + 0.5f), true);
2154     }
2155 }
2156 
2157 // Moves according to the visible lift.
2158 
Scroll(int pos,bool bAdjustCursor)2159 void CEdit::Scroll(int pos, bool bAdjustCursor)
2160 {
2161     int     max, line;
2162 
2163     m_lineFirst = pos;
2164 
2165     if ( m_lineFirst < 0 )  m_lineFirst = 0;
2166 
2167     max = m_lineTotal-m_lineVisible;
2168     if ( max < 0 )  max = 0;
2169     if ( m_lineFirst > max )  m_lineFirst = max;
2170 
2171     line = GetCursorLine(m_cursor1);
2172 
2173     if ( bAdjustCursor && m_bEdit )
2174     {
2175         // Cursor too high?
2176         if ( line < m_lineFirst )
2177         {
2178             MoveLine(m_lineFirst-line, false, false);
2179             return;
2180         }
2181 
2182         // Cursor too low?
2183         if ( line >= m_lineFirst+m_lineVisible )
2184         {
2185             MoveLine(m_lineFirst+m_lineVisible-line-1, false, false);
2186             return;
2187         }
2188     }
2189 
2190     Justif();
2191 }
2192 
2193 // Moves the cursor to the beginning of the line.
2194 
MoveHome(bool bWord,bool bSelect)2195 void CEdit::MoveHome(bool bWord, bool bSelect)
2196 {
2197     int     begin, tab;
2198 
2199     if ( bWord )
2200     {
2201         m_cursor1 = 0;
2202     }
2203     else
2204     {
2205         begin = m_cursor1;
2206         while ( begin > 0 && m_text[begin-1] != '\n' )
2207         {
2208             begin --;
2209         }
2210 
2211         tab = begin;
2212         while ( tab < m_len && (m_text[tab] == '\t' || m_text[tab] == ' ') )
2213         {
2214             tab ++;
2215         }
2216 
2217         if ( m_cursor1 == tab )
2218         {
2219             m_cursor1 = begin;
2220         }
2221         else
2222         {
2223             m_cursor1 = tab;
2224         }
2225     }
2226     if ( !bSelect )  m_cursor2 = m_cursor1;
2227 
2228     m_bUndoForce = true;
2229     Justif();
2230     ColumnFix();
2231 }
2232 
2233 // Moves the cursor to the end of the line.
2234 
MoveEnd(bool bWord,bool bSelect)2235 void CEdit::MoveEnd(bool bWord, bool bSelect)
2236 {
2237     if ( bWord )
2238     {
2239         m_cursor1 = m_len;
2240     }
2241     else
2242     {
2243         while ( m_cursor1 < m_len && m_text[m_cursor1] != '\n' )
2244         {
2245             m_cursor1 ++;
2246         }
2247     }
2248     if ( !bSelect )  m_cursor2 = m_cursor1;
2249 
2250     m_bUndoForce = true;
2251     Justif();
2252     ColumnFix();
2253 }
2254 
2255 // Moves the cursor through characters.
2256 
MoveChar(int move,bool bWord,bool bSelect)2257 void CEdit::MoveChar(int move, bool bWord, bool bSelect)
2258 {
2259     int     character;
2260 
2261     if ( move == -1 )  // back?
2262     {
2263         if ( bWord )
2264         {
2265             while ( m_cursor1 > 0 )
2266             {
2267                 character = static_cast<unsigned char>(m_text[m_cursor1-1]);
2268                 if ( !IsSpace(character) )  break;
2269                 m_cursor1 --;
2270             }
2271 
2272             if ( m_cursor1 > 0 )
2273             {
2274                 character = static_cast<unsigned char>(m_text[m_cursor1-1]);
2275                 if ( IsSpace(character) )
2276                 {
2277                     while ( m_cursor1 > 0 )
2278                     {
2279                         character = static_cast<unsigned char>(m_text[m_cursor1-1]);
2280                         if ( !IsSpace(character) )  break;
2281                         m_cursor1 --;
2282                     }
2283                 }
2284                 else if ( IsWord(character) )
2285                 {
2286                     while ( m_cursor1 > 0 )
2287                     {
2288                         character = static_cast<unsigned char>(m_text[m_cursor1-1]);
2289                         if ( !IsWord(character) )  break;
2290                         m_cursor1 --;
2291                     }
2292                 }
2293                 else if ( IsSep(character) )
2294                 {
2295                     while ( m_cursor1 > 0 )
2296                     {
2297                         character = static_cast<unsigned char>(m_text[m_cursor1-1]);
2298                         if ( !IsSep(character) )  break;
2299                         m_cursor1 --;
2300                     }
2301                 }
2302             }
2303         }
2304         else
2305         {
2306             m_cursor1 --;
2307             if ( m_cursor1 < 0 )  m_cursor1 = 0;
2308         }
2309     }
2310 
2311     if ( move == 1 )  // advance?
2312     {
2313         if ( bWord )
2314         {
2315             if ( m_cursor1 < m_len )
2316             {
2317                 character = static_cast<unsigned char>(m_text[m_cursor1]);
2318                 if ( IsSpace(character) )
2319                 {
2320                     while ( m_cursor1 < m_len )
2321                     {
2322                         character = static_cast<unsigned char>(m_text[m_cursor1]);
2323                         if ( !IsSpace(character) )  break;
2324                         m_cursor1 ++;
2325                     }
2326                 }
2327                 else if ( IsWord(character) )
2328                 {
2329                     while ( m_cursor1 < m_len )
2330                     {
2331                         character = static_cast<unsigned char>(m_text[m_cursor1]);
2332                         if ( !IsWord(character) )  break;
2333                         m_cursor1 ++;
2334                     }
2335                 }
2336                 else if ( IsSep(character) )
2337                 {
2338                     while ( m_cursor1 < m_len )
2339                     {
2340                         character = static_cast<unsigned char>(m_text[m_cursor1]);
2341                         if ( !IsSep(character) )  break;
2342                         m_cursor1 ++;
2343                     }
2344                 }
2345             }
2346 
2347             while ( m_cursor1 < m_len )
2348             {
2349                 character = static_cast<unsigned char>(m_text[m_cursor1]);
2350                 if ( !IsSpace(character) )  break;
2351                 m_cursor1 ++;
2352             }
2353         }
2354         else
2355         {
2356             m_cursor1 ++;
2357             if ( m_cursor1 > m_len )  m_cursor1 = m_len;
2358         }
2359     }
2360 
2361     if ( !bSelect )  m_cursor2 = m_cursor1;
2362 
2363     m_bUndoForce = true;
2364     Justif();
2365     ColumnFix();
2366 }
2367 
2368 // Moves the cursor lines.
2369 
MoveLine(int move,bool bWord,bool bSelect)2370 void CEdit::MoveLine(int move, bool bWord, bool bSelect)
2371 {
2372     float   column, indentLength = 0.0f;
2373     int     i, line, c;
2374 
2375     if ( move == 0 )  return;
2376 
2377     for ( i=0 ; i>move ; i-- )  // back?
2378     {
2379         while ( m_cursor1 > 0 && m_text[m_cursor1-1] != '\n' )
2380         {
2381             m_cursor1 --;
2382         }
2383         if ( m_cursor1 != 0 )
2384         {
2385             m_cursor1 --;
2386             while ( m_cursor1 > 0 )
2387             {
2388                 if ( m_text[--m_cursor1] == '\n' )
2389                 {
2390                     m_cursor1 ++;
2391                     break;
2392                 }
2393             }
2394         }
2395     }
2396 
2397     for ( i=0 ; i<move ; i++ )  // advance?
2398     {
2399         while ( m_cursor1 < m_len )
2400         {
2401             if ( m_text[m_cursor1++] == '\n' )
2402             {
2403                 break;
2404             }
2405         }
2406     }
2407 
2408     line = GetCursorLine(m_cursor1);
2409 
2410     column = m_column;
2411     if ( m_bAutoIndent )
2412     {
2413         indentLength = m_engine->GetText()->GetCharWidth(static_cast<Gfx::UTF8Char>(' '), m_fontType, m_fontSize, 0.0f)
2414                         * m_engine->GetEditIndentValue();
2415         column -= indentLength*m_lineIndent[line];
2416     }
2417 
2418     if ( m_format.empty() )
2419     {
2420         c = m_engine->GetText()->Detect(std::string(m_text.data()+m_lineOffset[line]),
2421                                         m_fontType, m_fontSize,
2422                                         m_lineOffset[line+1]-m_lineOffset[line]);
2423     }
2424     else
2425     {
2426         c = m_engine->GetText()->Detect(std::string(m_text.data()+m_lineOffset[line]),
2427                                         m_format.begin() + m_lineOffset[line],
2428                                         m_format.end(),
2429                                         m_fontSize,
2430                                         m_lineOffset[line+1]-m_lineOffset[line]);
2431     }
2432 
2433     m_cursor1 = m_lineOffset[line]+c;
2434     if ( !bSelect )  m_cursor2 = m_cursor1;
2435 
2436     m_bUndoForce = true;
2437     Justif();
2438 }
2439 
2440 // Sets the horizontal position.
2441 
ColumnFix()2442 void CEdit::ColumnFix()
2443 {
2444     float   indentLength;
2445     int     line;
2446 
2447     line = GetCursorLine(m_cursor1);
2448 
2449     if ( m_format.empty() )
2450     {
2451         m_column = m_engine->GetText()->GetStringWidth(
2452                                 std::string(m_text.data()+m_lineOffset[line]),
2453                                 m_fontType, m_fontSize);
2454     }
2455     else
2456     {
2457         m_column = m_engine->GetText()->GetStringWidth(
2458                                 std::string(m_text.data()+m_lineOffset[line]),
2459                                 m_format.begin() + m_lineOffset[line],
2460                                 m_format.end(),
2461                                 m_fontSize
2462                             );
2463     }
2464 
2465     if ( m_bAutoIndent )
2466     {
2467         indentLength = m_engine->GetText()->GetCharWidth(static_cast<Gfx::UTF8Char>(' '), m_fontType, m_fontSize, 0.0f)
2468                         * m_engine->GetEditIndentValue();
2469         m_column += indentLength*m_lineIndent[line];
2470     }
2471 }
2472 
2473 
2474 // Cut the selected characters or entire line.
2475 
Cut()2476 bool CEdit::Cut()
2477 {
2478     UndoMemorize(OPERUNDO_SPEC);
2479     Copy(true);
2480 
2481     DeleteOne(0);  // deletes the selected characters
2482     Justif();
2483     ColumnFix();
2484     SendModifEvent();
2485     return true;
2486 }
2487 
2488 // Copy the selected characters or entire line.
2489 
Copy(bool memorize_cursor)2490 bool CEdit::Copy(bool memorize_cursor)
2491 {
2492     int c1, c2;
2493 
2494     c1 = m_cursor1;
2495     c2 = m_cursor2;
2496     if ( c1 > c2 )
2497     {
2498         Math::Swap(c1, c2);  // always c1 <= c2
2499     }
2500 
2501     if ( c1 == c2 )
2502     {
2503         while ( c1 > 0 )
2504         {
2505             if ( m_text[c1 - 1] == '\n' )
2506             {
2507                 break;
2508             }
2509             c1--;
2510         }
2511         while ( c2 < m_len )
2512         {
2513             c2++;
2514             if ( m_text[c2 - 1] == '\n' )
2515             {
2516                 break;
2517             }
2518         }
2519     }
2520 
2521     if ( c1 == c2 )
2522     {
2523         return false;
2524     }
2525 
2526     std::stringstream ss;
2527     GetIndentedText(ss, c1, c2);
2528     SDL_SetClipboardText(ss.str().c_str()); //TODO: Move to CApplication
2529 
2530     if (memorize_cursor)
2531     {
2532         m_cursor1 = c1;
2533         m_cursor2 = c2;
2534     }
2535 
2536     return true;
2537 }
2538 
2539 // Paste the contents of the notebook.
2540 
Paste()2541 bool CEdit::Paste()
2542 {
2543     char    c;
2544     char*   text;
2545     int     iTabToInsert=0;
2546     int     iTmp; //temp for tab space equivalant insertion
2547 
2548     if ( !m_bEdit )
2549     {
2550         return false;
2551     }
2552 
2553     text = SDL_GetClipboardText(); // TODO: Move to CApplication
2554 
2555     if ( text == nullptr )
2556     {
2557         return false;
2558     }
2559 
2560     UndoMemorize(OPERUNDO_SPEC);
2561     for (unsigned int i = 0; i<strlen(text); ++i)
2562     {
2563         c = text[i];
2564         switch(c)
2565         {
2566         case '\r':
2567             continue;
2568         case '\t':
2569             if (m_bAutoIndent)
2570             {
2571                 if (0<m_cursor1 && m_cursor1<=m_len && '\n'!=m_text[m_cursor1-1])
2572                     iTabToInsert++;
2573                 continue;
2574             }
2575             break;
2576         case '\n':
2577             iTabToInsert=0;
2578         }
2579         if (0<iTabToInsert && m_bAutoIndent)
2580         {
2581             for (iTmp=m_engine->GetEditIndentValue()*iTabToInsert; iTmp>0; --iTmp)
2582                 InsertOne(' ');
2583             iTabToInsert=0;
2584         }
2585         InsertOne(c);
2586     }
2587     if (0<iTabToInsert && m_bAutoIndent && 0<m_cursor1
2588         && (m_cursor1>=m_len || '\n'!=m_text[m_cursor1]))
2589         for (iTmp=m_engine->GetEditIndentValue() ; iTmp>0; --iTmp)
2590             InsertOne(' ');
2591     SDL_free(text);
2592     Justif();
2593     ColumnFix();
2594     SendModifEvent();
2595     return true;
2596 }
2597 
2598 
2599 // Cancels the last action.
2600 
Undo()2601 bool CEdit::Undo()
2602 {
2603     if ( !m_bEdit )
2604     {
2605         return false;
2606     }
2607 
2608     return UndoRecall();
2609 }
2610 
2611 
2612 // Inserts a character.
2613 
Insert(char character)2614 void CEdit::Insert(char character)
2615 {
2616     int     i, level, tab;
2617 
2618     if ( !m_bEdit )
2619     {
2620         return;
2621     }
2622 
2623     if ( !m_bMulti )  // single-line?
2624     {
2625         if ( character == '\n' ||
2626              character == '\t' )  return;
2627     }
2628 
2629     UndoMemorize(OPERUNDO_INSERT);
2630 
2631     if ( m_bMulti && !m_bAutoIndent )
2632     {
2633         if ( character == '\n' )
2634         {
2635             InsertOne(character);
2636             level = IndentCompute();
2637             for ( i=0 ; i<level ; i++ )
2638             {
2639                 InsertOne('\t');
2640             }
2641         }
2642         else if ( character == '{' )
2643         {
2644             tab = IndentTabCount();
2645             if ( tab != -1 )
2646             {
2647                 level = IndentCompute();
2648                 IndentTabAdjust(level-tab);
2649             }
2650             InsertOne(character);
2651         }
2652         else if ( character == '}' )
2653         {
2654             tab = IndentTabCount();
2655             if ( tab != -1 )
2656             {
2657                 level = IndentCompute()-1;
2658                 IndentTabAdjust(level-tab);
2659             }
2660             InsertOne(character);
2661         }
2662         else
2663         {
2664             InsertOne(character);
2665         }
2666     }
2667     else if ( m_bAutoIndent )
2668     {
2669         if (character == '{')
2670         {
2671             InsertOne(character);
2672             InsertOne('}');
2673             MoveChar(-1, false, false);
2674         }
2675         else if (character == '\t')
2676         {
2677             for ( i=0 ; i<m_engine->GetEditIndentValue() ; i++ )
2678             {
2679                 InsertOne(' ');
2680             }
2681         }
2682         else if (character == '\n')
2683         {
2684             if (m_cursor1 > 1 && m_text[m_cursor1-1] == '{')
2685             {
2686                 InsertOne(character);
2687                 InsertOne('\n');
2688                 MoveChar(-1, false, false);
2689             }
2690             else
2691             {
2692                 InsertOne(character);
2693             }
2694         }
2695         else
2696         {
2697             InsertOne(character);
2698         }
2699     }
2700     else
2701     {
2702         InsertOne(character);
2703     }
2704 
2705     Justif();
2706     ColumnFix();
2707 }
2708 
2709 // Inserts a plain character.
2710 
InsertOne(char character)2711 void CEdit::InsertOne(char character)
2712 {
2713     int     i;
2714 
2715     if ( !m_bEdit )  return;
2716     if ( !m_bMulti && character == '\n' )  return;
2717 
2718     if ( m_cursor1 != m_cursor2 )
2719     {
2720         DeleteOne(0);  // deletes the selected characters
2721     }
2722 
2723     if ( m_len >= GetMaxChar() )  return;
2724 
2725     m_text.resize( m_text.size() + 1, '\0' );
2726     m_format.resize( m_format.size() + 1, m_fontType );
2727 
2728     for ( i=m_len ; i>m_cursor1 ; i-- )
2729     {
2730         m_text[i] = m_text[i-1];  // shoot
2731 
2732         if ( m_format.size() > static_cast<unsigned int>(i) )
2733         {
2734             m_format[i] = m_format[i-1];  // shoot
2735         }
2736     }
2737 
2738     m_len ++;
2739 
2740     m_text[m_cursor1] = character;
2741 
2742     if ( static_cast<unsigned int>(m_cursor1) < m_format.size() )
2743     {
2744         m_format[m_cursor1] = m_fontType;
2745     }
2746 
2747     m_cursor1++;
2748     m_cursor2 = m_cursor1;
2749 }
2750 
2751 // Deletes the character left of cursor or all selected characters.
2752 
Delete(int dir)2753 void CEdit::Delete(int dir)
2754 {
2755     if ( !m_bEdit )  return;
2756 
2757     UndoMemorize(OPERUNDO_DELETE);
2758     DeleteOne(dir);
2759 
2760     Justif();
2761     ColumnFix();
2762 }
2763 
2764 // Deletes the character left of cursor or all selected plain characters.
2765 
DeleteOne(int dir)2766 void CEdit::DeleteOne(int dir)
2767 {
2768     int     i, end, hole;
2769 
2770     if ( !m_bEdit )  return;
2771 
2772     if ( m_cursor1 == m_cursor2 )
2773     {
2774         if ( dir < 0 )
2775         {
2776             if ( m_cursor1 == 0 )  return;
2777             m_cursor1 --;
2778         }
2779         else
2780         {
2781             if ( m_cursor2 == m_len )  return;
2782             m_cursor2 ++;
2783         }
2784     }
2785 
2786     if ( m_cursor1 > m_cursor2 )  Math::Swap(m_cursor1, m_cursor2);
2787     hole = m_cursor2-m_cursor1;
2788     end = m_len-hole;
2789     for ( i=m_cursor1 ; i<end ; i++ )
2790     {
2791         m_text[i] = m_text[i+hole];
2792 
2793         if ( m_format.size() > static_cast<unsigned int>(i + hole) )
2794         {
2795             m_format[i] = m_format[i+hole];
2796         }
2797     }
2798     m_len -= hole;
2799     m_cursor2 = m_cursor1;
2800 }
2801 
2802 // Delete word
2803 
DeleteWord(int dir)2804 void CEdit::DeleteWord(int dir)
2805 {
2806     if ( !m_bEdit ) return;
2807 
2808     if ( dir < 0 )
2809     {
2810         if ( m_cursor1 > 0) m_cursor2 = --m_cursor1;
2811         else m_cursor2 = m_cursor1;
2812 
2813         if ( IsBreaker(m_text[m_cursor1]) )
2814         {
2815             Delete(1);
2816             return;
2817         }
2818         else ++m_cursor1;
2819 
2820         while ( m_cursor1 < m_len && !IsDelimiter(m_text[m_cursor1]) ) ++m_cursor1;
2821 
2822         while ( m_cursor2 > 0 && IsSpace(m_text[m_cursor2]) ) --m_cursor2;
2823 
2824         if ( !IsDelimiter(m_text[m_cursor2]) )
2825         {
2826             while ( m_cursor2 > 0 && !IsDelimiter(m_text[m_cursor2]) ) --m_cursor2;
2827             if ( IsBreaker(m_text[m_cursor2]) ) ++m_cursor2;
2828         }
2829 
2830         Delete(-1);
2831     }
2832     else
2833     {
2834         m_cursor2 = m_cursor1;
2835 
2836         while ( m_cursor1 < m_len && IsSpace(m_text[m_cursor1]) ) ++m_cursor1;
2837 
2838         if ( IsBreaker(m_text[m_cursor1]) )
2839         {
2840             ++m_cursor1;
2841             Delete(1);
2842             return;
2843         }
2844 
2845         while ( m_cursor1 < m_len && !IsDelimiter(m_text[m_cursor1]) ) ++m_cursor1;
2846 
2847         if ( !IsDelimiter(m_text[m_cursor2]) )
2848         {
2849             while ( m_cursor2 > 0 && !IsDelimiter(m_text[m_cursor2]) ) --m_cursor2;
2850             if ( IsBreaker(m_text[m_cursor2]) ) ++m_cursor2;
2851         }
2852 
2853         Delete(-1);
2854     }
2855 }
2856 
2857 // Calculates the indentation level of brackets {and}.
2858 
IndentCompute()2859 int CEdit::IndentCompute()
2860 {
2861     int     i, level;
2862 
2863     level = 0;
2864     for ( i=0 ; i<m_cursor1 ; i++ )
2865     {
2866         if ( m_text[i] == '{' )  level ++;
2867         if ( m_text[i] == '}' )  level --;
2868     }
2869 
2870     if ( level < 0 )  level = 0;
2871     return level;
2872 }
2873 
2874 // Counts the number of tabs before the cursor.
2875 // Returns -1 if there is something else.
2876 
IndentTabCount()2877 int CEdit::IndentTabCount()
2878 {
2879     int     i, nb;
2880 
2881     if ( m_cursor1 != m_cursor2 )  return -1;
2882 
2883     i = m_cursor1;
2884     nb = 0;
2885     while ( i > 0 )
2886     {
2887         if ( m_text[i-1] == '\n' )  return nb;
2888         if ( m_text[i-1] != '\t' )  return -1;
2889         nb ++;
2890         i --;
2891     }
2892     return nb;
2893 }
2894 
2895 // Adds or removes some tabs.
2896 
IndentTabAdjust(int number)2897 void CEdit::IndentTabAdjust(int number)
2898 {
2899     int     i;
2900 
2901     for ( i=0 ; i<number ; i++ )  // add?
2902     {
2903         InsertOne('\t');
2904     }
2905 
2906     for ( i=0 ; i>number ; i-- )  // delete?
2907     {
2908         DeleteOne(-1);
2909     }
2910 }
2911 
2912 
2913 // Indent the left or right the entire selection.
2914 
Shift(bool bLeft)2915 bool CEdit::Shift(bool bLeft)
2916 {
2917     bool    bInvert = false;
2918     int     c1, c2, i;
2919 
2920     if ( m_cursor1 == m_cursor2 )  return false;
2921 
2922     UndoMemorize(OPERUNDO_SPEC);
2923 
2924     c1 = m_cursor1;
2925     c2 = m_cursor2;
2926     if ( c1 > c2 )
2927     {
2928         Math::Swap(c1, c2);  // always c1 <= c2
2929         bInvert = true;
2930     }
2931 
2932     if ( c1 > 0 )
2933     {
2934         if ( m_text[c1-1] != '\n' )  return false;
2935     }
2936     if ( c2 < m_len )
2937     {
2938         if ( m_text[c2-1] != '\n' )  return false;
2939     }
2940 
2941     if ( bLeft )  // shifts left?
2942     {
2943         i = c1;
2944         while ( i < c2 )
2945         {
2946             if ( m_text[i] == '\t' )
2947             {
2948                 m_cursor1 = i;
2949                 m_cursor2 = i+1;
2950                 DeleteOne(0);
2951                 c2 --;
2952             }
2953             while ( i < c2 && m_text[i++] != '\n' );
2954         }
2955     }
2956     else    // shifts right?
2957     {
2958         i = c1;
2959         while ( i < c2 )
2960         {
2961             m_cursor1 = m_cursor2 = i;
2962             InsertOne('\t');
2963             c2 ++;
2964             while ( i < c2 && m_text[i++] != '\n' );
2965         }
2966     }
2967 
2968     if ( bInvert )  Math::Swap(c1, c2);
2969     m_cursor1 = c1;
2970     m_cursor2 = c2;
2971 
2972     Justif();
2973     ColumnFix();
2974     SendModifEvent();
2975     return true;
2976 }
2977 
2978 // Math::Min conversion <-> shift the selection.
2979 
MinMaj(bool bMaj)2980 bool CEdit::MinMaj(bool bMaj)
2981 {
2982     int     c1, c2, i, character;
2983 
2984     if ( m_cursor1 == m_cursor2 )  return false;
2985 
2986     UndoMemorize(OPERUNDO_SPEC);
2987 
2988     c1 = m_cursor1;
2989     c2 = m_cursor2;
2990     if ( c1 > c2 )  Math::Swap(c1, c2);  // always c1 <= c2
2991 
2992     for ( i=c1 ; i<c2 ; i++ )
2993     {
2994         character = static_cast<unsigned char>(m_text[i]);
2995         if ( bMaj )  character = toupper(character);
2996         else         character = tolower(character);
2997         m_text[i] = character;
2998     }
2999 
3000     Justif();
3001     ColumnFix();
3002     SendModifEvent();
3003     return true;
3004 }
3005 
3006 
3007 // Cut all text lines.
3008 
Justif()3009 void CEdit::Justif()
3010 {
3011     float   width, size, indentLength = 0.0f;
3012     int     i, j, k, line, indent;
3013     bool    bDual, bString, bRem;
3014 
3015     m_lineOffset.clear();
3016     m_lineIndent.clear();
3017 
3018     indent = 0;
3019     m_lineTotal = 0;
3020     m_lineOffset.push_back( 0 );
3021     m_lineIndent.push_back( indent );
3022     m_lineTotal ++;
3023 
3024     if ( m_bAutoIndent )
3025     {
3026         indentLength = m_engine->GetText()->GetCharWidth(static_cast<Gfx::UTF8Char>(' '), m_fontType, m_fontSize, 0.0f)
3027                         * m_engine->GetEditIndentValue();
3028     }
3029 
3030     bString = bRem = false;
3031     i = k = 0;
3032     while ( true )
3033     {
3034         bDual = false;
3035 
3036         width = m_dim.x-(7.5f/640.0f)*(m_fontSize/Gfx::FONT_SIZE_SMALL)*2.0f-(m_bMulti?MARGX*2.0f+SCROLL_WIDTH:0.0f);
3037         if ( m_bAutoIndent )
3038         {
3039             width -= indentLength*m_lineIndent[m_lineTotal-1];
3040         }
3041 
3042         if ( m_format.empty() )
3043         {
3044             // TODO check if good
3045 
3046             i += m_engine->GetText()->Justify(m_text.data()+i, m_fontType,
3047                                               m_fontSize, width);
3048         }
3049         else
3050         {
3051             size = m_fontSize;
3052 
3053             if ( m_format.size() > static_cast<unsigned int>(i) && (m_format[i]&Gfx::FONT_MASK_TITLE) == Gfx::FONT_TITLE_BIG )  // headline?
3054             {
3055                 size *= BIG_FONT;
3056                 bDual = true;
3057             }
3058 
3059             if ( m_format.size() > static_cast<unsigned int>(i) && (m_format[i]&Gfx::FONT_MASK_IMAGE) != 0 )  // image part?
3060             {
3061                 i ++;  // jumps just a character (index in m_image)
3062             }
3063             else
3064             {
3065                 // TODO check if good
3066                 i += m_engine->GetText()->Justify(std::string(m_text.data()+i),
3067                                                   m_format.begin() + i,
3068                                                   m_format.end(),
3069                                                   size,
3070                                                   width);
3071             }
3072         }
3073 
3074         if ( i >= m_len )  break;
3075 
3076         if ( m_bAutoIndent )
3077         {
3078             for ( j=m_lineOffset[m_lineTotal-1] ; j<i ; j++ )
3079             {
3080                 if ( !bRem && m_text[j] == '\"' )  bString = !bString;
3081                 if ( !bString &&
3082                      m_text[j] == '/' &&
3083                      m_text[j+1] == '/' )  bRem = true;
3084                 if ( m_text[j] == '\n' )  bString = bRem = false;
3085                 if ( m_text[j] == '{' && !bString && !bRem )  indent ++;
3086                 if ( m_text[j] == '}' && !bString && !bRem )  indent --;
3087             }
3088             if ( indent < 0 )  indent = 0;
3089         }
3090 
3091         m_lineOffset.push_back( i );
3092         m_lineIndent.push_back( indent );
3093         m_lineTotal ++;
3094         if ( bDual )
3095         {
3096             m_lineOffset.push_back( i );
3097             m_lineIndent.push_back( indent );
3098             m_lineTotal ++;
3099         }
3100         if ( k == i ) break;
3101         k = i;
3102     }
3103 
3104     if ( m_len > 0 && m_text[m_len-1] == '\n' )
3105     {
3106         m_lineOffset.push_back( m_len );
3107         m_lineIndent.push_back( 0 );
3108         m_lineTotal ++;
3109     }
3110     m_lineOffset.push_back( m_len );
3111     m_lineIndent.push_back( 0 );
3112 
3113     if ( m_bAutoIndent )
3114     {
3115         for ( i=0 ; i<=m_lineTotal ; i++ )
3116         {
3117             if ( m_text[m_lineOffset[i]] == '}' )
3118             {
3119                 if ( m_lineIndent[i] > 0 )  m_lineIndent[i] --;
3120             }
3121         }
3122     }
3123 
3124     if ( m_bMulti )
3125     {
3126         if ( m_bEdit )
3127         {
3128             line = GetCursorLine(m_cursor1);
3129             if ( line < m_lineFirst )
3130             {
3131                 m_lineFirst = line;
3132             }
3133             if ( line >= m_lineFirst+m_lineVisible )
3134             {
3135                 m_lineFirst = line-m_lineVisible+1;
3136             }
3137         }
3138     }
3139     else
3140     {
3141         m_lineFirst = 0;
3142     }
3143 
3144     UpdateScroll();
3145 
3146     m_timeBlink = 0.0f;  // lights the cursor immediately
3147 }
3148 
3149 // Returns the rank of the line where the cursor is located.
3150 
GetCursorLine(int cursor)3151 int CEdit::GetCursorLine(int cursor)
3152 {
3153     int     line, i;
3154 
3155     line = 0;
3156     for ( i=0 ; i<m_lineTotal ; i++ )
3157     {
3158         if ( cursor >= m_lineOffset[i] )
3159         {
3160             line = i;
3161         }
3162     }
3163     return line;
3164 }
3165 
3166 
3167 // Flush the buffer undo.
3168 
UndoFlush()3169 void CEdit::UndoFlush()
3170 {
3171     int     i;
3172 
3173     for ( i=0 ; i<EDITUNDOMAX ; i++ )
3174     {
3175         m_undo[i].text.clear();
3176     }
3177 
3178     m_bUndoForce = true;
3179     m_undoOper = OPERUNDO_SPEC;
3180 }
3181 
3182 // Memorize the current state before a change.
3183 
UndoMemorize(OperUndo oper)3184 void CEdit::UndoMemorize(OperUndo oper)
3185 {
3186     int     i, len;
3187 
3188     if ( !m_bUndoForce               &&
3189          oper       != OPERUNDO_SPEC &&
3190          m_undoOper != OPERUNDO_SPEC &&
3191          oper == m_undoOper          )  return;
3192 
3193     m_bUndoForce = false;
3194     m_undoOper = oper;
3195 
3196     m_undo[EDITUNDOMAX-1].text.clear();
3197 
3198     for ( i=EDITUNDOMAX-1 ; i>=1 ; i-- )
3199     {
3200         m_undo[i] = m_undo[i-1];
3201     }
3202 
3203     len = m_len;
3204     if ( len == 0 )  len ++;
3205     m_undo[0].text = m_text;
3206     m_undo[0].len = m_len;
3207 
3208     m_undo[0].cursor1 = m_cursor1;
3209     m_undo[0].cursor2 = m_cursor2;
3210     m_undo[0].lineFirst = m_lineFirst;
3211 }
3212 
3213 // Back to previous state.
3214 
UndoRecall()3215 bool CEdit::UndoRecall()
3216 {
3217     int     i;
3218 
3219     if ( m_undo[0].text.empty() )  return false;
3220 
3221     m_len = m_undo[0].len;
3222     m_text = m_undo[0].text;
3223 
3224     m_cursor1 = m_undo[0].cursor1;
3225     m_cursor2 = m_undo[0].cursor2;
3226     m_lineFirst = m_undo[0].lineFirst;
3227 
3228     for ( i=0 ; i<EDITUNDOMAX-1 ; i++ )
3229     {
3230         m_undo[i] = m_undo[i+1];
3231     }
3232     m_undo[EDITUNDOMAX-1].text.clear();
3233 
3234     m_bUndoForce = true;
3235     Justif();
3236     ColumnFix();
3237     SendModifEvent();
3238     return true;
3239 }
3240 
3241 
3242 // Clears the format of all characters.
3243 
ClearFormat()3244 bool CEdit::ClearFormat()
3245 {
3246     if ( m_format.empty() )
3247     {
3248         SetMultiFont(true);
3249     }
3250     m_format.clear();
3251 
3252     return true;
3253 }
3254 
3255 // Changes the format of a sequence of characters.
3256 
SetFormat(int cursor1,int cursor2,int format)3257 bool CEdit::SetFormat(int cursor1, int cursor2, int format)
3258 {
3259     int     i;
3260 
3261     if ( m_format.size() < static_cast<unsigned int>(cursor2) )
3262         SetMultiFont(true);
3263 
3264     for ( i=cursor1 ; i<cursor2 ; i++ )
3265     {
3266         m_format.at(i) = (m_format.at(i) & ~Gfx::FONT_MASK_HIGHLIGHT) | format;
3267     }
3268 
3269     return true;
3270 }
3271 
UpdateScroll()3272 void CEdit::UpdateScroll()
3273 {
3274     if (m_scroll != nullptr)
3275     {
3276         if ( m_lineTotal <= m_lineVisible )
3277         {
3278             m_scroll->SetVisibleRatio(1.0f);
3279             m_scroll->SetVisibleValue(0.0f);
3280             m_scroll->SetArrowStep(0.0f);
3281         }
3282         else
3283         {
3284             float value = static_cast<float>(m_lineVisible) / m_lineTotal;
3285             m_scroll->SetVisibleRatio(value);
3286 
3287             value = static_cast<float>(m_lineFirst) / (m_lineTotal - m_lineVisible);
3288             m_scroll->SetVisibleValue(value);
3289 
3290             value = 1.0f / (m_lineTotal - m_lineVisible);
3291             m_scroll->SetArrowStep(value);
3292         }
3293     }
3294 }
3295 
SetFocus(CControl * control)3296 void CEdit::SetFocus(CControl* control)
3297 {
3298     bool oldFocus = m_bFocus;
3299     CControl::SetFocus(control);
3300 
3301     if (oldFocus != m_bFocus)
3302     {
3303         UpdateFocus();
3304     }
3305 }
3306 
UpdateFocus()3307 void CEdit::UpdateFocus()
3308 {
3309     CApplication::GetInstancePointer()->SetTextInput(m_bFocus, m_eventType);
3310 }
3311 
3312 }
3313