X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/ef3f06797d8410b1dffe6244e2cc7597f5727adb..62795f413a7222863b4aee76c08764071f94bd87:/src/richtext/richtextbuffer.cpp?ds=sidebyside diff --git a/src/richtext/richtextbuffer.cpp b/src/richtext/richtextbuffer.cpp index 22a123116f..be804ef4d0 100644 --- a/src/richtext/richtextbuffer.cpp +++ b/src/richtext/richtextbuffer.cpp @@ -4,7 +4,6 @@ // Author: Julian Smart // Modified by: // Created: 2005-09-30 -// RCS-ID: $Id$ // Copyright: (c) Julian Smart // Licence: wxWindows licence ///////////////////////////////////////////////////////////////////////////// @@ -663,7 +662,7 @@ int wxRichTextObject::ConvertPixelsToTenthsMM(int ppi, int pixels, double scale) // Draw the borders and background for the given rectangle and attributes. // Width and height are taken to be the outer margin size, not the content. -bool wxRichTextObject::DrawBoxAttributes(wxDC& dc, wxRichTextBuffer* buffer, const wxRichTextAttr& attr, const wxRect& boxRect, int flags) +bool wxRichTextObject::DrawBoxAttributes(wxDC& dc, wxRichTextBuffer* buffer, const wxRichTextAttr& attr, const wxRect& boxRect, int flags, wxRichTextObject* obj) { // Assume boxRect is the area around the content wxRect marginRect = boxRect; @@ -693,12 +692,24 @@ bool wxRichTextObject::DrawBoxAttributes(wxDC& dc, wxRichTextBuffer* buffer, con if (flags & wxRICHTEXT_DRAW_GUIDELINES) { - wxRichTextAttr editBorderAttr = attr; + wxRichTextAttr editBorderAttr; // TODO: make guideline colour configurable editBorderAttr.GetTextBoxAttr().GetBorder().SetColour(*wxLIGHT_GREY); editBorderAttr.GetTextBoxAttr().GetBorder().SetWidth(1, wxTEXT_ATTR_UNITS_PIXELS); editBorderAttr.GetTextBoxAttr().GetBorder().SetStyle(wxTEXT_BOX_ATTR_BORDER_SOLID); + if (obj) + { + wxRichTextCell* cell = wxDynamicCast(obj, wxRichTextCell); + if (cell) + { + // This ensures that thin lines drawn by adjacent cells (left and above) + // don't get overwritten by the guidelines. + editBorderAttr.GetTextBoxAttr().GetBorder().GetLeft().Reset(); + editBorderAttr.GetTextBoxAttr().GetBorder().GetTop().Reset(); + } + } + DrawBorder(dc, buffer, editBorderAttr.GetTextBoxAttr().GetBorder(), borderRect, flags); } @@ -770,7 +781,7 @@ bool wxRichTextObject::DrawBorder(wxDC& dc, wxRichTextBuffer* buffer, const wxTe wxBrush brush(col); dc.SetPen(pen); dc.SetBrush(brush); - dc.DrawRectangle(rect.x + rect.width - borderRight, rect.y, borderRight, rect.height); + dc.DrawRectangle(rect.x + rect.width - borderRight, rect.y, borderRight, rect.height + 1); } } @@ -827,7 +838,7 @@ bool wxRichTextObject::DrawBorder(wxDC& dc, wxRichTextBuffer* buffer, const wxTe wxBrush brush(col); dc.SetPen(pen); dc.SetBrush(brush); - dc.DrawRectangle(rect.x, rect.y + rect.height - borderBottom, rect.width, borderBottom); + dc.DrawRectangle(rect.x, rect.y + rect.height - borderBottom + 1, rect.width, borderBottom); } } @@ -1841,14 +1852,14 @@ bool wxRichTextParagraphLayoutBox::Draw(wxDC& dc, wxRichTextDrawingContext& cont context.ApplyVirtualAttributes(attr, this); int flags = style; - if (selection.IsValid() && GetParentContainer() != this && selection.WithinSelection(GetRange().GetStart(), GetParentContainer())) + if (selection.IsValid() && GetParentContainer() != this && selection.GetContainer() == this && selection.WithinSelection(GetRange().GetStart(), GetParentContainer())) flags |= wxRICHTEXT_DRAW_SELECTED; // Don't draw guidelines if at top level int theseFlags = flags; if (!GetParent()) theseFlags &= ~wxRICHTEXT_DRAW_GUIDELINES; - DrawBoxAttributes(dc, GetBuffer(), attr, thisRect, theseFlags); + DrawBoxAttributes(dc, GetBuffer(), attr, thisRect, theseFlags, this); if (wxRichTextBuffer::GetFloatingLayoutMode()) DrawFloats(dc, context, range, selection, rect, descent, style); @@ -7447,7 +7458,10 @@ bool wxRichTextParagraphLayoutBox::InsertParagraphsWithUndo(wxRichTextBuffer* bu /// Submit command to insert the given text bool wxRichTextBuffer::InsertTextWithUndo(long pos, const wxString& text, wxRichTextCtrl* ctrl, int flags) { - return ctrl->GetFocusObject()->InsertTextWithUndo(this, pos, text, ctrl, flags); + if (ctrl) + return ctrl->GetFocusObject()->InsertTextWithUndo(this, pos, text, ctrl, flags); + else + return wxRichTextParagraphLayoutBox::InsertTextWithUndo(this, pos, text, ctrl, flags); } /// Submit command to insert the given text @@ -7709,6 +7723,39 @@ wxRichTextField* wxRichTextParagraphLayoutBox::InsertFieldWithUndo(wxRichTextBuf return obj; } +bool wxRichTextParagraphLayoutBox::SetObjectPropertiesWithUndo(wxRichTextObject& obj, const wxRichTextProperties& properties, wxRichTextObject* objToSet) +{ + wxRichTextBuffer* buffer = GetBuffer(); + wxCHECK_MSG(buffer, false, wxT("Invalid buffer")); + wxRichTextCtrl* rtc = buffer->GetRichTextCtrl(); + wxCHECK_MSG(rtc, false, wxT("Invalid rtc")); + + wxRichTextAction* action = NULL; + wxRichTextObject* clone = NULL; + + // The object on which to set properties will usually be 'obj', but use objToSet if it's valid. + // This is necessary e.g. on setting a wxRichTextCell's properties, when obj will be the parent table + if (objToSet == NULL) + objToSet = &obj; + + if (rtc->SuppressingUndo()) + objToSet->SetProperties(properties); + else + { + clone = obj.Clone(); + objToSet->SetProperties(properties); + + // The 'true' parameter in the next line says "Ignore first time"; otherwise the objects are prematurely switched + action = new wxRichTextAction(NULL, _("Change Properties"), wxRICHTEXT_CHANGE_OBJECT, buffer, obj.GetParentContainer(), rtc, true); + action->SetOldAndNewObjects(& obj, clone); + action->SetPosition(obj.GetRange().GetStart()); + action->SetRange(obj.GetRange()); + buffer->SubmitAction(action); + } + + return true; +} + /// Get the style that is appropriate for a new paragraph at this position. /// If the previous paragraph has a paragraph style name, look up the next-paragraph /// style. @@ -7865,11 +7912,14 @@ bool wxRichTextBuffer::SubmitAction(wxRichTextAction* action) if (BatchingUndo() && m_batchedCommand && !SuppressingUndo()) { - wxRichTextCommand* cmd = new wxRichTextCommand(action->GetName()); - cmd->AddAction(action); - cmd->Do(); - cmd->GetActions().Clear(); - delete cmd; + if (!action->GetIgnoreFirstTime()) + { + wxRichTextCommand* cmd = new wxRichTextCommand(action->GetName()); + cmd->AddAction(action); + cmd->Do(); + cmd->GetActions().Clear(); + delete cmd; + } m_batchedCommand->AddAction(action); } @@ -7879,7 +7929,14 @@ bool wxRichTextBuffer::SubmitAction(wxRichTextAction* action) cmd->AddAction(action); // Only store it if we're not suppressing undo. - return GetCommandProcessor()->Submit(cmd, !SuppressingUndo()); + if (!action->GetIgnoreFirstTime()) + { + return GetCommandProcessor()->Submit(cmd, !SuppressingUndo()); + } + else if (!SuppressingUndo()) + { + GetCommandProcessor()->Store(cmd); // Just store it, without Do()ing anything + } } return true; @@ -8317,6 +8374,7 @@ wxString wxRichTextBuffer::GetExtWildcard(bool combine, bool save, wxArrayInt* t return wildcard; } +#if wxUSE_FFILE && wxUSE_STREAMS /// Load a file bool wxRichTextBuffer::LoadFile(const wxString& filename, wxRichTextFileType type) { @@ -8345,7 +8403,9 @@ bool wxRichTextBuffer::SaveFile(const wxString& filename, wxRichTextFileType typ else return false; } +#endif // wxUSE_FFILE && wxUSE_STREAMS +#if wxUSE_STREAMS /// Load from a stream bool wxRichTextBuffer::LoadFile(wxInputStream& stream, wxRichTextFileType type) { @@ -8374,6 +8434,7 @@ bool wxRichTextBuffer::SaveFile(wxOutputStream& stream, wxRichTextFileType type) else return false; } +#endif // wxUSE_STREAMS /// Copy the range to the clipboard bool wxRichTextBuffer::CopyToClipboard(const wxRichTextRange& range) @@ -9221,6 +9282,22 @@ bool wxRichTextCell::Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxR return wxRichTextBox::Draw(dc, context, range, selection, rect, descent, style); } +int wxRichTextCell::HitTest(wxDC& dc, wxRichTextDrawingContext& context, const wxPoint& pt, long& textPosition, wxRichTextObject** obj, wxRichTextObject** contextObj, int flags) +{ + int ret = wxRichTextParagraphLayoutBox::HitTest(dc, context, pt, textPosition, obj, contextObj, flags); + if (ret != wxRICHTEXT_HITTEST_NONE) + { + return ret; + } + else + { + textPosition = m_ownRange.GetEnd()-1; + *obj = this; + *contextObj = this; + return wxRICHTEXT_HITTEST_AFTER|wxRICHTEXT_HITTEST_OUTSIDE; + } +} + /// Copy void wxRichTextCell::Copy(const wxRichTextCell& obj) { @@ -9269,18 +9346,20 @@ bool wxRichTextCell::EditProperties(wxWindow* parent, wxRichTextBuffer* buffer) else caption = _("Cell Properties"); + // We don't want position and floating controls for a cell. + wxRichTextSizePage::ShowPositionControls(false); + wxRichTextSizePage::ShowFloatingControls(false); + wxRichTextSizePage::ShowAlignmentControls(true); + wxRichTextObjectPropertiesDialog cellDlg(this, wxGetTopLevelParent(parent), wxID_ANY, caption); cellDlg.SetAttributes(attr); - wxRichTextSizePage* sizePage = wxDynamicCast(cellDlg.FindPage(wxCLASSINFO(wxRichTextSizePage)), wxRichTextSizePage); - if (sizePage) - { - // We don't want position and floating controls for a cell. - sizePage->ShowPositionControls(false); - sizePage->ShowFloatingControls(false); - } + bool ok = (cellDlg.ShowModal() == wxID_OK); - if (cellDlg.ShowModal() == wxID_OK) + wxRichTextSizePage::ShowPositionControls(true); + wxRichTextSizePage::ShowFloatingControls(true); + + if (ok) { if (multipleCells) { @@ -9301,6 +9380,29 @@ bool wxRichTextCell::EditProperties(wxWindow* parent, wxRichTextBuffer* buffer) return false; } +// The next 2 methods return span values. Note that the default is 1, not 0 +int wxRichTextCell::GetColspan() const +{ + int span = 1; + if (GetProperties().HasProperty(wxT("colspan"))) + { + span = GetProperties().GetPropertyLong(wxT("colspan")); + } + + return span; +} + +int wxRichTextCell::GetRowspan() const +{ + int span = 1; + if (GetProperties().HasProperty(wxT("rowspan"))) + { + span = GetProperties().GetPropertyLong(wxT("rowspan")); + } + + return span; +} + WX_DEFINE_OBJARRAY(wxRichTextObjectPtrArrayArray) IMPLEMENT_DYNAMIC_CLASS(wxRichTextTable, wxRichTextBox) @@ -9314,11 +9416,197 @@ wxRichTextTable::wxRichTextTable(wxRichTextObject* parent): wxRichTextBox(parent // Draws the object. bool wxRichTextTable::Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int style) { - return wxRichTextBox::Draw(dc, context, range, selection, rect, descent, style); + wxRichTextBox::Draw(dc, context, range, selection, rect, descent, style); + + int colCount = GetColumnCount(); + int rowCount = GetRowCount(); + int col, row; + for (col = 0; col < colCount; col++) + { + for (row = 0; row < rowCount; row++) + { + if (row == 0 || row == (rowCount-1) || col == 0 || col == (colCount-1)) + { + wxRichTextCell* cell = GetCell(row, col); + if (cell && cell->IsShown() && !cell->GetRange().IsOutside(range)) + { + wxRect childRect(cell->GetPosition(), cell->GetCachedSize()); + wxRichTextAttr attr(cell->GetAttributes()); + if (row != 0) + attr.GetTextBoxAttr().GetBorder().GetTop().Reset(); + if (row != (rowCount-1)) + attr.GetTextBoxAttr().GetBorder().GetBottom().Reset(); + if (col != 0) + attr.GetTextBoxAttr().GetBorder().GetLeft().Reset(); + if (col != (colCount-1)) + attr.GetTextBoxAttr().GetBorder().GetRight().Reset(); + + if (attr.GetTextBoxAttr().GetBorder().IsValid()) + { + wxRect boxRect(cell->GetPosition(), cell->GetCachedSize()); + wxRect marginRect = boxRect; + wxRect contentRect, borderRect, paddingRect, outlineRect; + + cell->GetBoxRects(dc, GetBuffer(), attr, marginRect, borderRect, contentRect, paddingRect, outlineRect); + cell->DrawBorder(dc, GetBuffer(), attr.GetTextBoxAttr().GetBorder(), borderRect); + } + } + } + } + } + + return true; } -WX_DECLARE_OBJARRAY(wxRect, wxRichTextRectArray); -WX_DEFINE_OBJARRAY(wxRichTextRectArray); + // Helper function for Layout() that clears the space needed by a cell with rowspan > 1 +int GetRowspanDisplacement(const wxRichTextTable* table, int row, int col, int paddingX, const wxArrayInt& colWidths) +{ + // If one or more cells above-left of this one has rowspan > 1, the affected cells below it + // will have been hidden and have width 0. As a result they are ignored by the layout algorithm, + // and all cells to their right are effectively shifted left. As a result there's no hole for + // the spanning cell to fill. + // So search back along the current row for hidden cells. However there's also the annoying issue of a + // rowspanning cell that also has colspam. So we can't rely on the rowspanning cell being directly above + // the first hidden one we come to. We also can't rely on a cell being hidden only by one type of span; + // there's nothing to stop a cell being hidden by colspan, and then again hidden from above by rowspan. + // The answer is to look above each hidden cell in turn, which I think covers all bases. + int deltaX = 0; + for (int prevcol = 0; prevcol < col; ++prevcol) + { + if (!table->GetCell(row, prevcol)->IsShown()) + { + // We've found a hidden cell. If it's hidden because of colspan to its left, it's + // already been taken into account; but not if there's a rowspanning cell above + for (int prevrow = row-1; prevrow >= 0; --prevrow) + { + wxRichTextCell* cell = table->GetCell(prevrow, prevcol); + if (cell && cell->IsShown()) + { + int rowSpan = cell->GetRowspan(); + if (rowSpan > 1 && rowSpan > (row-prevrow)) + { + // There is a rowspanning cell above above the hidden one, so we need + // to right-shift the index cell by this column's width. Furthermore, + // if the cell also colspans, we need to shift by all affected columns + for (int colSpan = 0; colSpan < cell->GetColspan(); ++colSpan) + deltaX += (colWidths[prevcol+colSpan] + paddingX); + break; + } + } + } + } + } + return deltaX; +} + + // Helper function for Layout() that expands any cell with rowspan > 1 +void ExpandCellsWithRowspan(const wxRichTextTable* table, int paddingY, int& bottomY, wxDC& dc, wxRichTextDrawingContext& context, const wxRect& availableSpace, int style) +{ + // This is called when the table's cell layout is otherwise complete. + // For any cell with rowspan > 1, expand downwards into the row(s) below. + + // Start by finding the current 'y' of the top of each row, plus the bottom of the available area for cells. + // Deduce this from the top of a visible cell in the row below. (If none are visible, the row will be invisible anyway and can be ignored.) + const int rowCount = table->GetRowCount(); + const int colCount = table->GetColumnCount(); + wxArrayInt rowTops; + rowTops.Add(0, rowCount+1); + int row; + for (row = 0; row < rowCount; ++row) + { + for (int column = 0; column < colCount; ++column) + { + wxRichTextCell* cell = table->GetCell(row, column); + if (cell && cell->IsShown()) + { + rowTops[row] = cell->GetPosition().y; + break; + } + } + } + rowTops[rowCount] = bottomY + paddingY; // The table bottom, which was passed to us + + bool needsRelay = false; + + for (row = 0; row < rowCount-1; ++row) // -1 as the bottom row can't rowspan + { + for (int col = 0; col < colCount; ++col) + { + wxRichTextCell* cell = table->GetCell(row, col); + if (cell && cell->IsShown()) + { + int span = cell->GetRowspan(); + if (span > 1) + { + span = wxMin(span, rowCount-row); // Don't try to span below the table! + if (span < 2) + continue; + + int availableHeight = rowTops[row+span] - rowTops[row] - paddingY; + wxSize newSize = wxSize(cell->GetCachedSize().GetWidth(), availableHeight); + wxRect availableCellSpace = wxRect(cell->GetPosition(), newSize); + cell->Invalidate(wxRICHTEXT_ALL); + cell->Layout(dc, context, availableCellSpace, availableSpace, style); + // Ensure there's room in the span to display its contents, else it'll overwrite lower rows + int overhang = cell->GetCachedSize().GetHeight() - availableHeight; + cell->SetCachedSize(newSize); + + if (overhang > 0) + { + // There are 3 things to get right: + // 1) The easiest is the rows below the span: they need to be downshifted by the overhang, and so does the table bottom itself + // 2) The rows within the span, including the one holding this cell, need to be deepened by their share of the overhang + // e.g. if rowspan == 3, each row should increase in depth by 1/3rd of the overhang. + // 3) The cell with the rowspan shouldn't be touched in 2); its height will be set to the whole span later. + int deltaY = overhang / span; + int spare = overhang % span; + + // Each row in the span needs to by deepened by its share of the overhang (give the first row any spare). + // This is achieved by increasing the value stored in the following row's rowTops + for (int spannedRows = 0; spannedRows < span; ++spannedRows) + { + rowTops[row+spannedRows+1] += ((deltaY * (spannedRows+1)) + (spannedRows == 0 ? spare:0)); + } + + // Any rows below the span need shifting down + for (int rowsBelow = row + span+1; rowsBelow <= rowCount; ++rowsBelow) + { + rowTops[rowsBelow] += overhang; + } + + needsRelay = true; + } + } + } + } + } + + if (!needsRelay) + return; + + // There were overflowing rowspanning cells, so layout yet again to make the increased row depths show + for (row = 0; row < rowCount; ++row) + { + for (int col = 0; col < colCount; ++col) + { + wxRichTextCell* cell = table->GetCell(row, col); + if (cell && cell->IsShown()) + { + wxPoint position(cell->GetPosition().x, rowTops[row]); + + // GetRowspan() will usually return 1, but may be greater + wxSize size(cell->GetCachedSize().GetWidth(), rowTops[row + cell->GetRowspan()] - rowTops[row] - paddingY); + + wxRect availableCellSpace = wxRect(position, size); + cell->Invalidate(wxRICHTEXT_ALL); + cell->Layout(dc, context, availableCellSpace, availableSpace, style); + cell->SetCachedSize(size); + } + } + + bottomY = rowTops[rowCount] - paddingY; + } +} // Lays the object out. rect is the space available for layout. Often it will // be the specified overall space for this object, if trying to constrain @@ -9329,7 +9617,7 @@ bool wxRichTextTable::Layout(wxDC& dc, wxRichTextDrawingContext& context, const { SetPosition(rect.GetPosition()); - // TODO: the meaty bit. Calculate sizes of all cells and rows. Try to use + // The meaty bit. Calculate sizes of all cells and rows. Try to use // minimum size if within alloted size, then divide up remaining size // between rows/cols. @@ -9343,12 +9631,13 @@ bool wxRichTextTable::Layout(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextAttr attr(GetAttributes()); context.ApplyVirtualAttributes(attr, this); + bool tableHasPercentWidth = (attr.GetTextBoxAttr().GetWidth().GetUnits() == wxTEXT_ATTR_UNITS_PERCENTAGE); // If we have no fixed table size, and assuming we're not pushed for // space, then we don't have to try to stretch the table to fit the contents. - bool stretchToFitTableWidth = false; - + bool stretchToFitTableWidth = tableHasPercentWidth; + int tableWidth = rect.width; - if (attr.GetTextBoxAttr().GetWidth().IsValid()) + if (attr.GetTextBoxAttr().GetWidth().IsValid() && !tableHasPercentWidth) { tableWidth = converter.GetPixels(attr.GetTextBoxAttr().GetWidth()); @@ -9445,12 +9734,9 @@ bool wxRichTextTable::Layout(wxDC& dc, wxRichTextDrawingContext& context, const { for (i = 0; i < m_colCount; i++) { - wxRichTextBox* cell = GetCell(j, i); - int colSpan = 1, rowSpan = 1; - if (cell->GetProperties().HasProperty(wxT("colspan"))) - colSpan = cell->GetProperties().GetPropertyLong(wxT("colspan")); - if (cell->GetProperties().HasProperty(wxT("rowspan"))) - rowSpan = cell->GetProperties().GetPropertyLong(wxT("rowspan")); + wxRichTextCell* cell = GetCell(j, i); + int colSpan = cell->GetColspan(); + int rowSpan = cell->GetRowspan(); if (colSpan > 1 || rowSpan > 1) { rectArray.Add(wxRect(i, j, colSpan, rowSpan)); @@ -9462,18 +9748,16 @@ bool wxRichTextTable::Layout(wxDC& dc, wxRichTextDrawingContext& context, const { for (i = 0; i < m_colCount; i++) { - wxRichTextBox* cell = GetCell(j, i); + wxRichTextCell* cell = GetCell(j, i); if (rectArray.GetCount() == 0) { cell->Show(true); } else { - int colSpan = 1, rowSpan = 1; - if (cell->GetProperties().HasProperty(wxT("colspan"))) - colSpan = cell->GetProperties().GetPropertyLong(wxT("colspan")); - if (cell->GetProperties().HasProperty(wxT("rowspan"))) - rowSpan = cell->GetProperties().GetPropertyLong(wxT("rowspan")); + int colSpan = cell->GetColspan(); + int rowSpan = cell->GetRowspan(); + if (colSpan > 1 || rowSpan > 1) { // Assume all spanning cells are shown @@ -9496,7 +9780,7 @@ bool wxRichTextTable::Layout(wxDC& dc, wxRichTextDrawingContext& context, const } } - // TODO: find the first spanned cell in each row that spans the most columns and doesn't + // Find the first spanned cell in each row that spans the most columns and doesn't // overlap with a spanned cell starting at a previous column position. // This means we need to keep an array of rects so we can check. However // it does also mean that some spans simply may not be taken into account @@ -9536,12 +9820,10 @@ bool wxRichTextTable::Layout(wxDC& dc, wxRichTextDrawingContext& context, const for (i = 0; i < m_colCount; i++) { - wxRichTextBox* cell = GetCell(j, i); + wxRichTextCell* cell = GetCell(j, i); if (cell->IsShown()) { - int colSpan = 1; - if (cell->GetProperties().HasProperty(wxT("colspan"))) - colSpan = cell->GetProperties().GetPropertyLong(wxT("colspan")); + int colSpan = cell->GetColspan(); // Lay out cell to find min/max widths cell->Invalidate(wxRICHTEXT_ALL); @@ -9661,13 +9943,10 @@ bool wxRichTextTable::Layout(wxDC& dc, wxRichTextDrawingContext& context, const for (i = 0; i < m_colCount; i++) { - wxRichTextBox* cell = GetCell(j, i); + wxRichTextCell* cell = GetCell(j, i); if (cell->IsShown()) { - int colSpan = 1; - if (cell->GetProperties().HasProperty(wxT("colspan"))) - colSpan = cell->GetProperties().GetPropertyLong(wxT("colspan")); - + int colSpan = cell->GetColspan(); if (colSpan > 1) { int spans = wxMin(colSpan, m_colCount - i); @@ -9750,7 +10029,6 @@ bool wxRichTextTable::Layout(wxDC& dc, wxRichTextDrawingContext& context, const int stretchColCount = 0; for (i = 0; i < m_colCount; i++) { - // TODO: we need to take into account min widths. // Subtract min width from width left, then // add the colShare to the min width if (colWidths[i] > 0) // absolute or proportional width has been specified @@ -9856,19 +10134,16 @@ bool wxRichTextTable::Layout(wxDC& dc, wxRichTextDrawingContext& context, const if (colWidths[i] > 0) // absolute or proportional width has been specified { - int colSpan = 1; - if (cell->GetProperties().HasProperty(wxT("colspan"))) - colSpan = cell->GetProperties().GetPropertyLong(wxT("colspan")); - + int colSpan = cell->GetColspan(); wxRect availableCellSpace; - // TODO: take into acount spans + // Take into account spans if (colSpan > 1) { // Calculate the size of this spanning cell from its constituent columns - int xx = x; + int xx = 0; int spans = wxMin(colSpan, m_colCount - i); - for (k = i; k < spans; k++) + for (k = i; k < (i+spans); k++) { if (k != i) xx += paddingX; @@ -9882,6 +10157,10 @@ bool wxRichTextTable::Layout(wxDC& dc, wxRichTextDrawingContext& context, const // Store actual width so we can force cell to be the appropriate width on the final loop actualWidths[i] = availableCellSpace.GetWidth(); + // We now need to shift right by the width of any rowspanning cells above-left of us + int deltaX = GetRowspanDisplacement(this, j, i, paddingX, colWidths); + availableCellSpace.SetX(availableCellSpace.GetX() + deltaX); + // Lay out cell cell->Invalidate(wxRICHTEXT_ALL); cell->Layout(dc, context, availableCellSpace, availableSpace, style); @@ -9889,7 +10168,7 @@ bool wxRichTextTable::Layout(wxDC& dc, wxRichTextDrawingContext& context, const // TODO: use GetCachedSize().x to compute 'natural' size x += (availableCellSpace.GetWidth() + paddingX); - if (cell->GetCachedSize().y > maxCellHeight) + if ((cell->GetCachedSize().y > maxCellHeight) && (cell->GetRowspan() < 2)) maxCellHeight = cell->GetCachedSize().y; } } @@ -9919,6 +10198,9 @@ bool wxRichTextTable::Layout(wxDC& dc, wxRichTextDrawingContext& context, const if (j < (m_rowCount-1)) y += paddingY; } + + // Finally we need to expand any cell with rowspan > 1. We couldn't earlier; lower rows' heights weren't known + ExpandCellsWithRowspan(this, paddingY, y, dc, context, availableSpace, style); // We need to add back the margins etc. { @@ -10087,12 +10369,17 @@ void wxRichTextTable::ClearTable() { m_cells.Clear(); DeleteChildren(); + m_rowCount = 0; + m_colCount = 0; } bool wxRichTextTable::CreateTable(int rows, int cols) { ClearTable(); + wxRichTextAttr cellattr; + cellattr.SetTextColour(GetBasicStyle().GetTextColour()); + m_rowCount = rows; m_colCount = cols; @@ -10105,6 +10392,8 @@ bool wxRichTextTable::CreateTable(int rows, int cols) for (j = 0; j < cols; j++) { wxRichTextCell* cell = new wxRichTextCell; + cell->GetAttributes() = cellattr; + AppendChild(cell); cell->AddParagraph(wxEmptyString); @@ -10239,12 +10528,67 @@ bool wxRichTextTable::SetCellStyle(const wxRichTextSelection& selection, const w return true; } +wxPosition wxRichTextTable::GetFocusedCell() const +{ + wxPosition position(-1, -1); + const wxRichTextObject* focus = GetBuffer()->GetRichTextCtrl()->GetFocusObject(); + + for (int row = 0; row < GetRowCount(); ++row) + { + for (int col = 0; col < GetColumnCount(); ++col) + { + if (GetCell(row, col) == focus) + { + position.SetRow(row); + position.SetCol(col); + return position; + } + } + } + + return position; +} + +int wxRichTextTable::HitTest(wxDC& dc, wxRichTextDrawingContext& context, const wxPoint& pt, long& textPosition, wxRichTextObject** obj, wxRichTextObject** contextObj, int flags) +{ + for (int row = 0; row < GetRowCount(); ++row) + { + for (int col = 0; col < GetColumnCount(); ++col) + { + wxRichTextCell* cell = GetCell(row, col); + if (cell->wxRichTextObject::HitTest(dc, context, pt, textPosition, obj, contextObj, flags) != wxRICHTEXT_HITTEST_NONE) + { + return cell->HitTest(dc, context, pt, textPosition, obj, contextObj, flags); + } + } + } + + return wxRICHTEXT_HITTEST_NONE; +} + bool wxRichTextTable::DeleteRows(int startRow, int noRows) { wxASSERT((startRow + noRows) <= m_rowCount); if ((startRow + noRows) > m_rowCount) return false; + wxCHECK_MSG(noRows != m_rowCount, false, "Trying to delete all the cells in a table"); + + wxRichTextBuffer* buffer = GetBuffer(); + wxRichTextCtrl* rtc = buffer->GetRichTextCtrl(); + + wxRichTextAction* action = NULL; + wxRichTextTable* clone = NULL; + if (!rtc->SuppressingUndo()) + { + // Create a clone containing the current state of the table. It will be used to Undo the action + clone = wxStaticCast(this->Clone(), wxRichTextTable); + clone->SetParent(GetParent()); + action = new wxRichTextAction(NULL, _("Delete Row"), wxRICHTEXT_CHANGE_OBJECT, buffer, this, rtc); + action->SetObject(this); + action->SetPosition(GetRange().GetStart()); + } + int i, j; for (i = startRow; i < (startRow+noRows); i++) { @@ -10262,6 +10606,13 @@ bool wxRichTextTable::DeleteRows(int startRow, int noRows) m_rowCount = m_rowCount - noRows; + if (!rtc->SuppressingUndo()) + { + buffer->SubmitAction(action); + // Finally store the original-state clone; doing so earlier would cause various failures + action->StoreObject(clone); + } + return true; } @@ -10271,6 +10622,23 @@ bool wxRichTextTable::DeleteColumns(int startCol, int noCols) if ((startCol + noCols) > m_colCount) return false; + wxCHECK_MSG(noCols != m_colCount, false, "Trying to delete all the cells in a table"); + + wxRichTextBuffer* buffer = GetBuffer(); + wxRichTextCtrl* rtc = buffer->GetRichTextCtrl(); + + wxRichTextAction* action = NULL; + wxRichTextTable* clone = NULL; + if (!rtc->SuppressingUndo()) + { + // Create a clone containing the current state of the table. It will be used to Undo the action + clone = wxStaticCast(this->Clone(), wxRichTextTable); + clone->SetParent(GetParent()); + action = new wxRichTextAction(NULL, _("Delete Column"), wxRICHTEXT_CHANGE_OBJECT, buffer, this, rtc); + action->SetObject(this); + action->SetPosition(GetRange().GetStart()); + } + bool deleteRows = (noCols == m_colCount); int i, j; @@ -10292,6 +10660,13 @@ bool wxRichTextTable::DeleteColumns(int startCol, int noCols) m_rowCount = 0; m_colCount = m_colCount - noCols; + if (!rtc->SuppressingUndo()) + { + buffer->SubmitAction(action); + // Finally store the original-state clone; doing so earlier would cause various failures + action->StoreObject(clone); + } + return true; } @@ -10301,6 +10676,24 @@ bool wxRichTextTable::AddRows(int startRow, int noRows, const wxRichTextAttr& at if (startRow > m_rowCount) return false; + wxRichTextBuffer* buffer = GetBuffer(); + wxRichTextAction* action = NULL; + wxRichTextTable* clone = NULL; + + if (!buffer->GetRichTextCtrl()->SuppressingUndo()) + { + // Create a clone containing the current state of the table. It will be used to Undo the action + clone = wxStaticCast(this->Clone(), wxRichTextTable); + clone->SetParent(GetParent()); + action = new wxRichTextAction(NULL, _("Add Row"), wxRICHTEXT_CHANGE_OBJECT, buffer, this, buffer->GetRichTextCtrl()); + action->SetObject(this); + action->SetPosition(GetRange().GetStart()); + } + + wxRichTextAttr cellattr = attr; + if (!cellattr.GetTextColour().IsOk()) + cellattr.SetTextColour(buffer->GetBasicStyle().GetTextColour()); + int i, j; for (i = 0; i < noRows; i++) { @@ -10320,7 +10713,7 @@ bool wxRichTextTable::AddRows(int startRow, int noRows, const wxRichTextAttr& at for (j = 0; j < m_colCount; j++) { wxRichTextCell* cell = new wxRichTextCell; - cell->GetAttributes() = attr; + cell->GetAttributes() = cellattr; AppendChild(cell); cell->AddParagraph(wxEmptyString); @@ -10329,6 +10722,14 @@ bool wxRichTextTable::AddRows(int startRow, int noRows, const wxRichTextAttr& at } m_rowCount = m_rowCount + noRows; + + if (!buffer->GetRichTextCtrl()->SuppressingUndo()) + { + buffer->SubmitAction(action); + // Finally store the original-state clone; doing so earlier would cause various failures + action->StoreObject(clone); + } + return true; } @@ -10338,6 +10739,24 @@ bool wxRichTextTable::AddColumns(int startCol, int noCols, const wxRichTextAttr& if (startCol > m_colCount) return false; + wxRichTextBuffer* buffer = GetBuffer(); + wxRichTextAction* action = NULL; + wxRichTextTable* clone = NULL; + + if (!buffer->GetRichTextCtrl()->SuppressingUndo()) + { + // Create a clone containing the current state of the table. It will be used to Undo the action + clone = wxStaticCast(this->Clone(), wxRichTextTable); + clone->SetParent(GetParent()); + action = new wxRichTextAction(NULL, _("Add Column"), wxRICHTEXT_CHANGE_OBJECT, buffer, this, buffer->GetRichTextCtrl()); + action->SetObject(this); + action->SetPosition(GetRange().GetStart()); + } + + wxRichTextAttr cellattr = attr; + if (!cellattr.GetTextColour().IsOk()) + cellattr.SetTextColour(buffer->GetBasicStyle().GetTextColour()); + int i, j; for (i = 0; i < m_rowCount; i++) { @@ -10345,7 +10764,7 @@ bool wxRichTextTable::AddColumns(int startCol, int noCols, const wxRichTextAttr& for (j = 0; j < noCols; j++) { wxRichTextCell* cell = new wxRichTextCell; - cell->GetAttributes() = attr; + cell->GetAttributes() = cellattr; AppendChild(cell); cell->AddParagraph(wxEmptyString); @@ -10359,6 +10778,13 @@ bool wxRichTextTable::AddColumns(int startCol, int noCols, const wxRichTextAttr& m_colCount = m_colCount + noCols; + if (!buffer->GetRichTextCtrl()->SuppressingUndo()) + { + buffer->SubmitAction(action); + // Finally store the original-state clone; doing so earlier would cause various failures + action->StoreObject(clone); + } + return true; } @@ -10377,6 +10803,90 @@ bool wxRichTextTable::EditProperties(wxWindow* parent, wxRichTextBuffer* buffer) return false; } +bool wxRichTextTableBlock::ComputeBlockForSelection(wxRichTextTable* table, wxRichTextCtrl* ctrl, bool requireCellSelection) +{ + if (!ctrl) + return false; + + ColStart() = 0; + ColEnd() = table->GetColumnCount()-1; + RowStart() = 0; + RowEnd() = table->GetRowCount()-1; + + wxRichTextSelection selection = ctrl->GetSelection(); + if (selection.IsValid() && selection.GetContainer() == table) + { + // Start with an invalid block and increase. + wxRichTextTableBlock selBlock(-1, -1, -1, -1); + wxRichTextRangeArray ranges = selection.GetRanges(); + int row, col; + for (row = 0; row < table->GetRowCount(); row++) + { + for (col = 0; col < table->GetColumnCount(); col++) + { + if (selection.WithinSelection(table->GetCell(row, col)->GetRange().GetStart())) + { + if (selBlock.ColStart() == -1) + selBlock.ColStart() = col; + if (selBlock.ColEnd() == -1) + selBlock.ColEnd() = col; + if (col < selBlock.ColStart()) + selBlock.ColStart() = col; + if (col > selBlock.ColEnd()) + selBlock.ColEnd() = col; + + if (selBlock.RowStart() == -1) + selBlock.RowStart() = row; + if (selBlock.RowEnd() == -1) + selBlock.RowEnd() = row; + if (row < selBlock.RowStart()) + selBlock.RowStart() = row; + if (row > selBlock.RowEnd()) + selBlock.RowEnd() = row; + } + } + } + + if (selBlock.RowStart() != -1 && selBlock.RowEnd() != -1 && selBlock.ColStart() != -1 && selBlock.ColEnd() != -1) + (*this) = selBlock; + } + else + { + // See if a whole cell's contents is selected, in which case we can treat the cell as selected. + // wxRTC lacks the ability to select a single cell. + wxRichTextCell* cell = wxDynamicCast(ctrl->GetFocusObject(), wxRichTextCell); + if (cell && (!requireCellSelection || (ctrl->HasSelection() && ctrl->GetSelectionRange() == cell->GetOwnRange()))) + { + int row, col; + if (table->GetCellRowColumnPosition(cell->GetRange().GetStart(), row, col)) + { + RowStart() = row; + RowEnd() = row; + ColStart() = col; + ColEnd() = col; + } + } + } + + return true; +} + +// Does this block represent the whole table? +bool wxRichTextTableBlock::IsWholeTable(wxRichTextTable* table) const +{ + return (ColStart() == 0 && RowStart() == 0 && ColEnd() == (table->GetColumnCount()-1) && RowEnd() == (table->GetRowCount()-1)); +} + +// Returns the cell focused in the table, if any +wxRichTextCell* wxRichTextTableBlock::GetFocusedCell(wxRichTextCtrl* ctrl) +{ + if (!ctrl) + return NULL; + + wxRichTextCell* cell = wxDynamicCast(ctrl->GetFocusObject(), wxRichTextCell); + return cell; +} + /* * Module to initialise and clean up handlers */ @@ -10426,8 +10936,8 @@ IMPLEMENT_DYNAMIC_CLASS(wxRichTextModule, wxModule) void wxRichTextModuleInit() { wxModule* module = new wxRichTextModule; - module->Init(); wxModule::RegisterModule(module); + wxModule::InitializeModules(); } @@ -10736,10 +11246,21 @@ bool wxRichTextAction::Do() case wxRICHTEXT_CHANGE_OBJECT: { wxRichTextObject* obj = m_objectAddress.GetObject(m_buffer); - // wxRichTextObject* obj = container->GetChildAtPosition(GetRange().GetStart()); - if (obj && m_object) + if (obj && m_object && m_ctrl) { - wxRichTextObjectList::compatibility_iterator node = container->GetChildren().Find(obj); + // The plan is to swap the current object with the stored, previous-state, clone + // We can't get 'node' from the containing buffer (as it doesn't directly store objects) + // so use the parent paragraph + wxRichTextParagraph* para = wxDynamicCast(obj->GetParent(), wxRichTextParagraph); + wxCHECK_MSG(para, false, "Invalid parent paragraph"); + + // The stored object, m_object, may have a stale parent paragraph. This would cause + // a crash during layout, so use obj's parent para, which should be the correct one. + // (An alternative would be to return the parent too from m_objectAddress.GetObject(), + // or to set obj's parent there before returning) + m_object->SetParent(para); + + wxRichTextObjectList::compatibility_iterator node = para->GetChildren().Find(obj); if (node) { wxRichTextObject* obj = node->GetData(); @@ -10748,6 +11269,12 @@ bool wxRichTextAction::Do() } } + // We can't rely on the current focus-object remaining valid, if it's e.g. a table's cell. + // And we can't cope with this in the calling code: a user may later click in the cell + // before deciding to Undo() or Redo(). So play safe and set focus to the buffer. + if (m_ctrl) + m_ctrl->SetFocusObject(m_buffer, false); + // InvalidateHierarchy goes up the hierarchy as well as down, otherwise with a nested object, // Layout() would stop prematurely at the top level. // Invalidate the whole buffer if there were floating objects @@ -10756,7 +11283,7 @@ bool wxRichTextAction::Do() else container->InvalidateHierarchy(GetRange()); - UpdateAppearance(GetPosition()); + UpdateAppearance(GetPosition(), true); // TODO: send new kind of modification event @@ -10928,13 +11455,16 @@ void wxRichTextAction::UpdateAppearance(long caretPosition, bool sendUpdateEvent // find the first line which is being drawn at the same position as it was // before. Since we're talking about a simple insertion, we can assume // that the rest of the window does not need to be redrawn. + long pos = GetRange().GetStart(); - wxRichTextParagraph* para = container->GetParagraphAtPosition(GetPosition()); + wxRichTextParagraph* para = container->GetParagraphAtPosition(pos, false /* is not caret pos */); // Since we support floating layout, we should redraw the whole para instead of just // the first line touching the invalid range. if (para) { - firstY = para->GetPosition().y; + // In case something was drawn above the paragraph, + // such as a line break, allow a little extra. + firstY = para->GetPosition().y - 4; } wxRichTextObjectList::compatibility_iterator node = container->GetChildren().Find(para); @@ -10982,7 +11512,7 @@ void wxRichTextAction::UpdateAppearance(long caretPosition, bool sendUpdateEvent // Stop, we're now the same as we were foundEnd = true; - lastY = pt.y; + lastY = pt.y + line->GetSize().y; node2 = wxRichTextLineList::compatibility_iterator(); node = wxRichTextObjectList::compatibility_iterator(); @@ -13482,9 +14012,8 @@ void wxTextAttrCollectCommonAttributes(wxTextAttr& currentStyle, const wxTextAtt } WX_DEFINE_OBJARRAY(wxRichTextVariantArray); - -// JACS 2013-01-27 WX_DEFINE_OBJARRAY(wxRichTextAttrArray); +WX_DEFINE_OBJARRAY(wxRichTextRectArray); IMPLEMENT_DYNAMIC_CLASS(wxRichTextProperties, wxObject)