/*-*- c++ -*-******************************************************** * wxllist: wxLayoutList, a layout engine for text and graphics * * * * (C) 1998-1999 by Karsten Ballüder (Ballueder@usa.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 #ifdef __BORLANDC__ # pragma hdrstop #endif #include "Mpch.h" #ifdef M_BASEDIR # 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 # include # include # include # include # include # include #endif #ifdef WXLAYOUT_USE_CARET # include #endif // WXLAYOUT_USE_CARET #include /// 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 char *g_aTypeStrings[] = { "invalid", "text", "cmd", "icon" }; void wxLayoutObject::Debug(void) { WXLO_DEBUG(("%s",g_aTypeStrings[GetType()])); } #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 //@} void ReadString(wxString &to, wxString &from) { to = ""; const char *cptr = from.c_str(); while(*cptr && *cptr != '\n') to += *cptr++; if(*cptr) cptr++; from = cptr; } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * wxLayoutObject * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* static */ wxLayoutObject * wxLayoutObject::Read(wxString &istr) { wxString tmp; ReadString(tmp, istr); int type = -1; sscanf(tmp.c_str(),"%d", &type); 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); default: 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(void) { 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 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, class wxLayoutList *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 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 void wxLayoutObjectText::Debug(void) { wxLayoutObject::Debug(); WXLO_DEBUG((" `%s`", m_Text.c_str())); } #endif /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * wxLayoutObjectIcon * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ wxLayoutObjectIcon::wxLayoutObjectIcon(wxBitmap const &icon) { m_Icon = new wxBitmap(icon); } void wxLayoutObjectIcon::Write(wxString &ostr) { /* Exports icon through a temporary file. */ wxString file = wxGetTempFileName("wxloexport"); ostr << 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; } else return obj; } wxLayoutObject * wxLayoutObjectIcon::Copy(void) { wxLayoutObjectIcon *obj = new wxLayoutObjectIcon(new wxBitmap(*m_Icon)); obj->SetUserData(m_UserData); return obj; } wxLayoutObjectIcon::wxLayoutObjectIcon(wxBitmap *icon) { m_Icon = icon; } void wxLayoutObjectIcon::Draw(wxDC &dc, wxPoint const &coords, wxLayoutList *wxllist, CoordType begin, CoordType /* 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); } wxLayoutObject * wxLayoutObjectCmd::Copy(void) { wxLayoutObjectCmd *obj = new wxLayoutObjectCmd( m_StyleInfo->size, m_StyleInfo->family, 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 << WXLO_TYPE_CMD << '\n' << m_StyleInfo->size << '\n' << m_StyleInfo->family << '\n' << m_StyleInfo->style << '\n' << m_StyleInfo->weight << '\n' << m_StyleInfo->underline << '\n' << m_StyleInfo->m_fg_valid << '\n' << m_StyleInfo->m_bg_valid << '\n'; if(m_StyleInfo->m_fg_valid) { ostr << m_StyleInfo->m_fg.Red() << '\n' << m_StyleInfo->m_fg.Green() << '\n' << m_StyleInfo->m_fg.Blue() << '\n'; } if(m_StyleInfo->m_bg_valid) { ostr << m_StyleInfo->m_bg.Red() << '\n' << m_StyleInfo->m_bg.Green() << '\n' << m_StyleInfo->m_bg.Blue() << '\n'; } } /* static */ wxLayoutObjectCmd * wxLayoutObjectCmd::Read(wxString &istr) { wxLayoutObjectCmd *obj = new wxLayoutObjectCmd; wxString tmp; ReadString(tmp, istr); sscanf(tmp.c_str(),"%d", &obj->m_StyleInfo->size); ReadString(tmp, istr); sscanf(tmp.c_str(),"%d", &obj->m_StyleInfo->family); ReadString(tmp, istr); sscanf(tmp.c_str(),"%d", &obj->m_StyleInfo->style); ReadString(tmp, istr); sscanf(tmp.c_str(),"%d", &obj->m_StyleInfo->weight); ReadString(tmp, istr); sscanf(tmp.c_str(),"%d", &obj->m_StyleInfo->underline); ReadString(tmp, istr); sscanf(tmp.c_str(),"%d", &obj->m_StyleInfo->m_fg_valid); ReadString(tmp, istr); sscanf(tmp.c_str(),"%d", &obj->m_StyleInfo->m_bg_valid); if(obj->m_StyleInfo->m_fg_valid) { int red, green, blue; ReadString(tmp, istr); sscanf(tmp.c_str(),"%d", &red); ReadString(tmp, istr); sscanf(tmp.c_str(),"%d", &green); ReadString(tmp, istr); sscanf(tmp.c_str(),"%d", &blue); obj->m_StyleInfo->m_fg = wxColour(red, green, blue); } if(obj->m_StyleInfo->m_bg_valid) { int red, green, blue; ReadString(tmp, istr); sscanf(tmp.c_str(),"%d", &red); ReadString(tmp, istr); sscanf(tmp.c_str(),"%d", &green); ReadString(tmp, istr); sscanf(tmp.c_str(),"%d", &blue); obj->m_StyleInfo->m_bg = wxColour(red, green, blue); } return obj; } wxLayoutObjectCmd::~wxLayoutObjectCmd() { delete m_StyleInfo; } wxLayoutStyleInfo * wxLayoutObjectCmd::GetStyle(void) const { return m_StyleInfo; } void wxLayoutObjectCmd::Draw(wxDC &dc, wxPoint const & /* coords */, wxLayoutList *wxllist, CoordType begin, CoordType /* 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_LineNumber = 0; m_Width = m_Height = 0; m_Length = 0; m_updateLeft = -1; MarkDirty(0); m_Previous = prev; m_Next = NULL; RecalculatePosition(llist); 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->MoveLines(+1); m_Next->RecalculatePositions(1,llist); } m_StyleInfo = llist->GetDefaultStyleInfo(); llist->IncNumLines(); } wxLayoutLine::~wxLayoutLine() { // kbList cleans itself } 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; } void wxLayoutLine::RecalculatePositions(int recurse, wxLayoutList *llist) { //FIXME: is this really needed? We run Layout() anyway. // Recursing here, drives computation time up exponentially, as // each line will cause all following lines to be recalculated. // Yes, or linenumbers go wrong. wxASSERT(recurse >= 0); wxPoint pos = m_Position; CoordType height = m_Height; // WXLO_TRACE("RecalculatePositions()"); RecalculatePosition(llist); if(m_Next) { if(recurse > 0) m_Next->RecalculatePositions(--recurse, llist); else if(pos != m_Position || m_Height != height) m_Next->RecalculatePositions(0, llist); } } wxLayoutObjectList::iterator wxLayoutLine::FindObject(CoordType xpos, CoordType *offset) const { wxASSERT(xpos >= 0); wxASSERT(offset); wxLayoutObjectList::iterator i, found = NULLIT; 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 != NULLIT; 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, CoordType xpos, CoordType *cxpos, bool *found) const { wxASSERT(cxpos); wxASSERT(cxpos); wxLayoutObjectList::iterator i; CoordType x = 0, cx = 0, width; for(i = m_ObjectList.begin(); i != NULLIT; i++) { width = (**i).GetWidth(); if( x <= xpos && xpos <= x + width ) { *cxpos = cx + (**i).GetOffsetScreen(dc, xpos-x); if(found) *found = true; return i; } x += (**i).GetWidth(); cx += (**i).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, relpos = -1; 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(); 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); // If we insert a command object, we need to recalculate all lines // to update their styleinfo structure. if(obj->GetType() == WXLO_TYPE_CMD) MarkNextDirty(-1); CoordType offset; wxLOiterator i = FindObject(xpos, &offset); if(i == NULLIT) { 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); if(i != NULLIT && (**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); while(npos > 0) { 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: len = (**i).GetLength(); m_Length -= len; npos -= len; // If we delete a command object, we need to recalculate all lines // to update their styleinfo structure. if((**i).GetType() == WXLO_TYPE_CMD) MarkNextDirty(-1); 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; } void wxLayoutLine::MarkNextDirty(int recurse) { wxLayoutLine *line = GetNextLine(); while(line && (recurse == -1 || recurse >= 0)) { line->MarkDirty(); line = line->GetNextLine(); if(recurse > 0) recurse --; } } bool wxLayoutLine::DeleteWord(CoordType xpos) { wxASSERT(xpos >= 0); CoordType offset; MarkDirty(xpos); wxLOiterator i = FindObject(xpos, &offset); for(;;) { if(i == NULLIT) 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; } } wxFAIL_MSG("unreachable"); } 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; if(update) { m_Next->MoveLines(-1); m_Next->RecalculatePositions(1, llist); /* We assume that if we have more than one object in the list, this means that we have a command object, so we need to update the following lines. */ if(m_ObjectList.size() > 1 || ( m_ObjectList.begin() != NULLIT && (**m_ObjectList.begin()).GetType() == WXLO_TYPE_CMD) ) MarkNextDirty(-1); } wxLayoutLine *next = m_Next; delete this; llist->DecNumLines(); return next; } void wxLayoutLine::Draw(wxDC &dc, wxLayoutList *llist, const wxPoint & offset) const { wxLayoutObjectList::iterator i; wxPoint pos = offset; pos = pos + GetPosition(); pos.y += m_BaseLine; CoordType xpos = 0; // cursorpos, lenght of line CoordType from, to, tempto; 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 != NULLIT; i++) { if(highlight == -1) // partially highlight line { // parts of the line need highlighting tempto = 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 suppressSIupdate) { wxLayoutObjectList::iterator i; // 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; if(cursorPos) { *cursorPos = m_Position; if(cursorSize) *cursorSize = wxPoint(0,0); } m_StyleInfo = llist->GetStyleInfo(); // save current style for(i = m_ObjectList.begin(); i != NULLIT; 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 = 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(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) { // FIXME isn't this done in RecalculatePositions() below anyhow? m_Next->RecalculatePositions(0, llist); } // 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->y < WXLO_MINIMUM_CURSOR_WIDTH) { CoordType width, height, descent; dc.GetTextExtent(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; } RecalculatePositions(1, llist); MarkClean(); } wxLayoutLine * wxLayoutLine::Break(CoordType xpos, wxLayoutList *llist) { wxASSERT(xpos >= 0); MarkDirty(xpos); /* If we are at the begin of a line, we want to move all other lines down and stay with the cursor where we are. However, if we are in an empty line, we want to move down with it. */ if(xpos == 0 && GetLength() > 0) { // insert an empty line before this one wxLayoutLine *prev = new wxLayoutLine(m_Previous, llist); if(m_Previous == NULL) { // We were in first line, need to link in new empty line // before this. prev->m_Next = this; m_Previous = prev; m_Previous->m_Height = 0; // this is a wild guess } if(m_Next) m_Next->RecalculatePositions(1, llist); return m_Previous; } CoordType offset; wxLOiterator i = FindObject(xpos, &offset); if(i == NULLIT) // 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) { 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->RecalculatePositions(2, llist); return newLine; } void wxLayoutLine::MergeNextLine(wxLayoutList *llist) { wxCHECK_RET(GetNextLine(),"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->MoveLines(-1); } 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 } delete oldnext; } 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().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 != NULLIT) offset = (**i).GetLength(); }while(i != NULLIT); /* 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 != NULLIT && (**i).GetType() != WXLO_TYPE_TEXT) { pos += (**i).GetLength(); i++; } if(i == NULLIT) return -1; //why should this happen? pos += (**i).GetLength(); i++; while(i != NULLIT && (**i).GetType() != WXLO_TYPE_TEXT) { pos += (**i).GetLength(); i++; } if(i == NULLIT) return -1; //this is possible, if there is only one text object // now we are at the second text object: pos -= (**i).GetLength(); return pos; // in front of it } #ifdef WXLAYOUT_DEBUG void wxLayoutLine::Debug(void) { wxString tmp; wxPoint pos = GetPosition(); WXLO_DEBUG(("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() != NULLIT) (**m_ObjectList.begin()).Debug(); } #endif void wxLayoutLine::Copy(wxLayoutList *llist, CoordType from, CoordType to) { 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 != NULLIT && last != NULLIT && *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; InvalidateUpdateRect(); Clear(); } wxLayoutList::~wxLayoutList() { InternalClear(); m_FirstLine->DeleteLine(false, this); wxASSERT_MSG( m_numLines == 0, "line count calculation broken" ); } void wxLayoutList::Empty(void) { 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(void) { Empty(); 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::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, char const *fg, char const *bg) { wxColour *cfg = NULL, *cbg = NULL; if( fg ) cfg = wxTheColourDatabase->FindColour(fg); if( bg ) cbg = wxTheColourDatabase->FindColour(bg); 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; } 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 ++; 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) { wxCHECK_MSG( m_CursorLine, false, "no current line" ); wxCHECK_MSG( n == -1 || n == +1, false, "not implemented yet" ); CoordType moveDistance = 0; CoordType offset; for ( wxLOiterator i = m_CursorLine->FindObject(m_CursorPos.x, &offset); n != 0; n > 0 ? i++ : i-- ) { if ( i == NULLIT ) return false; wxLayoutObject *obj = *i; 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 n--; canAdvance = false; } else if ( offset > 0 ) { // offset is off by 1, make it a valid index offset--; } } if ( canAdvance ) { const wxString& text = tobj->GetText(); const char *start = text.c_str(); const char *end = start + text.length(); const char *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++; } } n > 0 ? n-- : n++; moveDistance = p - start - offset; } } // except for the first iteration, offset is 0 offset = 0; } MoveCursorHorizontally(moveDistance); return true; } bool wxLayoutList::Insert(wxString const &text) { wxASSERT(m_CursorLine); wxASSERT_MSG( text.Find('\n') == wxNOT_FOUND, "use wxLayoutImportText!" ); if ( !text ) return true; AddCursorPosToUpdateRect(); if ( !m_CursorLine->Insert(m_CursorPos.x, text) ) return false; m_CursorPos.x += text.Length(); m_movedCursor = true; m_CursorLine->RecalculatePositions(0, this); 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; m_CursorLine->RecalculatePositions(0, this); return true; } bool wxLayoutList::Insert(wxLayoutList *llist) { wxASSERT(llist); bool rc = TRUE; for(wxLayoutLine *line = llist->GetFirstLine(); line; line = line->GetNextLine() ) { for(wxLOiterator i = line->GetFirstObject(); i != NULLIT; i++) rc |= Insert(*i); LineBreak(); } return rc; } bool wxLayoutList::LineBreak(void) { wxASSERT(m_CursorLine); bool setFirst = (m_CursorLine == m_FirstLine && m_CursorPos.x == 0); 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(setFirst) // we were at beginning of first line m_FirstLine = m_CursorLine; wxASSERT(m_FirstLine); if(m_CursorPos.x != 0) 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) { 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; AddCursorPosToUpdateRect(); LineBreak(); Delete(1); // delete the space m_CursorPos.x = newpos; m_CursorLine->RecalculatePositions(1, this); m_movedCursor = true; return true; } } bool wxLayoutList::Delete(CoordType npos) { wxCHECK_MSG(m_CursorLine, false, "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("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(); m_CursorLine->RecalculatePositions(2, this); 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); } m_CursorLine->RecalculatePositions(2, this); return n; } void wxLayoutList::Recalculate(wxDC &dc, CoordType bottom) { 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(wxDC &dc) { 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); // This one we always Layout() to get the current cursor // coordinates on the screen: m_CursorLine->MarkDirty(); bool wasDirty = false; wxLayoutLine *line = m_FirstLine; while(line) { if(! wasDirty) ApplyStyle(line->GetStyleInfo(), dc); if(forceAll || line->IsDirty() || (cpos && line->GetLineNumber() == cpos->y)) { // 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; } } else if(cpos && line->GetLineNumber() == cpos->y) line->Layout(dc, this, cpos, csize, NULL, cpos->x); else line->Layout(dc, this); // little condition to speed up redrawing: if(bottom != -1 && line->GetPosition().y > bottom) break; wasDirty = true; } line->RecalculatePositions(1, this); line = line->GetNextLine(); } // 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)); 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) { 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; } /* We need to re-layout all dirty lines to update styleinfos etc. However, somehow we don't find all dirty lines... */ Layout(dc); //,-1,true); //FIXME ApplyStyle(m_DefaultStyleInfo, dc); wxBrush brush(m_CurrentStyleInfo.m_bg, wxSOLID); dc.SetBrush(brush); dc.SetBackgroundMode(wxTRANSPARENT); bool style_set = false; while(line) { // only draw if between top and bottom: if((top == -1 || line->GetPosition().y + line->GetHeight() >= top)) { // if(! style_set) { ApplyStyle(line->GetStyleInfo(), dc); style_set = true; } line->Draw(dc, this, offset); } // little condition to speed up redrawing: if(bottom != -1 && line->GetPosition().y > bottom) break; line = line->GetNextLine(); } InvalidateUpdateRect(); WXLO_DEBUG(("Selection is %s : l%d,%ld/%ld,%ld", m_Selection.m_valid ? "valid" : "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) { // First, find the right line: wxLayoutLine *line = 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; #if 0 // we need to run a layout here to get font sizes right :-( // VZ: we can't call Layout() from here because it marks the line as // clean and it is not refreshed when it's called from wxLayoutList:: // Layout() - if we really need to do this, we should introduce an // extra argument to Layout() to prevent the line from MarkClean()ing // itself here line->Layout(dc, this); #endif line = line->GetNextLine(); } if ( !line ) { if ( found ) *found = false; return NULL; // not found } if ( cursorPos ) cursorPos->y = line->GetLineNumber(); // Now, find the object in the line: ApplyStyle(line->GetStyleInfo(), dc); wxLOiterator i = line->FindObjectScreen(dc, pos.x, cursorPos ? &cursorPos->x : NULL, found); return (i == NULLIT) ? NULL : *i; } wxPoint wxLayoutList::GetSize(void) 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 &dc, bool active, wxPoint const &translate) { if ( m_movedCursor ) m_movedCursor = false; wxPoint coords(m_CursorScreenPos); coords += translate; #ifdef WXLAYOUT_DEBUG WXLO_DEBUG(("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("Cursor is at (%d, %d)", m_CursorPos.x, m_CursorPos.y); #endif #ifdef WXLAYOUT_USE_CARET m_caret->Move(coords); #else // !WXLAYOUT_USE_CARET dc.SetBrush(*wxWHITE_BRUSH); //FIXME: wxGTK XOR is borken at the moment!!!dc.SetLogicalFunction(wxXOR); dc.SetLogicalFunction(wxXOR); dc.SetPen(wxPen(*wxBLACK,1,wxSOLID)); if(active) { 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.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(("Starting selection at %ld/%ld", 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(("Continuing selection at %ld/%ld", cpos.x, cpos.y)); if ( m_Selection.m_CursorB <= cpos ) { m_Selection.m_ScreenB = spos; m_Selection.m_CursorB = cpos; } else { m_Selection.m_ScreenA = spos; m_Selection.m_CursorA = cpos; } // 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; } } void wxLayoutList::EndSelection(const wxPoint& cposOrig, const wxPoint& spos) { wxPoint cpos(cposOrig); if(cpos.x == -1) cpos = m_CursorPos; ContinueSelection(cpos); WXLO_DEBUG(("Ending selection at %ld/%ld", cpos.x, cpos.y)); m_Selection.m_selecting = false; m_Selection.m_valid = true; } void wxLayoutList::DiscardSelection() { if ( !HasSelection() ) return; m_Selection.m_valid = m_Selection.m_selecting = false; m_Selection.m_discarded = true; } bool wxLayoutList::IsSelecting(void) 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; } /** 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) 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 *to = line->GetLength(); 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 *from = 0; return -1; } else return 0; } void wxLayoutList::DeleteSelection(void) { 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; } wxLayoutLine * firstLine = NULL, * lastLine = NULL; for(firstLine = m_FirstLine; firstLine && firstLine->GetLineNumber() < m_Selection.m_CursorA.y; firstLine=firstLine->GetNextLine()) ; if(!firstLine || firstLine->GetLineNumber() != m_Selection.m_CursorA.y) return; for(lastLine = m_FirstLine; lastLine && lastLine->GetLineNumber() < m_Selection.m_CursorB.y; lastLine=lastLine->GetNextLine()) ; if(!lastLine || lastLine->GetLineNumber() != m_Selection.m_CursorB.y) return; // We now know that the two lines are different: // First, delete what's left of this line: MoveCursorTo(m_Selection.m_CursorA); DeleteToEndOfLine(); wxLayoutLine *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: firstLine->RecalculatePositions(1, this); } /// 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 } wxLayoutList * wxLayoutList::Copy(const wxPoint &from, const wxPoint &to) { wxLayoutLine * firstLine = NULL, * lastLine = NULL; 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 *export; wxLayoutExportStatus status(llist); while((export = wxLayoutExport( &status, WXLO_EXPORT_AS_OBJECTS)) != NULL) { if(export->type == WXLO_EXPORT_EMPTYLINE) ; //FIXME missing support for linebreaks in string format else export->content.object->Write(string); delete export; } wxlo->SetData(string.c_str(), string.Length()+1); } 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; dc.SetTextForeground(m_CurrentStyleInfo.m_fg); } if(si.m_bg_valid) { m_CurrentStyleInfo.m_bg = si.m_bg; dc.SetTextBackground(m_CurrentStyleInfo.m_bg); } } #ifdef WXLAYOUT_DEBUG void wxLayoutList::Debug(void) { WXLO_DEBUG(("Cursor is in line %d, screen pos = (%d, %d)", 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(); } wxLayoutPrintout::~wxLayoutPrintout() { } 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; } if(ppiPrinterX == 0) // not yet set, need to guess { ppiPrinterX = 72; ppiPrinterY = 72; } // 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); 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; // 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); 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. */ #ifdef __WXMSW__ wxPrinterDC psdc("","",WXLLIST_TEMPFILE,false); #else wxPostScriptDC psdc(WXLLIST_TEMPFILE,false); #endif float scale = ScaleDC(&psdc); psdc.GetSize(&m_PageWidth, &m_PageHeight); // This sets a left/top origin of 15% and 20%: m_Offset = wxPoint((15*m_PageWidth)/100, m_PageHeight/20); // This is the length of the printable area. m_PrintoutHeight = m_PageHeight - (int) (m_PageHeight * 0.15); 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; wxRemoveFile(WXLLIST_TEMPFILE); } bool wxLayoutPrintout::HasPage(int pageNum) { return pageNum <= m_NumOfPages; } /* Stupid wxWindows 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(); }