1 /*-*- c++ -*-********************************************************
2 * wxFTCanvas: a canvas for editing formatted text *
4 * (C) 1998 by Karsten Ballüder (Ballueder@usa.net) *
7 *******************************************************************/
10 - each Object knows its size and how to draw itself
11 - the list is responsible for calculating positions
12 - the draw coordinates for each object are the top left corner
13 - coordinates only get calculated when things get redrawn
14 - during redraw each line gets iterated over twice, first just
15 calculating baselines and positions, second to actually draw it
16 - the cursor position is the position before an object, i.e. if the
17 buffer starts with a text-object, cursor 0,0 is just before the
22 #pragma implementation "wxllist.h"
25 // these two lines are for use in M:
27 //#include "gui/wxllist.h"
32 # include "iostream.h"
35 # include <wx/postscrp.h>
36 # include <wx/print.h>
39 #define BASELINESTRETCH 12
42 static const char *_t
[] = { "invalid", "text", "cmd", "icon",
46 wxLayoutObjectBase::Debug(void)
49 cerr
<< _t
[GetType()] << ": size=" << GetSize(&bl
).x
<< ","
50 << GetSize(&bl
).y
<< " bl=" << bl
;
53 # define WXL_VAR(x) cerr << #x"=" << x << endl
54 # define WXL_DBG_POINT(p) cerr << #p << ": " << p.x << ',' << p.y << endl
55 # define WXL_TRACE(f) cerr << #f":" << endl
58 # define WXL_DBG_POINT(p)
62 //-------------------------- wxLayoutObjectText
64 wxLayoutObjectText::wxLayoutObjectText(const String
&txt
)
73 wxLayoutObjectText::GetSize(CoordType
*baseLine
) const
75 if(baseLine
) *baseLine
= m_BaseLine
;
76 return wxPoint(m_Width
, m_Height
);
80 wxLayoutObjectText::Draw(wxDC
&dc
, wxPoint position
, CoordType baseLine
,
84 dc
.GetTextExtent(Str(m_Text
),&m_Width
, &m_Height
, &descent
);
85 //FIXME: wxGTK does not set descent to a descent value yet.
87 descent
= (2*m_Height
)/10; // crude fix
88 m_BaseLine
= m_Height
- descent
;
89 position
.y
+= baseLine
-m_BaseLine
;
91 dc
.DrawText(Str(m_Text
),position
.x
,position
.y
);
92 # ifdef WXLAYOUT_DEBUG
93 // dc.DrawRectangle(position.x, position.y, m_Width, m_Height);
99 wxLayoutObjectText::Debug(void)
101 wxLayoutObjectBase::Debug();
102 cerr
<< " `" << m_Text
<< '\'';
106 //-------------------------- wxLayoutObjectIcon
108 wxLayoutObjectIcon::wxLayoutObjectIcon(wxIcon
*icon
)
114 wxLayoutObjectIcon::Draw(wxDC
&dc
, wxPoint position
, CoordType baseLine
,
117 position
.y
+= baseLine
- m_Icon
->GetHeight();
119 dc
.DrawIcon(m_Icon
,position
.x
,position
.y
);
123 wxLayoutObjectIcon::GetSize(CoordType
*baseLine
) const
126 *baseLine
= m_Icon
->GetHeight();
127 return wxPoint(m_Icon
->GetWidth(), m_Icon
->GetHeight());
130 //-------------------------- wxLayoutObjectCmd
133 wxLayoutObjectCmd::wxLayoutObjectCmd(int size
, int family
, int style
, int
134 weight
, bool underline
,
135 wxColour
const *fg
, wxColour
const *bg
)
138 m_font
= new wxFont(size
,family
,style
,weight
,underline
);
143 wxLayoutObjectCmd::~wxLayoutObjectCmd()
149 wxLayoutObjectCmd::GetStyle(void) const
151 wxLayoutStyleInfo
*si
= new wxLayoutStyleInfo();
154 si
->size
= m_font
->GetPointSize();
155 si
->family
= m_font
->GetFamily();
156 si
->style
= m_font
->GetStyle();
157 si
->underline
= m_font
->GetUnderlined();
158 si
->weight
= m_font
->GetWeight();
160 si
->fg_red
= m_ColourFG
->Red();
161 si
->fg_green
= m_ColourFG
->Green();
162 si
->fg_blue
= m_ColourFG
->Blue();
163 si
->bg_red
= m_ColourBG
->Red();
164 si
->bg_green
= m_ColourBG
->Green();
165 si
->bg_blue
= m_ColourBG
->Blue();
171 wxLayoutObjectCmd::Draw(wxDC
&dc
, wxPoint position
, CoordType lineHeight
,
175 // this get called even when draw==false, so that recalculation
176 // uses right font sizes
179 dc
.SetTextForeground(*m_ColourFG
);
181 dc
.SetTextBackground(*m_ColourBG
);
184 //-------------------------- wxwxLayoutList
186 wxLayoutList::wxLayoutList()
188 m_DefaultSetting
= NULL
;
192 wxLayoutList::~wxLayoutList()
195 delete m_DefaultSetting
;
200 wxLayoutList::LineBreak(void)
202 Insert(new wxLayoutObjectLineBreak
);
203 m_CursorPosition
.x
= 0; m_CursorPosition
.y
++;
207 wxLayoutList::SetFont(int family
, int size
, int style
, int weight
,
208 int underline
, wxColour
const *fg
,
211 if(family
!= -1) m_FontFamily
= family
;
212 if(size
!= -1) m_FontPtSize
= size
;
213 if(style
!= -1) m_FontStyle
= style
;
214 if(weight
!= -1) m_FontWeight
= weight
;
215 if(underline
!= -1) m_FontUnderline
= underline
!= 0;
217 if(fg
!= NULL
) m_ColourFG
= fg
;
218 if(bg
!= NULL
) m_ColourBG
= bg
;
221 new wxLayoutObjectCmd(m_FontPtSize
,m_FontFamily
,m_FontStyle
,m_FontWeight
,m_FontUnderline
,
222 m_ColourFG
, m_ColourBG
));
226 wxLayoutList::SetFont(int family
, int size
, int style
, int weight
,
227 int underline
, char const *fg
, char const *bg
)
234 cfg
= wxTheColourDatabase
->FindColour(fg
);
236 cbg
= wxTheColourDatabase
->FindColour(bg
);
238 SetFont(family
,size
,style
,weight
,underline
,cfg
,cbg
);
242 /// for access by wxLayoutWindow:
244 wxLayoutList::GetSize(CoordType
*max_x
, CoordType
*max_y
,
245 CoordType
*lineHeight
)
247 wxASSERT(max_x
); wxASSERT(max_y
); wxASSERT(lineHeight
);
250 *lineHeight
= m_LineHeight
;
254 wxLayoutList::Draw(wxDC
&dc
, bool findObject
, wxPoint
const &findCoords
)
256 wxLayoutObjectList::iterator i
;
258 // in case we need to look for an object
259 wxLayoutObjectBase
*foundObject
= NULL
;
261 // first object in current line
262 wxLayoutObjectList::iterator headOfLine
;
264 // do we need to recalculate current line?
265 bool recalculate
= false;
267 // do we calculate or draw? Either true or false.
269 // drawing parameters:
270 wxPoint position
= wxPoint(0,0);
271 wxPoint position_HeadOfLine
;
272 CoordType baseLine
= m_FontPtSize
;
273 CoordType baseLineSkip
= (BASELINESTRETCH
* baseLine
)/10;
275 // where to draw the cursor
277 cursorPosition
= wxPoint(0,0),
278 cursorSize
= wxPoint(1,baseLineSkip
);
280 // the cursor position inside the object
281 CoordType cursorOffset
= 0;
282 // object under cursor
283 wxLayoutObjectList::iterator cursorObject
= FindCurrentObject(&cursorOffset
);
285 // queried from each object:
286 wxPoint size
= wxPoint(0,0);
287 CoordType objBaseLine
= baseLine
;
288 wxLayoutObjectType type
;
291 wxLayoutObjectText
*tobj
= NULL
;
294 // this is needed for printing to a printer:
295 // only interesting for printer/PS output
296 int pageWidth
, pageHeight
; //GetSize() still needs int at the moment
299 int top
, bottom
, left
, right
;
304 dc
.IsKindOf(CLASSINFO(wxPrinterDC
)) ||
306 dc
.IsKindOf(CLASSINFO(wxPostScriptDC
)))
308 dc
.GetSize(&pageWidth
, &pageHeight
);
309 dc
.StartDoc(_("Printing..."));
311 margins
.top
= (1*pageHeight
)/10; // 10%
312 margins
.bottom
= (9*pageHeight
)/10; // 90%
313 margins
.left
= (1*pageWidth
)/10;
314 margins
.right
= (9*pageWidth
)/10;
318 margins
.top
= 0; margins
.left
= 0;
322 position
.y
= margins
.right
;
323 position
.x
= margins
.left
;
325 // if the cursorobject is a cmd, we need to find the first
327 while(cursorObject
!= end()
328 && (*cursorObject
)->GetType() == WXLO_TYPE_CMD
)
331 headOfLine
= begin();
332 position_HeadOfLine
= position
;
334 // setting up the default:
335 dc
.SetTextForeground( *wxBLACK
);
336 dc
.SetTextBackground( *wxWHITE
);
337 dc
.SetBackgroundMode( wxSOLID
); // to enable setting of text background
338 dc
.SetFont( *wxNORMAL_FONT
);
341 //FIXME: who frees the brush, how long does it need to exist?
343 m_DefaultSetting
->Draw(dc
,wxPoint(0,0),0,true);
345 // we calculate everything for drawing a line, then rewind to the
346 // begin of line and actually draw it
354 type
= (*i
)->GetType();
356 // to initialise sizes of objects, we need to call Draw
357 (*i
)->Draw(dc
, position
, baseLine
, draw
);
359 // update coordinates for next object:
360 size
= (*i
)->GetSize(&objBaseLine
);
361 if(findObject
&& draw
) // we need to look for an object
363 if(findCoords
.y
>= position
.y
364 && findCoords
.y
<= position
.y
+size
.y
365 && findCoords
.x
>= position
.x
366 && findCoords
.x
<= position
.x
+size
.x
)
369 findObject
= false; // speeds things up
373 if(m_Editable
&& draw
&& i
== cursorObject
)
375 WXL_VAR((**cursorObject
).GetType());
376 WXL_VAR(m_CursorPosition
.x
); WXL_VAR(m_CursorPosition
.y
);
377 if(type
== WXLO_TYPE_TEXT
) // special treatment
379 long descent
= 0l; long width
, height
;
380 tobj
= (wxLayoutObjectText
*)*i
;
381 String str
= tobj
->GetText();
382 WXL_VAR(m_CursorPosition
.x
);
383 str
= str
.substr(0, cursorOffset
);
384 dc
.GetTextExtent(Str(str
), &width
,&height
, &descent
);
385 cursorPosition
= wxPoint(position
.x
+width
,
386 position
.y
+(baseLineSkip
-height
));
387 cursorSize
= wxPoint(1, height
);
389 else if(type
== WXLO_TYPE_LINEBREAK
)
391 WXL_VAR(cursorOffset
);
393 cursorPosition
= wxPoint(0, position
.y
+baseLineSkip
);
395 cursorPosition
= wxPoint(0, position
.y
);
396 cursorSize
= wxPoint(1,baseLineSkip
);
401 cursorPosition
= wxPoint(position
.x
, position
.y
);
402 cursorSize
= wxPoint(size
.x
> 0 ? size
.x
: 1,size
.y
> 0 ? size
.y
: baseLineSkip
);
406 // calculate next object's position:
407 position
.x
+= size
.x
;
408 if(position
.x
> m_MaxX
)
411 // do we need to increase the line's height?
412 if(size
.y
> baseLineSkip
)
414 baseLineSkip
= size
.y
;
417 if(objBaseLine
> baseLine
)
419 baseLine
= objBaseLine
;
423 // now check whether we have finished handling this line:
424 if(type
== WXLO_TYPE_LINEBREAK
|| i
== tail())
426 if(recalculate
) // do this line again
428 position
.x
= position_HeadOfLine
.x
;
433 if(! draw
) // finished calculating sizes
435 // if the this line needs to go onto a new page, we need
436 // to change pages before drawing it:
437 if(margins
.bottom
!= -1 && position
.y
> margins
.bottom
)
440 position_HeadOfLine
.y
= margins
.top
;
443 // do this line again, this time drawing it
444 position
= position_HeadOfLine
;
449 else // we have drawn a line, so continue calculating next one
453 // is it a linebreak?
454 if(type
== WXLO_TYPE_LINEBREAK
|| i
== tail())
456 position
.x
= margins
.left
;
457 position
.y
+= baseLineSkip
;
458 baseLine
= m_FontPtSize
;
459 objBaseLine
= baseLine
; // not all objects set it
460 baseLineSkip
= (BASELINESTRETCH
* baseLine
)/10;
463 position_HeadOfLine
= position
;
471 dc
.DrawRectangle(cursorPosition
.x
, cursorPosition
.y
,
472 cursorSize
.x
, cursorSize
.y
);
478 #ifdef WXLAYOUT_DEBUG
480 wxLayoutList::Debug(void)
483 wxLayoutObjectList::iterator i
;
486 "------------------------debug start-------------------------" << endl
;
487 for(i
= begin(); i
!= end(); i
++)
493 "-----------------------debug end----------------------------"
495 // show current object:
497 << m_CursorPosition
.x
<< ','
498 << m_CursorPosition
.y
;
500 i
= FindCurrentObject(&offs
);
501 cerr
<< " line length: " << GetLineLength(i
,offs
) << " ";
504 cerr
<< "<<no object found>>" << endl
;
505 return; // FIXME we should set cursor position to maximum allowed
508 if((*i
)->GetType() == WXLO_TYPE_TEXT
)
510 cerr
<< " \"" << ((wxLayoutObjectText
*)(*i
))->GetText() << "\", offs: "
514 cerr
<< ' ' << _t
[(*i
)->GetType()] << endl
;
519 /******************** editing stuff ********************/
521 // don't change this, I know how to optimise this and will do it real
526 * Finds the object belonging to a given cursor position cpos and
527 * returns an iterator to that object and stores the relative cursor
528 * position in offset.
530 * For linebreaks, the offset can be 0=before or 1=after.
532 * If the cpos coordinates don't exist, they are modified.
535 wxLayoutObjectList::iterator
536 wxLayoutList::FindObjectCursor(wxPoint
*cpos
, CoordType
*offset
)
538 wxPoint object
= wxPoint(0,0); // runs along the objects
540 wxLayoutObjectList::iterator i
;
542 #ifdef WXLAYOUT_DEBUG
543 cerr
<< "Looking for object at " << cpos
->x
<< ',' << cpos
->y
<<
546 for(i
= begin(); i
!= end() && object
.y
<= cpos
->y
; i
++)
548 width
= (**i
).CountPositions();
549 if(cpos
->y
== object
.y
) // a possible candidate
551 if((**i
).GetType() ==WXLO_TYPE_LINEBREAK
)
553 if(cpos
->x
== object
.x
)
555 if(offset
) *offset
= 0;
558 if(offset
) *offset
=1;
562 if(cpos
->x
>= object
.x
&& cpos
->x
<= object
.x
+width
) // overlap
564 if(offset
) *offset
= cpos
->x
-object
.x
;
565 #ifdef WXLAYOUT_DEBUG
566 cerr
<< " found object at " << object
.x
<< ',' <<
567 object
.y
<< ", type:" << _t
[(*i
)->GetType()] <<endl
;
572 // no overlap, increment coordinates
574 if((**i
).GetType() == WXLO_TYPE_LINEBREAK
)
580 #ifdef WXLAYOUT_DEBUG
581 cerr
<< " not found" << endl
;
583 // return last object, coordinates of that one:
587 if((**i
).GetType()==WXLO_TYPE_LINEBREAK
)
593 cpos
->x
= object
.x
; // would be the coordinate of next object
595 cpos
->x
+= width
; // last object's width
596 if(*offset
) *offset
= cpos
->x
-object
.x
;
597 return i
; // not found
600 wxLayoutObjectList::iterator
601 wxLayoutList::FindCurrentObject(CoordType
*offset
)
603 wxLayoutObjectList::iterator obj
= end();
605 obj
= FindObjectCursor(&m_CursorPosition
, offset
);
606 if(obj
== end()) // not ideal yet
609 if(obj
!= end()) // tail really exists
610 *offset
= (*obj
)->CountPositions(); // at the end of it
616 wxLayoutList::MoveCursor(int dx
, int dy
)
618 CoordType offs
, lineLength
;
619 wxLayoutObjectList::iterator i
;
621 bool rc
= true; // have we moved?
623 if(dy
> 0 && m_CursorPosition
.y
< m_MaxLine
)
624 m_CursorPosition
.y
+= dy
;
625 else if(dy
< 0 && m_CursorPosition
.y
> 0)
626 m_CursorPosition
.y
+= dy
; // dy is negative
627 if(m_CursorPosition
.y
< 0)
629 m_CursorPosition
.y
= 0;
632 else if (m_CursorPosition
.y
> m_MaxLine
)
634 m_CursorPosition
.y
= m_MaxLine
;
640 i
= FindCurrentObject(&offs
);
641 lineLength
= GetLineLength(i
,offs
);
642 if(m_CursorPosition
.x
< lineLength
)
644 m_CursorPosition
.x
++;
650 if(m_CursorPosition
.y
< m_MaxLine
)
652 m_CursorPosition
.y
++;
653 m_CursorPosition
.x
= 0;
659 break; // cannot move there
665 if(m_CursorPosition
.x
> 0)
667 m_CursorPosition
.x
--;
672 if(m_CursorPosition
.y
> 0)
674 m_CursorPosition
.y
--;
675 m_CursorPosition
.x
= 0;
676 i
= FindCurrentObject(&offs
);
677 lineLength
= GetLineLength(i
,offs
);
678 m_CursorPosition
.x
= lineLength
;
685 break; // cannot move left any more
690 i
= FindCurrentObject(&offs
);
691 lineLength
= GetLineLength(i
,offs
);
692 if(m_CursorPosition
.x
> lineLength
)
694 m_CursorPosition
.x
= lineLength
;
697 #ifdef WXLAYOUT_DEBUG
698 i
= FindCurrentObject(&offs
);
700 << m_CursorPosition
.x
<< ','
701 << m_CursorPosition
.y
;
705 cerr
<< "<<no object found>>" << endl
;
706 return rc
; // FIXME we should set cursor position to maximum allowed
709 if((*i
)->GetType() == WXLO_TYPE_TEXT
)
711 cerr
<< " \"" << ((wxLayoutObjectText
*)(*i
))->GetText() << "\", offs: "
715 cerr
<< ' ' << _t
[(*i
)->GetType()] << endl
;
721 wxLayoutList::Delete(CoordType count
)
731 wxLayoutObjectList::iterator i
;
735 i
= FindCurrentObject(&offs
);
736 startover
: // ugly, but easiest way to do it
738 return; // we cannot delete anything more
740 /* Here we need to treat linebreaks differently.
741 If offs==0 we are before the linebreak, otherwise behind. */
742 if((*i
)->GetType() == WXLO_TYPE_LINEBREAK
)
749 continue; // we're done
751 else // delete the object behind the linebreak
753 i
++; // we increment and continue as normal
758 else if((*i
)->GetType() == WXLO_TYPE_TEXT
)
760 wxLayoutObjectText
*tobj
= (wxLayoutObjectText
*)*i
;
761 CoordType len
= tobj
->CountPositions();
762 // If we find the end of a text object, this means that we
763 // have to delete from the object following it.
770 else if(len
<= count
) // delete this object
779 tobj
->GetText().erase(offs
,len
);
780 return; // we are done
783 else // all other objects: delete the object
785 CoordType len
= (*i
)->CountPositions();
786 erase(i
); // after this, i is the iterator for the following object
787 count
= count
> len
? count
-= len
: 0;
790 while(count
&& i
!= end());
794 wxLayoutList::Insert(wxLayoutObjectBase
*obj
)
798 wxLayoutObjectList::iterator i
= FindCurrentObject(&offs
);
800 WXL_TRACE(Insert(obj
));
806 // do we have to split a text object?
807 else if((*i
)->GetType() == WXLO_TYPE_TEXT
&& offs
!= (*i
)->CountPositions())
809 wxLayoutObjectText
*tobj
= (wxLayoutObjectText
*) *i
;
810 #ifdef WXLAYOUT_DEBUG
811 cerr
<< "text: '" << tobj
->GetText() << "'" << endl
;
814 String left
= tobj
->GetText().substr(0,offs
); // get part before cursor
816 tobj
->GetText() = tobj
->GetText().substr(offs
,(*i
)->CountPositions()-offs
); // keeps the right half
817 WXL_VAR(tobj
->GetText());
819 insert(i
,new wxLayoutObjectText(left
)); // inserts before
823 // all other cases, append after object:
824 wxLayoutObjectList::iterator j
= i
; // we want to apend after this object
832 m_CursorPosition
.x
+= obj
->CountPositions();
833 if(obj
->GetType() == WXLO_TYPE_LINEBREAK
)
838 wxLayoutList::Insert(String
const &text
)
840 wxLayoutObjectText
*tobj
= NULL
;
841 wxLayoutObjectList::iterator j
;
843 WXL_TRACE(Insert(text
));
849 wxLayoutObjectList::iterator i
= FindCurrentObject(&offs
);
853 Insert(new wxLayoutObjectText(text
));
857 switch((**i
).GetType())
860 // insert into an existing text object:
861 WXL_TRACE(inserting into existing object
);
862 tobj
= (wxLayoutObjectText
*)*i
;
864 tobj
->GetText().insert(offs
,text
);
866 case WXLO_TYPE_LINEBREAK
:
868 if(offs
== 0) // try to append to previous object
871 if(j
!= end() && (**j
).GetType() == WXLO_TYPE_TEXT
)
873 tobj
= (wxLayoutObjectText
*)*j
;
874 tobj
->GetText()+=text
;
877 insert(i
,new wxLayoutObjectText(text
));
879 else // cursor after linebreak
882 if(j
!= end() && (**j
).GetType() == WXLO_TYPE_TEXT
)
884 tobj
= (wxLayoutObjectText
*)*j
;
885 tobj
->GetText()=text
+tobj
->GetText();
890 push_back(new wxLayoutObjectText(text
));
892 insert(j
,new wxLayoutObjectText(text
));
898 WXL_TRACE(checking previous object
);
899 if(j
!= end() && (**j
).GetType() == WXLO_TYPE_TEXT
)
901 tobj
= (wxLayoutObjectText
*)*j
;
902 tobj
->GetText()+=text
;
904 else // insert a new text object
906 WXL_TRACE(creating
new object
);
907 Insert(new wxLayoutObjectText(text
)); //FIXME not too optimal, slow
908 return; // position gets incremented in Insert(obj)
911 m_CursorPosition
.x
+= strlen(text
.c_str());
915 wxLayoutList::GetLineLength(wxLayoutObjectList::iterator i
, CoordType offs
)
922 if(offs
== 0 && (**i
).GetType() == WXLO_TYPE_LINEBREAK
)
923 // we are before a linebrak
925 // search backwards for beginning of line:
926 while(i
!= begin() && (*i
)->GetType() != WXLO_TYPE_LINEBREAK
)
928 if((*i
)->GetType() == WXLO_TYPE_LINEBREAK
)
930 // now we can start counting:
931 while(i
!= end() && (*i
)->GetType() != WXLO_TYPE_LINEBREAK
)
933 len
+= (*i
)->CountPositions();
940 wxLayoutList::Clear(int family
, int size
, int style
, int weight
,
941 int underline
, char const *fg
, char const *bg
)
943 wxLayoutObjectList::iterator i
= begin();
945 while(i
!= end()) // == while valid
950 m_FontUnderline
= false;
951 m_FontFamily
= family
;
953 m_FontWeight
= weight
;
954 m_ColourFG
= wxTheColourDatabase
->FindColour(fg
);
955 m_ColourBG
= wxTheColourDatabase
->FindColour(bg
);
957 if(! m_ColourFG
) m_ColourFG
= wxBLACK
;
958 if(! m_ColourBG
) m_ColourBG
= wxWHITE
;
960 m_Position
= wxPoint(0,0);
961 m_CursorPosition
= wxPoint(0,0);
963 m_LineHeight
= (BASELINESTRETCH
*m_FontPtSize
)/10;
964 m_MaxX
= 0; m_MaxY
= 0;
968 delete m_DefaultSetting
;
969 m_DefaultSetting
= new
970 wxLayoutObjectCmd(m_FontPtSize
,m_FontFamily
,m_FontStyle
,
971 m_FontWeight
,m_FontUnderline
,
972 m_ColourFG
, m_ColourBG
);