/src/libreoffice/sc/source/ui/view/cliputil.cxx
Line | Count | Source |
1 | | /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
2 | | /* |
3 | | * This file is part of the LibreOffice project. |
4 | | * |
5 | | * This Source Code Form is subject to the terms of the Mozilla Public |
6 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
7 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. |
8 | | */ |
9 | | |
10 | | #include <cliputil.hxx> |
11 | | #include <attrib.hxx> |
12 | | #include <viewdata.hxx> |
13 | | #include <tabvwsh.hxx> |
14 | | #include <transobj.hxx> |
15 | | #include <document.hxx> |
16 | | #include <docsh.hxx> |
17 | | #include <docfunc.hxx> |
18 | | #include <dpobject.hxx> |
19 | | #include <globstr.hrc> |
20 | | #include <scresid.hxx> |
21 | | #include <clipparam.hxx> |
22 | | #include <clipoptions.hxx> |
23 | | #include <rangelst.hxx> |
24 | | #include <viewutil.hxx> |
25 | | #include <markdata.hxx> |
26 | | #include <gridwin.hxx> |
27 | | #include <scitems.hxx> |
28 | | #include <formulacell.hxx> |
29 | | |
30 | | #include <sfx2/classificationhelper.hxx> |
31 | | #include <comphelper/lok.hxx> |
32 | | #include <inputopt.hxx> |
33 | | #include <scmod.hxx> |
34 | | #include <warnbox.hxx> |
35 | | |
36 | | namespace |
37 | | { |
38 | | |
39 | | /// Paste only if SfxClassificationHelper recommends so. |
40 | | bool lcl_checkClassification(ScDocument* pSourceDoc, const ScDocument& rDestinationDoc) |
41 | 0 | { |
42 | 0 | if (!pSourceDoc) |
43 | 0 | return true; |
44 | | |
45 | 0 | ScClipOptions* pSourceOptions = pSourceDoc->GetClipOptions(); |
46 | 0 | ScDocShell* pDestinationShell = rDestinationDoc.GetDocumentShell(); |
47 | 0 | if (!pSourceOptions || !pDestinationShell) |
48 | 0 | return true; |
49 | | |
50 | 0 | SfxClassificationCheckPasteResult eResult = SfxClassificationHelper::CheckPaste(pSourceOptions->m_xDocumentProperties, pDestinationShell->getDocProperties()); |
51 | 0 | return SfxClassificationHelper::ShowPasteInfo(eResult); |
52 | 0 | } |
53 | | |
54 | | } |
55 | | |
56 | | void ScClipUtil::PasteFromClipboard( ScViewData& rViewData, ScTabViewShell* pTabViewShell, bool bShowDialog ) |
57 | 0 | { |
58 | 0 | const ScTransferObj* pOwnClip = ScTransferObj::GetOwnClipboard(ScTabViewShell::GetClipData(rViewData.GetActiveWin())); |
59 | 0 | ScDocument& rThisDoc = rViewData.GetDocument(); |
60 | 0 | SCCOL nThisCol = rViewData.GetCurX(); |
61 | 0 | SCROW nThisRow = rViewData.GetCurY(); |
62 | 0 | SCTAB nThisTab = rViewData.CurrentTabForData(); |
63 | 0 | ScDPObject* pDPObj = rThisDoc.GetDPAtCursor( nThisCol, nThisRow, nThisTab ); |
64 | |
|
65 | 0 | if ( pOwnClip && pDPObj ) |
66 | 0 | { |
67 | | // paste from Calc into DataPilot table: sort (similar to drag & drop) |
68 | |
|
69 | 0 | ScDocument* pClipDoc = pOwnClip->GetDocument(); |
70 | 0 | SCTAB nSourceTab = pOwnClip->GetVisibleTab(); |
71 | |
|
72 | 0 | SCCOL nClipStartX; |
73 | 0 | SCROW nClipStartY; |
74 | 0 | SCCOL nClipEndX; |
75 | 0 | SCROW nClipEndY; |
76 | 0 | pClipDoc->GetClipStart( nClipStartX, nClipStartY ); |
77 | 0 | pClipDoc->GetClipArea( nClipEndX, nClipEndY, true ); |
78 | 0 | nClipEndX = nClipEndX + nClipStartX; |
79 | 0 | nClipEndY = nClipEndY + nClipStartY; // GetClipArea returns the difference |
80 | |
|
81 | 0 | ScRange aSource( nClipStartX, nClipStartY, nSourceTab, nClipEndX, nClipEndY, nSourceTab ); |
82 | 0 | bool bDone = pTabViewShell->DataPilotMove( aSource, rViewData.GetCurPos() ); |
83 | 0 | if ( !bDone ) |
84 | 0 | pTabViewShell->ErrorMessage( STR_ERR_DATAPILOT_INPUT ); |
85 | 0 | } |
86 | 0 | else |
87 | 0 | { |
88 | | // normal paste |
89 | 0 | weld::WaitObject aWait( rViewData.GetDialogParent() ); |
90 | 0 | if (!pOwnClip) |
91 | 0 | { |
92 | 0 | pTabViewShell->PasteFromSystem(true); |
93 | | // Anchor To Cell rather than To Page |
94 | 0 | ScDrawView* pDrawView = pTabViewShell->GetScDrawView(); |
95 | 0 | if(pDrawView) |
96 | 0 | { |
97 | 0 | const SdrMarkList& rMarkList = pDrawView->GetMarkedObjectList(); |
98 | 0 | if (1 == rMarkList.GetMarkCount()) |
99 | 0 | { |
100 | 0 | SdrObject* pPickObj = rMarkList.GetMark(0)->GetMarkedSdrObj(); |
101 | 0 | if(pPickObj) |
102 | 0 | { |
103 | 0 | ScDrawLayer::SetCellAnchoredFromPosition( *pPickObj, rThisDoc, nThisTab, false ); |
104 | 0 | } |
105 | 0 | } |
106 | 0 | } |
107 | 0 | } |
108 | 0 | else |
109 | 0 | { |
110 | 0 | ScDocument* pClipDoc = pOwnClip->GetDocument(); |
111 | 0 | const ScClipParam& rClipParam = pClipDoc->GetClipParam(); |
112 | |
|
113 | 0 | if (!lcl_checkClassification(pClipDoc, rThisDoc)) |
114 | 0 | return; |
115 | | |
116 | 0 | InsertDeleteFlags nFlags = InsertDeleteFlags::ALL; |
117 | | // For multi-range paste, we paste values by default. |
118 | 0 | if (rClipParam.isMultiRange()) |
119 | 0 | nFlags &= ~InsertDeleteFlags::FORMULA; |
120 | | |
121 | | // --- Origin-matrix handling --- |
122 | | // The clip doc contains the expanded bounding box (original + |
123 | | // origin matrices). Paste the original selection (non-matrix |
124 | | // cells only), then let EnterMatrix expand each matrix origin |
125 | | // to its full array range. |
126 | 0 | if (!rClipParam.maOriginMatrixRanges.empty()) |
127 | 0 | { |
128 | 0 | const ScRange& rOrigRange = rClipParam.maOriginalRange; |
129 | 0 | SCTAB nClipTab = rOrigRange.aStart.Tab(); |
130 | | |
131 | | // Pre-compute destination ranges and grab token arrays |
132 | | // from the clip doc (before any paste modifies anything). |
133 | 0 | struct OriginMatrixInfo |
134 | 0 | { |
135 | 0 | ScRange maDestRange; // destination range for the full matrix |
136 | 0 | const ScTokenArray* mpCode; // token array from the origin formula cell |
137 | 0 | }; |
138 | 0 | std::vector<OriginMatrixInfo> aOriginInfos; |
139 | 0 | aOriginInfos.reserve(rClipParam.maOriginMatrixRanges.size()); |
140 | |
|
141 | 0 | formula::FormulaGrammar::Grammar eGram = rThisDoc.GetGrammar(); |
142 | 0 | for (size_t i = 0; i < rClipParam.maOriginMatrixRanges.size(); ++i) |
143 | 0 | { |
144 | 0 | const ScRange& rMat = rClipParam.maOriginMatrixRanges[i]; |
145 | 0 | SCCOL nDestCol = nThisCol + (rMat.aStart.Col() - rOrigRange.aStart.Col()); |
146 | 0 | SCROW nDestRow = nThisRow + (rMat.aStart.Row() - rOrigRange.aStart.Row()); |
147 | 0 | ScRange aDestMatRange(nDestCol, nDestRow, nThisTab, |
148 | 0 | nDestCol + rMat.aEnd.Col() - rMat.aStart.Col(), |
149 | 0 | nDestRow + rMat.aEnd.Row() - rMat.aStart.Row(), nThisTab); |
150 | | |
151 | | // Get the token array from the clip doc's origin cell. |
152 | | // Passing the token array (instead of a formula string) |
153 | | // to EnterMatrix ensures relative references are correctly |
154 | | // shifted to the destination position. |
155 | 0 | const ScTokenArray* pCode = nullptr; |
156 | 0 | ScFormulaCell* pFC = pClipDoc->GetFormulaCell(rMat.aStart); |
157 | 0 | if (pFC) |
158 | 0 | pCode = pFC->GetCode(); |
159 | |
|
160 | 0 | aOriginInfos.push_back({ aDestMatRange, pCode }); |
161 | 0 | } |
162 | | |
163 | | // Pre-check: detect existing matrices at the destination. |
164 | | // If fully covered by one of our expanded ranges, queue |
165 | | // for clearing. If only partially covered, abort. |
166 | 0 | ScRangeList aMatricesToClear; |
167 | 0 | for (const auto& rInfo : aOriginInfos) |
168 | 0 | { |
169 | 0 | const ScRange& rDest = rInfo.maDestRange; |
170 | 0 | for (SCROW r = rDest.aStart.Row(); r <= rDest.aEnd.Row(); ++r) |
171 | 0 | { |
172 | 0 | for (SCCOL c = rDest.aStart.Col(); c <= rDest.aEnd.Col(); ++c) |
173 | 0 | { |
174 | 0 | ScRange aExisting; |
175 | 0 | if (rThisDoc.GetMatrixFormulaRange(ScAddress(c, r, nThisTab), aExisting)) |
176 | 0 | { |
177 | 0 | if (rDest.Contains(aExisting)) |
178 | 0 | { |
179 | 0 | if (!aMatricesToClear.Contains(aExisting)) |
180 | 0 | aMatricesToClear.push_back(aExisting); |
181 | 0 | } |
182 | 0 | else |
183 | 0 | { |
184 | 0 | pTabViewShell->ErrorMessage(STR_MATRIXFRAGMENTERR); |
185 | 0 | return; |
186 | 0 | } |
187 | 0 | } |
188 | 0 | } |
189 | 0 | } |
190 | 0 | } |
191 | | |
192 | | // Check if the paste area would overwrite non-empty cells. |
193 | 0 | if (bShowDialog |
194 | 0 | && (nFlags & InsertDeleteFlags::CONTENTS) |
195 | 0 | && ScModule::get()->GetInputOptions().GetReplaceCellsWarn()) |
196 | 0 | { |
197 | 0 | bool bIsEmpty = true; |
198 | 0 | SCCOL nOrigEndCol = nThisCol + (rOrigRange.aEnd.Col() - rOrigRange.aStart.Col()); |
199 | 0 | SCROW nOrigEndRow = nThisRow + (rOrigRange.aEnd.Row() - rOrigRange.aStart.Row()); |
200 | | |
201 | | // Check the original selection area. |
202 | 0 | bIsEmpty = rThisDoc.IsBlockEmpty( |
203 | 0 | nThisCol, nThisRow, nOrigEndCol, nOrigEndRow, nThisTab); |
204 | | |
205 | | // Check the expanded matrix areas (beyond original selection). |
206 | 0 | for (size_t i = 0; i < aOriginInfos.size() && bIsEmpty; ++i) |
207 | 0 | { |
208 | 0 | const ScRange& rDest = aOriginInfos[i].maDestRange; |
209 | 0 | if (rDest.aEnd.Col() > nOrigEndCol) |
210 | 0 | { |
211 | 0 | SCCOL nCheckStart = std::max(rDest.aStart.Col(), static_cast<SCCOL>(nOrigEndCol + 1)); |
212 | 0 | bIsEmpty = rThisDoc.IsBlockEmpty( |
213 | 0 | nCheckStart, rDest.aStart.Row(), |
214 | 0 | rDest.aEnd.Col(), rDest.aEnd.Row(), nThisTab); |
215 | 0 | } |
216 | 0 | if (bIsEmpty && rDest.aEnd.Row() > nOrigEndRow) |
217 | 0 | { |
218 | 0 | SCROW nCheckStart = std::max(rDest.aStart.Row(), static_cast<SCROW>(nOrigEndRow + 1)); |
219 | 0 | SCCOL nColEnd = std::min(rDest.aEnd.Col(), nOrigEndCol); |
220 | 0 | bIsEmpty = rThisDoc.IsBlockEmpty( |
221 | 0 | rDest.aStart.Col(), nCheckStart, |
222 | 0 | nColEnd, rDest.aEnd.Row(), nThisTab); |
223 | 0 | } |
224 | 0 | } |
225 | |
|
226 | 0 | if (!bIsEmpty) |
227 | 0 | { |
228 | 0 | ScReplaceWarnBox aBox(rViewData.GetDialogParent()); |
229 | 0 | if (aBox.run() != RET_YES) |
230 | 0 | return; |
231 | 0 | } |
232 | 0 | } |
233 | | |
234 | | // --- All checks passed. Now modify. --- |
235 | 0 | ScDocShell* pDocSh = rViewData.GetDocShell(); |
236 | 0 | SfxUndoManager* pUndoMgr = pDocSh->GetUndoManager(); |
237 | 0 | OUString aUndo = ScResId(pClipDoc->IsCutMode() ? STR_UNDO_MOVE : STR_UNDO_COPY); |
238 | 0 | pUndoMgr->EnterListAction(aUndo, aUndo, 0, |
239 | 0 | rViewData.GetViewShell()->GetViewShellId()); |
240 | | |
241 | | // Clear existing complete matrices that will be replaced. |
242 | 0 | for (size_t i = 0; i < aMatricesToClear.size(); ++i) |
243 | 0 | { |
244 | 0 | const ScRange& rClear = aMatricesToClear[i]; |
245 | 0 | ScMarkData aClearMark(rThisDoc.GetSheetLimits()); |
246 | 0 | aClearMark.SelectTable(nThisTab, true); |
247 | 0 | aClearMark.SetMarkArea(rClear); |
248 | 0 | pDocSh->GetDocFunc().DeleteContents( |
249 | 0 | aClearMark, InsertDeleteFlags::ALL, true /*bRecord*/, true /*bApi*/); |
250 | 0 | } |
251 | | |
252 | | // Check whether the original selection contains any |
253 | | // non-matrix cells. If tracked matrices fully cover it |
254 | | // we can skip the expensive CopyToDocument + PasteFromClip |
255 | | // round-trip — EnterMatrix alone handles the paste. |
256 | 0 | sal_uLong nOrigCells |
257 | 0 | = static_cast<sal_uLong>(rOrigRange.aEnd.Col() - rOrigRange.aStart.Col() + 1) |
258 | 0 | * (rOrigRange.aEnd.Row() - rOrigRange.aStart.Row() + 1); |
259 | 0 | sal_uLong nMatrixCells = 0; |
260 | 0 | auto countMatrixCells = [&](const ScRangeList& rList) { |
261 | 0 | for (size_t i = 0; i < rList.size(); ++i) |
262 | 0 | { |
263 | 0 | ScRange aInt = rOrigRange.Intersection(rList[i]); |
264 | 0 | if (aInt.IsValid()) |
265 | 0 | nMatrixCells |
266 | 0 | += static_cast<sal_uLong>(aInt.aEnd.Col() - aInt.aStart.Col() + 1) |
267 | 0 | * (aInt.aEnd.Row() - aInt.aStart.Row() + 1); |
268 | 0 | } |
269 | 0 | }; |
270 | 0 | countMatrixCells(rClipParam.maOriginMatrixRanges); |
271 | 0 | countMatrixCells(rClipParam.maMatrixRanges); |
272 | |
|
273 | 0 | if (nMatrixCells < nOrigCells) |
274 | 0 | { |
275 | | // Build a temp clip doc with the original selection |
276 | | // and clear tracked matrix cells so that only |
277 | | // non-matrix cells remain for pasting. |
278 | 0 | auto pOrigDoc = std::make_shared<ScDocument>(SCDOCMODE_CLIP); |
279 | 0 | pOrigDoc->ResetClip(pClipDoc, nClipTab); |
280 | 0 | pClipDoc->CopyToDocument(rOrigRange, InsertDeleteFlags::ALL, false, *pOrigDoc); |
281 | |
|
282 | 0 | auto clearTrackedMatrixCells = [&](const ScRangeList& rList) { |
283 | 0 | for (size_t i = 0; i < rList.size(); ++i) |
284 | 0 | { |
285 | 0 | ScRange aInt = rOrigRange.Intersection(rList[i]); |
286 | 0 | if (aInt.IsValid()) |
287 | 0 | pOrigDoc->DeleteAreaTab(aInt.aStart.Col(), aInt.aStart.Row(), |
288 | 0 | aInt.aEnd.Col(), aInt.aEnd.Row(), |
289 | 0 | nClipTab, InsertDeleteFlags::ALL); |
290 | 0 | } |
291 | 0 | }; |
292 | 0 | clearTrackedMatrixCells(rClipParam.maOriginMatrixRanges); |
293 | 0 | clearTrackedMatrixCells(rClipParam.maMatrixRanges); |
294 | |
|
295 | 0 | ScClipParam aOrigParam(rOrigRange, false); |
296 | 0 | pOrigDoc->SetClipParam(aOrigParam); |
297 | |
|
298 | 0 | pTabViewShell->PasteFromClip(nFlags, pOrigDoc.get(), |
299 | 0 | ScPasteFunc::NONE, true /*bSkipEmpty*/, false, false, |
300 | 0 | INS_NONE, InsertDeleteFlags::NONE, false /*bShowDialog*/); |
301 | 0 | } |
302 | | |
303 | | // Expand each origin matrix using EnterMatrix. |
304 | 0 | for (const auto& rInfo : aOriginInfos) |
305 | 0 | { |
306 | 0 | if (!rInfo.mpCode) |
307 | 0 | continue; |
308 | | |
309 | 0 | const ScRange& rDest = rInfo.maDestRange; |
310 | 0 | ScMarkData aMark(rThisDoc.GetSheetLimits()); |
311 | 0 | aMark.SelectTable(nThisTab, true); |
312 | |
|
313 | 0 | pDocSh->GetDocFunc().EnterMatrix( |
314 | 0 | rDest, &aMark, rInfo.mpCode, OUString(), |
315 | 0 | true /*bApi*/, false /*bEnglish*/, |
316 | 0 | OUString(), eGram); |
317 | 0 | } |
318 | |
|
319 | 0 | pUndoMgr->LeaveListAction(); |
320 | | |
321 | | // Restore cursor and selection to the original paste range. |
322 | 0 | SCROW nOrigRows = rOrigRange.aEnd.Row() - rOrigRange.aStart.Row(); |
323 | 0 | SCCOL nOrigCols = rOrigRange.aEnd.Col() - rOrigRange.aStart.Col(); |
324 | 0 | ScRange aDestRange(nThisCol, nThisRow, nThisTab, |
325 | 0 | nThisCol + nOrigCols, nThisRow + nOrigRows, nThisTab); |
326 | 0 | rViewData.GetMarkData().ResetMark(); |
327 | 0 | pTabViewShell->SetCursor(nThisCol, nThisRow); |
328 | 0 | rViewData.GetMarkData().SetMarkArea(aDestRange); |
329 | 0 | pTabViewShell->MarkRange(aDestRange); |
330 | 0 | } |
331 | | // --- Non-origin matrix handling --- |
332 | 0 | else if (!rClipParam.maMatrixRanges.empty() |
333 | 0 | && rClipParam.maRanges.size() == 1 |
334 | 0 | && rClipParam.maRanges[0] == rClipParam.maOriginalRange) |
335 | 0 | { |
336 | 0 | ScRange aClipRange = rClipParam.getWholeRange(); |
337 | 0 | SCTAB nClipTab = aClipRange.aStart.Tab(); |
338 | | |
339 | | // If non-origin matrices fully cover the selection, |
340 | | // there are no non-matrix cells to paste — nothing to do. |
341 | 0 | sal_uLong nClipCells |
342 | 0 | = static_cast<sal_uLong>(aClipRange.aEnd.Col() - aClipRange.aStart.Col() + 1) |
343 | 0 | * (aClipRange.aEnd.Row() - aClipRange.aStart.Row() + 1); |
344 | 0 | sal_uLong nMatCells = 0; |
345 | 0 | for (size_t i = 0; i < rClipParam.maMatrixRanges.size(); ++i) |
346 | 0 | { |
347 | 0 | ScRange aInt = aClipRange.Intersection(rClipParam.maMatrixRanges[i]); |
348 | 0 | if (aInt.IsValid()) |
349 | 0 | nMatCells |
350 | 0 | += static_cast<sal_uLong>(aInt.aEnd.Col() - aInt.aStart.Col() + 1) |
351 | 0 | * (aInt.aEnd.Row() - aInt.aStart.Row() + 1); |
352 | 0 | } |
353 | 0 | if (nMatCells >= nClipCells) |
354 | 0 | return; |
355 | | |
356 | 0 | auto pTempDoc = std::make_shared<ScDocument>(SCDOCMODE_CLIP); |
357 | 0 | pTempDoc->ResetClip(pClipDoc, nClipTab); |
358 | 0 | pClipDoc->CopyToDocument(aClipRange, InsertDeleteFlags::ALL, false, *pTempDoc); |
359 | |
|
360 | 0 | for (size_t i = 0; i < rClipParam.maMatrixRanges.size(); ++i) |
361 | 0 | { |
362 | 0 | ScRange aClearRange = aClipRange.Intersection(rClipParam.maMatrixRanges[i]); |
363 | 0 | if (aClearRange.IsValid()) |
364 | 0 | pTempDoc->DeleteAreaTab(aClearRange.aStart.Col(), aClearRange.aStart.Row(), |
365 | 0 | aClearRange.aEnd.Col(), aClearRange.aEnd.Row(), |
366 | 0 | nClipTab, InsertDeleteFlags::ALL); |
367 | 0 | } |
368 | |
|
369 | 0 | ScClipParam aTempParam(rClipParam); |
370 | 0 | pTempDoc->SetClipParam(aTempParam); |
371 | |
|
372 | 0 | pTabViewShell->PasteFromClip( nFlags, pTempDoc.get(), |
373 | 0 | ScPasteFunc::NONE, true /*bSkipEmpty*/, false, false, |
374 | 0 | INS_NONE, InsertDeleteFlags::NONE, |
375 | 0 | bShowDialog ); |
376 | 0 | } |
377 | 0 | else |
378 | 0 | { |
379 | | // Normal paste: no matrix issues. |
380 | 0 | pTabViewShell->PasteFromClip( nFlags, pClipDoc, |
381 | 0 | ScPasteFunc::NONE, false, false, false, INS_NONE, InsertDeleteFlags::NONE, |
382 | 0 | bShowDialog ); |
383 | 0 | } |
384 | 0 | } |
385 | 0 | } |
386 | 0 | if (comphelper::LibreOfficeKit::isActive()) |
387 | 0 | { |
388 | 0 | bool entireColumnOrRowSelected = false; |
389 | 0 | if (pOwnClip) |
390 | 0 | { |
391 | 0 | ScClipParam clipParam = pOwnClip->GetDocument()->GetClipParam(); |
392 | 0 | if (clipParam.maRanges.size() > 0) |
393 | 0 | { |
394 | 0 | if (clipParam.maRanges[0].aEnd.Col() == pOwnClip->GetDocument()->MaxCol() |
395 | 0 | || clipParam.maRanges[0].aEnd.Row() == pOwnClip->GetDocument()->MaxRow()) |
396 | 0 | { |
397 | 0 | entireColumnOrRowSelected = true; |
398 | 0 | } |
399 | 0 | } |
400 | 0 | } |
401 | 0 | const SfxBoolItem& rItem = rThisDoc.GetAttr(nThisCol, nThisRow, nThisTab, ATTR_LINEBREAK); |
402 | 0 | if (rItem.GetValue() || entireColumnOrRowSelected) |
403 | 0 | { |
404 | 0 | pTabViewShell->OnLOKSetWidthOrHeight(nThisCol, true); |
405 | 0 | pTabViewShell->OnLOKSetWidthOrHeight(nThisRow, false); |
406 | |
|
407 | 0 | ScTabViewShell::notifyAllViewsSheetGeomInvalidation( |
408 | 0 | pTabViewShell, true /* bColumns */, true /* bRows */, true /* bSizes*/, |
409 | 0 | true /* bHidden */, true /* bFiltered */, true /* bGroups */, rViewData.GetTabNumber()); |
410 | 0 | } |
411 | 0 | } |
412 | 0 | pTabViewShell->CellContentChanged(); // => PasteFromSystem() ??? |
413 | 0 | } |
414 | | |
415 | | bool ScClipUtil::CheckDestRanges( |
416 | | const ScDocument& rDoc, SCCOL nSrcCols, SCROW nSrcRows, const ScMarkData& rMark, const ScRangeList& rDest) |
417 | 0 | { |
418 | 0 | for (size_t i = 0, n = rDest.size(); i < n; ++i) |
419 | 0 | { |
420 | 0 | ScRange aTest = rDest[i]; |
421 | | // Check for filtered rows in all selected sheets. |
422 | 0 | for (const auto& rTab : rMark) |
423 | 0 | { |
424 | 0 | aTest.aStart.SetTab(rTab); |
425 | 0 | aTest.aEnd.SetTab(rTab); |
426 | 0 | if (ScViewUtil::HasFiltered(aTest, rDoc)) |
427 | 0 | { |
428 | | // I don't know how to handle pasting into filtered rows yet. |
429 | 0 | return false; |
430 | 0 | } |
431 | 0 | } |
432 | | |
433 | | // Destination range must be an exact multiple of the source range. |
434 | 0 | SCROW nRows = aTest.aEnd.Row() - aTest.aStart.Row() + 1; |
435 | 0 | SCCOL nCols = aTest.aEnd.Col() - aTest.aStart.Col() + 1; |
436 | 0 | SCROW nRowTest = (nRows / nSrcRows) * nSrcRows; |
437 | 0 | SCCOL nColTest = (nCols / nSrcCols) * nSrcCols; |
438 | 0 | if ( rDest.size() > 1 && ( nRows != nRowTest || nCols != nColTest ) ) |
439 | 0 | { |
440 | | // Destination range is not a multiple of the source range. Bail out. |
441 | 0 | return false; |
442 | 0 | } |
443 | 0 | } |
444 | 0 | return true; |
445 | 0 | } |
446 | | |
447 | | /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |