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)
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);
+ for (int 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;
+
+ int row, col;
+ for (row = 0; row < rowCount-1; ++row) // -1 as the bottom row can't rowspan
+ {
+ for (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 (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
// layout to a particular size, or it could be the total space available in the
{
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.
{
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));
{
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
}
}
- // 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
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);
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);
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
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;
// 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);
// 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;
}
}
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.
{
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;
+}
+
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();
+
+ wxPosition position = GetFocusedCell();
+ int focusCol = position.GetCol();
+ int focusRow = position.GetRow();
+ if (focusRow >= startRow && focusRow < (startRow+noRows))
+ {
+ // Deleting a focused cell causes a segfault later when laying out, due to GetFocusedObject() returning an invalid object
+ if ((startRow + noRows) < m_rowCount)
+ {
+ // There are more rows after the one(s) to be deleted, so set focus in the first of them
+ rtc->SetFocusObject(GetCell(startRow + noRows, focusCol));
+ }
+ else
+ {
+ // Otherwise set focus in the preceding row
+ rtc->SetFocusObject(GetCell(startRow - 1, focusCol));
+ }
+ }
+
wxRichTextAction* action = NULL;
wxRichTextTable* clone = NULL;
- if (!buffer->GetRichTextCtrl()->SuppressingUndo())
+ 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, buffer->GetRichTextCtrl());
+ action = new wxRichTextAction(NULL, _("Delete row"), wxRICHTEXT_CHANGE_OBJECT, buffer, this, rtc);
action->SetObject(this);
action->SetPosition(GetRange().GetStart());
}
m_rowCount = m_rowCount - noRows;
- if (!buffer->GetRichTextCtrl()->SuppressingUndo())
+ if (!rtc->SuppressingUndo())
{
buffer->SubmitAction(action);
// Finally store the original-state clone; doing so earlier would cause various failures
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();
+
+ wxPosition position = GetFocusedCell();
+ int focusCol = position.GetCol();
+ int focusRow = position.GetRow();
+ if (focusCol >= startCol && focusCol < (startCol+noCols))
+ {
+ // Deleting a focused cell causes a segfault later when laying out, due to GetFocusedObject() returning an invalid object
+ if ((startCol + noCols) < m_colCount)
+ {
+ // There are more columns after the one(s) to be deleted, so set focus in the first of them
+ rtc->SetFocusObject(GetCell(focusRow, startCol + noCols));
+ }
+ else
+ {
+ // Otherwise set focus in the preceding column
+ rtc->SetFocusObject(GetCell(focusRow, startCol - 1));
+ }
+ }
+
wxRichTextAction* action = NULL;
wxRichTextTable* clone = NULL;
- if (!buffer->GetRichTextCtrl()->SuppressingUndo())
+ 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, buffer->GetRichTextCtrl());
+ action = new wxRichTextAction(NULL, _("Delete column"), wxRICHTEXT_CHANGE_OBJECT, buffer, this, rtc);
action->SetObject(this);
action->SetPosition(GetRange().GetStart());
}
m_rowCount = 0;
m_colCount = m_colCount - noCols;
- if (!buffer->GetRichTextCtrl()->SuppressingUndo())
+ if (!rtc->SuppressingUndo())
{
buffer->SubmitAction(action);
// Finally store the original-state clone; doing so earlier would cause various failures
wxRichTextObject* obj = m_objectAddress.GetObject(m_buffer);
if (obj && m_object && m_ctrl)
{
- // If the cloned object is unparented it will cause layout asserts later
- // An alternative (would it always be valid?) could be to do: m_object->SetParent(obj->GetParent())
- wxCHECK_MSG(m_object->GetParent(), false, "The stored object must have a valid parent");
-
// 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)
{
// 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);
// 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();