// Author: Julian Smart
// Modified by:
// Created: 2005-09-30
-// RCS-ID: $Id$
// Copyright: (c) Julian Smart
// Licence: wxWindows licence
/////////////////////////////////////////////////////////////////////////////
// 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;
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);
}
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);
}
}
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);
}
}
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);
/// 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
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.
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);
}
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;
return wildcard;
}
+#if wxUSE_FFILE && wxUSE_STREAMS
/// Load a file
bool wxRichTextBuffer::LoadFile(const wxString& filename, wxRichTextFileType type)
{
else
return false;
}
+#endif // wxUSE_FFILE && wxUSE_STREAMS
+#if wxUSE_STREAMS
/// Load from a stream
bool wxRichTextBuffer::LoadFile(wxInputStream& stream, wxRichTextFileType type)
{
else
return false;
}
+#endif // wxUSE_STREAMS
/// Copy the range to the clipboard
bool wxRichTextBuffer::CopyToClipboard(const wxRichTextRange& range)
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)
{
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)
{
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)
// 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
{
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.
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());
{
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.
{
{
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;
for (j = 0; j < cols; j++)
{
wxRichTextCell* cell = new wxRichTextCell;
+ cell->GetAttributes() = cellattr;
+
AppendChild(cell);
cell->AddParagraph(wxEmptyString);
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)
+ 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++)
{
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;
}
bool wxRichTextTable::DeleteColumns(int startCol, int noCols)
{
- wxASSERT((startCol + noCols) < m_colCount);
- if ((startCol + noCols) >= m_colCount)
+ wxASSERT((startCol + noCols) <= m_colCount);
+ 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;
for (i = 0; i < m_rowCount; i++)
{
wxRichTextObjectPtrArray& colArray = m_cells[deleteRows ? 0 : i];
- for (j = startCol; j < (startCol+noCols); j++)
+ for (j = 0; j < noCols; j++)
{
- wxRichTextObject* cell = colArray[j];
+ wxRichTextObject* cell = colArray[startCol];
RemoveChild(cell, true);
+ colArray.RemoveAt(startCol);
}
if (deleteRows)
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;
}
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++)
{
for (j = 0; j < m_colCount; j++)
{
wxRichTextCell* cell = new wxRichTextCell;
- cell->GetAttributes() = attr;
+ cell->GetAttributes() = cellattr;
AppendChild(cell);
cell->AddParagraph(wxEmptyString);
}
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;
}
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++)
{
for (j = 0; j < noCols; j++)
{
wxRichTextCell* cell = new wxRichTextCell;
- cell->GetAttributes() = attr;
+ cell->GetAttributes() = cellattr;
AppendChild(cell);
cell->AddParagraph(wxEmptyString);
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;
}
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
*/
void wxRichTextModuleInit()
{
wxModule* module = new wxRichTextModule;
- module->Init();
wxModule::RegisterModule(module);
+ wxModule::InitializeModules();
}
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();
}
}
+ // 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
else
container->InvalidateHierarchy(GetRange());
- UpdateAppearance(GetPosition());
+ UpdateAppearance(GetPosition(), true);
// TODO: send new kind of modification event
// 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();
}
WX_DEFINE_OBJARRAY(wxRichTextVariantArray);
-
-// JACS 2013-01-27
WX_DEFINE_OBJARRAY(wxRichTextAttrArray);
+WX_DEFINE_OBJARRAY(wxRichTextRectArray);
IMPLEMENT_DYNAMIC_CLASS(wxRichTextProperties, wxObject)