ElyTable: the chart search skips merged cells, looks four ways, and never throws (R2.10)

This commit is contained in:
ZacharyZhang-NY committed 2026-10-09 08:49:39 -04:00
1 parent 326fe24cba
commit 81487fb32d
3 files changed
+86 -42

No files matched your search

+1 -1
View File
@@ -268,6 +268,6 @@ cargo run -p ely-doc 启动一个应用(ElyDoc 先 cargo build -p ely
- 2026-10-09:ElyTable 的自动筛选(R2.9)。内核会重新推算未命名的自动筛选区:每次打开筛选列表时向下扩到紧邻的数据行(`ExtendDataArea`、`ExtendBackColorArea`,为接上后来追加的行),命令从单格找到它时按连续数据区重算(`ScDocShell::GetDBData`:应用条件、关闭筛选及其撤销);选区下方紧邻的合计行因此进了列表、随数据一起被隐藏,存后重开的筛选区同样会扩。补丁 `0040-lok-calc-autofilter-keeps-its-rows.patch`:LOK 下已有的自动筛选区保持设定时的行(同 Excel),不再隐式扩展(光标在区内时;光标在同表另一块数据里即按那一块);单格开筛选时仍取整个数据区。代价:紧贴在筛选区下方追加的行不自动并入,需重新选定后再开筛选。
- 2026-10-09:ElyTree 的差异按文件类型着色(R2.12)。组件库 7f2ac3e 至 b0dbfd4:`DiffViewer`(建立时按路径定下读法) 按路径的扩展名判定散文与纯文本(md、markdown、txt、rst、adoc、org、csv、tsv、log 等,无扩展名同,不着色),代码按语言着色(组件库 b0dbfd4 的 `forms::syntax`:Rust、JavaScript/TypeScript、类 C、Python、Ruby、Shell、YAML、INI、SQL、Lua、Haskell、Lisp、Erlang、TeX 与其他 `#` 语言各有行注释记号、关键字与常量;Shell 与 YAML 的 `#` 只在词首起注释;Makefile、Dockerfile、CMakeLists.txt 先按文件名认),不是完整的语法分析器(字符串插值、块注释、多行字符串照通用词法)(原先一律套 `//` 的通用词法,Markdown 的 `#`、Python 的注释都被当作代码上色)。同批带入上游的块编辑器跨块选择、浮层滚轮、输入法候选框位置、插入点按设备像素对齐与格式修正。
- 2026-10-09:ElyMark 的表格(R2.13)。表格的空格与分隔行写法有许多种(`|a|b|`、`|---|`、对齐空格),逐字重现做不到,原先凡写不回原样的表格都成了原文块。现在一块的序列化与原文在把其中每张表格化成规范形式(各列对齐方式加各格的原文,`tables.js` 的 `canon`)后相同,即为可视块(`preserve.js` 的 `kept`),引用与列表里的表格同样;格内有 Milkdown 会改写的写法(`[[…]]`、引用式链接等)即不同,整块仍为原文块,与段落的规则一致。Milkdown 的列表项必须以段落开头,表格作列表项的第一块时无法放进编辑器,照原文块保留。未改的表格照旧逐字写回,改过的表格按文档多数表格的写法重写(`style.js` 按表格的行节点投票:单元格两侧有无空格、是否按列对齐,经 remark-gfm 的 `tableCellPadding` 与 `tablePipeAlign`;原先投票的解析器不认 GFM,表格从未计票),分隔行随之重写(如 `|---|` 成 `| - |`)。
- 2026-10-09:ElyTable 新插图表的位置(R2.10)。内核按“定位区域”把图表放在其旁、并挤回当前可见区;带参数的请求(Ely 经 `FN_PARAM_5` 给区域)原把定位区域取为光标格,图表压在数据上或旁边的另一块数据上。补丁 `0041-lok-calc-chart-beside-its-data.patch`:LOK 下有请求区域时,图表放进数据区右侧(隔一列)第一块够大的空单元格(逐列向右找,跨过旁边的数据块),右侧没有即放在数据下方,不受可见区限制;`LokView` 在新选中对象(原先无选中)时把它滚入视口,拖动中的对象不滚动。
- 2026-10-09:ElyTable 新插图表的位置(R2.10)。内核按“定位区域”把图表放在其旁、并挤回当前可见区;带参数的请求(Ely 经 `FN_PARAM_5` 给区域)原把定位区域取为光标格,图表压在数据上或旁边的另一块数据上。补丁 `0041-lok-calc-chart-beside-its-data.patch`:LOK 下有请求区域时,图表放进数据区右侧(隔一列)第一块够大、无合并格的空单元格(逐列向右找,跨过旁边的数据块),右侧没有依次找下方、左侧、上方(尺寸从候选格起累加,表尾也快),不受可见区限制;四周都没有空处时照内核原样;`LokView` 在新选中对象(原先无选中)时把它滚入视口,拖动中的对象不滚动。
- 2026-10-09:ElyPDF 编辑与缩放不闪白(R2.6)。旧图块不再随编辑或比例变化即刻丢弃:编辑落地时全部当前图块、比例变化时离开视图的同页图块,连同当时的页面尺寸进入“底图”(`apps/ely-pdf/src/backdrop.rs`),按其在页面中的比例位置画在该页新图块之下,直到该页在视口里的新图块到齐、或页面尺寸与旋转变了才释放;每页画什么由 `PdfView::pieces` 决定,绘制与测试同用它。只有改内容的编辑保留底图并沿用已测的页面尺寸(前 100 页以外不再被占位尺寸覆盖);增删、移动、旋转、裁剪页面与撤销重做(`PdfView::edit_pages`)清空底图,因同一序号可能已是另一页。关闭标签时底图随图块交给窗口释放。原先比较整份尺寸表(超过 100 页的混合纸张文档尺寸表后段为占位,比较失败即全部清空)且只认同比例的图块(编辑后右栏收起、比例变化即清空),都会闪白。
- 2026-10-09:文档的缩放锚点(R2.7)。窄于视口的文档居中,缩放原按视口坐标求锚点、漏了居中的偏移,锚点随缩放漂移;现锚点先减去缩放前的偏移,缩放后按新的偏移(`LokView::slack`,绘制同用)换回。按键与菜单的缩放(无指针)另保持首页左缘在视口内;指针与捏合的缩放保持指针下的点不动(文档边界以外即止)。
+25 -8
View File
@@ -347,14 +347,7 @@ fn blocks(filled: &[(std::ops::RangeInclusive<usize>, std::ops::RangeInclusive<u
format!("<table:table-row>{cells}</table:table-row>")
})
.collect();
format!(
r#"<?xml version="1.0" encoding="UTF-8"?>
<office:document xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0"
xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0"
xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.spreadsheet">
<office:body><office:spreadsheet><table:table table:name="S">{rows}</table:table></office:spreadsheet></office:body>
</office:document>"#
)
sheet(&rows)
}
/// Charts `source` and checks it clears each data block.
@@ -380,6 +373,20 @@ fn charted(sheet: &str, visible: (i32, i32, i32, i32), source: &str, data: &[(&s
}
}
/// A sheet body as flat XML.
fn sheet(rows: &str) -> String {
format!(
r#"<?xml version="1.0" encoding="UTF-8"?>
<office:document xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0"
xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0"
xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.spreadsheet">
<office:body><office:spreadsheet><table:table table:name="S">{rows}</table:table></office:spreadsheet></office:body>
</office:document>"#
)
}
const ONE: &str = r#"<table:table-cell office:value-type="float" office:value="1"><text:p>1</text:p></table:table-cell>"#;
#[test]
fn a_new_chart_lands_beside_its_data() {
if skip() {
@@ -391,4 +398,14 @@ fn a_new_chart_lands_beside_its_data() {
charted(&blocks(&[(0..=15, 1..=50)]), (0, 0, 15000, 10000), "A1:P50", &[("A1", "P50")]);
// A view narrower than the chart.
charted(&blocks(&[(0..=2, 1..=5)]), (0, 0, 6000, 3500), "A1:C5", &[("A1", "C5")]);
// A merged block beside the data is taken, not free.
let merged = format!(
r#"<table:table-row>{ONE}{ONE}{ONE}<table:table-cell table:number-columns-spanned="13" table:number-rows-spanned="20"><text:p>MERGED</text:p></table:table-cell></table:table-row><table:table-row table:number-rows-repeated="4">{ONE}{ONE}{ONE}</table:table-row>"#
);
charted(&sheet(&merged), (0, 0, 15000, 10000), "A1:C5", &[("A1", "C5"), ("D1", "D1")]);
// Data in the last cells: left or above.
let corner = format!(
r#"<table:table-row table:number-rows-repeated="1048574"><table:table-cell table:number-columns-repeated="16384"/></table:table-row><table:table-row table:number-rows-repeated="2"><table:table-cell table:number-columns-repeated="16382"/>{ONE}{ONE}</table:table-row>"#
);
charted(&sheet(&corner), (0, 0, 15000, 10000), "XFC1048575:XFD1048576", &[("XFC1048575", "XFD1048576")]);
}
@@ -5,64 +5,89 @@ request arguments (a kit client gives the range in FN_PARAM_5) was the
cursor cell alone; GetChartInsertPos then pulled the chart back into
the visible area. The chart covered the data it charts, or a block
next to it. Under LOK, with a requested range, the chart goes in the
first empty cells right of its data large enough for it, else below
the data, whatever the visible area; the view scrolls to it.
first empty, unmerged cells right of its data large enough for it,
else below, left or above, whatever the visible area. Sizes are summed
from the candidate cell, so a search near the sheet's end stays quick.
diff --git a/sc/source/ui/drawfunc/fuins2.cxx b/sc/source/ui/drawfunc/fuins2.cxx
index b0169d0..b8768af 100644
index b0169d0..2a6a2e9 100644
--- a/sc/source/ui/drawfunc/fuins2.cxx
+++ b/sc/source/ui/drawfunc/fuins2.cxx
@@ -47,6 +47,7 @@
@@ -47,6 +47,8 @@
#include <vcl/weldutils.hxx>
#include <comphelper/lok.hxx>
+#include <stdexcept>
+#include <o3tl/unit_conversion.hxx>
+#include <optional>
#include <comphelper/storagehelper.hxx>
#include <comphelper/propertysequence.hxx>
#include <com/sun/star/embed/EmbedVerbs.hpp>
@@ -215,6 +216,41 @@ void lcl_ChartInit(const uno::Reference <embed::XEmbeddedObject>& xObj, ScViewDa
@@ -215,6 +217,64 @@ void lcl_ChartInit(const uno::Reference <embed::XEmbeddedObject>& xObj, ScViewDa
// don't create chart listener here (range may be modified in chart dialog)
}
+
+/// Cells from (nCol, nRow) wide and tall enough for rSize.
+ScRange lcl_Room(const ScDocument& rDoc, SCTAB nTab, SCCOL nCol, SCROW nRow, const Size& rSize)
+/// The last column from nCol spanning nWidth twips, if any.
+std::optional<SCCOL> lcl_Across(const ScDocument& rDoc, SCTAB nTab, SCCOL nCol, tools::Long nWidth)
+{
+ SCCOL nRight = nCol;
+ while (nRight < rDoc.MaxCol() && rDoc.GetMMRect(nCol, nRow, nRight, nRow, nTab).GetWidth() < rSize.Width())
+ ++nRight;
+ SCROW nBottom = nRow;
+ while (nBottom < rDoc.MaxRow() && rDoc.GetMMRect(nCol, nRow, nCol, nBottom, nTab).GetHeight() < rSize.Height())
+ ++nBottom;
+ return ScRange(nCol, nRow, nTab, nRight, nBottom, nTab);
+ tools::Long nSum = 0;
+ for (SCCOL n = nCol; n <= rDoc.MaxCol(); ++n)
+ if ((nSum += rDoc.GetColWidth(n, nTab)) >= nWidth)
+ return n;
+ return std::nullopt;
+}
+
+/// LOK: empty cells right of rData, else below.
+Point lcl_EmptyBeside(const ScDocument& rDoc, const ScRange& rData, const Size& rSize)
+/// The last row from nRow spanning nHeight twips, if any.
+std::optional<SCROW> lcl_Down(const ScDocument& rDoc, SCTAB nTab, SCROW nRow, tools::Long nHeight)
+{
+ tools::Long nSum = 0;
+ for (SCROW n = nRow; n <= rDoc.MaxRow(); ++n)
+ if ((nSum += rDoc.GetRowHeight(n, nTab)) >= nHeight)
+ return n;
+ return std::nullopt;
+}
+
+/// LOK: empty cells right of rData, else below, left, above.
+std::optional<Point> lcl_EmptyBeside(const ScDocument& rDoc, const ScRange& rData, const Size& rSize)
+{
+ const SCTAB nTab = rData.aStart.Tab();
+ const auto empty = [&](const ScRange& r) {
+ return rDoc.IsBlockEmpty(r.aStart.Col(), r.aStart.Row(), r.aEnd.Col(), r.aEnd.Row(), nTab);
+ const tools::Long nWidth = o3tl::convert(rSize.Width(), o3tl::Length::mm100, o3tl::Length::twip);
+ const tools::Long nHeight = o3tl::convert(rSize.Height(), o3tl::Length::mm100, o3tl::Length::twip);
+ // Merged cells take their whole area, anchor or not.
+ const auto free = [&](SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2) {
+ return rDoc.IsBlockEmpty(nCol1, nRow1, nCol2, nRow2, nTab)
+ && !rDoc.HasAttrib(nCol1, nRow1, nTab, nCol2, nRow2, nTab, HasAttrFlags::Merged | HasAttrFlags::Overlapped);
+ };
+ for (SCCOL nCol = rData.aEnd.Col() + 2; nCol <= rDoc.MaxCol(); ++nCol)
+ const auto at = [&](SCCOL nCol, SCROW nRow) { return rDoc.GetMMRect(nCol, nRow, nCol, nRow, nTab).TopLeft(); };
+ const SCROW nTop = rData.aStart.Row();
+ const SCCOL nLeft = rData.aStart.Col();
+ // Sideways the rows are fixed; up and down the columns are.
+ if (const auto oBottom = lcl_Down(rDoc, nTab, nTop, nHeight))
+ {
+ const ScRange aRoom = lcl_Room(rDoc, nTab, nCol, rData.aStart.Row(), rSize);
+ if (aRoom.aEnd.Col() < rDoc.MaxCol() && empty(aRoom))
+ return rDoc.GetMMRect(nCol, aRoom.aStart.Row(), nCol, aRoom.aStart.Row(), nTab).TopLeft();
+ for (SCCOL nCol = rData.aEnd.Col() + 2; nCol <= rDoc.MaxCol(); ++nCol)
+ if (const auto oRight = lcl_Across(rDoc, nTab, nCol, nWidth); oRight && free(nCol, nTop, *oRight, *oBottom))
+ return at(nCol, nTop);
+ }
+ for (SCROW nRow = rData.aEnd.Row() + 2; nRow <= rDoc.MaxRow(); ++nRow)
+ const auto oRight = lcl_Across(rDoc, nTab, nLeft, nWidth);
+ for (SCROW nRow = rData.aEnd.Row() + 2; oRight && nRow <= rDoc.MaxRow(); ++nRow)
+ if (const auto oBottom = lcl_Down(rDoc, nTab, nRow, nHeight); oBottom && free(nLeft, nRow, *oRight, *oBottom))
+ return at(nLeft, nRow);
+ if (const auto oBottom = lcl_Down(rDoc, nTab, nTop, nHeight))
+ {
+ const ScRange aRoom = lcl_Room(rDoc, nTab, rData.aStart.Col(), nRow, rSize);
+ if (empty(aRoom))
+ return rDoc.GetMMRect(aRoom.aStart.Col(), nRow, aRoom.aStart.Col(), nRow, nTab).TopLeft();
+ for (SCCOL nCol = nLeft - 2; nCol >= 0; --nCol)
+ if (const auto oEnd = lcl_Across(rDoc, nTab, nCol, nWidth); oEnd && *oEnd < nLeft - 1 && free(nCol, nTop, *oEnd, *oBottom))
+ return at(nCol, nTop);
+ }
+ throw std::runtime_error("no empty cells hold the chart");
+ for (SCROW nRow = nTop - 2; oRight && nRow >= 0; --nRow)
+ if (const auto oEnd = lcl_Down(rDoc, nTab, nRow, nHeight); oEnd && *oEnd < nTop - 1 && free(nLeft, nRow, *oRight, *oEnd))
+ return at(nLeft, nRow);
+ return std::nullopt;
+}
+
}
FuInsertOLE::FuInsertOLE(ScTabViewShell& rViewSh, vcl::Window* pWin, ScDrawView* pViewP,
@@ -452,6 +488,14 @@ FuInsertChart::FuInsertChart(ScTabViewShell& rViewSh, vcl::Window* pWin, ScDrawV
@@ -452,6 +512,14 @@ FuInsertChart::FuInsertChart(ScTabViewShell& rViewSh, vcl::Window* pWin, ScDrawV
aRangeString = static_cast<const SfxStringItem*>(pItem)->GetValue();
aPositionRange = rViewSh.GetViewData().GetCurPos();
@@ -77,15 +102,17 @@ index b0169d0..b8768af 100644
}
else
{
@@ -612,7 +656,10 @@ FuInsertChart::FuInsertChart(ScTabViewShell& rViewSh, vcl::Window* pWin, ScDrawV
@@ -612,7 +680,12 @@ FuInsertChart::FuInsertChart(ScTabViewShell& rViewSh, vcl::Window* pWin, ScDrawV
// object position
// get chart position (from window size and data range)
- Point aStart = rViewSh.GetChartInsertPos( aSize, aPositionRange );
+ // LOK: beside the data, never over it, view or not.
+ Point aStart = pRange && comphelper::LibreOfficeKit::isActive()
+ ? lcl_EmptyBeside(rData.GetDocument(), aPositionRange, aSize)
+ : rViewSh.GetChartInsertPos( aSize, aPositionRange );
+ std::optional<Point> oBeside;
+ if (pRange && comphelper::LibreOfficeKit::isActive())
+ oBeside = lcl_EmptyBeside(rData.GetDocument(), aPositionRange, aSize);
+ // A sheet with no room around the data: as before.
+ Point aStart = oBeside ? *oBeside : rViewSh.GetChartInsertPos( aSize, aPositionRange );
tools::Rectangle aRect (aStart, aSize);
rtl::Reference<SdrOle2Obj> pObj = new SdrOle2Obj(