ElyTable: the chart search skips merged cells, looks four ways, and never throws (R2.10)
This commit is contained in:
3 files changed
+86
-42
No files matched your search
@@ -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`,绘制同用)换回。按键与菜单的缩放(无指针)另保持首页左缘在视口内;指针与捏合的缩放保持指针下的点不动(文档边界以外即止)。
|
||||
@@ -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(
|
||||
Reference in new issue
Block a user