BUGS
=====================================================================
-- Delete():
- 1 - occasionally delete deletes too much, maybe when at begin of
- line?
- 2 - when in an empty line, Delete() doesn't always merge lines
- 3 - line numbers aren't updated properly, may be related to 2.
- 4 - deleting lines leaves later parts of the list unaffected
- --> just redrawing at least the next two lines doesn't seem
- enough, strange, don't positions change?
- dmalloc shows duplicate deletion after merging two lines and
deleting the second half
TODO
=====================================================================
-- Add word wrap to wxlwindow/wxllist.
-- Cursor to mouseclick
-- Focus feedback for cursor
- Selections
-
- More optimisations
m_lwin = new wxLayoutWindow(this);
m_lwin->SetMouseTracking(true);
m_lwin->SetEditable(true);
+ m_lwin->SetWrapMargin(40);
m_lwin->Clear(wxROMAN,16,wxNORMAL,wxNORMAL, false);
m_lwin->SetFocus();
};
llist->Insert("italics ");
llist->SetFont(-1,-1,wxNORMAL);
llist->LineBreak();
-
+
llist->Insert("and ");
llist->SetFont(-1,-1,wxSLANT);
llist->Insert("slanted");
llist->SetFont(-1,-1,wxNORMAL);
llist->Insert(" text.");
llist->LineBreak();
-
llist->Insert("and ");
llist->SetFont(-1,-1,-1,-1,-1,"blue");
llist->Insert("blue");
break;
case ID_NOWRAP:
case ID_WRAP:
-//// m_lwin->GetLayoutList()->SetWrapMargin(
-//// event.GetId() == ID_NOWRAP ? -1 : 40);
+ m_lwin->SetWrapMargin(event.GetId() == ID_NOWRAP ? 0 : 40);
break;
case ID_ADD_SAMPLE:
AddSampleText(m_lwin->GetLayoutList());
dc.DrawText(m_Text, coords.x, coords.y-m_Top);
}
+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 substract 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)
{
wxASSERT(m_font);
dc.SetFont(*m_font);
- if(m_ColourFG) dc.SetTextForeground(*m_ColourFG);
- if(m_ColourBG) dc.SetTextBackground(*m_ColourBG);
+ if(m_ColourFG)
+ dc.SetTextForeground(*m_ColourFG);
+ if(m_ColourBG)
+ dc.SetTextBackground(*m_ColourBG);
}
void
wxLayoutLine::RecalculatePosition(void)
{
if(m_Previous)
- m_Position = m_Previous->RecalculatePosition() +
+ m_Position = m_Previous->GetPosition() +
wxPoint(0,m_Previous->GetHeight());
else
m_Position = wxPoint(0,0);
void
wxLayoutLine::RecalculatePositions(int recurse)
{
- wxPoint pos = RecalculatePosition();
-
- if(pos != m_Position)
- {
- m_Position = pos;
- if(m_Next) m_Next->RecalculatePositions(--recurse);
- }
- else
+ wxASSERT(recurse >= 0);
+ wxPoint pos = m_Position;
+ CoordType height = m_Height;
+
+// WXLO_TRACE("RecalculatePositions()");
+ RecalculatePosition();
+ if(m_Next)
{
- m_Position = pos;
- if(recurse && m_Next)
- m_Next->RecalculatePositions(--recurse);
+ if(recurse > 0)
+ {
+ if(m_Next) m_Next->RecalculatePositions(--recurse);
+ }
+ else if(pos != m_Position || m_Height != height)
+ if(m_Next) m_Next->RecalculatePositions();
}
-
}
wxLayoutObjectList::iterator
wxLayoutObjectList::iterator i;
CoordType x = 0, len;
+
for(i = m_ObjectList.begin(); i != NULLIT; i++)
{
len = (**i).GetLength();
return NULLIT;
}
+wxLayoutObjectList::iterator
+wxLayoutLine::FindObjectScreen(wxDC &dc, CoordType xpos, CoordType *cxpos) const
+{
+ wxASSERT(cxpos);
+ wxASSERT(xpos);
+ wxLayoutObjectList::iterator i;
+ CoordType x = 0, cx = 0, width;
+
+ for(i = m_ObjectList.begin(); i != NULLIT; i++)
+ {
+ (**i).Layout(dc);
+ width = (**i).GetWidth();
+ if( x <= xpos && xpos <= x + width )
+ {
+ *cxpos = cx + (**i).GetOffsetScreen(dc, xpos-x);
+ wxLogDebug("wxLayoutLine::FindObjectScreen: cursor xpos = %ld", *cxpos);
+ return i;
+ }
+ x += (**i).GetWidth();
+ cx += (**i).GetLength();
+ }
+ // behind last object:
+ *cxpos = cx;
+ return m_ObjectList.tail();
+}
+
bool
wxLayoutLine::Insert(CoordType xpos, wxLayoutObject *obj)
{
if(offset == len )
{
if( i == m_ObjectList.tail()) // last object?
- {
m_ObjectList.push_back(obj);
- m_Length += obj->GetLength();
- }
else
{ // insert after current object
i++;
m_ObjectList.insert(i,obj);
- m_Length += obj->GetLength();
}
+ m_Length += obj->GetLength();
return true;
}
/* Otherwise we need to split the current object.
CoordType
wxLayoutLine::Delete(CoordType xpos, CoordType npos)
{
- CoordType offset;
+ CoordType offset, len;
wxASSERT(xpos >= 0);
wxASSERT(npos >= 0);
wxLOiterator i = FindObject(xpos, &offset);
while(npos > 0)
{
- if(i == NULLIT) return false; // FIXME
+ if(i == NULLIT) 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:
- m_Length -= (**i).GetLength();
- npos -= m_Length;
+ len = (**i).GetLength();
+ m_Length -= len;
+ npos -= len;
m_ObjectList.erase(i);
}
else
}
void
-wxLayoutLine::Layout(wxDC &dc, wxPoint *cursorPos, wxPoint
- *cursorSize,
+wxLayoutLine::Layout(wxDC &dc, wxPoint *cursorPos,
+ wxPoint *cursorSize,
int cx)
{
wxLayoutObjectList::iterator i;
}
}
-wxLayoutObject *
-wxLayoutLine::FindObject(CoordType xpos)
-{
- wxASSERT(xpos >= 0);
- if(xpos > GetWidth()) return NULL;
-
- CoordType x = 0;
- for(wxLOiterator i = m_ObjectList.begin(); i != NULLIT; i++)
- {
- x += (**i).GetWidth();
- if(x > xpos) // we just crossed it
- return *i;
- }
- return NULL;
-}
wxLayoutLine *
wxLayoutLine::Break(CoordType xpos)
// current text object gets set to left half
tobj->GetText() = left; // set new text
newLine->Append(new wxLayoutObjectText(right));
- m_Length -= m_Length - offset;
+ m_Length -= right.Length();
i++; // don't move this object to the new list
}
else
RecalculatePositions(1);
}
+CoordType
+wxLayoutLine::GetWrapPosition(CoordType column)
+{
+ CoordType offset;
+ wxLOiterator i = FindObject(column, &offset);
+ if(i == NULLIT) 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()[offset]))
+ return column;
+ else
+ {
+ offset--;
+ column--;
+ }
+ }while(offset != -1);
+ }
+ else
+ column -= (**i).GetLength();
+ // This is both "else" and what has to be done after checking
+ // all positions of the text object:
+ i--;
+ offset = (**i).GetLength();
+ }while(i != NULLIT);
+ return -1;
+}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
return true;
}
+bool
+wxLayoutList::WrapLine(CoordType column)
+{
+ if(m_CursorPos.x <= column || column < 1)
+ return false; // do nothing yet
+ else
+ {
+ CoordType xpos = m_CursorLine->GetWrapPosition(column);
+ if(xpos == -1)
+ return false; // cannot break line
+ //else:
+ CoordType newpos = m_CursorPos.x - xpos - 1;
+ m_CursorPos.x = xpos;
+ LineBreak();
+ Delete(1); // delete the space
+ m_CursorPos.x = newpos;
+ return true;
+ }
+}
+
bool
wxLayoutList::Delete(CoordType npos)
{
// First, check if line is empty:
if(m_CursorLine->GetLength() == 0)
{ // in this case, updating could probably be optimised
- m_CursorLine = m_CursorLine->DeleteLine(true);
+#ifdef WXLO_DEBUG
+ wxASSERT(DeleteLines(1) == 0);
+#else
+ DeleteLines(1);
+#endif
+
left--;
}
else
return n;
}
+void
+wxLayoutList::Recalculate(wxDC &dc, CoordType bottom) const
+{
+ wxLayoutLine *line = m_FirstLine;
+
+ // first, make sure everything is calculated - this might not be
+ // needed, optimise it later
+ m_DefaultSetting->Layout(dc);
+ while(line)
+ {
+ line->RecalculatePosition(); // 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();
+ }
+}
+
void
wxLayoutList::Layout(wxDC &dc, CoordType bottom) const
{
}
wxLayoutObject *
-wxLayoutList::FindObject(wxPoint const pos)
+wxLayoutList::FindObjectScreen(wxDC &dc, wxPoint const pos, wxPoint *cursorPos)
{
// First, find the right line:
wxLayoutLine *line = m_FirstLine;
wxPoint p;
+ // we need to run a layout here to get font sizes right :-(
+ m_DefaultSetting->Layout(dc);
while(line)
{
p = line->GetPosition();
if(p.y <= pos.y && p.y+line->GetHeight() >= pos.y)
break;
+ line->Layout(dc);
line = line->GetNextLine();
}
- if(! line) return NULL; // not found
+ if(line == NULL) return NULL; // not found
+ if(cursorPos) cursorPos->y = line->GetLineNumber();
// Now, find the object in the line:
- return line->FindObject(pos.x);
+ wxLOiterator i = line->FindObjectScreen(dc, pos.x, & cursorPos->x);
+ return (i == NULLIT) ? NULL : *i;
}
(long)m_CursorLine->GetLength()));
#endif
- if(active)
- dc.SetBrush(*wxBLACK_BRUSH);
+ dc.SetBrush(*wxBLACK_BRUSH);
+ dc.SetLogicalFunction(wxXOR);
dc.SetPen(wxPen(*wxBLACK,1,wxSOLID));
dc.SetLogicalFunction(wxXOR);
- dc.DrawRectangle(coords.x, coords.y, m_CursorSize.x, m_CursorSize.y);
+ if(active)
+ dc.DrawRectangle(coords.x, coords.y, m_CursorSize.x,
+ m_CursorSize.y);
+ else
+ dc.DrawLine(coords.x, coords.y+m_CursorSize.y-1,
+ coords.x+m_CursorSize.x, coords.y+m_CursorSize.y-1);
dc.SetLogicalFunction(wxCOPY);
dc.SetBrush(wxNullBrush);
}
# define WXLAYOUT_DEBUG
#endif
+#ifdef WXLAYOUT_DEBUG
+# define WXLO_TRACE(x) wxLogDebug(x)
+#else
+# define WXLO_TRACE(x)
+#endif
+
+
+
#ifndef WXLO_DEFAULTFONTSIZE
# define WXLO_DEFAULTFONTSIZE 12
#endif
+
/// Types of currently supported layout objects.
enum wxLayoutObjectType
{
virtual CoordType GetWidth(void) const { return 0; }
/// returns the number of cursor positions occupied by this object
virtual CoordType GetLength(void) const { return 1; }
+ /** Returns the cursor offset relating to the screen x position
+ relative to begin of object.
+ @param dc the wxDC to use for calculations
+ @param xpos relative x position from head of object
+ @return cursor coordinate offset
+ */
+ virtual CoordType GetOffsetScreen(wxDC &dc, CoordType xpos) const { return 0; }
/// constructor
wxLayoutObject() { m_UserData = NULL; }
virtual wxPoint GetSize(CoordType * top, CoordType *bottom) const;
/// Return just the width of the object on the screen.
virtual CoordType GetWidth(void) const { return m_Width; }
+ /** Returns the cursor offset relating to the screen x position
+ relative to begin of object.
+ @param dc the wxDC to use for calculations
+ @param xpos relative x position from head of object
+ @return cursor coordinate offset
+ */
+ virtual CoordType GetOffsetScreen(wxDC &dc, CoordType xpos) const;
+
#ifdef WXLAYOUT_DEBUG
virtual void Debug(void);
@return true if a word was deleted
*/
bool DeleteWord(CoordType npos);
+
+ /** Finds a suitable position left to the given column to break the
+ line.
+ @param column we want to break the line to the left of this
+ @return column for breaking line or -1 if no suitable location found
+ */
+ CoordType GetWrapPosition(CoordType column);
/** Finds the object which covers the cursor position xpos in this
line.
wxLayoutObjectList::iterator FindObject(CoordType xpos, CoordType
*offset) const ;
+ /** Finds the object which covers the screen position xpos in this
+ line.
+ @param dc the wxDC to use for calculations
+ @param xpos the screen x coordinate
+ @param offset where to store the difference between xpos and
+ the object's head
+ @return iterator to the object or NULLIT
+ */
+ wxLayoutObjectList::iterator FindObjectScreen(wxDC &dc,
+ CoordType xpos,
+ CoordType *offset) const ;
+
/** Get the first object in the list. This is used by the wxlparser
functions to export the list.
@return iterator to the first object
int cx = 0);
/** This function finds an object belonging to a given cursor
position. It assumes that Layout() has been called before.
+ @param dc the wxDC to use for calculations
@param xpos screen x position
@return pointer to the object
*/
- wxLayoutObject * FindObject(CoordType xpos);
+ wxLayoutObject * FindObjectScreen(wxDC &dc, CoordType xpos);
//@}
/**@name List traversal */
the list or until the coordinates no longer changed.
*/
void RecalculatePositions(int recurse = 0);
+ /// Recalculates the position of this line on the canvas.
+ wxPoint RecalculatePosition(void);
private:
/// Destructor is private. Use DeleteLine() to remove it.
~wxLayoutLine();
*/
void SetHeight(CoordType height)
{ m_Height = height; RecalculatePositions(true); }
- /// Recalculates the position of this line on the canvas.
- wxPoint RecalculatePosition(void);
/** Moves the linenumbers one on, because a line has been inserted
or deleted.
bool Insert(wxLayoutObject *obj);
/// Inserts a linebreak at current cursor position.
bool LineBreak(void);
+ /** Wraps the current line. Searches to the left of the cursor to
+ break the line. Does nothing if the cursor position is before
+ the break position parameter.
+ @param column the break position for the line, maximum length
+ @return true if line got broken
+ */
+ bool WrapLine(CoordType column);
/** This function deletes npos cursor positions.
@param npos how many positions
@return true if everything got deleted
@param bottom optional y coordinate where to stop calculating
*/
void Layout(wxDC &dc, CoordType bottom = -1) const;
+
+ /** Calculates new sizes for everything in the list, like Layout()
+ but this is needed after the list got changed.
+ @param dc the wxDC to draw on
+ @param bottom optional y coordinate where to stop calculating
+ */
+ void Recalculate(wxDC &dc, CoordType bottom = -1) const;
+
/** Returns the size of the list in screen coordinates.
The return value only makes sense after the list has been
drawn.
bool active = true,
const wxPoint & translate = wxPoint(0,0));
- /** This function finds an object belonging to a given cursor
+ /** This function finds an object belonging to a given screen
position. It assumes that Layout() has been called before.
@param pos screen position
+ @param cursorPos if non NULL, store cursor position in there
@return pointer to the object
*/
- wxLayoutObject * FindObject(wxPoint const pos);
+ wxLayoutObject * FindObjectScreen(wxDC &dc,
+ wxPoint const pos,
+ wxPoint *cursorPos = NULL);
//@}
# include "wxlwindow.h"
#endif
+#include <ctype.h>
+
+
+#define WXLO_XOFFSET 4
+#define WXLO_YOFFSET 4
+
BEGIN_EVENT_TABLE(wxLayoutWindow,wxScrolledWindow)
EVT_PAINT (wxLayoutWindow::OnPaint)
EVT_CHAR (wxLayoutWindow::OnChar)
EVT_SET_FOCUS(wxLayoutWindow::OnSetFocus)
EVT_KILL_FOCUS(wxLayoutWindow::OnKillFocus)
END_EVENT_TABLE()
- /*
-
- EVT_MENU(WXLOWIN_MENU_LARGER, wxLayoutWindow::OnMenu)
- EVT_MENU(WXLOWIN_MENU_SMALLER, wxLayoutWindow::OnMenu)
- EVT_MENU(WXLOWIN_MENU_UNDERLINE_ON, wxLayoutWindow::OnMenu)
- EVT_MENU(WXLOWIN_MENU_UNDERLINE_OFF, wxLayoutWindow::OnMenu)
- EVT_MENU(WXLOWIN_MENU_BOLD_ON, wxLayoutWindow::OnMenu)
- EVT_MENU(WXLOWIN_MENU_BOLD_OFF, wxLayoutWindow::OnMenu)
- EVT_MENU(WXLOWIN_MENU_ITALICS_ON, wxLayoutWindow::OnMenu)
- EVT_MENU(WXLOWIN_MENU_ITALICS_OFF, wxLayoutWindow::OnMenu)
- EVT_MENU(WXLOWIN_MENU_ROMAN, wxLayoutWindow::OnMenu)
- EVT_MENU(WXLOWIN_MENU_TYPEWRITER, wxLayoutWindow::OnMenu)
- EVT_MENU(WXLOWIN_MENU_SANSSERIF, wxLayoutWindow::OnMenu)
- */
wxLayoutWindow::wxLayoutWindow(wxWindow *parent)
: wxScrolledWindow(parent, -1, wxDefaultPosition, wxDefaultSize,
m_bitmap = new wxBitmap(4,4);
m_bitmapSize = wxPoint(4,4);
m_llist = new wxLayoutList();
+ SetWrapMargin(0);
wxPoint max = m_llist->GetSize();
SetScrollbars(10, 20 /*lineHeight*/, max.x/10+1, max.y/20+1);
EnableScrolling(true,true);
m_maxx = max.x; m_maxy = max.y;
+ SetCursor(wxCURSOR_IBEAM);
SetDirty();
}
void
wxLayoutWindow::OnMouse(int eventId, wxMouseEvent& event)
{
- if(!m_doSendEvents) // nothing to do
- return;
-
wxPaintDC dc( this );
PrepareDC( dc );
SetFocus();
findPos.x = dc.DeviceToLogicalX(event.GetX());
findPos.y = dc.DeviceToLogicalY(event.GetY());
+ findPos.x -= WXLO_XOFFSET;
+ findPos.y -= WXLO_YOFFSET;
+
+ if(findPos.x < 0) findPos.x = 0;
+ if(findPos.y < 0) findPos.y = 0;
+
#ifdef WXLAYOUT_DEBUG
wxLogDebug("wxLayoutWindow::OnMouse: (%d, %d) -> (%d, %d)",
event.GetX(), event.GetY(), findPos.x, findPos.y);
#endif
m_ClickPosition = findPos;
- wxLayoutObject *obj = m_llist->FindObject(findPos);
+ wxPoint cursorPos;
+ wxLayoutObject *obj = m_llist->FindObjectScreen(dc, findPos, &cursorPos);
#ifdef WXLAYOUT_DEBUG
if(obj)
else
wxLogDebug("wxLayoutWindow::OnMouse: Found no object.");
#endif
-
+
+ // always move cursor to mouse click:
+ if(obj && eventId == WXLOWIN_MENU_LCLICK)
+ {
+ m_llist->MoveCursorTo(cursorPos);
+ DoPaint(false);
+ }
+ if(!m_doSendEvents) // nothing to do
+ return;
+
// only do the menu if activated, editable and not on a clickable object
if(eventId == WXLOWIN_MENU_RCLICK
&& IsEditable()
case 'k':
m_llist->DeleteToEndOfLine();
break;
+#ifdef WXLAYOUT_DEBUG
+ case WXK_F1:
+ m_llist->SetFont(-1,-1,-1,-1,true); // underlined
+ break;
+#endif
default:
;
}
if(m_llist->MoveCursorHorizontally(-1)) m_llist->Delete(1);
break;
case WXK_RETURN:
+ if(m_WrapMargin > 0)
+ m_llist->WrapLine(m_WrapMargin);
m_llist->LineBreak();
break;
default:
{
wxString tmp;
tmp += keyCode;
+ if(m_WrapMargin > 0 && isspace(keyCode))
+ m_llist->WrapLine(m_WrapMargin);
m_llist->Insert(tmp);
-//// m_llist->WrapLine();
}
break;
}
// Maybe we need to change the scrollbar sizes or positions,
// so layout the list and check:
- if(IsDirty() || scrollToCursor)
+ if(IsDirty())
m_llist->Layout(dc);
+ // this is needed even when only the cursor moved
+ m_llist->Layout(dc,y0+y1);
+
if(IsDirty())
ResizeScrollbars();
m_memDC->SetDeviceOrigin(0,0);
m_memDC->Clear();
- // The +4 give the window a tiny border on the left and top, looks nice.
- wxPoint offset(-x0+4,-y0+4);
+ // The offsets give the window a tiny border on the left and top, looks nice.
+ wxPoint offset(-x0+WXLO_XOFFSET,-y0+WXLO_YOFFSET);
m_llist->Draw(*m_memDC,offset);
if(IsEditable())
m_llist->DrawCursor(*m_memDC,m_HaveFocus,offset);
void
wxLayoutWindow::OnKillFocus(wxFocusEvent &ev)
{
- m_HaveFocus = true;
+ m_HaveFocus = false;
DoPaint(); // to repaint the cursor
}
void EnablePopup(bool enable = true) { m_DoPopupMenu = enable; }
+ /** Sets the wrap margin.
+ @param margin set this to 0 to disable it
+ */
+ void SetWrapMargin(CoordType margin) { m_WrapMargin = margin; }
+
/** Redraws the window.
@param scrollToCursor if true, scroll the window so that the
cursor becomes visible
/// Can user edit the window?
bool m_Editable;
+ /// wrap margin
+ CoordType m_WrapMargin;
/// Is list dirty?
bool m_Dirty;
wxMemoryDC *m_memDC;