+ wxCheckSetFont(dc, font);
+
+ int charHeight = dc.GetCharHeight();
+
+ int bulletWidth = (int) (((float) charHeight) * wxRichTextBuffer::GetBulletProportion());
+ int bulletHeight = bulletWidth;
+
+ int x = rect.x;
+
+ // Calculate the top position of the character (as opposed to the whole line height)
+ int y = rect.y + (rect.height - charHeight);
+
+ // Calculate where the bullet should be positioned
+ y = y + (charHeight+1)/2 - (bulletHeight+1)/2;
+
+ // The margin between a bullet and text.
+ int margin = paragraph->ConvertTenthsMMToPixels(dc, wxRichTextBuffer::GetBulletRightMargin());
+
+ if (bulletAttr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ALIGN_RIGHT)
+ x = rect.x + rect.width - bulletWidth - margin;
+ else if (bulletAttr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ALIGN_CENTRE)
+ x = x + (rect.width)/2 - bulletWidth/2;
+
+ if (bulletAttr.GetBulletName() == wxT("standard/square"))
+ {
+ dc.DrawRectangle(x, y, bulletWidth, bulletHeight);
+ }
+ else if (bulletAttr.GetBulletName() == wxT("standard/diamond"))
+ {
+ wxPoint pts[5];
+ pts[0].x = x; pts[0].y = y + bulletHeight/2;
+ pts[1].x = x + bulletWidth/2; pts[1].y = y;
+ pts[2].x = x + bulletWidth; pts[2].y = y + bulletHeight/2;
+ pts[3].x = x + bulletWidth/2; pts[3].y = y + bulletHeight;
+
+ dc.DrawPolygon(4, pts);
+ }
+ else if (bulletAttr.GetBulletName() == wxT("standard/triangle"))
+ {
+ wxPoint pts[3];
+ pts[0].x = x; pts[0].y = y;
+ pts[1].x = x + bulletWidth; pts[1].y = y + bulletHeight/2;
+ pts[2].x = x; pts[2].y = y + bulletHeight;
+
+ dc.DrawPolygon(3, pts);
+ }
+ else if (bulletAttr.GetBulletName() == wxT("standard/circle-outline"))
+ {
+ wxCheckSetBrush(dc, *wxTRANSPARENT_BRUSH);
+ dc.DrawEllipse(x, y, bulletWidth, bulletHeight);
+ }
+ else // "standard/circle", and catch-all
+ {
+ dc.DrawEllipse(x, y, bulletWidth, bulletHeight);
+ }
+
+ return true;
+}
+
+bool wxRichTextStdRenderer::DrawTextBullet(wxRichTextParagraph* paragraph, wxDC& dc, const wxRichTextAttr& attr, const wxRect& rect, const wxString& text)
+{
+ if (!text.empty())
+ {
+ wxFont font;
+ if ((attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_SYMBOL) && !attr.GetBulletFont().IsEmpty() && attr.HasFont())
+ {
+ wxRichTextAttr fontAttr;
+ fontAttr.SetFontSize(attr.GetFontSize());
+ fontAttr.SetFontStyle(attr.GetFontStyle());
+ fontAttr.SetFontWeight(attr.GetFontWeight());
+ fontAttr.SetFontUnderlined(attr.GetFontUnderlined());
+ fontAttr.SetFontFaceName(attr.GetBulletFont());
+ font = paragraph->GetBuffer()->GetFontTable().FindFont(fontAttr);
+ }
+ else if (attr.HasFont())
+ font = paragraph->GetBuffer()->GetFontTable().FindFont(attr);
+ else
+ font = (*wxNORMAL_FONT);
+
+ wxCheckSetFont(dc, font);
+
+ if (attr.GetTextColour().IsOk())
+ dc.SetTextForeground(attr.GetTextColour());
+
+ dc.SetBackgroundMode(wxBRUSHSTYLE_TRANSPARENT);
+
+ int charHeight = dc.GetCharHeight();
+ wxCoord tw, th;
+ dc.GetTextExtent(text, & tw, & th);
+
+ int x = rect.x;
+
+ // Calculate the top position of the character (as opposed to the whole line height)
+ int y = rect.y + (rect.height - charHeight);
+
+ // The margin between a bullet and text.
+ int margin = paragraph->ConvertTenthsMMToPixels(dc, wxRichTextBuffer::GetBulletRightMargin());
+
+ if (attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ALIGN_RIGHT)
+ x = (rect.x + rect.width) - tw - margin;
+ else if (attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ALIGN_CENTRE)
+ x = x + (rect.width)/2 - tw/2;
+
+ dc.DrawText(text, x, y);
+
+ return true;
+ }
+ else
+ return false;
+}
+
+bool wxRichTextStdRenderer::DrawBitmapBullet(wxRichTextParagraph* WXUNUSED(paragraph), wxDC& WXUNUSED(dc), const wxRichTextAttr& WXUNUSED(attr), const wxRect& WXUNUSED(rect))
+{
+ // Currently unimplemented. The intention is to store bitmaps by name in a media store associated
+ // with the buffer. The store will allow retrieval from memory, disk or other means.
+ return false;
+}
+
+/// Enumerate the standard bullet names currently supported
+bool wxRichTextStdRenderer::EnumerateStandardBulletNames(wxArrayString& bulletNames)
+{
+ bulletNames.Add(wxTRANSLATE("standard/circle"));
+ bulletNames.Add(wxTRANSLATE("standard/circle-outline"));
+ bulletNames.Add(wxTRANSLATE("standard/square"));
+ bulletNames.Add(wxTRANSLATE("standard/diamond"));
+ bulletNames.Add(wxTRANSLATE("standard/triangle"));
+
+ return true;
+}
+
+/*!
+ * wxRichTextBox
+ */
+
+IMPLEMENT_DYNAMIC_CLASS(wxRichTextBox, wxRichTextParagraphLayoutBox)
+
+wxRichTextBox::wxRichTextBox(wxRichTextObject* parent):
+ wxRichTextParagraphLayoutBox(parent)
+{
+}
+
+/// Draw the item
+bool wxRichTextBox::Draw(wxDC& dc, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int style)
+{
+ if (!IsShown())
+ return true;
+
+ // TODO: if the active object in the control, draw an indication.
+ // We need to add the concept of active object, and not just focus object,
+ // so we can apply commands (properties, delete, ...) to objects such as text boxes and images.
+ // Ultimately we would like to be able to interactively resize an active object
+ // using drag handles.
+ return wxRichTextParagraphLayoutBox::Draw(dc, range, selection, rect, descent, style);
+}
+
+/// Copy
+void wxRichTextBox::Copy(const wxRichTextBox& obj)
+{
+ wxRichTextParagraphLayoutBox::Copy(obj);
+}
+
+// Edit properties via a GUI
+bool wxRichTextBox::EditProperties(wxWindow* parent, wxRichTextBuffer* buffer)
+{
+ wxRichTextObjectPropertiesDialog boxDlg(this, wxGetTopLevelParent(parent), wxID_ANY, _("Box Properties"));
+ boxDlg.SetAttributes(GetAttributes());
+
+ if (boxDlg.ShowModal() == wxID_OK)
+ {
+ // By passing wxRICHTEXT_SETSTYLE_RESET, indeterminate attributes set by the user will be set as
+ // indeterminate in the object.
+ boxDlg.ApplyStyle(buffer->GetRichTextCtrl(), wxRICHTEXT_SETSTYLE_WITH_UNDO|wxRICHTEXT_SETSTYLE_RESET);
+ return true;
+ }
+ else
+ return false;
+}
+
+IMPLEMENT_DYNAMIC_CLASS(wxRichTextCell, wxRichTextBox)
+
+wxRichTextCell::wxRichTextCell(wxRichTextObject* parent):
+ wxRichTextBox(parent)
+{
+}
+
+/// Draw the item
+bool wxRichTextCell::Draw(wxDC& dc, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int style)
+{
+ return wxRichTextBox::Draw(dc, range, selection, rect, descent, style);
+}
+
+/// Copy
+void wxRichTextCell::Copy(const wxRichTextCell& obj)
+{
+ wxRichTextBox::Copy(obj);
+}
+
+// Edit properties via a GUI
+bool wxRichTextCell::EditProperties(wxWindow* parent, wxRichTextBuffer* buffer)
+{
+ // We need to gather common attributes for all selected cells.
+
+ wxRichTextTable* table = wxDynamicCast(GetParent(), wxRichTextTable);
+ bool multipleCells = false;
+ wxRichTextAttr attr;
+
+ if (table && buffer && buffer->GetRichTextCtrl() && buffer->GetRichTextCtrl()->GetSelection().IsValid() &&
+ buffer->GetRichTextCtrl()->GetSelection().GetContainer() == GetParent())
+ {
+ wxRichTextAttr clashingAttr, absentAttr;
+ const wxRichTextSelection& sel = buffer->GetRichTextCtrl()->GetSelection();
+ size_t i;
+ int selectedCellCount = 0;
+ for (i = 0; i < sel.GetCount(); i++)
+ {
+ const wxRichTextRange& range = sel[i];
+ wxRichTextCell* cell = table->GetCell(range.GetStart());
+ if (cell)
+ {
+ wxRichTextAttr cellStyle = cell->GetAttributes();
+
+ CollectStyle(attr, cellStyle, clashingAttr, absentAttr);
+
+ selectedCellCount ++;
+ }
+ }
+ multipleCells = selectedCellCount > 1;
+ }
+ else
+ {
+ attr = GetAttributes();
+ }
+
+ wxString caption;
+ if (multipleCells)
+ caption = _("Multiple Cell Properties");
+ else
+ caption = _("Cell Properties");
+
+ wxRichTextObjectPropertiesDialog cellDlg(this, wxGetTopLevelParent(parent), wxID_ANY, caption);
+ cellDlg.SetAttributes(attr);
+
+ wxRichTextSizePage* sizePage = wxDynamicCast(cellDlg.FindPage(CLASSINFO(wxRichTextSizePage)), wxRichTextSizePage);
+ if (sizePage)
+ {
+ // We don't want position and floating controls for a cell.
+ sizePage->ShowPositionControls(false);
+ sizePage->ShowFloatingControls(false);
+ }
+
+ if (cellDlg.ShowModal() == wxID_OK)
+ {
+ if (multipleCells)
+ {
+ const wxRichTextSelection& sel = buffer->GetRichTextCtrl()->GetSelection();
+ // Apply the style; we interpret indeterminate attributes as 'don't touch this attribute'
+ // since it may represent clashing attributes across multiple objects.
+ table->SetCellStyle(sel, attr);
+ }
+ else
+ // For a single object, indeterminate attributes set by the user should be reflected in the
+ // actual object style, so pass the wxRICHTEXT_SETSTYLE_RESET flag to assign
+ // the style directly instead of applying (which ignores indeterminate attributes,
+ // leaving them as they were).
+ cellDlg.ApplyStyle(buffer->GetRichTextCtrl(), wxRICHTEXT_SETSTYLE_WITH_UNDO|wxRICHTEXT_SETSTYLE_RESET);
+ return true;
+ }
+ else
+ return false;
+}
+
+WX_DEFINE_OBJARRAY(wxRichTextObjectPtrArrayArray)
+
+IMPLEMENT_DYNAMIC_CLASS(wxRichTextTable, wxRichTextBox)
+
+wxRichTextTable::wxRichTextTable(wxRichTextObject* parent): wxRichTextBox(parent)
+{
+ m_rowCount = 0;
+ m_colCount = 0;
+}
+
+// Draws the object.
+bool wxRichTextTable::Draw(wxDC& dc, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int style)
+{
+ return wxRichTextBox::Draw(dc, range, selection, rect, descent, style);
+}
+
+WX_DECLARE_OBJARRAY(wxRect, wxRichTextRectArray);
+WX_DEFINE_OBJARRAY(wxRichTextRectArray);
+
+// 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
+// parent. rect is the overall size, so we must subtract margins and padding.
+// to get the actual available space.
+bool wxRichTextTable::Layout(wxDC& dc, const wxRect& rect, int style)
+{
+ SetPosition(rect.GetPosition());
+
+ // TODO: 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.
+
+ double scale = 1.0;
+ wxRichTextBuffer* buffer = GetBuffer();
+ if (buffer) scale = buffer->GetScale();
+
+ wxRect availableSpace = GetAvailableContentArea(dc, rect);
+ wxTextAttrDimensionConverter converter(dc, scale, availableSpace.GetSize());
+
+ // 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;
+
+ int tableWidth = rect.width;
+ if (GetAttributes().GetTextBoxAttr().GetWidth().IsValid())
+ {
+ tableWidth = converter.GetPixels(GetAttributes().GetTextBoxAttr().GetWidth());
+
+ // Fixed table width, so we do want to stretch columns out if necessary.
+ stretchToFitTableWidth = true;
+
+ // Shouldn't be able to exceed the size passed to this function
+ tableWidth = wxMin(rect.width, tableWidth);
+ }
+
+ // Get internal padding
+ int paddingLeft = 0, paddingRight = 0, paddingTop = 0, paddingBottom = 0;
+ if (GetAttributes().GetTextBoxAttr().GetPadding().GetLeft().IsValid())
+ paddingLeft = converter.GetPixels(GetAttributes().GetTextBoxAttr().GetPadding().GetLeft());
+ if (GetAttributes().GetTextBoxAttr().GetPadding().GetRight().IsValid())
+ paddingRight = converter.GetPixels(GetAttributes().GetTextBoxAttr().GetPadding().GetRight());
+ if (GetAttributes().GetTextBoxAttr().GetPadding().GetTop().IsValid())
+ paddingTop = converter.GetPixels(GetAttributes().GetTextBoxAttr().GetPadding().GetTop());
+ if (GetAttributes().GetTextBoxAttr().GetPadding().GetLeft().IsValid())
+ paddingBottom = converter.GetPixels(GetAttributes().GetTextBoxAttr().GetPadding().GetBottom());
+
+ // Assume that left and top padding are also used for inter-cell padding.
+ int paddingX = paddingLeft;
+ int paddingY = paddingTop;
+
+ int totalLeftMargin = 0, totalRightMargin = 0, totalTopMargin = 0, totalBottomMargin = 0;
+ GetTotalMargin(dc, buffer, GetAttributes(), totalLeftMargin, totalRightMargin, totalTopMargin, totalBottomMargin);
+
+ // Internal table width - the area for content
+ int internalTableWidth = tableWidth - totalLeftMargin - totalRightMargin;
+
+ int rowCount = m_cells.GetCount();
+ if (m_colCount == 0 || rowCount == 0)
+ {
+ wxRect overallRect(rect.x, rect.y, totalLeftMargin + totalRightMargin, totalTopMargin + totalBottomMargin);
+ SetCachedSize(overallRect.GetSize());
+
+ // Zero content size
+ SetMinSize(overallRect.GetSize());
+ SetMaxSize(GetMinSize());
+ return true;
+ }
+
+ // The final calculated widths
+ wxArrayInt colWidths(m_colCount);
+
+ wxArrayInt absoluteColWidths(m_colCount);
+ // wxArrayInt absoluteColWidthsSpanning(m_colCount);
+ wxArrayInt percentageColWidths(m_colCount);
+ // wxArrayInt percentageColWidthsSpanning(m_colCount);
+ // These are only relevant when the first column contains spanning information.
+ // wxArrayInt columnSpans(m_colCount); // Each contains 1 for non-spanning cell, > 1 for spanning cell.
+ wxArrayInt maxColWidths(m_colCount);
+ wxArrayInt minColWidths(m_colCount);
+
+ wxSize tableSize(tableWidth, 0);
+
+ int i, j, k;
+
+ for (i = 0; i < m_colCount; i++)
+ {
+ absoluteColWidths[i] = 0;
+ // absoluteColWidthsSpanning[i] = 0;
+ percentageColWidths[i] = -1;
+ // percentageColWidthsSpanning[i] = -1;
+ colWidths[i] = 0;
+ maxColWidths[i] = 0;
+ minColWidths[i] = 0;
+ // columnSpans[i] = 1;
+ }
+
+ // (0) Determine which cells are visible according to spans
+ // 1 2 3 4 5
+ // __________________
+ // | | | | | 1
+ // |------| |----|
+ // |------| | | 2
+ // |------| | | 3
+ // |------------------|
+ // |__________________| 4
+
+ // To calculate cell visibility:
+ // First find all spanning cells. Build an array of span records with start x, y and end x, y.
+ // Then for each cell, test whether we're within one of those cells, and unless we're at the start of
+ // that cell, hide the cell.
+
+ // We can also use this array to match the size of spanning cells to the grid. Or just do
+ // this when we iterate through all cells.
+
+ // 0.1: add spanning cells to an array
+ wxRichTextRectArray rectArray;
+ for (j = 0; j < m_rowCount; j++)
+ {
+ 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"));
+ if (colSpan > 1 || rowSpan > 1)
+ {
+ rectArray.Add(wxRect(i, j, colSpan, rowSpan));
+ }
+ }
+ }
+ // 0.2: find which cells are subsumed by a spanning cell
+ for (j = 0; j < m_rowCount; j++)
+ {
+ for (i = 0; i < m_colCount; i++)
+ {
+ wxRichTextBox* 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"));
+ if (colSpan > 1 || rowSpan > 1)
+ {
+ // Assume all spanning cells are shown
+ cell->Show(true);
+ }
+ else
+ {
+ bool shown = true;
+ for (k = 0; k < (int) rectArray.GetCount(); k++)
+ {
+ if (rectArray[k].Contains(wxPoint(i, j)))
+ {
+ shown = false;
+ break;
+ }
+ }
+ cell->Show(shown);
+ }
+ }
+ }
+ }
+
+ // TODO: 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
+ // where there are different spans happening on different rows. In these cases,
+ // they will simply be as wide as their constituent columns.
+
+ // (1) Do an initial layout for all cells to get minimum and maximum size, and get
+ // the absolute or percentage width of each column.
+
+ for (j = 0; j < m_rowCount; j++)
+ {
+ // First get the overall margins so we can calculate percentage widths based on
+ // the available content space for all cells on the row
+
+ int overallRowContentMargin = 0;
+ int visibleCellCount = 0;
+
+ for (i = 0; i < m_colCount; i++)
+ {
+ wxRichTextBox* cell = GetCell(j, i);
+ if (cell->IsShown())
+ {
+ int cellTotalLeftMargin = 0, cellTotalRightMargin = 0, cellTotalTopMargin = 0, cellTotalBottomMargin = 0;
+ GetTotalMargin(dc, buffer, cell->GetAttributes(), cellTotalLeftMargin, cellTotalRightMargin, cellTotalTopMargin, cellTotalBottomMargin);
+
+ overallRowContentMargin += (cellTotalLeftMargin + cellTotalRightMargin);
+ visibleCellCount ++;
+ }
+ }
+
+ // Add in inter-cell padding
+ overallRowContentMargin += ((visibleCellCount-1) * paddingX);
+
+ int rowContentWidth = internalTableWidth - overallRowContentMargin;
+ wxSize rowTableSize(rowContentWidth, 0);
+ wxTextAttrDimensionConverter converter(dc, scale, rowTableSize);
+
+ for (i = 0; i < m_colCount; i++)
+ {
+ wxRichTextBox* cell = GetCell(j, i);
+ if (cell->IsShown())
+ {
+ int colSpan = 1;
+ if (cell->GetProperties().HasProperty(wxT("colspan")))
+ colSpan = cell->GetProperties().GetPropertyLong(wxT("colspan"));
+
+ // Lay out cell to find min/max widths
+ cell->Invalidate(wxRICHTEXT_ALL);
+ cell->Layout(dc, availableSpace, style);
+
+ if (colSpan == 1)
+ {
+ int absoluteCellWidth = -1;
+ int percentageCellWidth = -1;
+
+ // I think we need to calculate percentages from the internal table size,
+ // minus the padding between cells which we'll need to calculate from the
+ // (number of VISIBLE cells - 1)*paddingX. Then percentages that add up to 100%
+ // will add up to 100%. In CSS, the width specifies the cell's content rect width,
+ // so if we want to conform to that we'll need to add in the overall cell margins.
+ // However, this will make it difficult to specify percentages that add up to
+ // 100% and still fit within the table width.
+ // Let's say two cells have 50% width. They have 10 pixels of overall margin each.
+ // The table content rect is 500 pixels and the inter-cell padding is 20 pixels.
+ // If we're using internal content size for the width, we would calculate the
+ // the overall cell width for n cells as:
+ // (500 - 20*(n-1) - overallCellMargin1 - overallCellMargin2 - ...) * percentage / 100
+ // + thisOverallCellMargin
+ // = 500 - 20 - 10 - 10) * 0.5 + 10 = 240 pixels overall cell width.
+ // Adding this back, we get 240 + 240 + 20 = 500 pixels.
+
+ if (cell->GetAttributes().GetTextBoxAttr().GetWidth().IsValid())
+ {
+ int w = converter.GetPixels(cell->GetAttributes().GetTextBoxAttr().GetWidth());
+ if (cell->GetAttributes().GetTextBoxAttr().GetWidth().GetUnits() == wxTEXT_ATTR_UNITS_PERCENTAGE)
+ {
+ percentageCellWidth = w;
+ }
+ else
+ {
+ absoluteCellWidth = w;
+ }
+ // Override absolute width with minimum width if necessary
+ if (cell->GetMinSize().x > 0 && absoluteCellWidth !=1 && cell->GetMinSize().x > absoluteCellWidth)
+ absoluteCellWidth = cell->GetMinSize().x;
+ }
+
+ if (absoluteCellWidth != -1)
+ {
+ if (absoluteCellWidth > absoluteColWidths[i])
+ absoluteColWidths[i] = absoluteCellWidth;
+ }
+
+ if (percentageCellWidth != -1)
+ {
+ if (percentageCellWidth > percentageColWidths[i])
+ percentageColWidths[i] = percentageCellWidth;
+ }
+
+ if (colSpan == 1 && cell->GetMinSize().x && cell->GetMinSize().x > minColWidths[i])
+ minColWidths[i] = cell->GetMinSize().x;
+ if (colSpan == 1 && cell->GetMaxSize().x && cell->GetMaxSize().x > maxColWidths[i])
+ maxColWidths[i] = cell->GetMaxSize().x;
+ }
+ }
+ }
+ }
+
+ // (2) Allocate initial column widths from minimum widths, absolute values and proportions
+ // TODO: simply merge this into (1).
+ for (i = 0; i < m_colCount; i++)
+ {
+ if (absoluteColWidths[i] > 0)
+ {
+ colWidths[i] = absoluteColWidths[i];
+ }
+ else if (percentageColWidths[i] > 0)
+ {
+ colWidths[i] = percentageColWidths[i];
+
+ // This is rubbish - we calculated the absolute widths from percentages, so
+ // we can't do it again here.
+ //colWidths[i] = (int) (double(percentageColWidths[i]) * double(tableWidth) / 100.0 + 0.5);
+ }
+ }
+
+ // (3) Process absolute or proportional widths of spanning columns,
+ // now that we know what our fixed column widths are going to be.
+ // Spanned cells will try to adjust columns so the span will fit.
+ // Even existing fixed column widths can be expanded if necessary.
+ // Actually, currently fixed columns widths aren't adjusted; instead,
+ // the algorithm favours earlier rows and adjusts unspecified column widths
+ // the first time only. After that, we can't know whether the column has been
+ // specified explicitly or not. (We could make a note if necessary.)
+ for (j = 0; j < m_rowCount; j++)
+ {
+ // First get the overall margins so we can calculate percentage widths based on
+ // the available content space for all cells on the row
+
+ int overallRowContentMargin = 0;
+ int visibleCellCount = 0;
+
+ for (i = 0; i < m_colCount; i++)
+ {
+ wxRichTextBox* cell = GetCell(j, i);
+ if (cell->IsShown())
+ {
+ int cellTotalLeftMargin = 0, cellTotalRightMargin = 0, cellTotalTopMargin = 0, cellTotalBottomMargin = 0;
+ GetTotalMargin(dc, buffer, cell->GetAttributes(), cellTotalLeftMargin, cellTotalRightMargin, cellTotalTopMargin, cellTotalBottomMargin);
+
+ overallRowContentMargin += (cellTotalLeftMargin + cellTotalRightMargin);
+ visibleCellCount ++;
+ }
+ }
+
+ // Add in inter-cell padding
+ overallRowContentMargin += ((visibleCellCount-1) * paddingX);
+
+ int rowContentWidth = internalTableWidth - overallRowContentMargin;
+ wxSize rowTableSize(rowContentWidth, 0);
+ wxTextAttrDimensionConverter converter(dc, scale, rowTableSize);
+
+ for (i = 0; i < m_colCount; i++)
+ {
+ wxRichTextBox* cell = GetCell(j, i);
+ if (cell->IsShown())
+ {
+ int colSpan = 1;
+ if (cell->GetProperties().HasProperty(wxT("colspan")))
+ colSpan = cell->GetProperties().GetPropertyLong(wxT("colspan"));
+
+ if (colSpan > 1)
+ {
+ int spans = wxMin(colSpan, m_colCount - i);
+ int cellWidth = 0;
+ if (spans > 0)
+ {
+ if (cell->GetAttributes().GetTextBoxAttr().GetWidth().IsValid())
+ {
+ cellWidth = converter.GetPixels(cell->GetAttributes().GetTextBoxAttr().GetWidth());
+ // Override absolute width with minimum width if necessary
+ if (cell->GetMinSize().x > 0 && cellWidth !=1 && cell->GetMinSize().x > cellWidth)
+ cellWidth = cell->GetMinSize().x;
+ }
+ else
+ {
+ // Do we want to do this? It's the only chance we get to
+ // use the cell's min/max sizes, so we need to work out
+ // how we're going to balance the unspecified spanning cell
+ // width with the possibility more-constrained constituent cell widths.
+ // Say there's a tiny bitmap giving it a max width of 10 pixels. We
+ // don't want to constraint all the spanned columns to fit into this cell.
+ // OK, let's say that if any of the constituent columns don't fit,
+ // then we simply stop constraining the columns; instead, we'll just fit the spanning
+ // cells to the columns later.
+ cellWidth = cell->GetMinSize().x;
+ if (cell->GetMaxSize().x > cellWidth)
+ cellWidth = cell->GetMaxSize().x;
+ }
+
+ // Subtract the padding between cells
+ int spanningWidth = cellWidth;
+ spanningWidth -= paddingX * (spans-1);
+
+ if (spanningWidth > 0)
+ {
+ // Now share the spanning width between columns within that span
+ // TODO: take into account min widths of columns within the span
+ int spanningWidthLeft = spanningWidth;
+ int stretchColCount = 0;
+ for (k = i; k < (i+spans); k++)
+ {
+ if (colWidths[k] > 0) // absolute or proportional width has been specified
+ spanningWidthLeft -= colWidths[k];
+ else
+ stretchColCount ++;
+ }
+ // Now divide what's left between the remaining columns
+ int colShare = 0;
+ if (stretchColCount > 0)
+ colShare = spanningWidthLeft / stretchColCount;
+ int colShareRemainder = spanningWidthLeft - (colShare * stretchColCount);
+
+ // If fixed-width columns are currently too big, then we'll later
+ // stretch the spanned cell to fit.
+
+ if (spanningWidthLeft > 0)
+ {
+ for (k = i; k < (i+spans); k++)
+ {
+ if (colWidths[k] <= 0) // absolute or proportional width has not been specified
+ {
+ int newWidth = colShare;
+ if (k == (i+spans-1))
+ newWidth += colShareRemainder; // ensure all pixels are filled
+ colWidths[k] = newWidth;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // (4) Next, share any remaining space out between columns that have not yet been calculated.
+ // TODO: take into account min widths of columns within the span
+ int tableWidthMinusPadding = internalTableWidth - (m_colCount-1)*paddingX;
+ int widthLeft = tableWidthMinusPadding;
+ 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
+ widthLeft -= colWidths[i];
+ else
+ {
+ if (minColWidths[i] > 0)
+ widthLeft -= minColWidths[i];
+
+ stretchColCount ++;
+ }
+ }
+
+ // Now divide what's left between the remaining columns
+ int colShare = 0;
+ if (stretchColCount > 0)
+ colShare = widthLeft / stretchColCount;
+ int colShareRemainder = widthLeft - (colShare * stretchColCount);
+
+ // Check we don't have enough space, in which case shrink all columns, overriding
+ // any absolute/proportional widths
+ // TODO: actually we would like to divide up the shrinkage according to size.
+ // How do we calculate the proportions that will achieve this?
+ // Could first choose an arbitrary value for stretching cells, and then calculate
+ // factors to multiply each width by.
+ // TODO: want to record this fact and pass to an iteration that tries e.g. min widths
+ if (widthLeft < 0 || (stretchToFitTableWidth && (stretchColCount == 0)))
+ {
+ colShare = tableWidthMinusPadding / m_colCount;
+ colShareRemainder = tableWidthMinusPadding - (colShare * m_colCount);
+ for (i = 0; i < m_colCount; i++)
+ {
+ colWidths[i] = 0;
+ minColWidths[i] = 0;
+ }
+ }
+
+ // We have to adjust the columns if either we need to shrink the
+ // table to fit the parent/table width, or we explicitly set the
+ // table width and need to stretch out the table.
+ if (widthLeft < 0 || stretchToFitTableWidth)
+ {
+ for (i = 0; i < m_colCount; i++)
+ {
+ if (colWidths[i] <= 0) // absolute or proportional width has not been specified
+ {
+ if (minColWidths[i] > 0)
+ colWidths[i] = minColWidths[i] + colShare;
+ else
+ colWidths[i] = colShare;
+ if (i == (m_colCount-1))
+ colWidths[i] += colShareRemainder; // ensure all pixels are filled
+ }
+ }
+ }
+
+ // TODO: if spanned cells have no specified or max width, make them the
+ // as big as the columns they span. Do this for all spanned cells in all
+ // rows, of course. Size any spanned cells left over at the end - even if they
+ // have width > 0, make sure they're limited to the appropriate column edge.
+
+
+/*
+ Sort out confusion between content width
+ and overall width later. For now, assume we specify overall width.
+
+ So, now we've laid out the table to fit into the given space
+ and have used specified widths and minimum widths.
+
+ Now we need to consider how we will try to take maximum width into account.
+
+*/
+
+ // (??) TODO: take max width into account
+
+ // (6) Lay out all cells again with the current values
+
+ int maxRight = 0;
+ int y = availableSpace.y;
+ for (j = 0; j < m_rowCount; j++)
+ {
+ int x = availableSpace.x; // TODO: take into account centering etc.
+ int maxCellHeight = 0;
+ int maxSpecifiedCellHeight = 0;
+
+ wxArrayInt actualWidths(m_colCount);
+
+ wxTextAttrDimensionConverter converter(dc, scale);
+ for (i = 0; i < m_colCount; i++)
+ {
+ wxRichTextCell* cell = GetCell(j, i);
+ if (cell->IsShown())
+ {
+ // Get max specified cell height
+ // Don't handle percentages for height
+ if (cell->GetAttributes().GetTextBoxAttr().GetHeight().IsValid() && cell->GetAttributes().GetTextBoxAttr().GetHeight().GetUnits() != wxTEXT_ATTR_UNITS_PERCENTAGE)
+ {
+ int h = converter.GetPixels(cell->GetAttributes().GetTextBoxAttr().GetHeight());
+ if (h > maxSpecifiedCellHeight)
+ maxSpecifiedCellHeight = h;
+ }
+
+ 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"));
+
+ wxRect availableCellSpace;
+
+ // TODO: take into acount spans
+ if (colSpan > 1)
+ {
+ // Calculate the size of this spanning cell from its constituent columns
+ int xx = x;
+ int spans = wxMin(colSpan, m_colCount - i);
+ for (k = i; k < spans; k++)
+ {
+ if (k != i)
+ xx += paddingX;
+ xx += colWidths[k];
+ }
+ availableCellSpace = wxRect(x, y, xx, -1);
+ }
+ else
+ availableCellSpace = wxRect(x, y, colWidths[i], -1);
+
+ // Store actual width so we can force cell to be the appropriate width on the final loop
+ actualWidths[i] = availableCellSpace.GetWidth();
+
+ // Lay out cell
+ cell->Invalidate(wxRICHTEXT_ALL);
+ cell->Layout(dc, availableCellSpace, style);
+
+ // TODO: use GetCachedSize().x to compute 'natural' size
+
+ x += (availableCellSpace.GetWidth() + paddingX);
+ if (cell->GetCachedSize().y > maxCellHeight)
+ maxCellHeight = cell->GetCachedSize().y;
+ }
+ }
+ }
+
+ maxCellHeight = wxMax(maxCellHeight, maxSpecifiedCellHeight);
+
+ for (i = 0; i < m_colCount; i++)
+ {
+ wxRichTextCell* cell = GetCell(j, i);
+ if (cell->IsShown())
+ {
+ wxRect availableCellSpace = wxRect(cell->GetPosition(), wxSize(actualWidths[i], maxCellHeight));
+ // Lay out cell with new height
+ cell->Invalidate(wxRICHTEXT_ALL);
+ cell->Layout(dc, availableCellSpace, style);
+
+ // Make sure the cell size really is the appropriate size,
+ // not the calculated box size
+ cell->SetCachedSize(wxSize(actualWidths[i], maxCellHeight));
+
+ maxRight = wxMax(maxRight, cell->GetPosition().x + cell->GetCachedSize().x);
+ }
+ }
+
+ y += maxCellHeight;
+ if (j < (m_rowCount-1))
+ y += paddingY;
+ }
+
+ // We need to add back the margins etc.
+ {
+ wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
+ contentRect = wxRect(wxPoint(0, 0), wxSize(maxRight - availableSpace.x, y - availableSpace.y));
+ GetBoxRects(dc, GetBuffer(), GetAttributes(), marginRect, borderRect, contentRect, paddingRect, outlineRect);
+ SetCachedSize(marginRect.GetSize());
+ }
+
+ // TODO: calculate max size
+ {
+ SetMaxSize(GetCachedSize());
+ }
+
+ // TODO: calculate min size
+ {
+ SetMinSize(GetCachedSize());
+ }
+
+ // TODO: currently we use either a fixed table width or the parent's size.
+ // We also want to be able to calculate the table width from its content,
+ // whether using fixed column widths or cell content min/max width.
+ // Probably need a boolean flag to say whether we need to stretch cells
+ // to fit the table width, or to simply use min/max cell widths. The
+ // trouble with this is that if cell widths are not specified, they
+ // will be tiny; we could use arbitrary defaults but this seems unsatisfactory.
+ // Anyway, ignoring that problem, we probably need to factor layout into a function
+ // that can can calculate the maximum unconstrained layout in case table size is
+ // not specified. Then LayoutToBestSize() can choose to use either parent size to
+ // constrain Layout(), or the previously-calculated max size to constraint layout.
+
+ return true;
+}
+
+// Finds the absolute position and row height for the given character position
+bool wxRichTextTable::FindPosition(wxDC& dc, long index, wxPoint& pt, int* height, bool forceLineStart)
+{
+ wxRichTextCell* child = GetCell(index+1);
+ if (child)
+ {
+ // Find the position at the start of the child cell, since the table doesn't
+ // have any caret position of its own.
+ return child->FindPosition(dc, -1, pt, height, forceLineStart);
+ }
+ else
+ return false;
+}
+
+// Get the cell at the given character position (in the range of the table).
+wxRichTextCell* wxRichTextTable::GetCell(long pos) const
+{
+ int row = 0, col = 0;
+ if (GetCellRowColumnPosition(pos, row, col))
+ {
+ return GetCell(row, col);
+ }
+ else
+ return NULL;
+}
+
+// Get the row/column for a given character position
+bool wxRichTextTable::GetCellRowColumnPosition(long pos, int& row, int& col) const
+{
+ if (m_colCount == 0 || m_rowCount == 0)
+ return false;
+
+ row = (int) (pos / m_colCount);
+ col = pos - (row * m_colCount);
+
+ wxASSERT(row < m_rowCount && col < m_colCount);
+
+ if (row < m_rowCount && col < m_colCount)
+ return true;
+ else
+ return false;
+}
+
+// Calculate range, taking row/cell ordering into account instead of relying
+// on list ordering.
+void wxRichTextTable::CalculateRange(long start, long& end)
+{
+ long current = start;
+ long lastEnd = current;
+
+ if (IsTopLevel())
+ {
+ current = 0;
+ lastEnd = 0;
+ }
+
+ int i, j;
+ for (i = 0; i < m_rowCount; i++)
+ {
+ for (j = 0; j < m_colCount; j++)
+ {
+ wxRichTextCell* child = GetCell(i, j);
+ if (child)
+ {
+ long childEnd = 0;
+
+ child->CalculateRange(current, childEnd);
+
+ lastEnd = childEnd;
+ current = childEnd + 1;
+ }
+ }
+ }
+
+ // A top-level object always has a range of size 1,
+ // because its children don't count at this level.
+ end = start;
+ m_range.SetRange(start, start);
+
+ // An object with no children has zero length
+ if (m_children.GetCount() == 0)
+ lastEnd --;
+ m_ownRange.SetRange(0, lastEnd);
+}
+
+// Gets the range size.
+bool wxRichTextTable::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, int flags, wxPoint position, wxArrayInt* partialExtents) const
+{
+ return wxRichTextBox::GetRangeSize(range, size, descent, dc, flags, position, partialExtents);
+}
+
+// Deletes content in the given range.
+bool wxRichTextTable::DeleteRange(const wxRichTextRange& WXUNUSED(range))
+{
+ // TODO: implement deletion of cells
+ return true;
+}
+
+// Gets any text in this object for the given range.
+wxString wxRichTextTable::GetTextForRange(const wxRichTextRange& range) const
+{
+ return wxRichTextBox::GetTextForRange(range);
+}
+
+// Copies this object.
+void wxRichTextTable::Copy(const wxRichTextTable& obj)
+{
+ wxRichTextBox::Copy(obj);
+
+ ClearTable();
+
+ m_rowCount = obj.m_rowCount;
+ m_colCount = obj.m_colCount;
+
+ m_cells.Add(wxRichTextObjectPtrArray(), m_rowCount);
+
+ int i, j;
+ for (i = 0; i < m_rowCount; i++)
+ {
+ wxRichTextObjectPtrArray& colArray = m_cells[i];
+ for (j = 0; j < m_colCount; j++)
+ {
+ wxRichTextCell* cell = wxDynamicCast(obj.GetCell(i, j)->Clone(), wxRichTextCell);
+ AppendChild(cell);
+
+ colArray.Add(cell);
+ }
+ }
+}
+
+void wxRichTextTable::ClearTable()
+{
+ m_cells.Clear();
+ DeleteChildren();
+}
+
+bool wxRichTextTable::CreateTable(int rows, int cols)
+{
+ ClearTable();
+
+ m_rowCount = rows;
+ m_colCount = cols;
+
+ m_cells.Add(wxRichTextObjectPtrArray(), rows);
+
+ int i, j;
+ for (i = 0; i < rows; i++)
+ {
+ wxRichTextObjectPtrArray& colArray = m_cells[i];
+ for (j = 0; j < cols; j++)
+ {
+ wxRichTextCell* cell = new wxRichTextCell;
+ AppendChild(cell);
+ cell->AddParagraph(wxEmptyString);
+
+ colArray.Add(cell);
+ }
+ }
+
+ return true;
+}
+
+wxRichTextCell* wxRichTextTable::GetCell(int row, int col) const
+{
+ wxASSERT(row < m_rowCount);
+ wxASSERT(col < m_colCount);
+
+ if (row < m_rowCount && col < m_colCount)
+ {
+ wxRichTextObjectPtrArray& colArray = m_cells[row];
+ wxRichTextObject* obj = colArray[col];
+ return wxDynamicCast(obj, wxRichTextCell);
+ }
+ else
+ return NULL;
+}
+
+// Returns a selection object specifying the selections between start and end character positions.
+// For example, a table would deduce what cells (of range length 1) are selected when dragging across the table.
+wxRichTextSelection wxRichTextTable::GetSelection(long start, long end) const
+{
+ wxRichTextSelection selection;
+ selection.SetContainer((wxRichTextTable*) this);
+
+ if (start > end)
+ {
+ long tmp = end;
+ end = start;
+ start = tmp;
+ }
+
+ wxASSERT( start >= 0 && end < (m_colCount * m_rowCount));
+
+ if (end >= (m_colCount * m_rowCount))
+ return selection;
+
+ // We need to find the rectangle of cells that is described by the rectangle
+ // with start, end as the diagonal. Make sure we don't add cells that are
+ // not currenty visible because they are overlapped by spanning cells.
+/*
+ --------------------------
+ | 0 | 1 | 2 | 3 | 4 |
+ --------------------------
+ | 5 | 6 | 7 | 8 | 9 |
+ --------------------------
+ | 10 | 11 | 12 | 13 | 14 |
+ --------------------------
+ | 15 | 16 | 17 | 18 | 19 |
+ --------------------------
+
+ Let's say we select 6 -> 18.
+
+ Left and right edge cols of rectangle are 1 and 3 inclusive. Find least/greatest to find
+ which is left and which is right.
+
+ Top and bottom edge rows are 1 and 3 inclusive. Again, find least/greatest to find top and bottom.
+
+ Now go through rows from 1 to 3 and only add cells that are (a) within above column range
+ and (b) shown.
+
+
+*/
+
+ int leftCol = start - m_colCount * int(start/m_colCount);
+ int rightCol = end - m_colCount * int(end/m_colCount);
+
+ int topRow = int(start/m_colCount);
+ int bottomRow = int(end/m_colCount);
+
+ if (leftCol > rightCol)
+ {
+ int tmp = rightCol;
+ rightCol = leftCol;
+ leftCol = tmp;
+ }
+
+ if (topRow > bottomRow)
+ {
+ int tmp = bottomRow;
+ bottomRow = topRow;
+ topRow = tmp;
+ }
+
+ int i, j;
+ for (i = topRow; i <= bottomRow; i++)
+ {
+ for (j = leftCol; j <= rightCol; j++)
+ {
+ wxRichTextCell* cell = GetCell(i, j);
+ if (cell && cell->IsShown())
+ selection.Add(cell->GetRange());
+ }
+ }
+
+ return selection;
+}
+
+// Sets the attributes for the cells specified by the selection.
+bool wxRichTextTable::SetCellStyle(const wxRichTextSelection& selection, const wxRichTextAttr& style, int flags)
+{
+ if (selection.GetContainer() != this)
+ return false;
+
+ wxRichTextBuffer* buffer = GetBuffer();
+ bool haveControl = (buffer && buffer->GetRichTextCtrl() != NULL);
+ bool withUndo = haveControl && ((flags & wxRICHTEXT_SETSTYLE_WITH_UNDO) != 0);
+
+ if (withUndo)
+ buffer->BeginBatchUndo(_("Set Cell Style"));
+
+ wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
+ while (node)
+ {
+ wxRichTextCell* cell = wxDynamicCast(node->GetData(), wxRichTextCell);
+ if (cell && selection.WithinSelection(cell->GetRange().GetStart()))
+ SetStyle(cell, style, flags);
+ node = node->GetNext();
+ }
+
+ // Do action, or delay it until end of batch.
+ if (withUndo)
+ buffer->EndBatchUndo();