Coverage Report

Created: 2026-09-28 10:59

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/sc/source/ui/operation/MoveBlockOperation.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 <operation/MoveBlockOperation.hxx>
11
12
#include <osl/diagnose.h>
13
#include <sfx2/app.hxx>
14
15
#include <clipparam.hxx>
16
#include <docsh.hxx>
17
#include <docfunc.hxx>
18
#include <document.hxx>
19
#include <drwlayer.hxx>
20
#include <editable.hxx>
21
#include <globstr.hrc>
22
#include <markdata.hxx>
23
#include <undoblk.hxx>
24
25
namespace sc
26
{
27
MoveBlockOperation::MoveBlockOperation(ScDocShell& rDocShell, const ScRange& rSource,
28
                                       const ScAddress& rDestPos, bool bCut, bool bRecord,
29
                                       bool bPaint, bool bApi)
30
0
    : Operation(OperationType::MoveBlock, bRecord, bApi)
31
0
    , mrDocShell(rDocShell)
32
0
    , maSource(rSource)
33
0
    , maDestPos(rDestPos)
34
0
    , mbCut(bCut)
35
0
    , mbPaint(bPaint)
36
0
{
37
0
}
38
39
bool MoveBlockOperation::canRunTheOperation() const
40
0
{
41
0
    return !isInputOnSheetViewAutoFilter(maSource)
42
0
           && !isInputOnSheetViewAutoFilter(ScRange(maDestPos));
43
0
}
44
45
bool MoveBlockOperation::runImplementation()
46
0
{
47
0
    ScDocShellModificator aModificator(mrDocShell);
48
49
0
    ScRange aSource = convertRange(maSource);
50
0
    ScAddress aDestPos = convertAddress(maDestPos);
51
52
0
    SCCOL nStartCol = aSource.aStart.Col();
53
0
    SCROW nStartRow = aSource.aStart.Row();
54
0
    SCTAB nStartTab = aSource.aStart.Tab();
55
0
    SCCOL nEndCol = aSource.aEnd.Col();
56
0
    SCROW nEndRow = aSource.aEnd.Row();
57
0
    SCTAB nEndTab = aSource.aEnd.Tab();
58
0
    SCCOL nDestCol = aDestPos.Col();
59
0
    SCROW nDestRow = aDestPos.Row();
60
0
    SCTAB nDestTab = aDestPos.Tab();
61
62
0
    ScDocument& rDoc = mrDocShell.GetDocument();
63
0
    if (!rDoc.ValidRow(nStartRow) || !rDoc.ValidRow(nEndRow) || !rDoc.ValidRow(nDestRow))
64
0
    {
65
0
        OSL_FAIL("invalid row in MoveBlock");
66
0
        return false;
67
0
    }
68
69
    //  adjust related scenarios too - but only when moved within one sheet
70
0
    bool bScenariosAdded = false;
71
0
    if (mbRecord && !rDoc.IsUndoEnabled())
72
0
        mbRecord = false;
73
74
0
    SCTAB nTabCount = rDoc.GetTableCount();
75
0
    if (nDestTab == nStartTab && !rDoc.IsScenario(nEndTab))
76
0
        while (nEndTab + 1 < nTabCount && rDoc.IsScenario(nEndTab + 1))
77
0
        {
78
0
            ++nEndTab;
79
0
            bScenariosAdded = true;
80
0
        }
81
82
0
    SCTAB nSrcTabCount = nEndTab - nStartTab + 1;
83
0
    SCTAB nDestEndTab = nDestTab + nSrcTabCount - 1;
84
0
    SCTAB nTab;
85
86
0
    ScDocumentUniquePtr pClipDoc(new ScDocument(SCDOCMODE_CLIP));
87
88
0
    ScMarkData aSourceMark(rDoc.GetSheetLimits());
89
0
    for (nTab = nStartTab; nTab <= nEndTab; nTab++)
90
0
        aSourceMark.SelectTable(nTab, true); // select source
91
0
    aSourceMark.SetMarkArea(aSource);
92
93
0
    ScDocShellRef aDragShellRef;
94
0
    if (rDoc.HasOLEObjectsInArea(aSource))
95
0
    {
96
0
        aDragShellRef = new ScDocShell; // DocShell needs a Ref immediately
97
0
        aDragShellRef->DoInitNew();
98
0
    }
99
0
    ScDrawLayer::SetGlobalDrawPersist(aDragShellRef.get());
100
101
0
    ScClipParam aClipParam(ScRange(nStartCol, nStartRow, nStartTab, nEndCol, nEndRow, nStartTab),
102
0
                           mbCut);
103
0
    rDoc.CopyToClip(aClipParam, pClipDoc.get(), &aSourceMark, bScenariosAdded, true);
104
105
0
    ScDrawLayer::SetGlobalDrawPersist(nullptr);
106
107
0
    SCCOL nOldEndCol = nEndCol;
108
0
    SCROW nOldEndRow = nEndRow;
109
0
    bool bClipOver = false;
110
0
    for (nTab = nStartTab; nTab <= nEndTab; nTab++)
111
0
    {
112
0
        SCCOL nTmpEndCol = nOldEndCol;
113
0
        SCROW nTmpEndRow = nOldEndRow;
114
0
        if (rDoc.ExtendMerge(nStartCol, nStartRow, nTmpEndCol, nTmpEndRow, nTab))
115
0
            bClipOver = true;
116
0
        if (nTmpEndCol > nEndCol)
117
0
            nEndCol = nTmpEndCol;
118
0
        if (nTmpEndRow > nEndRow)
119
0
            nEndRow = nTmpEndRow;
120
0
    }
121
122
0
    SCCOL nDestEndCol = nDestCol + (nOldEndCol - nStartCol);
123
0
    SCROW nDestEndRow = nDestRow + (nOldEndRow - nStartRow);
124
125
0
    SCCOL nUndoEndCol = nDestCol + (nEndCol - nStartCol); // extended in destination block
126
0
    SCROW nUndoEndRow = nDestRow + (nEndRow - nStartRow);
127
128
0
    bool bIncludeFiltered = mbCut;
129
0
    if (!bIncludeFiltered)
130
0
    {
131
        //  adjust sizes to include only non-filtered rows
132
133
0
        SCCOL nClipX;
134
0
        SCROW nClipY;
135
0
        pClipDoc->GetClipArea(nClipX, nClipY, false);
136
0
        SCROW nUndoAdd = nUndoEndRow - nDestEndRow;
137
0
        nDestEndRow = nDestRow + nClipY;
138
0
        nUndoEndRow = nDestEndRow + nUndoAdd;
139
0
    }
140
141
0
    if (!rDoc.ValidCol(nUndoEndCol) || !rDoc.ValidRow(nUndoEndRow))
142
0
    {
143
0
        if (!mbApi)
144
0
            mrDocShell.ErrorMessage(STR_PASTE_FULL);
145
0
        return false;
146
0
    }
147
148
    //  Test for cell protection
149
150
0
    ScEditableTester aTester;
151
0
    for (nTab = nDestTab; nTab <= nDestEndTab; nTab++)
152
0
        aTester.TestBlock(rDoc, nTab, nDestCol, nDestRow, nUndoEndCol, nUndoEndRow);
153
0
    if (mbCut)
154
0
        for (nTab = nStartTab; nTab <= nEndTab; nTab++)
155
0
            aTester.TestBlock(rDoc, nTab, nStartCol, nStartRow, nEndCol, nEndRow);
156
157
0
    if (!aTester.IsEditable())
158
0
    {
159
0
        if (!mbApi)
160
0
            mrDocShell.ErrorMessage(aTester.GetMessageId());
161
0
        return false;
162
0
    }
163
164
    //  Test for merged cells- when moving after delete
165
166
0
    if (bClipOver && !mbCut)
167
0
        if (rDoc.HasAttrib(nDestCol, nDestRow, nDestTab, nUndoEndCol, nUndoEndRow, nDestEndTab,
168
0
                           HasAttrFlags::Merged | HasAttrFlags::Overlapped))
169
0
        { // "Merge of already merged cells not possible"
170
0
            if (!mbApi)
171
0
                mrDocShell.ErrorMessage(STR_MSSG_MOVEBLOCKTO_0);
172
0
            return false;
173
0
        }
174
175
    //  Are there borders in the cells? (for painting)
176
177
0
    sal_uInt16 nSourceExt = 0;
178
0
    mrDocShell.UpdatePaintExt(nSourceExt, nStartCol, nStartRow, nStartTab, nEndCol, nEndRow,
179
0
                              nEndTab);
180
0
    sal_uInt16 nDestExt = 0;
181
0
    mrDocShell.UpdatePaintExt(nDestExt, nDestCol, nDestRow, nDestTab, nDestEndCol, nDestEndRow,
182
0
                              nDestEndTab);
183
184
    //  do it
185
186
0
    ScDocumentUniquePtr pUndoDoc;
187
188
0
    if (mbRecord)
189
0
    {
190
0
        bool bWholeCols = (nStartRow == 0 && nEndRow == rDoc.MaxRow());
191
0
        bool bWholeRows = (nStartCol == 0 && nEndCol == rDoc.MaxCol());
192
0
        InsertDeleteFlags nUndoFlags = (InsertDeleteFlags::ALL & ~InsertDeleteFlags::OBJECTS)
193
0
                                       | InsertDeleteFlags::NOCAPTIONS;
194
195
0
        pUndoDoc.reset(new ScDocument(SCDOCMODE_UNDO));
196
0
        pUndoDoc->InitUndo(rDoc, nStartTab, nEndTab, bWholeCols, bWholeRows);
197
198
0
        if (mbCut)
199
0
        {
200
0
            rDoc.CopyToDocument(nStartCol, nStartRow, nStartTab, nEndCol, nEndRow, nEndTab,
201
0
                                nUndoFlags, false, *pUndoDoc);
202
0
        }
203
204
0
        if (nDestTab != nStartTab)
205
0
            pUndoDoc->AddUndoTab(nDestTab, nDestEndTab, bWholeCols, bWholeRows);
206
0
        rDoc.CopyToDocument(nDestCol, nDestRow, nDestTab, nDestEndCol, nDestEndRow, nDestEndTab,
207
0
                            nUndoFlags, false, *pUndoDoc);
208
0
        rDoc.BeginDrawUndo();
209
0
    }
210
211
0
    bool bSourceHeight = false; // adjust heights?
212
0
    if (mbCut)
213
0
    {
214
0
        ScMarkData aDelMark(rDoc.GetSheetLimits()); // only for tables
215
0
        for (nTab = nStartTab; nTab <= nEndTab; nTab++)
216
0
        {
217
0
            rDoc.DeleteAreaTab(nStartCol, nStartRow, nOldEndCol, nOldEndRow, nTab,
218
0
                               InsertDeleteFlags::ALL);
219
0
            aDelMark.SelectTable(nTab, true);
220
0
        }
221
0
        rDoc.DeleteObjectsInArea(nStartCol, nStartRow, nOldEndCol, nOldEndRow, aDelMark);
222
223
        //  Test for merged cells
224
225
0
        if (bClipOver)
226
0
            if (rDoc.HasAttrib(nDestCol, nDestRow, nDestTab, nUndoEndCol, nUndoEndRow, nDestEndTab,
227
0
                               HasAttrFlags::Merged | HasAttrFlags::Overlapped))
228
0
            {
229
0
                rDoc.CopyFromClip(aSource, aSourceMark, InsertDeleteFlags::ALL, nullptr,
230
0
                                  pClipDoc.get());
231
0
                for (nTab = nStartTab; nTab <= nEndTab; nTab++)
232
0
                {
233
0
                    SCCOL nTmpEndCol = nEndCol;
234
0
                    SCROW nTmpEndRow = nEndRow;
235
0
                    rDoc.ExtendMerge(nStartCol, nStartRow, nTmpEndCol, nTmpEndRow, nTab, true);
236
0
                }
237
238
                // Report error only after restoring content
239
0
                if (!mbApi) // "Merge of already merged cells not possible"
240
0
                    mrDocShell.ErrorMessage(STR_MSSG_MOVEBLOCKTO_0);
241
242
0
                return false;
243
0
            }
244
245
0
        bSourceHeight = mrDocShell.GetDocFunc().AdjustRowHeight(aSource, false, mbApi);
246
0
    }
247
248
0
    ScRange aPasteDest(nDestCol, nDestRow, nDestTab, nDestEndCol, nDestEndRow, nDestEndTab);
249
250
0
    ScMarkData aDestMark(rDoc.GetSheetLimits());
251
0
    for (nTab = nDestTab; nTab <= nDestEndTab; nTab++)
252
0
        aDestMark.SelectTable(nTab, true); // select destination
253
0
    aDestMark.SetMarkArea(aPasteDest);
254
255
    /*  Do not copy drawing objects here. While pasting, the
256
        function ScDocument::UpdateReference() is called which calls
257
        ScDrawLayer::MoveCells() which may move away inserted objects to wrong
258
        positions (e.g. if source and destination range overlaps).*/
259
260
0
    rDoc.CopyFromClip(aPasteDest, aDestMark, InsertDeleteFlags::ALL & ~InsertDeleteFlags::OBJECTS,
261
0
                      pUndoDoc.get(), pClipDoc.get(), true, false, bIncludeFiltered);
262
263
    // skipped rows and merged cells don't mix
264
0
    if (!bIncludeFiltered && pClipDoc->HasClipFilteredRows())
265
0
        mrDocShell.GetDocFunc().UnmergeCells(aPasteDest, false, nullptr);
266
267
0
    bool bDestHeight = mrDocShell.GetDocFunc().AdjustRowHeight(
268
0
        ScRange(0, nDestRow, nDestTab, rDoc.MaxCol(), nDestEndRow, nDestEndTab), false, mbApi);
269
270
    /*  Paste drawing objects after adjusting formula references
271
        and row heights. There are no cell notes or drawing objects, if the
272
        clipdoc does not contain a drawing layer.*/
273
0
    if (pClipDoc->GetDrawLayer())
274
0
        rDoc.CopyFromClip(aPasteDest, aDestMark, InsertDeleteFlags::OBJECTS, nullptr,
275
0
                          pClipDoc.get(), true, false, bIncludeFiltered);
276
277
0
    if (mbRecord)
278
0
    {
279
0
        ScRange aUndoRange(nStartCol, nStartRow, nStartTab, nOldEndCol, nOldEndRow, nEndTab);
280
0
        ScAddress aDestPosUndo(nDestCol, nDestRow, nDestTab);
281
282
0
        mrDocShell.GetUndoManager()->AddUndoAction(std::make_unique<ScUndoDragDrop>(
283
0
            mrDocShell, aUndoRange, aDestPosUndo, mbCut, std::move(pUndoDoc), bScenariosAdded));
284
0
    }
285
286
0
    SCCOL nDestPaintEndCol = nDestEndCol;
287
0
    SCROW nDestPaintEndRow = nDestEndRow;
288
0
    for (nTab = nDestTab; nTab <= nDestEndTab; nTab++)
289
0
    {
290
0
        SCCOL nTmpEndCol = nDestEndCol;
291
0
        SCROW nTmpEndRow = nDestEndRow;
292
0
        rDoc.ExtendMerge(nDestCol, nDestRow, nTmpEndCol, nTmpEndRow, nTab, true);
293
0
        if (nTmpEndCol > nDestPaintEndCol)
294
0
            nDestPaintEndCol = nTmpEndCol;
295
0
        if (nTmpEndRow > nDestPaintEndRow)
296
0
            nDestPaintEndRow = nTmpEndRow;
297
0
    }
298
299
0
    if (mbCut)
300
0
        for (nTab = nStartTab; nTab <= nEndTab; nTab++)
301
0
            rDoc.RefreshAutoFilter(nStartCol, nStartRow, nEndCol, nEndRow, nTab);
302
303
0
    if (mbPaint)
304
0
    {
305
        //  destination range:
306
307
0
        SCCOL nPaintStartX = nDestCol;
308
0
        SCROW nPaintStartY = nDestRow;
309
0
        SCCOL nPaintEndX = nDestPaintEndCol;
310
0
        SCROW nPaintEndY = nDestPaintEndRow;
311
0
        PaintPartFlags nFlags = PaintPartFlags::Grid;
312
313
0
        if (nStartRow == 0 && nEndRow == rDoc.MaxRow()) // copy widths too?
314
0
        {
315
0
            nPaintEndX = rDoc.MaxCol();
316
0
            nPaintStartY = 0;
317
0
            nPaintEndY = rDoc.MaxRow();
318
0
            nFlags |= PaintPartFlags::Top;
319
0
        }
320
0
        if (bDestHeight || (nStartCol == 0 && nEndCol == rDoc.MaxCol()))
321
0
        {
322
0
            nPaintEndY = rDoc.MaxRow();
323
0
            nPaintStartX = 0;
324
0
            nPaintEndX = rDoc.MaxCol();
325
0
            nFlags |= PaintPartFlags::Left;
326
0
        }
327
0
        if (bScenariosAdded)
328
0
        {
329
0
            nPaintStartX = 0;
330
0
            nPaintStartY = 0;
331
0
            nPaintEndX = rDoc.MaxCol();
332
0
            nPaintEndY = rDoc.MaxRow();
333
0
        }
334
335
0
        mrDocShell.PostPaint(nPaintStartX, nPaintStartY, nDestTab, nPaintEndX, nPaintEndY,
336
0
                             nDestEndTab, nFlags, nSourceExt | nDestExt);
337
338
0
        if (mbCut)
339
0
        {
340
            //  source range:
341
342
0
            nPaintStartX = nStartCol;
343
0
            nPaintStartY = nStartRow;
344
0
            nPaintEndX = nEndCol;
345
0
            nPaintEndY = nEndRow;
346
0
            nFlags = PaintPartFlags::Grid;
347
348
0
            if (bSourceHeight)
349
0
            {
350
0
                nPaintEndY = rDoc.MaxRow();
351
0
                nPaintStartX = 0;
352
0
                nPaintEndX = rDoc.MaxCol();
353
0
                nFlags |= PaintPartFlags::Left;
354
0
            }
355
0
            if (bScenariosAdded)
356
0
            {
357
0
                nPaintStartX = 0;
358
0
                nPaintStartY = 0;
359
0
                nPaintEndX = rDoc.MaxCol();
360
0
                nPaintEndY = rDoc.MaxRow();
361
0
            }
362
363
0
            mrDocShell.PostPaint(nPaintStartX, nPaintStartY, nStartTab, nPaintEndX, nPaintEndY,
364
0
                                 nEndTab, nFlags, nSourceExt);
365
0
        }
366
0
    }
367
368
0
    syncSheetViews();
369
370
0
    aModificator.SetDocumentModified();
371
372
0
    SfxGetpApp()->Broadcast(SfxHint(SfxHintId::ScAreaLinksChanged));
373
374
0
    mrDocShell.ResolveSpilledOutputs();
375
376
0
    return true;
377
0
}
378
}
379
380
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */