a1d2d6b1e8
git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@33677 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
3405 lines
85 KiB
C++
3405 lines
85 KiB
C++
/*-*- c++ -*-********************************************************
|
|
* wxllist: wxLayoutList, a layout engine for text and graphics *
|
|
* *
|
|
* (C) 1998-2000 by Karsten Ballüder (Ballueder@gmx.net) *
|
|
* *
|
|
* $Id$
|
|
*******************************************************************/
|
|
|
|
/*
|
|
|
|
Some docs:
|
|
|
|
Layout() recalculates the objects, sizes, etc.
|
|
Draw() just draws them with the current settings, without
|
|
re-layout()ing them again
|
|
|
|
Each line has its own wxLayoutStyleInfo structure which gets updated
|
|
from within Layout(). Thanks to this, we don't need to re-layout all
|
|
lines if we want to draw one, but can just use its styleinfo to set
|
|
the right font.
|
|
|
|
*/
|
|
|
|
#ifdef __GNUG__
|
|
# pragma implementation "wxllist.h"
|
|
#endif
|
|
|
|
#include "wx/wxprec.h"
|
|
|
|
#ifdef __BORLANDC__
|
|
# pragma hdrstop
|
|
#endif
|
|
|
|
#include "Mpch.h"
|
|
|
|
#ifdef M_BASEDIR
|
|
# include "Mcommon.h"
|
|
# include "gui/wxllist.h"
|
|
# include "gui/wxlparser.h"
|
|
# define SHOW_SELECTIONS 1
|
|
#else
|
|
# include "wxllist.h"
|
|
# include "wxlparser.h"
|
|
# define SHOW_SELECTIONS 1
|
|
#endif
|
|
|
|
#ifndef USE_PCH
|
|
#if wxUSE_IOSTREAMH
|
|
#include <iostream.h>
|
|
#else
|
|
#include <iostream>
|
|
#endif
|
|
|
|
# include "wx/dc.h"
|
|
# include "wx/dcps.h"
|
|
# include "wx/print.h"
|
|
# include "wx/log.h"
|
|
# include "wx/filefn.h"
|
|
#endif
|
|
|
|
#ifdef WXLAYOUT_USE_CARET
|
|
# include "wx/caret.h"
|
|
#endif // WXLAYOUT_USE_CARET
|
|
|
|
#include <ctype.h>
|
|
|
|
|
|
/// This should never really get created
|
|
#define WXLLIST_TEMPFILE "__wxllist.tmp"
|
|
|
|
#ifdef WXLAYOUT_DEBUG
|
|
|
|
# define TypeString(t) g_aTypeStrings[t]
|
|
# define WXLO_DEBUG(x) wxLogDebug x
|
|
|
|
static const wxChar *g_aTypeStrings[] =
|
|
{
|
|
_T("invalid"), _T("text"), _T("cmd"), _T("icon")
|
|
};
|
|
wxString
|
|
wxLayoutObject::DebugDump() const
|
|
{
|
|
wxString str;
|
|
str.Printf(wxT("%s"), g_aTypeStrings[GetType()]);
|
|
return str;
|
|
}
|
|
#else
|
|
# define TypeString(t) ""
|
|
# define WXLO_DEBUG(x)
|
|
#endif
|
|
|
|
|
|
// FIXME under MSW, this constant is needed to make the thing properly redraw
|
|
// itself - I don't know where the size calculation error is and I can't
|
|
// waste time looking for it right now. Search for occurences of
|
|
// MSW_CORRECTION to find all the places where I did it.
|
|
#ifdef __WXMSW__
|
|
static const int MSW_CORRECTION = 10;
|
|
#else
|
|
static const int MSW_CORRECTION = 0;
|
|
#endif
|
|
|
|
/// Cursors smaller than this disappear in XOR drawing mode
|
|
#define WXLO_MINIMUM_CURSOR_WIDTH 4
|
|
|
|
/// Use this character to estimate a cursor size when none is available.
|
|
#define WXLO_CURSORCHAR "E"
|
|
/** @name Helper functions */
|
|
//@{
|
|
/// allows me to compare to wxPoints
|
|
bool operator <=(wxPoint const &p1, wxPoint const &p2)
|
|
{
|
|
return p1.y < p2.y || (p1.y == p2.y && p1.x <= p2.x);
|
|
}
|
|
|
|
/*
|
|
The following STAY HERE until we have a working wxGTK again!!!
|
|
*/
|
|
#ifndef wxWANTS_CHARS
|
|
/// allows me to compare to wxPoints
|
|
bool operator ==(wxPoint const &p1, wxPoint const &p2)
|
|
{
|
|
return p1.x == p2.x && p1.y == p2.y;
|
|
}
|
|
|
|
/// allows me to compare to wxPoints
|
|
bool operator !=(wxPoint const &p1, wxPoint const &p2)
|
|
{
|
|
return p1.x != p2.x || p1.y != p2.y;
|
|
}
|
|
|
|
wxPoint & operator += (wxPoint &p1, wxPoint const &p2)
|
|
{
|
|
p1.x += p2.x;
|
|
p1.y += p2.y;
|
|
return p1;
|
|
}
|
|
#endif // old wxGTK
|
|
|
|
/// allows me to compare to wxPoints
|
|
bool operator>(wxPoint const &p1, wxPoint const &p2)
|
|
{
|
|
return !(p1 <= p2);
|
|
}
|
|
|
|
/// grows a wxRect so that it includes the given point
|
|
|
|
static
|
|
void GrowRect(wxRect &r, CoordType x, CoordType y)
|
|
{
|
|
if(r.x > x)
|
|
r.x = x;
|
|
else if(r.x + r.width < x)
|
|
r.width = x - r.x;
|
|
|
|
if(r.y > y)
|
|
r.y = y;
|
|
else if(r.y + r.height < y)
|
|
r.height = y - r.y;
|
|
}
|
|
|
|
#if 0
|
|
// unused
|
|
/// returns true if the point is in the rectangle
|
|
static
|
|
bool Contains(const wxRect &r, const wxPoint &p)
|
|
{
|
|
return r.x <= p.x && r.y <= p.y && (r.x+r.width) >= p.x && (r.y + r.height) >= p.y;
|
|
}
|
|
#endif
|
|
|
|
|
|
//@}
|
|
|
|
|
|
static
|
|
void ReadString(wxString &to, wxString &from)
|
|
{
|
|
to = wxT("");
|
|
const wxChar *cptr = from.c_str();
|
|
while(*cptr && *cptr != wxT('\n'))
|
|
{
|
|
to += cptr;
|
|
cptr++;
|
|
}
|
|
|
|
if(*cptr) cptr++;
|
|
|
|
from = cptr;
|
|
}
|
|
|
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
|
|
wxLayoutObject
|
|
|
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
|
|
/* static */
|
|
wxLayoutObject *
|
|
wxLayoutObject::Read(wxString &istr)
|
|
{
|
|
wxString tmp;
|
|
ReadString(tmp, istr);
|
|
long l = WXLO_TYPE_INVALID;
|
|
tmp.ToLong(&l);
|
|
int type = (int) l;
|
|
|
|
switch(type)
|
|
{
|
|
case WXLO_TYPE_TEXT:
|
|
return wxLayoutObjectText::Read(istr);
|
|
case WXLO_TYPE_CMD:
|
|
return wxLayoutObjectCmd::Read(istr);
|
|
case WXLO_TYPE_ICON:
|
|
return wxLayoutObjectIcon::Read(istr);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
|
|
wxLayoutObjectText
|
|
|
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
|
|
wxLayoutObjectText::wxLayoutObjectText(const wxString &txt)
|
|
{
|
|
m_Text = txt;
|
|
m_Width = 0;
|
|
m_Height = 0;
|
|
m_Top = 0;
|
|
m_Bottom = 0;
|
|
}
|
|
|
|
wxLayoutObject *
|
|
wxLayoutObjectText::Copy()
|
|
{
|
|
wxLayoutObjectText *obj = new wxLayoutObjectText(m_Text);
|
|
obj->m_Width = m_Width;
|
|
obj->m_Height = m_Height;
|
|
obj->m_Top = m_Top;
|
|
obj->m_Bottom = m_Bottom;
|
|
obj->SetUserData(m_UserData);
|
|
return obj;
|
|
}
|
|
|
|
|
|
void
|
|
wxLayoutObjectText::Write(wxString &ostr)
|
|
{
|
|
ostr << (int) WXLO_TYPE_TEXT << '\n'
|
|
<< m_Text << '\n';
|
|
}
|
|
/* static */
|
|
wxLayoutObjectText *
|
|
wxLayoutObjectText::Read(wxString &istr)
|
|
{
|
|
wxString text;
|
|
ReadString(text, istr);
|
|
|
|
return new wxLayoutObjectText(text);
|
|
}
|
|
|
|
wxPoint
|
|
wxLayoutObjectText::GetSize(CoordType *top, CoordType *bottom) const
|
|
{
|
|
|
|
*top = m_Top; *bottom = m_Bottom;
|
|
return wxPoint(m_Width, m_Height);
|
|
}
|
|
|
|
void
|
|
wxLayoutObjectText::Draw(wxDC &dc, wxPoint const &coords,
|
|
wxLayoutList *wxllist,
|
|
CoordType begin, CoordType end)
|
|
{
|
|
if( end <= 0 )
|
|
{
|
|
// draw the whole object normally
|
|
dc.DrawText(m_Text, coords.x, coords.y-m_Top);
|
|
}
|
|
else
|
|
{
|
|
// highlight the bit between begin and len
|
|
CoordType
|
|
xpos = coords.x,
|
|
ypos = coords.y-m_Top;
|
|
long width, height, descent;
|
|
|
|
if(begin < 0) begin = 0;
|
|
if( end > (signed)m_Text.Length() )
|
|
end = m_Text.Length();
|
|
|
|
wxString str = m_Text.Mid(0, begin);
|
|
dc.DrawText(str, xpos, ypos);
|
|
dc.GetTextExtent(str, &width, &height, &descent);
|
|
xpos += width;
|
|
wxllist->StartHighlighting(dc);
|
|
str = m_Text.Mid(begin, end-begin);
|
|
dc.DrawText(str, xpos, ypos);
|
|
dc.GetTextExtent(str, &width, &height, &descent);
|
|
xpos += width;
|
|
wxllist->EndHighlighting(dc);
|
|
str = m_Text.Mid(end, m_Text.Length()-end);
|
|
dc.DrawText(str, xpos, ypos);
|
|
}
|
|
}
|
|
|
|
CoordType
|
|
wxLayoutObjectText::GetOffsetScreen(wxDC &dc, CoordType xpos) const
|
|
{
|
|
CoordType
|
|
offs = 1,
|
|
maxlen = m_Text.Length();
|
|
long
|
|
width = 0,
|
|
height, descent = 0l;
|
|
|
|
if(xpos == 0) return 0; // easy
|
|
|
|
while(width < xpos && offs < maxlen)
|
|
{
|
|
dc.GetTextExtent(m_Text.substr(0,offs),
|
|
&width, &height, &descent);
|
|
offs++;
|
|
}
|
|
/* We have to subtract 1 to compensate for the offs++, and another
|
|
one because we don't want to position the cursor behind the
|
|
object what we clicked on, but before - otherwise it looks
|
|
funny. */
|
|
return (xpos > 2) ? offs-2 : 0;
|
|
}
|
|
|
|
void
|
|
wxLayoutObjectText::Layout(wxDC &dc, class wxLayoutList *WXUNUSED(llist))
|
|
{
|
|
long descent = 0l;
|
|
|
|
// now this is done in wxLayoutLine::Layout(), but this code might be
|
|
// reenabled later - in principle, it's more efficient
|
|
#if 0
|
|
CoordType widthOld = m_Width,
|
|
heightOld = m_Height;
|
|
#endif // 0
|
|
|
|
#ifdef __WXDEBUG__
|
|
CoordType a,b,c,d,e,f;
|
|
dc.GetTextExtent(_T("test "), &a, &b, &c);
|
|
dc.GetTextExtent(_T("test"), &d, &e, &f);
|
|
wxASSERT(a != d);
|
|
wxASSERT(b == e);
|
|
wxASSERT(c == f);
|
|
dc.GetTextExtent(_T(" "), &d, &e, &f);
|
|
wxASSERT(a > 0);
|
|
#endif
|
|
dc.GetTextExtent(m_Text, &m_Width, &m_Height, &descent);
|
|
|
|
#if 0
|
|
if ( widthOld != m_Width || heightOld != m_Height )
|
|
{
|
|
// as the text length changed, it must be refreshed
|
|
wxLayoutLine *line = GetLine();
|
|
|
|
wxCHECK_RET( line, "wxLayoutObjectText can't refresh itself" );
|
|
|
|
// as our size changed, we need to repaint the part which was appended
|
|
wxPoint position(line->GetPosition());
|
|
|
|
// this is not the most efficient way (we repaint the whole line), but
|
|
// it's not too slow and is *simple*
|
|
if ( widthOld < m_Width )
|
|
widthOld = m_Width;
|
|
if ( heightOld < m_Height )
|
|
heightOld = m_Height;
|
|
|
|
llist->SetUpdateRect(position.x + widthOld + MSW_CORRECTION,
|
|
position.y + heightOld + MSW_CORRECTION);
|
|
}
|
|
#endif // 0
|
|
|
|
m_Bottom = descent;
|
|
m_Top = m_Height - m_Bottom;
|
|
}
|
|
|
|
|
|
#ifdef WXLAYOUT_DEBUG
|
|
wxString
|
|
wxLayoutObjectText::DebugDump() const
|
|
{
|
|
wxString str;
|
|
str = wxLayoutObject::DebugDump();
|
|
wxString str2;
|
|
str2.Printf(wxT(" `%s`"), m_Text.c_str());
|
|
return str+str2;
|
|
}
|
|
#endif
|
|
|
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
|
|
wxLayoutObjectIcon
|
|
|
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
|
|
wxLayoutObjectIcon::wxLayoutObjectIcon(wxBitmap const &icon)
|
|
{
|
|
if ( !icon.Ok() )
|
|
{
|
|
wxFAIL_MSG(wxT("invalid icon"));
|
|
|
|
m_Icon = NULL;
|
|
|
|
return;
|
|
}
|
|
|
|
#ifdef __WXMSW__
|
|
// FIXME ugly, ugly, ugly - but the only way to avoid slicing
|
|
m_Icon = icon.GetHBITMAP() ? new wxBitmap(icon)
|
|
: new wxBitmap(wxBitmap((const wxBitmap &)icon));
|
|
#else // !MSW
|
|
m_Icon = new wxBitmap(icon);
|
|
#endif // MSW/!MSW
|
|
}
|
|
|
|
|
|
void
|
|
wxLayoutObjectIcon::Write(wxString &ostr)
|
|
{
|
|
/* Exports icon through a temporary file. */
|
|
|
|
wxString file = wxGetTempFileName(_T("wxloexport"));
|
|
|
|
ostr << (int) WXLO_TYPE_ICON << '\n'
|
|
<< file << '\n';
|
|
m_Icon->SaveFile(file, WXLO_BITMAP_FORMAT);
|
|
}
|
|
/* static */
|
|
wxLayoutObjectIcon *
|
|
wxLayoutObjectIcon::Read(wxString &istr)
|
|
{
|
|
wxString file;
|
|
ReadString(file, istr);
|
|
|
|
if(! wxFileExists(file))
|
|
return NULL;
|
|
wxLayoutObjectIcon *obj = new wxLayoutObjectIcon;
|
|
|
|
if(!obj->m_Icon->LoadFile(file, WXLO_BITMAP_FORMAT))
|
|
{
|
|
delete obj;
|
|
return NULL;
|
|
}
|
|
|
|
return obj;
|
|
}
|
|
|
|
wxLayoutObject *
|
|
wxLayoutObjectIcon::Copy()
|
|
{
|
|
wxLayoutObjectIcon *obj = new wxLayoutObjectIcon(new
|
|
wxBitmap(*m_Icon));
|
|
obj->SetUserData(m_UserData);
|
|
return obj;
|
|
}
|
|
|
|
wxLayoutObjectIcon::wxLayoutObjectIcon(wxBitmap *icon)
|
|
{
|
|
m_Icon = icon;
|
|
if(! m_Icon)
|
|
m_Icon = new wxBitmap;
|
|
}
|
|
|
|
void
|
|
wxLayoutObjectIcon::Draw(wxDC &dc, wxPoint const &coords,
|
|
wxLayoutList *WXUNUSED(wxllist),
|
|
CoordType WXUNUSED(begin), CoordType WXUNUSED(len) )
|
|
{
|
|
dc.DrawBitmap(*m_Icon, coords.x, coords.y-m_Icon->GetHeight(),
|
|
(m_Icon->GetMask() == NULL) ? false : true);
|
|
}
|
|
|
|
void
|
|
wxLayoutObjectIcon::Layout(wxDC & /* dc */, class wxLayoutList * )
|
|
{
|
|
}
|
|
|
|
wxPoint
|
|
wxLayoutObjectIcon::GetSize(CoordType *top, CoordType *bottom) const
|
|
{
|
|
*top = m_Icon->GetHeight();
|
|
*bottom = 0;
|
|
return wxPoint(m_Icon->GetWidth(), m_Icon->GetHeight());
|
|
}
|
|
|
|
|
|
|
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
|
|
wxLayoutObjectCmd
|
|
|
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
|
|
|
|
wxLayoutStyleInfo::wxLayoutStyleInfo(int ifamily,
|
|
int isize,
|
|
int istyle,
|
|
int iweight,
|
|
int iul,
|
|
wxColour *fg,
|
|
wxColour *bg)
|
|
{
|
|
family = ifamily;
|
|
size = isize;
|
|
style = istyle;
|
|
weight = iweight;
|
|
underline = iul != 0;
|
|
|
|
m_fg_valid = fg != 0;
|
|
m_bg_valid = bg != 0;
|
|
m_fg = m_fg_valid ? *fg : *wxBLACK;
|
|
m_bg = m_bg_valid ? *bg : *wxWHITE;
|
|
}
|
|
|
|
#define COPY_SI_(what) if(right.what != -1) what = right.what;
|
|
|
|
wxLayoutStyleInfo &
|
|
wxLayoutStyleInfo::operator=(const wxLayoutStyleInfo &right)
|
|
{
|
|
COPY_SI_(family);
|
|
COPY_SI_(style);
|
|
COPY_SI_(size);
|
|
COPY_SI_(weight);
|
|
COPY_SI_(underline);
|
|
if(right.m_fg_valid) m_fg = right.m_fg;
|
|
if(right.m_bg_valid) m_bg = right.m_bg;
|
|
return *this;
|
|
}
|
|
|
|
wxLayoutObjectCmd::wxLayoutObjectCmd(int family, int size, int style, int
|
|
weight, int underline,
|
|
wxColour *fg, wxColour *bg)
|
|
|
|
{
|
|
m_StyleInfo = new wxLayoutStyleInfo(family, size,style,weight,underline,fg,bg);
|
|
}
|
|
|
|
wxLayoutObjectCmd::wxLayoutObjectCmd(const wxLayoutStyleInfo &si)
|
|
|
|
{
|
|
m_StyleInfo = new wxLayoutStyleInfo;
|
|
*m_StyleInfo = si;
|
|
}
|
|
|
|
wxLayoutObject *
|
|
wxLayoutObjectCmd::Copy()
|
|
{
|
|
wxLayoutObjectCmd *obj = new wxLayoutObjectCmd(
|
|
m_StyleInfo->family,
|
|
m_StyleInfo->size,
|
|
m_StyleInfo->style,
|
|
m_StyleInfo->weight,
|
|
m_StyleInfo->underline,
|
|
m_StyleInfo->m_fg_valid ?
|
|
&m_StyleInfo->m_fg : NULL,
|
|
m_StyleInfo->m_bg_valid ?
|
|
&m_StyleInfo->m_bg : NULL);
|
|
obj->SetUserData(m_UserData);
|
|
return obj;
|
|
}
|
|
|
|
void
|
|
wxLayoutObjectCmd::Write(wxString &ostr)
|
|
{
|
|
ostr << (int) WXLO_TYPE_CMD << '\n'
|
|
<< (int) m_StyleInfo->family << '\n'
|
|
<< (int) m_StyleInfo->size << '\n'
|
|
<< (int) m_StyleInfo->style << '\n'
|
|
<< (int) m_StyleInfo->weight << '\n'
|
|
<< (int) m_StyleInfo->underline << '\n'
|
|
<< (int) m_StyleInfo->m_fg_valid << '\n'
|
|
<< (int) m_StyleInfo->m_bg_valid << '\n';
|
|
if(m_StyleInfo->m_fg_valid)
|
|
{
|
|
ostr << (int) m_StyleInfo->m_fg.Red() << '\n'
|
|
<< (int) m_StyleInfo->m_fg.Green() << '\n'
|
|
<< (int) m_StyleInfo->m_fg.Blue() << '\n';
|
|
}
|
|
if(m_StyleInfo->m_bg_valid)
|
|
{
|
|
ostr << (int) m_StyleInfo->m_bg.Red() << '\n'
|
|
<< (int) m_StyleInfo->m_bg.Green() << '\n'
|
|
<< (int) m_StyleInfo->m_bg.Blue() << '\n';
|
|
}
|
|
}
|
|
/* static */
|
|
wxLayoutObjectCmd *
|
|
wxLayoutObjectCmd::Read(wxString &istr)
|
|
{
|
|
wxLayoutObjectCmd *obj = new wxLayoutObjectCmd;
|
|
|
|
long l = 0;
|
|
wxString tmp;
|
|
ReadString(tmp, istr);
|
|
tmp.ToLong(&l);
|
|
obj->m_StyleInfo->family = (int) l;
|
|
|
|
|
|
ReadString(tmp, istr);
|
|
tmp.ToLong(&l);
|
|
obj->m_StyleInfo->size = (int) l;
|
|
|
|
ReadString(tmp, istr);
|
|
tmp.ToLong(&l);
|
|
obj->m_StyleInfo->style = (int) l;
|
|
|
|
ReadString(tmp, istr);
|
|
tmp.ToLong(&l);
|
|
obj->m_StyleInfo->weight = (int) l;
|
|
|
|
ReadString(tmp, istr);
|
|
tmp.ToLong(&l);
|
|
obj->m_StyleInfo->underline = (int) l;
|
|
|
|
ReadString(tmp, istr);
|
|
tmp.ToLong(&l);
|
|
obj->m_StyleInfo->m_fg_valid = (int) l;
|
|
|
|
ReadString(tmp, istr);
|
|
tmp.ToLong(&l);
|
|
obj->m_StyleInfo->m_bg_valid = (int) l;
|
|
|
|
if(obj->m_StyleInfo->m_fg_valid)
|
|
{
|
|
unsigned char red, green, blue;
|
|
ReadString(tmp, istr);
|
|
tmp.ToLong(&l);
|
|
red = (unsigned char) l;
|
|
|
|
ReadString(tmp, istr);
|
|
tmp.ToLong(&l);
|
|
green = (unsigned char) l;
|
|
|
|
ReadString(tmp, istr);
|
|
tmp.ToLong(&l);
|
|
blue = (unsigned char) l;
|
|
|
|
obj->m_StyleInfo->m_fg = wxColour(red, green, blue);
|
|
}
|
|
|
|
if(obj->m_StyleInfo->m_bg_valid)
|
|
{
|
|
unsigned char red, green, blue;
|
|
ReadString(tmp, istr);
|
|
tmp.ToLong(&l);
|
|
red = (unsigned char) l;
|
|
|
|
ReadString(tmp, istr);
|
|
tmp.ToLong(&l);
|
|
green = (unsigned char) l;
|
|
|
|
ReadString(tmp, istr);
|
|
tmp.ToLong(&l);
|
|
blue = (unsigned char) l;
|
|
|
|
obj->m_StyleInfo->m_bg = wxColour(red, green, blue);
|
|
}
|
|
|
|
return obj;
|
|
}
|
|
|
|
|
|
wxLayoutObjectCmd::~wxLayoutObjectCmd()
|
|
{
|
|
delete m_StyleInfo;
|
|
}
|
|
|
|
wxLayoutStyleInfo *
|
|
wxLayoutObjectCmd::GetStyle() const
|
|
{
|
|
return m_StyleInfo;
|
|
}
|
|
|
|
void
|
|
wxLayoutObjectCmd::Draw(wxDC &dc, wxPoint const & WXUNUSED(coords),
|
|
wxLayoutList *wxllist,
|
|
CoordType WXUNUSED(begin), CoordType WXUNUSED(len))
|
|
{
|
|
wxASSERT(m_StyleInfo);
|
|
wxllist->ApplyStyle(*m_StyleInfo, dc);
|
|
}
|
|
|
|
void
|
|
wxLayoutObjectCmd::Layout(wxDC &dc, class wxLayoutList * llist)
|
|
{
|
|
// this get called, so that recalculation uses right font sizes
|
|
Draw(dc, wxPoint(0,0), llist);
|
|
}
|
|
|
|
|
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
|
|
The wxLayoutLine object
|
|
|
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
|
|
wxLayoutLine::wxLayoutLine(wxLayoutLine *prev, wxLayoutList *llist)
|
|
{
|
|
m_Width = m_Height = 0;
|
|
m_Length = 0;
|
|
|
|
m_updateLeft = -1;
|
|
m_Previous = prev;
|
|
m_Next = NULL;
|
|
MarkDirty(0);
|
|
|
|
m_LineNumber = 0;
|
|
RecalculatePosition(llist);
|
|
|
|
MarkDirty();
|
|
if(m_Previous)
|
|
{
|
|
m_LineNumber = m_Previous->GetLineNumber() + 1;
|
|
m_Next = m_Previous->GetNextLine();
|
|
m_Previous->m_Next = this;
|
|
}
|
|
|
|
if(m_Next)
|
|
{
|
|
m_Next->m_Previous = this;
|
|
m_Next->ReNumber();
|
|
}
|
|
|
|
m_StyleInfo = llist->GetDefaultStyleInfo();
|
|
|
|
llist->IncNumLines();
|
|
}
|
|
|
|
wxPoint
|
|
wxLayoutLine::RecalculatePosition(wxLayoutList *llist)
|
|
{
|
|
wxASSERT(m_Previous || GetLineNumber() == 0);
|
|
|
|
wxPoint posOld(m_Position);
|
|
|
|
if(m_Previous)
|
|
{
|
|
m_Position = m_Previous->GetPosition();
|
|
m_Position.y += m_Previous->GetHeight();
|
|
}
|
|
else
|
|
m_Position = wxPoint(0,0);
|
|
|
|
if ( m_Position != posOld )
|
|
{
|
|
// the whole line moved and must be repainted
|
|
llist->SetUpdateRect(m_Position);
|
|
llist->SetUpdateRect(m_Position.x + GetWidth() + MSW_CORRECTION,
|
|
m_Position.y + GetHeight() + MSW_CORRECTION);
|
|
llist->SetUpdateRect(posOld);
|
|
llist->SetUpdateRect(posOld.x + GetWidth() + MSW_CORRECTION,
|
|
posOld.y + GetHeight() + MSW_CORRECTION);
|
|
}
|
|
|
|
return m_Position;
|
|
}
|
|
|
|
|
|
wxLayoutObjectList::iterator
|
|
wxLayoutLine::FindObject(CoordType xpos, CoordType *offset) const
|
|
{
|
|
wxASSERT(xpos >= 0);
|
|
wxASSERT(offset);
|
|
wxLayoutObjectList::iterator
|
|
i,
|
|
found(NULL),
|
|
nulled(NULL);
|
|
CoordType x = 0, len;
|
|
|
|
/* We search through the objects. As we don't like returning the
|
|
object that the cursor is behind, we just remember such an
|
|
object in "found" so we can return it if there is really no
|
|
further object following it. */
|
|
for(i = m_ObjectList.begin(); i != nulled; i++)
|
|
{
|
|
len = (**i).GetLength();
|
|
if( x <= xpos && xpos <= x + len )
|
|
{
|
|
*offset = xpos-x;
|
|
if(xpos == x + len) // is there another object behind?
|
|
found = i;
|
|
else // we are really inside this object
|
|
return i;
|
|
}
|
|
x += (**i).GetLength();
|
|
}
|
|
return found; // ==NULL if really none found
|
|
}
|
|
|
|
wxLayoutObjectList::iterator
|
|
wxLayoutLine::FindObjectScreen(wxDC &dc, wxLayoutList *llist,
|
|
CoordType xpos, CoordType *cxpos,
|
|
bool *found) const
|
|
{
|
|
wxASSERT(cxpos);
|
|
|
|
llist->ApplyStyle(GetStyleInfo(), dc);
|
|
|
|
wxLayoutObjectList::iterator i, nulled(NULL);
|
|
CoordType x = 0, cx = 0, width;
|
|
|
|
for(i = m_ObjectList.begin(); i != nulled; i++)
|
|
{
|
|
wxLayoutObject *obj = *i;
|
|
if ( obj->GetType() == WXLO_TYPE_CMD )
|
|
{
|
|
// this will set the correct font for the objects which follow
|
|
obj->Layout(dc, llist);
|
|
}
|
|
|
|
width = obj->GetWidth();
|
|
if( x <= xpos && xpos <= x + width )
|
|
{
|
|
*cxpos = cx + obj->GetOffsetScreen(dc, xpos-x);
|
|
|
|
if ( found )
|
|
*found = true;
|
|
return i;
|
|
}
|
|
|
|
x += obj->GetWidth();
|
|
cx += obj->GetLength();
|
|
}
|
|
|
|
// behind last object:
|
|
*cxpos = cx;
|
|
|
|
if (found)
|
|
*found = false;
|
|
return m_ObjectList.tail();
|
|
}
|
|
|
|
/** Finds text in this line.
|
|
@param needle the text to find
|
|
@param xpos the position where to start the search
|
|
@return the cursoor coord where it was found or -1
|
|
*/
|
|
CoordType
|
|
wxLayoutLine::FindText(const wxString &needle, CoordType xpos) const
|
|
{
|
|
int cpos = 0;
|
|
wxString const *text;
|
|
|
|
for(wxLOiterator i = m_ObjectList.begin(); i != m_ObjectList.end(); i++)
|
|
{
|
|
if(cpos >= xpos) // search from here!
|
|
{
|
|
if((**i).GetType() == WXLO_TYPE_TEXT)
|
|
{
|
|
text = & ((wxLayoutObjectText*)(*i))->GetText();
|
|
int relpos = text->Find(needle);
|
|
if(relpos >= cpos-xpos) // -1 if not found
|
|
{
|
|
return cpos+relpos;
|
|
}
|
|
}
|
|
cpos += (**i).GetLength();
|
|
}
|
|
}
|
|
return -1; // not found
|
|
}
|
|
|
|
bool
|
|
wxLayoutLine::Insert(CoordType xpos, wxLayoutObject *obj)
|
|
{
|
|
wxASSERT(xpos >= 0);
|
|
wxASSERT(obj != NULL);
|
|
|
|
MarkDirty(xpos);
|
|
|
|
CoordType offset;
|
|
wxLOiterator i = FindObject(xpos, &offset);
|
|
wxLayoutObjectList::iterator nulled(NULL);
|
|
if(i == nulled)
|
|
{
|
|
if(xpos == 0 ) // aha, empty line!
|
|
{
|
|
m_ObjectList.push_back(obj);
|
|
m_Length += obj->GetLength();
|
|
return true;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
|
|
CoordType len = (**i).GetLength();
|
|
if(offset == 0 /*&& i != m_ObjectList.begin()*/) // why?
|
|
{ // insert before this object
|
|
m_ObjectList.insert(i,obj);
|
|
m_Length += obj->GetLength();
|
|
return true;
|
|
}
|
|
if(offset == len )
|
|
{
|
|
if( i == m_ObjectList.tail()) // last object?
|
|
m_ObjectList.push_back(obj);
|
|
else
|
|
{ // insert after current object
|
|
i++;
|
|
m_ObjectList.insert(i,obj);
|
|
}
|
|
m_Length += obj->GetLength();
|
|
return true;
|
|
}
|
|
/* Otherwise we need to split the current object.
|
|
Fortunately this can only be a text object. */
|
|
wxASSERT((**i).GetType() == WXLO_TYPE_TEXT);
|
|
wxString left, right;
|
|
wxLayoutObjectText *tobj = (wxLayoutObjectText *) *i;
|
|
left = tobj->GetText().substr(0,offset);
|
|
right = tobj->GetText().substr(offset,len-offset);
|
|
// current text object gets set to right half
|
|
tobj->GetText() = right; // set new text
|
|
// before it we insert the new object
|
|
m_ObjectList.insert(i,obj);
|
|
m_Length += obj->GetLength();
|
|
// and before that we insert the left half
|
|
m_ObjectList.insert(i,new wxLayoutObjectText(left));
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
wxLayoutLine::Insert(CoordType xpos, const wxString& text)
|
|
{
|
|
wxASSERT(xpos >= 0);
|
|
|
|
MarkDirty(xpos);
|
|
|
|
CoordType offset;
|
|
wxLOiterator i = FindObject(xpos, &offset);
|
|
wxLayoutObjectList::iterator nulled(NULL);
|
|
if(i != nulled && (**i).GetType() == WXLO_TYPE_TEXT)
|
|
{
|
|
wxLayoutObjectText *tobj = (wxLayoutObjectText *) *i;
|
|
tobj->GetText().insert(offset, text);
|
|
m_Length += text.Length();
|
|
}
|
|
else
|
|
{
|
|
if ( !Insert(xpos, new wxLayoutObjectText(text)) )
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
CoordType
|
|
wxLayoutLine::Delete(CoordType xpos, CoordType npos)
|
|
{
|
|
CoordType offset, len;
|
|
|
|
wxASSERT(xpos >= 0);
|
|
wxASSERT(npos >= 0);
|
|
MarkDirty(xpos);
|
|
wxLOiterator i = FindObject(xpos, &offset);
|
|
wxLayoutObjectList::iterator nulled(NULL);
|
|
while(npos > 0)
|
|
{
|
|
if(i == nulled) return npos;
|
|
// now delete from that object:
|
|
if((**i).GetType() != WXLO_TYPE_TEXT)
|
|
{
|
|
if(offset != 0) // at end of line after a non-text object
|
|
return npos;
|
|
// always len == 1:
|
|
len = (**i).GetLength();
|
|
m_Length -= len;
|
|
npos -= len;
|
|
m_ObjectList.erase(i);
|
|
}
|
|
else
|
|
{
|
|
// tidy up: remove empty text objects
|
|
if((**i).GetLength() == 0)
|
|
{
|
|
m_ObjectList.erase(i);
|
|
continue;
|
|
}
|
|
// Text object:
|
|
CoordType max = (**i).GetLength() - offset;
|
|
if(npos < max) max = npos;
|
|
if(max == 0)
|
|
{
|
|
if(xpos == GetLength())
|
|
return npos;
|
|
else
|
|
{ // at the end of an object
|
|
// move to begin of next object:
|
|
i++; offset = 0;
|
|
continue; // start over
|
|
}
|
|
}
|
|
npos -= max;
|
|
m_Length -= max;
|
|
if(offset == 0 && max == (**i).GetLength())
|
|
m_ObjectList.erase(i); // remove the whole object
|
|
else
|
|
((wxLayoutObjectText *)(*i))->GetText().Remove(offset,max);
|
|
}
|
|
}
|
|
|
|
return npos;
|
|
}
|
|
|
|
bool
|
|
wxLayoutLine::DeleteWord(CoordType xpos)
|
|
{
|
|
wxASSERT(xpos >= 0);
|
|
CoordType offset;
|
|
MarkDirty(xpos);
|
|
|
|
wxLOiterator i = FindObject(xpos, &offset);
|
|
wxLayoutObjectList::iterator nulled(NULL);
|
|
for(;;)
|
|
{
|
|
if(i == nulled) return false;
|
|
if((**i).GetType() != WXLO_TYPE_TEXT)
|
|
{
|
|
// This should only happen when at end of line, behind a non-text
|
|
// object:
|
|
if(offset == (**i).GetLength()) return false;
|
|
m_Length -= (**i).GetLength(); // -1
|
|
m_ObjectList.erase(i);
|
|
return true; // we are done
|
|
}
|
|
else
|
|
{ // text object:
|
|
if(offset == (**i).GetLength()) // at end of object
|
|
{
|
|
i++; offset = 0;
|
|
continue;
|
|
}
|
|
|
|
wxLayoutObjectText *tobj = (wxLayoutObjectText *)*i;
|
|
size_t count = 0;
|
|
wxString str = tobj->GetText();
|
|
str = str.substr(offset,str.Length()-offset);
|
|
// Find out how many positions we need to delete:
|
|
// 1. eat leading space
|
|
while(isspace(str.c_str()[count])) count++;
|
|
// 2. eat the word itself:
|
|
while(isalnum(str.c_str()[count])) count++;
|
|
// now delete it:
|
|
wxASSERT(count+offset <= (size_t) (**i).GetLength());
|
|
((wxLayoutObjectText *)*i)->GetText().erase(offset,count);
|
|
m_Length -= count;
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
wxFAIL_MSG(wxT("unreachable"));
|
|
#endif
|
|
}
|
|
|
|
wxLayoutLine *
|
|
wxLayoutLine::DeleteLine(bool update, wxLayoutList *llist)
|
|
{
|
|
// maintain linked list integrity
|
|
if(m_Next)
|
|
m_Next->m_Previous = m_Previous;
|
|
if(m_Previous)
|
|
m_Previous->m_Next = m_Next;
|
|
|
|
// get the line numbers right again
|
|
if ( update && m_Next)
|
|
m_Next->ReNumber();
|
|
|
|
MarkDirty();
|
|
|
|
// we can't use m_Next after "delete this", so we must save this pointer
|
|
// first
|
|
wxLayoutLine *next = m_Next;
|
|
delete this;
|
|
|
|
llist->DecNumLines();
|
|
|
|
return next;
|
|
}
|
|
|
|
void
|
|
wxLayoutLine::Draw(wxDC &dc,
|
|
wxLayoutList *llist,
|
|
const wxPoint & offset) const
|
|
{
|
|
wxLayoutObjectList::iterator i, nulled(NULL);
|
|
wxPoint pos = offset;
|
|
pos = pos + GetPosition();
|
|
|
|
pos.y += m_BaseLine;
|
|
|
|
CoordType xpos = 0; // cursorpos, length of line
|
|
|
|
CoordType from, to;
|
|
|
|
int highlight = llist->IsSelected(this, &from, &to);
|
|
// WXLO_DEBUG(("highlight=%d", highlight ));
|
|
if(highlight == 1) // we need to draw the whole line inverted!
|
|
llist->StartHighlighting(dc);
|
|
else
|
|
llist->EndHighlighting(dc);
|
|
|
|
for(i = m_ObjectList.begin(); i != nulled; i++)
|
|
{
|
|
if(highlight == -1) // partially highlight line
|
|
{
|
|
// parts of the line need highlighting
|
|
|
|
// Next line commented, code has no effect
|
|
// xpos+(**i).GetLength();
|
|
(**i).Draw(dc, pos, llist, from-xpos, to-xpos);
|
|
}
|
|
else
|
|
(**i).Draw(dc, pos, llist);
|
|
pos.x += (**i).GetWidth();
|
|
xpos += (**i).GetLength();
|
|
}
|
|
}
|
|
|
|
/*
|
|
This function does all the recalculation, that is, it should only be
|
|
called from within wxLayoutList::Layout(), as it uses the current
|
|
list's styleinfo and updates it.
|
|
*/
|
|
void
|
|
wxLayoutLine::Layout(wxDC &dc,
|
|
wxLayoutList *llist,
|
|
wxPoint *cursorPos,
|
|
wxPoint *cursorSize,
|
|
wxLayoutStyleInfo *cursorStyle,
|
|
int cx,
|
|
bool WXUNUSED(suppressSIupdate))
|
|
{
|
|
wxLayoutObjectList::iterator i, nulled(NULL);
|
|
|
|
// when a line becomes dirty, we redraw it from the place where it was
|
|
// changed till the end of line (because the following wxLayoutObjects are
|
|
// moved when the preceding one changes) - calculate the update rectangle.
|
|
CoordType updateTop = m_Position.y,
|
|
updateLeft = -1,
|
|
updateWidth = m_Width,
|
|
updateHeight = m_Height;
|
|
|
|
CoordType
|
|
topHeight = 0,
|
|
bottomHeight = 0; // above and below baseline
|
|
CoordType
|
|
objTopHeight, objBottomHeight; // above and below baseline
|
|
CoordType
|
|
len, count = 0;
|
|
|
|
CoordType heightOld = m_Height;
|
|
|
|
m_Height = 0;
|
|
m_Width = 0;
|
|
m_BaseLine = 0;
|
|
|
|
bool cursorFound = false;
|
|
|
|
RecalculatePosition(llist);
|
|
|
|
if(cursorPos)
|
|
{
|
|
*cursorPos = m_Position;
|
|
if(cursorSize) *cursorSize = wxPoint(0,0);
|
|
}
|
|
|
|
m_StyleInfo = llist->GetStyleInfo(); // save current style
|
|
for(i = m_ObjectList.begin(); i != nulled; i++)
|
|
{
|
|
wxLayoutObject *obj = *i;
|
|
obj->Layout(dc, llist);
|
|
wxPoint sizeObj = obj->GetSize(&objTopHeight, &objBottomHeight);
|
|
|
|
if(cursorPos && ! cursorFound)
|
|
{
|
|
// we need to check whether the text cursor is here
|
|
len = obj->GetLength();
|
|
if(count <= cx && count+len > cx)
|
|
{
|
|
if(obj->GetType() == WXLO_TYPE_TEXT)
|
|
{
|
|
len = cx - count; // pos in object
|
|
CoordType width, height, descent;
|
|
dc.GetTextExtent((*(wxLayoutObjectText*)*i).GetText().substr(0,len),
|
|
&width, &height, &descent);
|
|
cursorPos->x += width;
|
|
cursorPos->y = m_Position.y;
|
|
wxString str;
|
|
if(len < obj->GetLength())
|
|
str = (*(wxLayoutObjectText*)*i).GetText().substr(len,1);
|
|
else
|
|
str = _T(WXLO_CURSORCHAR);
|
|
dc.GetTextExtent(str, &width, &height, &descent);
|
|
|
|
if(cursorStyle) // set style info
|
|
*cursorStyle = llist->GetStyleInfo();
|
|
if ( cursorSize )
|
|
{
|
|
// Just in case some joker inserted an empty string object:
|
|
if(width == 0)
|
|
width = WXLO_MINIMUM_CURSOR_WIDTH;
|
|
if(height == 0)
|
|
height = sizeObj.y;
|
|
cursorSize->x = width;
|
|
cursorSize->y = height;
|
|
}
|
|
|
|
cursorFound = true; // no more checks
|
|
}
|
|
else
|
|
{
|
|
// on some other object
|
|
CoordType top, bottom; // unused
|
|
if(cursorSize)
|
|
*cursorSize = obj->GetSize(&top,&bottom);
|
|
cursorPos->y = m_Position.y;
|
|
cursorFound = true; // no more checks
|
|
}
|
|
}
|
|
else
|
|
{
|
|
count += len;
|
|
cursorPos->x += obj->GetWidth();
|
|
}
|
|
} // cursor finding
|
|
|
|
m_Width += sizeObj.x;
|
|
if(sizeObj.y > m_Height)
|
|
{
|
|
m_Height = sizeObj.y;
|
|
}
|
|
|
|
if(objTopHeight > topHeight)
|
|
topHeight = objTopHeight;
|
|
if(objBottomHeight > bottomHeight)
|
|
bottomHeight = objBottomHeight;
|
|
}
|
|
|
|
if ( IsDirty() )
|
|
{
|
|
if ( updateHeight < m_Height )
|
|
updateHeight = m_Height;
|
|
if ( updateWidth < m_Width )
|
|
updateWidth = m_Width;
|
|
|
|
// update all line if we don't know where to start from
|
|
if ( updateLeft == -1 )
|
|
updateLeft = 0;
|
|
|
|
llist->SetUpdateRect(updateLeft, updateTop);
|
|
llist->SetUpdateRect(updateLeft + updateWidth + MSW_CORRECTION,
|
|
updateTop + updateHeight + MSW_CORRECTION);
|
|
}
|
|
|
|
if(topHeight + bottomHeight > m_Height)
|
|
{
|
|
m_Height = topHeight+bottomHeight;
|
|
}
|
|
|
|
m_BaseLine = topHeight;
|
|
|
|
if(m_Height == 0)
|
|
{
|
|
CoordType width, height, descent;
|
|
dc.GetTextExtent(_T(WXLO_CURSORCHAR), &width, &height, &descent);
|
|
m_Height = height;
|
|
m_BaseLine = m_Height - descent;
|
|
}
|
|
|
|
// tell next line about coordinate change
|
|
if(m_Next && m_Height != heightOld)
|
|
{
|
|
m_Next->MarkDirty();
|
|
}
|
|
|
|
// We need to check whether we found a valid cursor size:
|
|
if(cursorPos && cursorSize)
|
|
{
|
|
// this might be the case if the cursor is at the end of the
|
|
// line or on a command object:
|
|
if(cursorSize->x < WXLO_MINIMUM_CURSOR_WIDTH)
|
|
{
|
|
CoordType width, height, descent;
|
|
dc.GetTextExtent(_T(WXLO_CURSORCHAR), &width, &height, &descent);
|
|
cursorSize->x = width;
|
|
cursorSize->y = height;
|
|
}
|
|
if(m_BaseLine >= cursorSize->y) // the normal case anyway
|
|
cursorPos->y += m_BaseLine-cursorSize->y;
|
|
}
|
|
MarkClean();
|
|
}
|
|
|
|
|
|
wxLayoutLine *
|
|
wxLayoutLine::Break(CoordType xpos, wxLayoutList *llist)
|
|
{
|
|
wxASSERT(xpos >= 0);
|
|
|
|
MarkDirty(xpos);
|
|
|
|
CoordType offset;
|
|
wxLOiterator i = FindObject(xpos, &offset);
|
|
wxLayoutObjectList::iterator nulled(NULL);
|
|
if(i == nulled)
|
|
// must be at the end of the line then
|
|
return new wxLayoutLine(this, llist);
|
|
// split this line:
|
|
|
|
wxLayoutLine *newLine = new wxLayoutLine(this, llist);
|
|
// split object at i:
|
|
if((**i).GetType() == WXLO_TYPE_TEXT
|
|
&& offset != 0
|
|
&& offset != (**i).GetLength() )
|
|
{
|
|
wxString left, right;
|
|
wxLayoutObjectText *tobj = (wxLayoutObjectText *) *i;
|
|
left = tobj->GetText().substr(0,offset);
|
|
right = tobj->GetText().substr(offset,tobj->GetLength()-offset);
|
|
// current text object gets set to left half
|
|
tobj->GetText() = left; // set new text
|
|
newLine->Append(new wxLayoutObjectText(right));
|
|
m_Length -= right.Length();
|
|
i++; // don't move this object to the new list
|
|
}
|
|
else
|
|
{
|
|
if(offset > 0)
|
|
i++; // move objects from here to new list
|
|
}
|
|
|
|
while(i != m_ObjectList.end())
|
|
{
|
|
wxLayoutObject *obj = *i;
|
|
newLine->Append(obj);
|
|
m_Length -= obj->GetLength();
|
|
|
|
m_ObjectList.remove(i); // remove without deleting it
|
|
}
|
|
if(m_Next)
|
|
m_Next->MarkDirty();
|
|
return newLine;
|
|
}
|
|
|
|
bool
|
|
wxLayoutLine::Wrap(CoordType wrapmargin, wxLayoutList *llist)
|
|
{
|
|
wxLayoutObjectList::iterator nulled(NULL);
|
|
if(GetLength() < wrapmargin)
|
|
return false; // nothing to do
|
|
|
|
// find the object which covers the wrapmargin:
|
|
CoordType offset;
|
|
wxLOiterator i = FindObject(wrapmargin, &offset);
|
|
wxCHECK_MSG( i != nulled, false,
|
|
wxT("Cannot find object covering wrapmargin."));
|
|
|
|
// from this object on, the rest of the line must be copied to the
|
|
// next one:
|
|
wxLOiterator copyObject = nulled;
|
|
// if we split a text-object, we must pre-pend some text to the
|
|
// next line later on, remember it here:
|
|
wxString prependText = _T("");
|
|
// we might need to adjust the cursor position later, so remember it
|
|
size_t xpos = llist->GetCursorPos().x;
|
|
// by how much did we shorten the current line:
|
|
size_t shorter = 0;
|
|
// remember cursor location of object
|
|
size_t objectCursorPos = 0;
|
|
|
|
size_t breakpos = offset;
|
|
|
|
if( (**i).GetType() != WXLO_TYPE_TEXT )
|
|
{
|
|
// break before a non-text object
|
|
copyObject = i;
|
|
}
|
|
else
|
|
{
|
|
bool foundSpace = false;
|
|
do
|
|
{
|
|
// while(i != nulled && (**i).GetType() != WXLO_TYPE_TEXT)
|
|
// i--;
|
|
// try to find a suitable place to split the object:
|
|
wxLayoutObjectText *tobj = (wxLayoutObjectText *)*i;
|
|
if((**i).GetType() == WXLO_TYPE_TEXT
|
|
&& tobj->GetText().Length() >= breakpos)
|
|
{
|
|
do
|
|
{
|
|
foundSpace = isspace(tobj->GetText()[breakpos]) != 0;
|
|
if ( foundSpace )
|
|
break;
|
|
}
|
|
while ( breakpos-- > 0 );
|
|
}
|
|
else
|
|
{
|
|
breakpos = 0;
|
|
}
|
|
|
|
if(! foundSpace) // breakpos == 0!
|
|
{
|
|
if(i == m_ObjectList.begin())
|
|
return false; // could not break line
|
|
else
|
|
{
|
|
i--;
|
|
while(i != m_ObjectList.begin()
|
|
&& (**i).GetType() != WXLO_TYPE_TEXT )
|
|
{
|
|
i--;
|
|
}
|
|
breakpos = (**i).GetLength();
|
|
}
|
|
}
|
|
}while(! foundSpace);
|
|
// before we actually break the object, we need to know at which
|
|
// cursorposition it starts, so we can restore the cursor if needed:
|
|
if( this == llist->GetCursorLine() && xpos >= breakpos )
|
|
{
|
|
for(wxLOiterator j = m_ObjectList.begin();
|
|
j != nulled && j != i; j++)
|
|
objectCursorPos += (**j).GetLength();
|
|
}
|
|
// now we know where to break it:
|
|
wxLayoutObjectText *tobj = (wxLayoutObjectText *)*i;
|
|
shorter = tobj->GetLength() - breakpos;
|
|
// remember text to copy from this object
|
|
prependText = tobj->GetText().Mid(breakpos+1);
|
|
tobj->SetText(tobj->GetText().Left(breakpos));
|
|
// copy every following object:
|
|
copyObject = i; copyObject ++;
|
|
}
|
|
|
|
// make sure there is an empty m_Next line:
|
|
(void) new wxLayoutLine(this, llist);
|
|
wxASSERT(m_Next);
|
|
// We need to move this and all following objects to the next
|
|
// line. Starting from the end of line, to keep the order right.
|
|
if(copyObject != nulled)
|
|
{
|
|
wxLOiterator j;
|
|
for(j = m_ObjectList.tail(); j != copyObject; j--)
|
|
m_Next->Prepend(*j);
|
|
m_Next->Prepend(*copyObject);
|
|
// and now remove them from this list:
|
|
while( copyObject != m_ObjectList.end() )
|
|
{
|
|
shorter += (**copyObject).GetLength();
|
|
m_ObjectList.remove(copyObject); // remove without deleting it
|
|
}
|
|
}
|
|
m_Length -= shorter;
|
|
|
|
if(prependText.Length() > 0)
|
|
m_Next->Insert(0, prependText);
|
|
|
|
// do we need to adjust the cursor position?
|
|
if( this == llist->GetCursorLine() && xpos >= breakpos)
|
|
{
|
|
xpos = objectCursorPos + (xpos - objectCursorPos - breakpos -
|
|
((xpos > breakpos) ? 1 : 0 ));
|
|
#if 0
|
|
// this assert is useless when xpos has unsigned type
|
|
wxASSERT(xpos >= 0);
|
|
#endif
|
|
llist->MoveCursorTo( wxPoint( xpos, m_Next->GetLineNumber()) );
|
|
}
|
|
return true; // we wrapped the line
|
|
}
|
|
|
|
void
|
|
wxLayoutLine::ReNumber()
|
|
{
|
|
CoordType lineNo = m_Previous ? m_Previous->m_LineNumber+1 : 0;
|
|
m_LineNumber = lineNo++;
|
|
|
|
for(wxLayoutLine *next = GetNextLine();
|
|
next; next = next->GetNextLine())
|
|
next->m_LineNumber = lineNo++;
|
|
}
|
|
|
|
void
|
|
wxLayoutLine::MergeNextLine(wxLayoutList *llist)
|
|
{
|
|
wxCHECK_RET( GetNextLine(),
|
|
wxT("wxLayout internal error: no next line to merge"));
|
|
wxLayoutObjectList &list = GetNextLine()->m_ObjectList;
|
|
wxLOiterator i;
|
|
|
|
MarkDirty(GetWidth());
|
|
|
|
wxLayoutObject *last = NULL;
|
|
for(i = list.begin(); i != list.end();)
|
|
{
|
|
wxLayoutObject *current = *i;
|
|
|
|
// merge text objects together for efficiency
|
|
if ( last && last->GetType() == WXLO_TYPE_TEXT &&
|
|
current->GetType() == WXLO_TYPE_TEXT )
|
|
{
|
|
wxLayoutObjectText *textObj = (wxLayoutObjectText *)last;
|
|
wxString text(textObj->GetText());
|
|
text += ((wxLayoutObjectText *)current)->GetText();
|
|
textObj->SetText(text);
|
|
|
|
list.erase(i); // remove and delete it
|
|
}
|
|
else
|
|
{
|
|
// just append the object "as was"
|
|
Append(current);
|
|
|
|
list.remove(i); // remove without deleting it
|
|
}
|
|
}
|
|
wxASSERT(list.empty());
|
|
|
|
wxLayoutLine *oldnext = GetNextLine();
|
|
wxLayoutLine *nextLine = oldnext->GetNextLine();
|
|
SetNext(nextLine);
|
|
if ( nextLine )
|
|
{
|
|
nextLine->ReNumber();
|
|
}
|
|
else
|
|
{
|
|
// this is now done in Delete(), but if this function is ever called
|
|
// from elsewhere, we might have to move refresh code back here (in
|
|
// order not to duplicate it)
|
|
#if 0
|
|
wxPoint pos(oldnext->GetPosition());
|
|
llist->SetUpdateRect(pos);
|
|
llist->SetUpdateRect(pos.x + oldnext->GetWidth() + MSW_CORRECTION,
|
|
pos.y + oldnext->GetHeight() + MSW_CORRECTION);
|
|
#endif // 0
|
|
}
|
|
|
|
llist->DecNumLines();
|
|
|
|
delete oldnext;
|
|
}
|
|
|
|
CoordType
|
|
wxLayoutLine::GetWrapPosition(CoordType column)
|
|
{
|
|
CoordType offset;
|
|
wxLOiterator i = FindObject(column, &offset);
|
|
wxLayoutObjectList::iterator nulled(NULL);
|
|
if(i == nulled) return -1; // cannot wrap
|
|
|
|
// go backwards through the list and look for space in text objects
|
|
do
|
|
{
|
|
if((**i).GetType() == WXLO_TYPE_TEXT)
|
|
{
|
|
do
|
|
{
|
|
if(isspace(((wxLayoutObjectText*)*i)->GetText().c_str()[(size_t)offset]))
|
|
return column;
|
|
else
|
|
{
|
|
offset--;
|
|
column--;
|
|
}
|
|
}while(offset != -1);
|
|
i--; // move on to previous object
|
|
}
|
|
else
|
|
{
|
|
column -= (**i).GetLength();
|
|
i--;
|
|
}
|
|
if( i != nulled)
|
|
offset = (**i).GetLength();
|
|
}while(i != nulled);
|
|
/* If we reached the begin of the list and have more than one
|
|
object, that one is longer than the margin, so break behind
|
|
it. */
|
|
CoordType pos = 0;
|
|
i = m_ObjectList.begin();
|
|
while(i != nulled && (**i).GetType() != WXLO_TYPE_TEXT)
|
|
{
|
|
pos += (**i).GetLength();
|
|
i++;
|
|
}
|
|
if(i == nulled) return -1; //why should this happen?
|
|
|
|
// now we are behind the one long text object and need to find the
|
|
// first space in it
|
|
for(offset = 0; offset < (**i).GetLength(); offset++)
|
|
if( isspace(((wxLayoutObjectText*)*i)->GetText().c_str()[(size_t)offset]))
|
|
{
|
|
return pos+offset;
|
|
}
|
|
pos += (**i).GetLength();
|
|
return pos;
|
|
}
|
|
|
|
|
|
#ifdef WXLAYOUT_DEBUG
|
|
void
|
|
wxLayoutLine::Debug() const
|
|
{
|
|
wxLayoutObjectList::iterator nulled(NULL);
|
|
wxPoint pos = GetPosition();
|
|
WXLO_DEBUG((wxT("Line %ld, Pos (%ld,%ld), Height %ld, BL %ld, Font: %d"),
|
|
(long int) GetLineNumber(),
|
|
(long int) pos.x, (long int) pos.y,
|
|
(long int) GetHeight(),
|
|
(long int) m_BaseLine,
|
|
(int) m_StyleInfo.family));
|
|
if(m_ObjectList.begin() != nulled)
|
|
{
|
|
WXLO_DEBUG(((**m_ObjectList.begin()).DebugDump().c_str()));
|
|
}
|
|
|
|
}
|
|
#endif
|
|
|
|
void
|
|
wxLayoutLine::Copy(wxLayoutList *llist,
|
|
CoordType from,
|
|
CoordType to)
|
|
{
|
|
wxLayoutObjectList::iterator nulled(NULL);
|
|
CoordType firstOffset, lastOffset;
|
|
|
|
if(to == -1) to = GetLength();
|
|
if(from == to) return;
|
|
|
|
wxLOiterator first = FindObject(from, &firstOffset);
|
|
wxLOiterator last = FindObject(to, &lastOffset);
|
|
|
|
// Common special case: only one object
|
|
if( first != nulled && last != nulled && *first == *last )
|
|
{
|
|
if( (**first).GetType() == WXLO_TYPE_TEXT )
|
|
{
|
|
llist->Insert(new wxLayoutObjectText(
|
|
((wxLayoutObjectText
|
|
*)*first)->GetText().substr(firstOffset,
|
|
lastOffset-firstOffset))
|
|
);
|
|
return;
|
|
}
|
|
else // what can we do?
|
|
{
|
|
if(lastOffset > firstOffset) // i.e. +1 :-)
|
|
llist->Insert( (**first).Copy() );
|
|
return;
|
|
}
|
|
}
|
|
|
|
// If we reach here, we can safely copy the whole first object from
|
|
// the firstOffset position on:
|
|
if((**first).GetType() == WXLO_TYPE_TEXT && firstOffset != 0)
|
|
{
|
|
llist->Insert(new wxLayoutObjectText(
|
|
((wxLayoutObjectText *)*first)->GetText().substr(firstOffset))
|
|
);
|
|
}
|
|
else if(firstOffset == 0)
|
|
llist->Insert( (**first).Copy() );
|
|
// else nothing to copy :-(
|
|
|
|
// Now we copy all objects before the last one:
|
|
wxLOiterator i = first; i++;
|
|
for( ; i != last; i++)
|
|
llist->Insert( (**i).Copy() );
|
|
|
|
// And now the last object:
|
|
if(lastOffset != 0)
|
|
{
|
|
if( (**last).GetType() == WXLO_TYPE_TEXT )
|
|
{
|
|
llist->Insert(new wxLayoutObjectText(
|
|
((wxLayoutObjectText *)*last)->GetText().substr(0,lastOffset))
|
|
);
|
|
}
|
|
else
|
|
llist->Insert( (**last).Copy() );
|
|
}
|
|
}
|
|
|
|
|
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
|
|
The wxLayoutList object
|
|
|
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
|
|
wxLayoutList::wxLayoutList()
|
|
{
|
|
#ifdef WXLAYOUT_USE_CARET
|
|
m_caret = NULL;
|
|
#endif // WXLAYOUT_USE_CARET
|
|
|
|
m_numLines = 0;
|
|
m_FirstLine = NULL;
|
|
SetAutoFormatting(true);
|
|
ForceTotalLayout(true); // for the first time, do all
|
|
InvalidateUpdateRect();
|
|
Clear();
|
|
}
|
|
|
|
wxLayoutList::~wxLayoutList()
|
|
{
|
|
SetAutoFormatting(false);
|
|
InternalClear();
|
|
Empty();
|
|
m_FirstLine->DeleteLine(false, this);
|
|
|
|
wxASSERT_MSG( m_numLines == 0, wxT("line count calculation broken"));
|
|
}
|
|
|
|
void
|
|
wxLayoutList::Empty()
|
|
{
|
|
while(m_FirstLine)
|
|
m_FirstLine = m_FirstLine->DeleteLine(false, this);
|
|
|
|
m_CursorPos = wxPoint(0,0);
|
|
m_CursorScreenPos = wxPoint(0,0);
|
|
m_CursorSize = wxPoint(0,0);
|
|
m_movedCursor = true;
|
|
m_FirstLine = new wxLayoutLine(NULL, this); // empty first line
|
|
m_CursorLine = m_FirstLine;
|
|
InvalidateUpdateRect();
|
|
}
|
|
|
|
|
|
void
|
|
wxLayoutList::InternalClear()
|
|
{
|
|
m_Selection.m_selecting = false;
|
|
m_Selection.m_valid = false;
|
|
|
|
m_DefaultStyleInfo.family = wxSWISS;
|
|
m_DefaultStyleInfo.size = WXLO_DEFAULTFONTSIZE;
|
|
m_DefaultStyleInfo.style = wxNORMAL;
|
|
m_DefaultStyleInfo.weight = wxNORMAL;
|
|
m_DefaultStyleInfo.underline = 0;
|
|
m_DefaultStyleInfo.m_fg_valid = true;
|
|
m_DefaultStyleInfo.m_fg = *wxBLACK;
|
|
m_DefaultStyleInfo.m_bg_valid = true;
|
|
m_DefaultStyleInfo.m_bg = *wxWHITE;
|
|
|
|
m_CurrentStyleInfo = m_DefaultStyleInfo;
|
|
m_CursorStyleInfo = m_DefaultStyleInfo;
|
|
}
|
|
|
|
void
|
|
wxLayoutList::Read(wxString &istr)
|
|
{
|
|
/* In order to handle input of formatted string "nicely", we need
|
|
to restore our current font settings after the string. So first
|
|
of all, we create a StyleInfo structure with our current
|
|
settings. */
|
|
wxLayoutStyleInfo current_si = GetStyleInfo();
|
|
|
|
while(istr.Length())
|
|
{
|
|
// check for a linebreak:
|
|
wxString tmp;
|
|
tmp = istr.BeforeFirst('\n');
|
|
long l = WXLO_TYPE_INVALID;
|
|
tmp.ToLong(&l);
|
|
int type = (int) l;
|
|
|
|
if(type == WXLO_TYPE_LINEBREAK)
|
|
{
|
|
LineBreak();
|
|
istr = istr.AfterFirst('\n');
|
|
}
|
|
else
|
|
{
|
|
wxLayoutObject *obj = wxLayoutObject::Read(istr);
|
|
if(obj)
|
|
Insert(obj);
|
|
}
|
|
}
|
|
/* Now we use the current_si to restore our last font settings: */
|
|
Insert(new wxLayoutObjectCmd(current_si));
|
|
}
|
|
|
|
|
|
void
|
|
wxLayoutList::SetFont(int family, int size, int style, int weight,
|
|
int underline, wxColour *fg,
|
|
wxColour *bg)
|
|
{
|
|
if(family != -1) m_CurrentStyleInfo.family = family;
|
|
if(size != -1) m_CurrentStyleInfo.size = size;
|
|
if(style != -1) m_CurrentStyleInfo.style = style;
|
|
if(weight != -1) m_CurrentStyleInfo.weight = weight;
|
|
if(underline != -1) m_CurrentStyleInfo.underline = underline != 0;
|
|
if(fg) m_CurrentStyleInfo.m_fg = *fg;
|
|
if(bg) m_CurrentStyleInfo.m_bg = *bg;
|
|
Insert(
|
|
new wxLayoutObjectCmd(
|
|
m_CurrentStyleInfo.family,
|
|
m_CurrentStyleInfo.size,
|
|
m_CurrentStyleInfo.style,
|
|
m_CurrentStyleInfo.weight,
|
|
m_CurrentStyleInfo.underline,
|
|
fg, bg));
|
|
}
|
|
|
|
void
|
|
wxLayoutList::SetFont(int family, int size, int style, int weight,
|
|
int underline, wxChar const *fg, wxChar const *bg)
|
|
|
|
{
|
|
wxColour cfg = wxTheColourDatabase->Find((fg)?fg:wxT("BLACK"));
|
|
wxColour cbg = wxTheColourDatabase->Find((bg)?bg:wxT("WHITE"));
|
|
|
|
SetFont(family,size,style,weight,underline,&cfg,&cbg);
|
|
}
|
|
|
|
void
|
|
wxLayoutList::Clear(int family, int size, int style, int weight,
|
|
int underline, wxColour *fg, wxColour *bg)
|
|
{
|
|
InternalClear();
|
|
m_DefaultStyleInfo = wxLayoutStyleInfo(family, size, style, weight,
|
|
underline, fg, bg);
|
|
m_CurrentStyleInfo = m_DefaultStyleInfo;
|
|
|
|
// Empty() should be called after we set m_DefaultStyleInfo because
|
|
// otherwise the style info for the first line (created in Empty()) would be
|
|
// incorrect
|
|
Empty();
|
|
}
|
|
|
|
wxPoint
|
|
wxLayoutList::FindText(const wxString &needle, const wxPoint &cpos) const
|
|
{
|
|
int xpos;
|
|
|
|
wxLayoutLine *line;
|
|
for(line = m_FirstLine;
|
|
line;
|
|
line = line->GetNextLine())
|
|
{
|
|
if(line->GetLineNumber() >= cpos.y)
|
|
{
|
|
xpos = line->FindText(needle,
|
|
(line->GetLineNumber() == cpos.y) ?
|
|
cpos.x : 0);
|
|
if(xpos != -1)
|
|
return wxPoint(xpos, line->GetLineNumber());
|
|
}
|
|
}
|
|
return wxPoint(-1,-1);
|
|
}
|
|
|
|
|
|
bool
|
|
wxLayoutList::MoveCursorTo(wxPoint const &p)
|
|
{
|
|
AddCursorPosToUpdateRect();
|
|
|
|
wxPoint cursorPosOld = m_CursorPos;
|
|
|
|
wxLayoutLine *line = m_FirstLine;
|
|
while(line && line->GetLineNumber() != p.y)
|
|
line = line->GetNextLine();
|
|
if(line && line->GetLineNumber() == p.y) // found it
|
|
{
|
|
m_CursorPos.y = p.y;
|
|
m_CursorLine = line;
|
|
CoordType len = line->GetLength();
|
|
if(len >= p.x)
|
|
{
|
|
m_CursorPos.x = p.x;
|
|
}
|
|
else
|
|
{
|
|
m_CursorPos.x = len;
|
|
}
|
|
}
|
|
|
|
m_movedCursor = m_CursorPos != cursorPosOld;
|
|
|
|
return m_CursorPos == p;
|
|
}
|
|
|
|
bool
|
|
wxLayoutList::MoveCursorVertically(int n)
|
|
{
|
|
AddCursorPosToUpdateRect();
|
|
|
|
wxPoint cursorPosOld = m_CursorPos;
|
|
|
|
bool rc;
|
|
if(n < 0) // move up
|
|
{
|
|
if(m_CursorLine == m_FirstLine) return false;
|
|
while(n < 0 && m_CursorLine)
|
|
{
|
|
m_CursorLine = m_CursorLine->GetPreviousLine();
|
|
m_CursorPos.y--;
|
|
n++;
|
|
}
|
|
if(! m_CursorLine)
|
|
{
|
|
m_CursorLine = m_FirstLine;
|
|
m_CursorPos.y = 0;
|
|
rc = false;
|
|
}
|
|
else
|
|
{
|
|
if(m_CursorPos.x > m_CursorLine->GetLength())
|
|
m_CursorPos.x = m_CursorLine->GetLength();
|
|
rc = true;
|
|
}
|
|
}
|
|
else // move down
|
|
{
|
|
wxLayoutLine *last = m_CursorLine;
|
|
if(! m_CursorLine->GetNextLine()) return false;
|
|
while(n > 0 && m_CursorLine)
|
|
{
|
|
n--;
|
|
m_CursorPos.y ++;
|
|
last = m_CursorLine;
|
|
m_CursorLine = m_CursorLine->GetNextLine();
|
|
}
|
|
if(! m_CursorLine)
|
|
{
|
|
m_CursorLine = last;
|
|
m_CursorPos.y --;
|
|
rc = false;
|
|
}
|
|
else
|
|
{
|
|
if(m_CursorPos.x > m_CursorLine->GetLength())
|
|
m_CursorPos.x = m_CursorLine->GetLength();
|
|
rc = true;
|
|
}
|
|
}
|
|
|
|
m_movedCursor = m_CursorPos != cursorPosOld;
|
|
|
|
return rc;
|
|
}
|
|
|
|
bool
|
|
wxLayoutList::MoveCursorHorizontally(int n)
|
|
{
|
|
AddCursorPosToUpdateRect();
|
|
|
|
wxPoint cursorPosOld = m_CursorPos;
|
|
|
|
int move;
|
|
while(n < 0)
|
|
{
|
|
if(m_CursorPos.x == 0) // at begin of line
|
|
{
|
|
if(! MoveCursorVertically(-1))
|
|
break;
|
|
MoveCursorToEndOfLine();
|
|
n++;
|
|
continue;
|
|
}
|
|
move = -n;
|
|
if(move > m_CursorPos.x) move = m_CursorPos.x;
|
|
m_CursorPos.x -= move; n += move;
|
|
}
|
|
|
|
while(n > 0)
|
|
{
|
|
int len = m_CursorLine->GetLength();
|
|
if(m_CursorPos.x == len) // at end of line
|
|
{
|
|
if(! MoveCursorVertically(1))
|
|
break;
|
|
MoveCursorToBeginOfLine();
|
|
n--;
|
|
continue;
|
|
}
|
|
move = n;
|
|
if( move >= len-m_CursorPos.x) move = len-m_CursorPos.x;
|
|
m_CursorPos.x += move;
|
|
n -= move;
|
|
}
|
|
|
|
m_movedCursor = m_CursorPos != cursorPosOld;
|
|
|
|
return n == 0;
|
|
}
|
|
|
|
bool
|
|
wxLayoutList::MoveCursorWord(int n, bool untilNext)
|
|
{
|
|
wxLayoutObjectList::iterator nulled(NULL);
|
|
wxCHECK_MSG( m_CursorLine, false, wxT("no current line") );
|
|
wxCHECK_MSG( n == -1 || n == +1, false, wxT("not implemented yet") );
|
|
|
|
CoordType moveDistance = 0;
|
|
CoordType offset;
|
|
wxLayoutLine *lineCur = m_CursorLine;
|
|
for ( wxLOiterator i = lineCur->FindObject(m_CursorPos.x, &offset);
|
|
n != 0;
|
|
n > 0 ? i++ : i-- )
|
|
{
|
|
if ( i == nulled )
|
|
{
|
|
if ( n > 0 )
|
|
{
|
|
// moving forward, pass to the first object of the next line
|
|
moveDistance++;
|
|
lineCur = lineCur->GetNextLine();
|
|
if ( lineCur )
|
|
i = lineCur->GetFirstObject();
|
|
}
|
|
else
|
|
{
|
|
// moving backwards, pass to the last object of the prev line
|
|
moveDistance--;
|
|
lineCur = lineCur->GetPreviousLine();
|
|
if ( lineCur )
|
|
i = lineCur->GetLastObject();
|
|
}
|
|
|
|
if ( i == nulled )
|
|
{
|
|
// moved to the end/beginning of text
|
|
return false;
|
|
}
|
|
|
|
offset = -1;
|
|
}
|
|
|
|
wxLayoutObject *obj = *i;
|
|
|
|
if ( offset == -1 )
|
|
{
|
|
// calculate offset: we are either at the very beginning or the very
|
|
// end of the object, so it isn't very difficult (the only time when
|
|
// offset is != -1 is for the very first iteration when its value is
|
|
// returned by FindObject)
|
|
if ( n > 0 )
|
|
offset = 0;
|
|
else
|
|
offset = obj->GetLength();
|
|
}
|
|
|
|
if( obj->GetType() != WXLO_TYPE_TEXT )
|
|
{
|
|
// any visible non text objects count as one word
|
|
if ( obj->IsVisibleObject() )
|
|
{
|
|
n > 0 ? n-- : n++;
|
|
|
|
moveDistance += obj->GetLength();
|
|
}
|
|
}
|
|
else // text object
|
|
{
|
|
wxLayoutObjectText *tobj = (wxLayoutObjectText *)obj;
|
|
|
|
bool canAdvance = true;
|
|
|
|
if ( offset == tobj->GetLength() )
|
|
{
|
|
// at end of object
|
|
if ( n > 0 )
|
|
{
|
|
// can't move further in this text object
|
|
canAdvance = false;
|
|
|
|
// still should move over the object border
|
|
moveDistance++;
|
|
n--;
|
|
}
|
|
else if ( offset > 0 )
|
|
{
|
|
// offset is off by 1, make it a valid index
|
|
offset--;
|
|
}
|
|
}
|
|
|
|
if ( canAdvance )
|
|
{
|
|
const wxString& text = tobj->GetText();
|
|
const wxChar *start = text.c_str();
|
|
const wxChar *end = start + text.length();
|
|
const wxChar *p = start + offset;
|
|
|
|
if ( n < 0 )
|
|
{
|
|
if ( offset > 0 )
|
|
p--;
|
|
}
|
|
|
|
// to the beginning/end of the next/prev word
|
|
while ( p >= start && p < end && isspace(*p) )
|
|
{
|
|
n > 0 ? p++ : p--;
|
|
}
|
|
|
|
// go to the end/beginning of the word (in a broad sense...)
|
|
while ( p >= start && p < end && !isspace(*p) )
|
|
{
|
|
n > 0 ? p++ : p--;
|
|
}
|
|
|
|
if ( n > 0 )
|
|
{
|
|
if ( untilNext )
|
|
{
|
|
// now advance to the beginning of the next word
|
|
while ( isspace(*p) && p < end )
|
|
p++;
|
|
}
|
|
}
|
|
else // backwards
|
|
{
|
|
// in these 2 cases we took 1 char too much
|
|
if ( (p < start) || isspace(*p) )
|
|
{
|
|
p++;
|
|
}
|
|
}
|
|
|
|
CoordType moveDelta = p - start - offset;
|
|
if ( (n < 0) && (offset == tobj->GetLength() - 1) )
|
|
{
|
|
// because we subtracted 1 from offset in this case above, now
|
|
// compensate for it
|
|
moveDelta--;
|
|
}
|
|
|
|
if ( moveDelta != 0 )
|
|
{
|
|
moveDistance += moveDelta;
|
|
|
|
n > 0 ? n-- : n++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// except for the first iteration, offset is calculated in the beginning
|
|
// of the loop
|
|
offset = -1;
|
|
}
|
|
|
|
MoveCursorHorizontally(moveDistance);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
wxLayoutList::Insert(wxString const &text)
|
|
{
|
|
wxASSERT(m_CursorLine);
|
|
wxASSERT_MSG( text.Find(wxT('\n')) == wxNOT_FOUND,
|
|
wxT("use wxLayoutImportText!") );
|
|
|
|
if ( !text )
|
|
return true;
|
|
|
|
AddCursorPosToUpdateRect();
|
|
|
|
wxASSERT(m_CursorLine->GetLength() >= m_CursorPos.x);
|
|
|
|
if ( !m_CursorLine->Insert(m_CursorPos.x, text) )
|
|
return false;
|
|
m_CursorPos.x += text.Length();
|
|
|
|
m_movedCursor = true;
|
|
|
|
if(m_AutoFormat)
|
|
m_CursorLine->MarkDirty();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
wxLayoutList::Insert(wxLayoutObject *obj)
|
|
{
|
|
wxASSERT(m_CursorLine);
|
|
|
|
if(! m_CursorLine)
|
|
m_CursorLine = GetFirstLine();
|
|
|
|
AddCursorPosToUpdateRect();
|
|
|
|
m_CursorLine->Insert(m_CursorPos.x, obj);
|
|
m_CursorPos.x += obj->GetLength();
|
|
m_movedCursor = true;
|
|
|
|
if(m_AutoFormat)
|
|
m_CursorLine->MarkDirty();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
wxLayoutList::Insert(wxLayoutList *llist)
|
|
{
|
|
wxLayoutObjectList::iterator nulled(NULL);
|
|
wxASSERT(llist);
|
|
bool rc = true;
|
|
|
|
for(wxLayoutLine *line = llist->GetFirstLine();
|
|
line;
|
|
line = line->GetNextLine()
|
|
)
|
|
{
|
|
for(wxLOiterator i = line->GetFirstObject();
|
|
i != nulled;
|
|
i++)
|
|
rc |= Insert(*i);
|
|
LineBreak();
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
bool
|
|
wxLayoutList::LineBreak()
|
|
{
|
|
wxASSERT(m_CursorLine);
|
|
|
|
AddCursorPosToUpdateRect();
|
|
|
|
wxPoint position(m_CursorLine->GetPosition());
|
|
|
|
CoordType
|
|
width = m_CursorLine->GetWidth(),
|
|
height = m_CursorLine->GetHeight();
|
|
|
|
m_CursorLine = m_CursorLine->Break(m_CursorPos.x, this);
|
|
if(m_CursorLine->GetPreviousLine() == NULL)
|
|
m_FirstLine = m_CursorLine;
|
|
m_CursorPos.y++;
|
|
m_CursorPos.x = 0;
|
|
|
|
// The following code will produce a height which is guaranteed to
|
|
// be too high: old lineheight + the height of both new lines.
|
|
// We can probably drop the old line height and start with height =
|
|
// 0. FIXME
|
|
wxLayoutLine *prev = m_CursorLine->GetPreviousLine();
|
|
if(prev)
|
|
height += prev->GetHeight();
|
|
height += m_CursorLine->GetHeight();
|
|
|
|
m_movedCursor = true;
|
|
|
|
SetUpdateRect(position);
|
|
SetUpdateRect(position.x + width + MSW_CORRECTION,
|
|
position.y + height + MSW_CORRECTION);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
wxLayoutList::WrapLine(CoordType column)
|
|
{
|
|
return m_CursorLine->Wrap(column, this);
|
|
}
|
|
|
|
bool
|
|
wxLayoutList::WrapAll(CoordType column)
|
|
{
|
|
wxLayoutLine *line = m_FirstLine;
|
|
if(! line)
|
|
return false;
|
|
bool rc = true;
|
|
while(line && rc)
|
|
{
|
|
rc &= line->Wrap(column, this);
|
|
line = line->GetNextLine();
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
bool
|
|
wxLayoutList::Delete(CoordType npos)
|
|
{
|
|
wxCHECK_MSG(m_CursorLine, false, wxT("can't delete in non existing line"));
|
|
|
|
if ( npos == 0 )
|
|
return true;
|
|
|
|
AddCursorPosToUpdateRect();
|
|
|
|
// were other lines appended to this one (this is important to know because
|
|
// this means that our width _increased_ as the result of deletion)
|
|
bool wasMerged = false;
|
|
|
|
// the size of the region to update
|
|
CoordType totalHeight = m_CursorLine->GetHeight(),
|
|
totalWidth = m_CursorLine->GetWidth();
|
|
|
|
CoordType left;
|
|
do
|
|
{
|
|
left = m_CursorLine->Delete(m_CursorPos.x, npos);
|
|
|
|
if( left > 0 )
|
|
{
|
|
// More to delete, continue on next line.
|
|
|
|
// First, check if line is empty:
|
|
if(m_CursorLine->GetLength() == 0)
|
|
{
|
|
// in this case, updating could probably be optimised
|
|
#ifdef WXLO_DEBUG
|
|
wxASSERT(DeleteLines(1) == 0);
|
|
#else
|
|
DeleteLines(1);
|
|
#endif
|
|
|
|
left--;
|
|
}
|
|
else
|
|
{
|
|
// Need to join next line
|
|
if(! m_CursorLine->GetNextLine())
|
|
break; // cannot
|
|
else
|
|
{
|
|
wasMerged = true;
|
|
wxLayoutLine *next = m_CursorLine->GetNextLine();
|
|
if ( next )
|
|
{
|
|
totalHeight += next->GetHeight();
|
|
totalWidth += next->GetWidth();
|
|
|
|
m_CursorLine->MergeNextLine(this);
|
|
left--;
|
|
}
|
|
else
|
|
{
|
|
wxFAIL_MSG(wxT("can't delete all this"));
|
|
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
while ( left> 0 );
|
|
|
|
// we need to update the whole tail of the line and the lines which
|
|
// disappeared
|
|
if ( wasMerged )
|
|
{
|
|
wxPoint position(m_CursorLine->GetPosition());
|
|
SetUpdateRect(position);
|
|
SetUpdateRect(position.x + totalWidth + MSW_CORRECTION,
|
|
position.y + totalHeight + MSW_CORRECTION);
|
|
}
|
|
|
|
return left == 0;
|
|
}
|
|
|
|
int
|
|
wxLayoutList::DeleteLines(int n)
|
|
{
|
|
wxASSERT(m_CursorLine);
|
|
wxLayoutLine *line;
|
|
|
|
AddCursorPosToUpdateRect();
|
|
|
|
while(n > 0)
|
|
{
|
|
if(!m_CursorLine->GetNextLine())
|
|
{ // we cannot delete this line, but we can clear it
|
|
MoveCursorToBeginOfLine();
|
|
DeleteToEndOfLine();
|
|
if(m_AutoFormat)
|
|
m_CursorLine->MarkDirty();
|
|
return n-1;
|
|
}
|
|
//else:
|
|
line = m_CursorLine;
|
|
m_CursorLine = m_CursorLine->DeleteLine(true, this);
|
|
n--;
|
|
if(line == m_FirstLine) m_FirstLine = m_CursorLine;
|
|
wxASSERT(m_FirstLine);
|
|
wxASSERT(m_CursorLine);
|
|
}
|
|
if(m_AutoFormat)
|
|
m_CursorLine->MarkDirty();
|
|
return n;
|
|
}
|
|
|
|
void
|
|
wxLayoutList::Recalculate(wxDC &dc, CoordType bottom)
|
|
{
|
|
if(! m_AutoFormat)
|
|
return;
|
|
wxLayoutLine *line = m_FirstLine;
|
|
|
|
// first, make sure everything is calculated - this might not be
|
|
// needed, optimise it later
|
|
ApplyStyle(m_DefaultStyleInfo, dc);
|
|
while(line)
|
|
{
|
|
line->RecalculatePosition(this); // so we don't need to do it all the time
|
|
// little condition to speed up redrawing:
|
|
if(bottom != -1 && line->GetPosition().y > bottom) break;
|
|
line = line->GetNextLine();
|
|
}
|
|
}
|
|
|
|
wxPoint
|
|
wxLayoutList::GetCursorScreenPos() const
|
|
{
|
|
return m_CursorScreenPos;
|
|
}
|
|
|
|
/*
|
|
Is called before each Draw(). Now, it will re-layout all lines which
|
|
have changed.
|
|
*/
|
|
void
|
|
wxLayoutList::Layout(wxDC &dc, CoordType bottom, bool forceAll,
|
|
wxPoint *cpos, wxPoint *csize)
|
|
{
|
|
// first, make sure everything is calculated - this might not be
|
|
// needed, optimise it later
|
|
ApplyStyle(m_DefaultStyleInfo, dc);
|
|
|
|
|
|
if(m_ReLayoutAll)
|
|
{
|
|
forceAll = true;
|
|
bottom = -1;
|
|
}
|
|
|
|
ForceTotalLayout(false);
|
|
|
|
|
|
// If one line was dirty, we need to re-calculate all
|
|
// following lines, too.
|
|
bool wasDirty = forceAll;
|
|
// we need to layout until we reach at least the cursor line,
|
|
// otherwise we won't be able to scroll to it
|
|
bool cursorReached = false;
|
|
wxLayoutLine *line = m_FirstLine;
|
|
while(line)
|
|
{
|
|
if(! wasDirty)
|
|
ApplyStyle(line->GetStyleInfo(), dc);
|
|
if(
|
|
// if any previous line was dirty, we need to layout all
|
|
// following lines:
|
|
wasDirty
|
|
// go on until we find the cursorline
|
|
|| ! cursorReached
|
|
// layout dirty lines:
|
|
|| line->IsDirty()
|
|
// always layout the cursor line toupdate the cursor
|
|
// position and size:
|
|
|| line == m_CursorLine
|
|
// or if it's the line we are asked to look for:
|
|
|| (cpos && line->GetLineNumber() == cpos->y)
|
|
// layout at least the desired region:
|
|
|| (bottom == -1 )
|
|
|| (line->GetPosition().y <= bottom)
|
|
)
|
|
{
|
|
if(line->IsDirty())
|
|
wasDirty = true;
|
|
|
|
// The following Layout() calls will update our
|
|
// m_CurrentStyleInfo if needed.
|
|
if(line == m_CursorLine)
|
|
{
|
|
line->Layout(dc, this,
|
|
(wxPoint *)&m_CursorScreenPos,
|
|
(wxPoint *)&m_CursorSize,
|
|
&m_CursorStyleInfo,
|
|
m_CursorPos.x);
|
|
// we cannot layout the line twice, so copy the coords:
|
|
if(cpos && line ->GetLineNumber() == cpos->y)
|
|
{
|
|
*cpos = m_CursorScreenPos;
|
|
if ( csize )
|
|
*csize = m_CursorSize;
|
|
}
|
|
|
|
cursorReached = true;
|
|
}
|
|
else
|
|
{
|
|
if(cpos && line->GetLineNumber() == cpos->y)
|
|
{
|
|
line->Layout(dc, this,
|
|
cpos,
|
|
csize, NULL, cpos->x);
|
|
cursorReached = true;
|
|
}
|
|
else
|
|
line->Layout(dc, this);
|
|
}
|
|
}
|
|
|
|
line = line->GetNextLine();
|
|
}
|
|
|
|
#ifndef WXLAYOUT_USE_CARET
|
|
// can only be 0 if we are on the first line and have no next line
|
|
wxASSERT(m_CursorSize.x != 0 || (m_CursorLine &&
|
|
m_CursorLine->GetNextLine() == NULL &&
|
|
m_CursorLine == m_FirstLine));
|
|
#endif // WXLAYOUT_USE_CARET
|
|
|
|
AddCursorPosToUpdateRect();
|
|
}
|
|
|
|
wxPoint
|
|
wxLayoutList::GetScreenPos(wxDC &dc, const wxPoint &cpos, wxPoint *csize)
|
|
{
|
|
wxPoint pos = cpos;
|
|
Layout(dc, -1, false, &pos, csize);
|
|
return pos;
|
|
}
|
|
|
|
void
|
|
wxLayoutList::Draw(wxDC &dc,
|
|
wxPoint const &offset,
|
|
CoordType top,
|
|
CoordType bottom,
|
|
bool clipStrictly)
|
|
{
|
|
wxLayoutLine *line = m_FirstLine;
|
|
|
|
if ( m_Selection.m_discarded )
|
|
{
|
|
// calculate them if we don't have them already
|
|
if ( !m_Selection.HasValidScreenCoords() )
|
|
{
|
|
m_Selection.m_ScreenA = GetScreenPos(dc, m_Selection.m_CursorA);
|
|
m_Selection.m_ScreenB = GetScreenPos(dc, m_Selection.m_CursorB);
|
|
}
|
|
|
|
// invalidate the area which was previousle selected - and which is not
|
|
// selected any more
|
|
SetUpdateRect(m_Selection.m_ScreenA);
|
|
SetUpdateRect(m_Selection.m_ScreenB);
|
|
|
|
m_Selection.m_discarded = false;
|
|
}
|
|
|
|
/* This call to Layout() will re-calculate and update all lines
|
|
marked as dirty.
|
|
*/
|
|
Layout(dc, bottom);
|
|
|
|
ApplyStyle(m_DefaultStyleInfo, dc);
|
|
wxBrush brush(m_CurrentStyleInfo.m_bg, wxSOLID);
|
|
dc.SetBrush(brush);
|
|
dc.SetBackgroundMode(wxTRANSPARENT);
|
|
|
|
while(line)
|
|
{
|
|
// only draw if between top and bottom:
|
|
if((top == -1 ||
|
|
line->GetPosition().y + line->GetHeight() > top))
|
|
{
|
|
ApplyStyle(line->GetStyleInfo(), dc);
|
|
// little condition to speed up redrawing:
|
|
if( bottom != -1
|
|
&& line->GetPosition().y
|
|
+(clipStrictly ? line->GetHeight() : 0) >= bottom)
|
|
break;
|
|
|
|
line->Draw(dc, this, offset);
|
|
}
|
|
|
|
line = line->GetNextLine();
|
|
}
|
|
|
|
InvalidateUpdateRect();
|
|
|
|
WXLO_DEBUG((wxT("Selection is %s : %d,%d/%d,%d"),
|
|
m_Selection.m_valid ? wxT("valid") : wxT("invalid"),
|
|
m_Selection.m_CursorA.x, m_Selection.m_CursorA.y,
|
|
m_Selection.m_CursorB.x, m_Selection.m_CursorB.y));
|
|
}
|
|
|
|
wxLayoutObject *
|
|
wxLayoutList::FindObjectScreen(wxDC &dc, wxPoint const pos,
|
|
wxPoint *cursorPos, bool *found)
|
|
{
|
|
wxLayoutObjectList::iterator nulled(NULL);
|
|
// First, find the right line:
|
|
wxLayoutLine
|
|
*line = m_FirstLine,
|
|
*lastline = m_FirstLine;
|
|
wxPoint p;
|
|
|
|
ApplyStyle(m_DefaultStyleInfo, dc);
|
|
while(line)
|
|
{
|
|
p = line->GetPosition();
|
|
if(p.y <= pos.y && p.y+line->GetHeight() >= pos.y)
|
|
break;
|
|
lastline = line;
|
|
line = line->GetNextLine();
|
|
}
|
|
|
|
bool didFind = line != NULL;
|
|
|
|
if ( !line )
|
|
{
|
|
// use the last line:
|
|
line = lastline;
|
|
}
|
|
|
|
if ( cursorPos )
|
|
cursorPos->y = line->GetLineNumber();
|
|
|
|
bool foundinline = true;
|
|
long cx = 0;
|
|
|
|
// Now, find the object in the line:
|
|
wxLOiterator i;
|
|
|
|
if (cursorPos)
|
|
{
|
|
i = line->FindObjectScreen(dc, this,
|
|
pos.x,
|
|
&cx,
|
|
&foundinline);
|
|
cursorPos->x = cx;
|
|
}
|
|
else
|
|
i = line->FindObjectScreen(dc, this,
|
|
pos.x,
|
|
NULL,
|
|
&foundinline);
|
|
|
|
if ( found )
|
|
*found = didFind && foundinline;
|
|
|
|
return (i == nulled) ? NULL : *i;
|
|
|
|
}
|
|
|
|
wxPoint
|
|
wxLayoutList::GetSize() const
|
|
{
|
|
wxLayoutLine
|
|
*line = m_FirstLine,
|
|
*last = line;
|
|
if(! line)
|
|
return wxPoint(0,0);
|
|
|
|
wxPoint maxPoint(0,0);
|
|
|
|
// find last line:
|
|
while(line)
|
|
{
|
|
if(line->GetWidth() > maxPoint.x)
|
|
maxPoint.x = line->GetWidth();
|
|
last = line;
|
|
line = line->GetNextLine();
|
|
}
|
|
|
|
maxPoint.y = last->GetPosition().y + last->GetHeight();
|
|
|
|
// if the line was just added, its height would be 0 and we can't call
|
|
// Layout() from here because we don't have a dc and we might be not drawing
|
|
// at all, besides... So take the cursor height by default (taking 0 is bad
|
|
// because then the scrollbars won't be resized and the new line won't be
|
|
// shown at all)
|
|
if ( last->IsDirty() )
|
|
{
|
|
if ( last->GetHeight() == 0 )
|
|
maxPoint.y += m_CursorSize.y;
|
|
if ( last->GetWidth() == 0 && maxPoint.x < m_CursorSize.x )
|
|
maxPoint.x = m_CursorSize.x;
|
|
}
|
|
|
|
return maxPoint;
|
|
}
|
|
|
|
|
|
void
|
|
wxLayoutList::DrawCursor(wxDC &
|
|
#ifdef WXLAYOUT_USE_CARET
|
|
WXUNUSED(dc)
|
|
#else
|
|
dc
|
|
#endif
|
|
, bool
|
|
#ifdef WXLAYOUT_USE_CARET
|
|
WXUNUSED(active)
|
|
#else
|
|
active
|
|
#endif
|
|
, wxPoint const &translate)
|
|
{
|
|
if ( m_movedCursor )
|
|
m_movedCursor = false;
|
|
|
|
wxPoint coords(m_CursorScreenPos);
|
|
coords += translate;
|
|
|
|
#ifdef WXLAYOUT_DEBUG
|
|
WXLO_DEBUG((wxT("Drawing cursor (%ld,%ld) at %ld,%ld, size %ld,%ld, line: %ld, len %ld"),
|
|
(long)m_CursorPos.x, (long)m_CursorPos.y,
|
|
(long)coords.x, (long)coords.y,
|
|
(long)m_CursorSize.x, (long)m_CursorSize.y,
|
|
(long)m_CursorLine->GetLineNumber(),
|
|
(long)m_CursorLine->GetLength()));
|
|
|
|
wxLogStatus(wxT("Cursor is at (%d, %d)"), m_CursorPos.x, m_CursorPos.y);
|
|
#endif
|
|
|
|
#ifdef WXLAYOUT_USE_CARET
|
|
m_caret->Move(coords);
|
|
#else // !WXLAYOUT_USE_CARET
|
|
|
|
wxASSERT(m_CursorSize.x >= WXLO_MINIMUM_CURSOR_WIDTH);
|
|
dc.SetBrush(*wxWHITE_BRUSH);
|
|
//FIXME: wxGTK XOR is borken at the moment!!!dc.SetLogicalFunction(wxXOR);
|
|
dc.SetPen(wxPen(*wxBLACK,1,wxSOLID));
|
|
if(active)
|
|
{
|
|
dc.SetLogicalFunction(wxXOR);
|
|
dc.DrawRectangle(coords.x, coords.y,
|
|
m_CursorSize.x, m_CursorSize.y);
|
|
SetUpdateRect(coords.x, coords.y);
|
|
SetUpdateRect(coords.x+m_CursorSize.x,
|
|
coords.y+m_CursorSize.y);
|
|
}
|
|
else
|
|
{
|
|
dc.SetLogicalFunction(wxCOPY);
|
|
dc.DrawLine(coords.x, coords.y+m_CursorSize.y-1,
|
|
coords.x, coords.y);
|
|
SetUpdateRect(coords.x, coords.y+m_CursorSize.y-1);
|
|
SetUpdateRect(coords.x, coords.y);
|
|
}
|
|
|
|
dc.SetLogicalFunction(wxCOPY);
|
|
//dc.SetBrush(wxNullBrush);
|
|
#endif // WXLAYOUT_USE_CARET/!WXLAYOUT_USE_CARET
|
|
}
|
|
|
|
void
|
|
wxLayoutList::SetUpdateRect(CoordType x, CoordType y)
|
|
{
|
|
if(m_UpdateRectValid)
|
|
{
|
|
GrowRect(m_UpdateRect, x, y);
|
|
}
|
|
else
|
|
{
|
|
m_UpdateRect.x = x;
|
|
m_UpdateRect.y = y;
|
|
m_UpdateRect.width = 4; // large enough to avoid surprises from
|
|
m_UpdateRect.height = 4;// wxGTK :-)
|
|
m_UpdateRectValid = true;
|
|
}
|
|
}
|
|
|
|
void
|
|
wxLayoutList::StartSelection(const wxPoint& cposOrig, const wxPoint& spos)
|
|
{
|
|
wxPoint cpos(cposOrig);
|
|
if ( cpos.x == -1 )
|
|
cpos = m_CursorPos;
|
|
|
|
WXLO_DEBUG((wxT("Starting selection at %d/%d"), cpos.x, cpos.y));
|
|
|
|
m_Selection.m_CursorA = cpos;
|
|
m_Selection.m_CursorB = cpos;
|
|
m_Selection.m_ScreenA = spos;
|
|
m_Selection.m_ScreenB = spos;
|
|
m_Selection.m_selecting = true;
|
|
m_Selection.m_valid = false;
|
|
}
|
|
|
|
void
|
|
wxLayoutList::ContinueSelection(const wxPoint& cposOrig, const wxPoint& spos)
|
|
{
|
|
wxPoint cpos(cposOrig);
|
|
if(cpos.x == -1)
|
|
cpos = m_CursorPos;
|
|
|
|
wxASSERT(m_Selection.m_selecting == true);
|
|
wxASSERT(m_Selection.m_valid == false);
|
|
WXLO_DEBUG((wxT("Continuing selection at %d/%d"), cpos.x, cpos.y));
|
|
|
|
m_Selection.m_ScreenB = spos;
|
|
m_Selection.m_CursorB = cpos;
|
|
}
|
|
|
|
void
|
|
wxLayoutList::EndSelection(const wxPoint& cposOrig, const wxPoint& spos)
|
|
{
|
|
wxPoint cpos(cposOrig);
|
|
|
|
if(cpos.x == -1) cpos = m_CursorPos;
|
|
|
|
ContinueSelection(cpos, spos);
|
|
|
|
WXLO_DEBUG((wxT("Ending selection at %d/%d"), cpos.x, cpos.y));
|
|
|
|
// we always want m_CursorA <= m_CursorB!
|
|
if( m_Selection.m_CursorA > m_Selection.m_CursorB )
|
|
{
|
|
// exchange the start/end points
|
|
wxPoint help = m_Selection.m_CursorB;
|
|
m_Selection.m_CursorB = m_Selection.m_CursorA;
|
|
m_Selection.m_CursorA = help;
|
|
|
|
help = m_Selection.m_ScreenB;
|
|
m_Selection.m_ScreenB = m_Selection.m_ScreenA;
|
|
m_Selection.m_ScreenA = help;
|
|
}
|
|
|
|
m_Selection.m_selecting = false;
|
|
m_Selection.m_valid = true;
|
|
/// In case we just clicked somewhere, the selection will have zero
|
|
/// size, so we discard it immediately.
|
|
if(m_Selection.m_CursorA == m_Selection.m_CursorB)
|
|
{
|
|
DiscardSelection();
|
|
}
|
|
}
|
|
|
|
void
|
|
wxLayoutList::DiscardSelection()
|
|
{
|
|
if ( !HasSelection() )
|
|
return;
|
|
|
|
m_Selection.m_valid =
|
|
m_Selection.m_selecting = false;
|
|
m_Selection.m_discarded = true;
|
|
}
|
|
|
|
bool
|
|
wxLayoutList::IsSelecting() const
|
|
{
|
|
return m_Selection.m_selecting;
|
|
}
|
|
|
|
bool
|
|
wxLayoutList::IsSelected(const wxPoint &cursor) const
|
|
{
|
|
if ( !HasSelection() )
|
|
return false;
|
|
|
|
return (
|
|
(m_Selection.m_CursorA <= cursor
|
|
&& cursor <= m_Selection.m_CursorB)
|
|
|| (m_Selection.m_CursorB <= cursor
|
|
&& cursor <= m_Selection.m_CursorA)
|
|
);
|
|
}
|
|
|
|
|
|
/** Tests whether this layout line is selected and needs
|
|
highlighting.
|
|
@param line to test for
|
|
@return 0 = not selected, 1 = fully selected, -1 = partially
|
|
selected
|
|
*/
|
|
int
|
|
wxLayoutList::IsSelected(const wxLayoutLine *line, CoordType *from,
|
|
CoordType *to)
|
|
{
|
|
wxASSERT(line); wxASSERT(to); wxASSERT(from);
|
|
|
|
if(! m_Selection.m_valid && ! m_Selection.m_selecting)
|
|
return 0;
|
|
|
|
CoordType y = line->GetLineNumber();
|
|
if ( (m_Selection.m_CursorA.y < y && m_Selection.m_CursorB.y > y)
|
|
|| (m_Selection.m_CursorB.y < y && m_Selection.m_CursorA.y > y) )
|
|
{
|
|
return 1;
|
|
}
|
|
else if (m_Selection.m_CursorA.y == y)
|
|
{
|
|
*from = m_Selection.m_CursorA.x;
|
|
if(m_Selection.m_CursorB.y == y)
|
|
{
|
|
*to = m_Selection.m_CursorB.x;
|
|
}
|
|
else
|
|
{
|
|
if(m_Selection.m_CursorB > m_Selection.m_CursorA)
|
|
*to = line->GetLength();
|
|
else
|
|
*to = 0;
|
|
}
|
|
|
|
if(*to < *from)
|
|
{
|
|
CoordType help = *to;
|
|
*to = *from;
|
|
*from = help;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
else if (m_Selection.m_CursorB.y == y)
|
|
{
|
|
*to = m_Selection.m_CursorB.x;
|
|
if (m_Selection.m_CursorA.y == y)
|
|
{
|
|
*from = m_Selection.m_CursorA.x;
|
|
}
|
|
else
|
|
{
|
|
if(m_Selection.m_CursorB > m_Selection.m_CursorA)
|
|
*from = 0;
|
|
else
|
|
*from = line->GetLength();
|
|
}
|
|
|
|
if(*to < *from)
|
|
{
|
|
CoordType help = *to;
|
|
*to = *from;
|
|
*from = help;
|
|
}
|
|
return -1;
|
|
}
|
|
else
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
void
|
|
wxLayoutList::DeleteSelection()
|
|
{
|
|
if (! m_Selection.m_valid)
|
|
return;
|
|
|
|
m_Selection.m_valid = false;
|
|
|
|
// Only delete part of the current line?
|
|
if (m_Selection.m_CursorA.y == m_Selection.m_CursorB.y)
|
|
{
|
|
MoveCursorTo(m_Selection.m_CursorA);
|
|
Delete(m_Selection.m_CursorB.x - m_Selection.m_CursorA.x);
|
|
return;
|
|
}
|
|
|
|
// We now know that the two lines are different:
|
|
|
|
wxLayoutLine
|
|
* firstLine = GetLine(m_Selection.m_CursorA.y),
|
|
* lastLine = GetLine(m_Selection.m_CursorB.y);
|
|
|
|
// be a bit paranoid:
|
|
if(! firstLine || ! lastLine)
|
|
return;
|
|
|
|
// First, delete what's left of this line:
|
|
MoveCursorTo(m_Selection.m_CursorA);
|
|
DeleteToEndOfLine();
|
|
|
|
wxLayoutLine *prevLine = firstLine->GetPreviousLine(),
|
|
*nextLine = firstLine->GetNextLine();
|
|
|
|
while(nextLine && nextLine != lastLine)
|
|
{
|
|
nextLine = nextLine->DeleteLine(false, this);
|
|
}
|
|
|
|
// Now nextLine = lastLine;
|
|
Delete(1); // This joins firstLine and nextLine
|
|
Delete(m_Selection.m_CursorB.x); // This deletes the first x positions
|
|
|
|
// Recalculate the line positions and numbers but notice that firstLine
|
|
// might not exist any more - it could be deleted by Delete(1) above
|
|
wxLayoutLine *firstLine2 = prevLine ? prevLine->GetNextLine() : m_FirstLine;
|
|
firstLine2->MarkDirty();
|
|
}
|
|
|
|
/// Starts highlighting the selection
|
|
void
|
|
wxLayoutList::StartHighlighting(wxDC &dc)
|
|
{
|
|
#if SHOW_SELECTIONS
|
|
dc.SetTextForeground(m_CurrentStyleInfo.m_bg);
|
|
dc.SetTextBackground(m_CurrentStyleInfo.m_fg);
|
|
dc.SetBackgroundMode(wxSOLID);
|
|
#endif
|
|
}
|
|
|
|
/// Ends highlighting the selection
|
|
void
|
|
wxLayoutList::EndHighlighting(wxDC &dc)
|
|
{
|
|
#if SHOW_SELECTIONS
|
|
dc.SetTextForeground(m_CurrentStyleInfo.m_fg);
|
|
dc.SetTextBackground(m_CurrentStyleInfo.m_bg);
|
|
dc.SetBackgroundMode(wxTRANSPARENT);
|
|
#endif
|
|
}
|
|
|
|
|
|
wxLayoutLine *
|
|
wxLayoutList::GetLine(CoordType index) const
|
|
{
|
|
wxASSERT_MSG( (0 <= index) && (index < (CoordType)m_numLines),
|
|
wxT("invalid index") );
|
|
|
|
wxLayoutLine *line;
|
|
CoordType n = index;
|
|
#ifdef DEBUG
|
|
CoordType lineNo = 0;
|
|
#endif
|
|
|
|
for ( line = m_FirstLine; line && n-- > 0; line = line->GetNextLine() )
|
|
{
|
|
#ifdef DEBUG
|
|
wxASSERT(line->GetLineNumber() == lineNo );
|
|
lineNo++;
|
|
#endif
|
|
}
|
|
|
|
if ( line )
|
|
{
|
|
// should be the right one
|
|
wxASSERT( line->GetLineNumber() == index );
|
|
}
|
|
|
|
return line;
|
|
}
|
|
|
|
|
|
wxLayoutList *
|
|
wxLayoutList::Copy(const wxPoint &from,
|
|
const wxPoint &to)
|
|
{
|
|
wxLayoutLine
|
|
* firstLine,
|
|
* lastLine;
|
|
|
|
for(firstLine = m_FirstLine;
|
|
firstLine && firstLine->GetLineNumber() < from.y;
|
|
firstLine=firstLine->GetNextLine())
|
|
;
|
|
|
|
if(!firstLine || firstLine->GetLineNumber() != from.y)
|
|
return NULL;
|
|
|
|
for(lastLine = m_FirstLine;
|
|
lastLine && lastLine->GetLineNumber() < to.y;
|
|
lastLine=lastLine->GetNextLine())
|
|
;
|
|
|
|
if(!lastLine || lastLine->GetLineNumber() != to.y)
|
|
return NULL;
|
|
|
|
if(to <= from)
|
|
{
|
|
wxLayoutLine *tmp = firstLine;
|
|
firstLine = lastLine;
|
|
lastLine = tmp;
|
|
}
|
|
|
|
wxLayoutList *llist = new wxLayoutList();
|
|
|
|
if(firstLine == lastLine)
|
|
{
|
|
firstLine->Copy(llist, from.x, to.x);
|
|
}
|
|
else
|
|
{
|
|
// Extract objects from first line
|
|
firstLine->Copy(llist, from.x);
|
|
llist->LineBreak();
|
|
// Extract all lines between
|
|
for ( wxLayoutLine *line = firstLine->GetNextLine();
|
|
line != lastLine;
|
|
line = line->GetNextLine() )
|
|
{
|
|
line->Copy(llist);
|
|
llist->LineBreak();
|
|
}
|
|
|
|
// Extract objects from last line
|
|
lastLine->Copy(llist, 0, to.x);
|
|
}
|
|
|
|
return llist;
|
|
}
|
|
|
|
wxLayoutList *
|
|
wxLayoutList::GetSelection(wxLayoutDataObject *wxlo, bool invalidate)
|
|
{
|
|
if(! m_Selection.m_valid)
|
|
{
|
|
if(m_Selection.m_selecting)
|
|
EndSelection();
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
if(invalidate) m_Selection.m_valid = false;
|
|
|
|
wxLayoutList *llist = Copy( m_Selection.m_CursorA,
|
|
m_Selection.m_CursorB );
|
|
|
|
if(llist && wxlo) // export as data object, too
|
|
{
|
|
wxString string;
|
|
|
|
wxLayoutExportObject *exp;
|
|
wxLayoutExportStatus status(llist);
|
|
while((exp = wxLayoutExport( &status, WXLO_EXPORT_AS_OBJECTS)) != NULL)
|
|
{
|
|
if(exp->type == WXLO_EXPORT_EMPTYLINE)
|
|
string << (int) WXLO_TYPE_LINEBREAK << '\n';
|
|
else
|
|
exp->content.object->Write(string);
|
|
delete exp;
|
|
}
|
|
|
|
wxlo->SetLayoutData(string);
|
|
}
|
|
|
|
return llist;
|
|
}
|
|
|
|
|
|
|
|
#define COPY_SI(what) if(si.what != -1) { m_CurrentStyleInfo.what = si.what; fontChanged = true; }
|
|
|
|
void
|
|
wxLayoutList::ApplyStyle(wxLayoutStyleInfo const &si, wxDC &dc)
|
|
{
|
|
bool fontChanged = false;
|
|
COPY_SI(family);
|
|
COPY_SI(size);
|
|
COPY_SI(style);
|
|
COPY_SI(weight);
|
|
COPY_SI(underline);
|
|
if(fontChanged)
|
|
dc.SetFont( m_FontCache.GetFont(m_CurrentStyleInfo) );
|
|
|
|
if(si.m_fg_valid)
|
|
{
|
|
m_CurrentStyleInfo.m_fg = si.m_fg;
|
|
m_CurrentStyleInfo.m_fg_valid = true;
|
|
dc.SetTextForeground(m_CurrentStyleInfo.m_fg);
|
|
}
|
|
|
|
if(si.m_bg_valid)
|
|
{
|
|
m_CurrentStyleInfo.m_bg = si.m_bg;
|
|
m_CurrentStyleInfo.m_bg_valid = true;
|
|
dc.SetTextBackground(m_CurrentStyleInfo.m_bg);
|
|
}
|
|
}
|
|
|
|
|
|
#ifdef WXLAYOUT_DEBUG
|
|
|
|
void
|
|
wxLayoutList::Debug()
|
|
{
|
|
WXLO_DEBUG((wxT("Cursor is in line %d, screen pos = (%d, %d)"),
|
|
(int)m_CursorLine->GetLineNumber(),
|
|
m_CursorScreenPos.x, m_CursorScreenPos.y));
|
|
|
|
wxLayoutLine *line;
|
|
for(line = m_FirstLine; line; line = line->GetNextLine())
|
|
{
|
|
line->Debug();
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
|
|
wxLayoutPrintout
|
|
|
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
|
|
wxLayoutPrintout::wxLayoutPrintout(wxLayoutList *llist,
|
|
wxString const & title)
|
|
:wxPrintout(title)
|
|
{
|
|
m_llist = llist;
|
|
m_title = title;
|
|
// remove any highlighting which could interfere with printing:
|
|
m_llist->StartSelection();
|
|
m_llist->EndSelection();
|
|
// force a full layout of the list:
|
|
m_llist->ForceTotalLayout();
|
|
// layout is called in ScaleDC() when we have a DC
|
|
}
|
|
|
|
float
|
|
wxLayoutPrintout::ScaleDC(wxDC *dc)
|
|
{
|
|
// The following bit is taken from the printing sample, let's see
|
|
// whether it works for us.
|
|
|
|
/* You might use THIS code to set the printer DC to ROUGHLY reflect
|
|
* the screen text size. This page also draws lines of actual length 5cm
|
|
* on the page.
|
|
*/
|
|
|
|
// Get the logical pixels per inch of screen and printer
|
|
int ppiScreenX, ppiScreenY;
|
|
GetPPIScreen(&ppiScreenX, &ppiScreenY);
|
|
int ppiPrinterX, ppiPrinterY;
|
|
GetPPIPrinter(&ppiPrinterX, &ppiPrinterY);
|
|
|
|
if(ppiScreenX == 0) // not yet set, need to guess
|
|
{
|
|
ppiScreenX = 100;
|
|
ppiScreenY = 100;
|
|
}
|
|
wxUnusedVar(ppiScreenY);
|
|
|
|
if(ppiPrinterX == 0) // not yet set, need to guess
|
|
{
|
|
ppiPrinterX = 72;
|
|
ppiPrinterY = 72;
|
|
}
|
|
wxUnusedVar(ppiPrinterY);
|
|
|
|
// This scales the DC so that the printout roughly represents the
|
|
// the screen scaling. The text point size _should_ be the right size
|
|
// but in fact is too small for some reason. This is a detail that will
|
|
// need to be addressed at some point but can be fudged for the
|
|
// moment.
|
|
float scale = (float)((float)ppiPrinterX/(float)ppiScreenX);
|
|
|
|
// Now we have to check in case our real page size is reduced
|
|
// (e.g. because we're drawing to a print preview memory DC)
|
|
int pageWidth, pageHeight;
|
|
int w, h;
|
|
dc->GetSize(&w, &h);
|
|
GetPageSizePixels(&pageWidth, &pageHeight);
|
|
wxUnusedVar(pageHeight);
|
|
if(pageWidth != 0) // doesn't work always
|
|
{
|
|
// If printer pageWidth == current DC width, then this doesn't
|
|
// change. But w might be the preview bitmap width, so scale down.
|
|
scale = scale * (float)(w/(float)pageWidth);
|
|
}
|
|
|
|
dc->SetUserScale(scale, scale);
|
|
return scale;
|
|
}
|
|
|
|
bool wxLayoutPrintout::OnPrintPage(int page)
|
|
{
|
|
wxDC *dc = GetDC();
|
|
|
|
ScaleDC(dc);
|
|
|
|
if (dc)
|
|
{
|
|
int top, bottom;
|
|
top = (page - 1)*m_PrintoutHeight;
|
|
bottom = top + m_PrintoutHeight;
|
|
|
|
WXLO_DEBUG((wxT("OnPrintPage(%d) printing from %d to %d"), page, top,
|
|
bottom));
|
|
|
|
// SetDeviceOrigin() doesn't work here, so we need to manually
|
|
// translate all coordinates.
|
|
wxPoint translate(m_Offset.x,m_Offset.y-top);
|
|
m_llist->Draw(*dc, translate, top, bottom, true /* clip strictly */);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void wxLayoutPrintout::GetPageInfo(int *minPage, int *maxPage, int *selPageFrom, int *selPageTo)
|
|
{
|
|
/* We allocate a temporary wxDC for printing, so that we can
|
|
determine the correct paper size and scaling. We don't actually
|
|
print anything on it. */
|
|
#if defined(__WXMSW__) || defined(__WXMAC__)
|
|
wxPrinterDC *psdc = new wxPrinterDC(wxEmptyString,wxEmptyString,_T(WXLLIST_TEMPFILE),false);
|
|
#else
|
|
wxPrintData data;
|
|
data.SetFilename(WXLLIST_TEMPFILE);
|
|
wxPostScriptDC *psdc = new wxPostScriptDC(data);
|
|
#endif
|
|
|
|
psdc->StartDoc(m_title);
|
|
// before we draw anything, me must make sure the list is properly
|
|
// laid out
|
|
m_llist->Layout(*psdc);
|
|
|
|
float scale = ScaleDC(psdc);
|
|
|
|
psdc->GetSize(&m_PageWidth, &m_PageHeight);
|
|
|
|
// This sets a left/top origin of 15% and 5%:
|
|
m_Offset = wxPoint((15*m_PageWidth)/100, (5*m_PageHeight)/100);
|
|
|
|
// This is the length of the printable area.
|
|
m_PrintoutHeight = m_PageHeight - 2*m_Offset.y;
|
|
m_PrintoutHeight = (int)( m_PrintoutHeight / scale); // we want to use the real paper height
|
|
|
|
m_NumOfPages = 1 +
|
|
(int)( m_llist->GetSize().y / (float)(m_PrintoutHeight));
|
|
|
|
*minPage = 1;
|
|
*maxPage = m_NumOfPages;
|
|
|
|
*selPageFrom = 1;
|
|
*selPageTo = m_NumOfPages;
|
|
psdc->EndDoc();
|
|
delete psdc;
|
|
wxRemoveFile(_T(WXLLIST_TEMPFILE));
|
|
}
|
|
|
|
bool wxLayoutPrintout::HasPage(int pageNum)
|
|
{
|
|
return pageNum <= m_NumOfPages;
|
|
}
|
|
|
|
/*
|
|
Stupid wxWidgets doesn't draw proper ellipses, so we comment this
|
|
out. It's a waste of paper anyway.
|
|
*/
|
|
#if 0
|
|
void
|
|
wxLayoutPrintout::DrawHeader(wxDC &dc,
|
|
wxPoint topleft, wxPoint bottomright,
|
|
int pageno)
|
|
{
|
|
// make backups of all essential parameters
|
|
const wxBrush& brush = dc.GetBrush();
|
|
const wxPen& pen = dc.GetPen();
|
|
const wxFont& font = dc.GetFont();
|
|
|
|
dc.SetBrush(*wxWHITE_BRUSH);
|
|
dc.SetPen(wxPen(*wxBLACK,0,wxSOLID));
|
|
dc.DrawRoundedRectangle(topleft.x,
|
|
topleft.y,bottomright.x-topleft.x,
|
|
bottomright.y-topleft.y);
|
|
dc.SetBrush(*wxBLACK_BRUSH);
|
|
wxFont myfont = wxFont((WXLO_DEFAULTFONTSIZE*12)/10,
|
|
wxSWISS,wxNORMAL,wxBOLD,false,"Helvetica");
|
|
dc.SetFont(myfont);
|
|
|
|
wxString page;
|
|
page = "9999/9999 "; // many pages...
|
|
long w,h;
|
|
dc.GetTextExtent(page,&w,&h);
|
|
page.Printf("%d/%d", pageno, (int) m_NumOfPages);
|
|
dc.DrawText(page,bottomright.x-w,topleft.y+h/2);
|
|
dc.GetTextExtent("XXXX", &w,&h);
|
|
dc.DrawText(m_title, topleft.x+w,topleft.y+h/2);
|
|
|
|
// restore settings
|
|
dc.SetPen(pen);
|
|
dc.SetBrush(brush);
|
|
dc.SetFont(font);
|
|
}
|
|
#endif
|
|
|
|
|
|
wxFont &
|
|
wxFontCache::GetFont(int family, int size, int style, int weight,
|
|
bool underline)
|
|
{
|
|
for(wxFCEList::iterator i = m_FontList.begin();
|
|
i != m_FontList.end(); i++)
|
|
if( (**i).Matches(family, size, style, weight, underline) )
|
|
return (**i).GetFont();
|
|
// not found:
|
|
wxFontCacheEntry *fce = new wxFontCacheEntry(family, size, style,
|
|
weight, underline);
|
|
m_FontList.push_back(fce);
|
|
return fce->GetFont();
|
|
}
|
|
|