Coverage Report

Created: 2026-06-30 11:14

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/sc/source/ui/operation/QueryOperation.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/QueryOperation.hxx>
11
12
#include <dbdata.hxx>
13
#include <docsh.hxx>
14
#include <editable.hxx>
15
#include <globstr.hrc>
16
#include <tabvwsh.hxx>
17
#include <patattr.hxx>
18
#include <queryentry.hxx>
19
#include <markdata.hxx>
20
#include <progress.hxx>
21
#include <undodat.hxx>
22
#include <scresid.hxx>
23
#include <globalnames.hxx>
24
#include <SheetViewOperationsTester.hxx>
25
26
#include <osl/diagnose.h>
27
#include <vcl/weld/WaitObject.hxx>
28
#include <vcl/weld/Window.hxx>
29
30
namespace sc
31
{
32
QueryOperation::QueryOperation(ScDocShell& rDocShell, SCTAB nTab, ScQueryParam const& rQueryParam,
33
                               ScRange const* pAdvSource, bool bRecord, bool bApi)
34
0
    : Operation(OperationType::Query, bRecord, bApi)
35
0
    , mrDocShell(rDocShell)
36
0
    , mnTab(nTab)
37
0
    , mrQueryParam(rQueryParam)
38
0
    , mpAdvSource(pAdvSource)
39
0
{
40
0
}
41
42
bool QueryOperation::runImplementation()
43
0
{
44
0
    ScDocShellModificator aModificator(mrDocShell);
45
46
0
    ScDocument& rDoc = mrDocShell.GetDocument();
47
48
0
    ScTabViewShell* pViewSh = mrDocShell.GetBestViewShell();
49
0
    if (pViewSh
50
0
        && ScTabViewShell::isAnyEditViewInRange(pViewSh, /*bColumns*/ false, mrQueryParam.nRow1,
51
0
                                                mrQueryParam.nRow2))
52
0
    {
53
0
        return false;
54
0
    }
55
56
0
    if (mbRecord && !rDoc.IsUndoEnabled())
57
0
        mbRecord = false;
58
59
0
    ScDBData* pDBData = rDoc.GetDBAtArea(mnTab, mrQueryParam.nCol1, mrQueryParam.nRow1,
60
0
                                         mrQueryParam.nCol2, mrQueryParam.nRow2);
61
0
    if (!pDBData)
62
0
    {
63
0
        OSL_FAIL("Query: no DBData");
64
0
        return false;
65
0
    }
66
67
    //  Change from Inplace to non-Inplace, only then cancel Inplace:
68
    //  (only if "Persistent"  is selected in the dialog)
69
70
0
    if (!mrQueryParam.bInplace && pDBData->HasQueryParam() && mrQueryParam.bDestPers)
71
0
    {
72
0
        ScQueryParam aOldQuery;
73
0
        pDBData->GetQueryParam(aOldQuery);
74
0
        if (aOldQuery.bInplace)
75
0
        {
76
            //  cancel old filtering
77
78
0
            SCSIZE nEC = aOldQuery.GetEntryCount();
79
0
            for (SCSIZE i = 0; i < nEC; i++)
80
0
                aOldQuery.GetEntry(i).bDoQuery = false;
81
0
            aOldQuery.bDuplicate = true;
82
0
            QueryOperation aOperation(mrDocShell, mnTab, aOldQuery, nullptr, mbRecord, mbApi);
83
0
            aOperation.run();
84
0
        }
85
0
    }
86
87
0
    ScQueryParam aLocalParam(mrQueryParam); // for Paint / destination range
88
0
    bool bCopy = !mrQueryParam.bInplace; // copied in Table::Query
89
0
    ScDBData* pDestData = nullptr; // range to be copied to
90
0
    bool bDoSize = false; // adjust destination size (insert/delete)
91
0
    SCCOL nFormulaCols = 0; // only at bDoSize
92
0
    bool bKeepFmt = false;
93
0
    ScRange aOldDest;
94
0
    ScRange aDestTotal;
95
0
    if (bCopy && mrQueryParam.nDestCol == mrQueryParam.nCol1
96
0
        && mrQueryParam.nDestRow == mrQueryParam.nRow1 && mrQueryParam.nDestTab == mnTab)
97
0
        bCopy = false;
98
0
    SCTAB nDestTab = mnTab;
99
0
    if (bCopy)
100
0
    {
101
0
        aLocalParam.MoveToDest();
102
0
        nDestTab = mrQueryParam.nDestTab;
103
0
        if (!rDoc.ValidColRow(aLocalParam.nCol2, aLocalParam.nRow2))
104
0
        {
105
0
            if (!mbApi)
106
0
                mrDocShell.ErrorMessage(STR_PASTE_FULL);
107
0
            return false;
108
0
        }
109
110
0
        if (!checkSheetViewProtection())
111
0
            return false;
112
113
0
        ScEditableTester aTester = ScEditableTester::CreateAndTestBlock(
114
0
            rDoc, nDestTab, aLocalParam.nCol1, aLocalParam.nRow1, aLocalParam.nCol2,
115
0
            aLocalParam.nRow2);
116
0
        if (!aTester.IsEditable())
117
0
        {
118
0
            if (!mbApi)
119
0
                mrDocShell.ErrorMessage(aTester.GetMessageId());
120
0
            return false;
121
0
        }
122
123
0
        pDestData = rDoc.GetDBAtCursor(mrQueryParam.nDestCol, mrQueryParam.nDestRow,
124
0
                                       mrQueryParam.nDestTab, ScDBDataPortion::TOP_LEFT);
125
0
        if (pDestData)
126
0
        {
127
0
            pDestData->GetArea(aOldDest);
128
0
            aDestTotal = ScRange(mrQueryParam.nDestCol, mrQueryParam.nDestRow, nDestTab,
129
0
                                 mrQueryParam.nDestCol + mrQueryParam.nCol2 - mrQueryParam.nCol1,
130
0
                                 mrQueryParam.nDestRow + mrQueryParam.nRow2 - mrQueryParam.nRow1,
131
0
                                 nDestTab);
132
133
0
            bDoSize = pDestData->IsDoSize();
134
            //  test if formulas need to be filled in (nFormulaCols):
135
0
            if (bDoSize && aOldDest.aEnd.Col() == aDestTotal.aEnd.Col())
136
0
            {
137
0
                SCCOL nTestCol = aOldDest.aEnd.Col() + 1; // next to the range
138
0
                SCROW nTestRow = mrQueryParam.nDestRow + (aLocalParam.bHasHeader ? 1 : 0);
139
0
                while (nTestCol <= rDoc.MaxCol()
140
0
                       && rDoc.GetCellType(ScAddress(nTestCol, nTestRow, mnTab))
141
0
                              == CELLTYPE_FORMULA)
142
0
                {
143
0
                    ++nTestCol;
144
0
                    ++nFormulaCols;
145
0
                }
146
0
            }
147
148
0
            bKeepFmt = pDestData->IsKeepFmt();
149
0
            if (bDoSize && !rDoc.CanFitBlock(aOldDest, aDestTotal))
150
0
            {
151
0
                if (!mbApi)
152
0
                    mrDocShell.ErrorMessage(STR_MSSG_DOSUBTOTALS_2); // cannot insert rows
153
0
                return false;
154
0
            }
155
0
        }
156
0
    }
157
158
    //      execute
159
160
0
    weld::WaitObject aWait(ScDocShell::GetActiveDialogParent());
161
162
0
    bool bKeepSub = false; // repeat existing partial results?
163
0
    bool bKeepTotals = false;
164
0
    if (mrQueryParam.GetEntry(0).bDoQuery) // not at cancellation
165
0
    {
166
0
        ScSubTotalParam aSubTotalParam;
167
0
        pDBData->GetSubTotalParam(aSubTotalParam); // partial results exist?
168
169
0
        if (aSubTotalParam.aGroups[0].bActive && !aSubTotalParam.bRemoveOnly)
170
0
            bKeepSub = true;
171
172
0
        if (pDBData->HasTotals() && pDBData->GetTableStyleInfo())
173
0
            bKeepTotals = true;
174
0
    }
175
176
0
    ScDocumentUniquePtr pUndoDoc;
177
0
    std::unique_ptr<ScDBCollection> pUndoDB;
178
0
    const ScRange* pOld = nullptr;
179
180
0
    if (mbRecord)
181
0
    {
182
0
        pUndoDoc.reset(new ScDocument(SCDOCMODE_UNDO));
183
0
        if (bCopy)
184
0
        {
185
0
            pUndoDoc->InitUndo(rDoc, nDestTab, nDestTab, false, true);
186
0
            rDoc.CopyToDocument(aLocalParam.nCol1, aLocalParam.nRow1, nDestTab, aLocalParam.nCol2,
187
0
                                aLocalParam.nRow2, nDestTab, InsertDeleteFlags::ALL, false,
188
0
                                *pUndoDoc);
189
            //  secure attributes in case they were copied along
190
191
0
            if (pDestData)
192
0
            {
193
0
                rDoc.CopyToDocument(aOldDest, InsertDeleteFlags::ALL, false, *pUndoDoc);
194
0
                pOld = &aOldDest;
195
0
            }
196
0
        }
197
0
        else
198
0
        {
199
0
            pUndoDoc->InitUndo(rDoc, mnTab, mnTab, false, true);
200
0
            rDoc.CopyToDocument(0, mrQueryParam.nRow1, mnTab, rDoc.MaxCol(), mrQueryParam.nRow2,
201
0
                                mnTab, InsertDeleteFlags::NONE, false, *pUndoDoc);
202
0
        }
203
204
0
        ScDBCollection* pDocDB = rDoc.GetDBCollection();
205
0
        if (!pDocDB->empty())
206
0
            pUndoDB.reset(new ScDBCollection(*pDocDB));
207
208
0
        rDoc.BeginDrawUndo();
209
0
    }
210
211
0
    std::unique_ptr<ScDocument> pAttribDoc;
212
0
    ScRange aAttribRange;
213
0
    if (pDestData) // delete destination range
214
0
    {
215
0
        if (bKeepFmt)
216
0
        {
217
            //  smaller of the end columns, header+1 row
218
0
            aAttribRange = aOldDest;
219
0
            if (aAttribRange.aEnd.Col() > aDestTotal.aEnd.Col())
220
0
                aAttribRange.aEnd.SetCol(aDestTotal.aEnd.Col());
221
0
            aAttribRange.aEnd.SetRow(aAttribRange.aStart.Row() + (aLocalParam.bHasHeader ? 1 : 0));
222
223
            //  also for filled-in formulas
224
0
            aAttribRange.aEnd.SetCol(aAttribRange.aEnd.Col() + nFormulaCols);
225
226
0
            pAttribDoc.reset(new ScDocument(SCDOCMODE_UNDO));
227
0
            pAttribDoc->InitUndo(rDoc, nDestTab, nDestTab, false, true);
228
0
            rDoc.CopyToDocument(aAttribRange, InsertDeleteFlags::ATTRIB, false, *pAttribDoc);
229
0
        }
230
231
0
        if (bDoSize)
232
0
            rDoc.FitBlock(aOldDest, aDestTotal);
233
0
        else
234
0
            rDoc.DeleteAreaTab(aOldDest, InsertDeleteFlags::ALL); // simply delete
235
0
    }
236
237
    //  execute filtering on the document
238
0
    SCSIZE nCount = rDoc.Query(mnTab, mrQueryParam, bKeepSub, bKeepTotals);
239
0
    pDBData->CalcSaveFilteredCount(nCount);
240
0
    if (bCopy)
241
0
    {
242
0
        aLocalParam.nRow2 = aLocalParam.nRow1 + nCount;
243
0
        if (!aLocalParam.bHasHeader && nCount > 0)
244
0
            --aLocalParam.nRow2;
245
246
0
        if (bDoSize)
247
0
        {
248
            //  adjust to the real result range
249
            //  (this here is always a reduction)
250
251
0
            ScRange aNewDest(aLocalParam.nCol1, aLocalParam.nRow1, nDestTab, aLocalParam.nCol2,
252
0
                             aLocalParam.nRow2, nDestTab);
253
0
            rDoc.FitBlock(aDestTotal, aNewDest, false); // sal_False - don't delete
254
255
0
            if (nFormulaCols > 0)
256
0
            {
257
                //  fill in formulas
258
                //! Undo (Query and Repeat) !!!
259
260
0
                ScRange aNewForm(aLocalParam.nCol2 + 1, aLocalParam.nRow1, nDestTab,
261
0
                                 aLocalParam.nCol2 + nFormulaCols, aLocalParam.nRow2, nDestTab);
262
0
                ScRange aOldForm = aNewForm;
263
0
                aOldForm.aEnd.SetRow(aOldDest.aEnd.Row());
264
0
                rDoc.FitBlock(aOldForm, aNewForm, false);
265
266
0
                ScMarkData aMark(rDoc.GetSheetLimits());
267
0
                aMark.SelectOneTable(nDestTab);
268
0
                SCROW nFStartY = aLocalParam.nRow1 + (aLocalParam.bHasHeader ? 1 : 0);
269
270
0
                sal_uLong nProgCount = nFormulaCols;
271
0
                nProgCount *= aLocalParam.nRow2 - nFStartY;
272
0
                ScProgress aProgress(rDoc.GetDocumentShell(), ScResId(STR_FILL_SERIES_PROGRESS),
273
0
                                     nProgCount, true);
274
275
0
                rDoc.Fill(aLocalParam.nCol2 + 1, nFStartY, aLocalParam.nCol2 + nFormulaCols,
276
0
                          nFStartY, &aProgress, aMark, aLocalParam.nRow2 - nFStartY, FILL_TO_BOTTOM,
277
0
                          FILL_SIMPLE);
278
0
            }
279
0
        }
280
281
0
        if (pAttribDoc) // copy back the memorized attributes
282
0
        {
283
            //  Header
284
0
            if (aLocalParam.bHasHeader)
285
0
            {
286
0
                ScRange aHdrRange = aAttribRange;
287
0
                aHdrRange.aEnd.SetRow(aHdrRange.aStart.Row());
288
0
                pAttribDoc->CopyToDocument(aHdrRange, InsertDeleteFlags::ATTRIB, false, rDoc);
289
0
            }
290
291
            //  Data
292
0
            SCCOL nAttrEndCol = aAttribRange.aEnd.Col();
293
0
            SCROW nAttrRow = aAttribRange.aStart.Row() + (aLocalParam.bHasHeader ? 1 : 0);
294
0
            for (SCCOL nCol = aAttribRange.aStart.Col(); nCol <= nAttrEndCol; nCol++)
295
0
            {
296
0
                const ScPatternAttr* pSrcPattern = pAttribDoc->GetPattern(nCol, nAttrRow, nDestTab);
297
0
                OSL_ENSURE(pSrcPattern, "Pattern is 0");
298
0
                if (pSrcPattern)
299
0
                {
300
0
                    rDoc.ApplyPatternAreaTab(nCol, nAttrRow, nCol, aLocalParam.nRow2, nDestTab,
301
0
                                             *pSrcPattern);
302
0
                    const ScStyleSheet* pStyle = pSrcPattern->GetStyleSheet();
303
0
                    if (pStyle)
304
0
                        rDoc.ApplyStyleAreaTab(nCol, nAttrRow, nCol, aLocalParam.nRow2, nDestTab,
305
0
                                               *pStyle);
306
0
                }
307
0
            }
308
0
        }
309
0
    }
310
311
    //  saving: Inplace always, otherwise depending on setting
312
    //          old Inplace-Filter may have already been removed
313
314
0
    bool bSave = mrQueryParam.bInplace || mrQueryParam.bDestPers;
315
0
    if (bSave) // memorize
316
0
    {
317
0
        pDBData->SetQueryParam(mrQueryParam);
318
0
        pDBData->SetHeader(mrQueryParam.bHasHeader); //! ???
319
0
        pDBData->SetAdvancedQuerySource(mpAdvSource); // after SetQueryParam
320
0
    }
321
322
0
    if (bCopy) // memorize new DB range
323
0
    {
324
        //  Selection is done afterwards from outside (dbfunc).
325
        //  Currently through the DB area at the destination position,
326
        //  so a range must be created there in any case.
327
328
0
        ScDBData* pNewData;
329
0
        if (pDestData)
330
0
            pNewData = pDestData; // range exists -> adjust (always!)
331
0
        else // create range
332
0
            pNewData = mrDocShell.GetDBData(ScRange(aLocalParam.nCol1, aLocalParam.nRow1, nDestTab,
333
0
                                                    aLocalParam.nCol2, aLocalParam.nRow2, nDestTab),
334
0
                                            SC_DB_MAKE, ScGetDBSelection::ForceMark);
335
336
0
        if (pNewData)
337
0
        {
338
0
            pNewData->SetArea(nDestTab, aLocalParam.nCol1, aLocalParam.nRow1, aLocalParam.nCol2,
339
0
                              aLocalParam.nRow2);
340
341
            //  query parameter is no longer set at the destination, only leads to confusion
342
            //  and mistakes with the query parameter at the source range (#37187#)
343
0
        }
344
0
        else
345
0
        {
346
0
            OSL_FAIL("Target are not available");
347
0
        }
348
0
    }
349
350
0
    if (!bCopy)
351
0
    {
352
0
        rDoc.InvalidatePageBreaks(mnTab);
353
0
        rDoc.UpdatePageBreaks(mnTab);
354
0
    }
355
356
    // #i23299# Subtotal functions depend on cell's filtered states.
357
0
    ScRange aDirtyRange(0, aLocalParam.nRow1, nDestTab, rDoc.MaxCol(), aLocalParam.nRow2, nDestTab);
358
0
    rDoc.SetSubTotalCellsDirty(aDirtyRange);
359
360
0
    if (mbRecord)
361
0
    {
362
        // create undo action after executing, because of drawing layer undo
363
0
        mrDocShell.GetUndoManager()->AddUndoAction(
364
0
            std::make_unique<ScUndoQuery>(mrDocShell, mnTab, mrQueryParam, std::move(pUndoDoc),
365
0
                                          std::move(pUndoDB), pOld, bDoSize, mpAdvSource));
366
0
    }
367
368
0
    if (pViewSh)
369
0
    {
370
        // could there be horizontal autofilter ?
371
        // maybe it would be better to set bColumns to !rQueryParam.bByRow ?
372
        // anyway at the beginning the value of bByRow is 'false'
373
        // then after the first sort action it becomes 'true'
374
0
        pViewSh->OnLOKShowHideColRow(/*bColumns*/ false, mrQueryParam.nRow1 - 1);
375
0
    }
376
377
0
    if (bCopy)
378
0
    {
379
0
        SCCOL nEndX = aLocalParam.nCol2;
380
0
        SCROW nEndY = aLocalParam.nRow2;
381
0
        if (pDestData)
382
0
        {
383
0
            if (aOldDest.aEnd.Col() > nEndX)
384
0
                nEndX = aOldDest.aEnd.Col();
385
0
            if (aOldDest.aEnd.Row() > nEndY)
386
0
                nEndY = aOldDest.aEnd.Row();
387
0
        }
388
0
        if (bDoSize)
389
0
            nEndY = rDoc.MaxRow();
390
391
        // remove AutoFilter button flags
392
0
        mrDocShell.DBAreaDeleted(nDestTab, aLocalParam.nCol1, aLocalParam.nRow1, aLocalParam.nCol2);
393
394
0
        mrDocShell.PostPaint(
395
0
            ScRange(aLocalParam.nCol1, aLocalParam.nRow1, nDestTab, nEndX, nEndY, nDestTab),
396
0
            PaintPartFlags::Grid);
397
0
    }
398
0
    else
399
0
        mrDocShell.PostPaint(
400
0
            ScRange(0, mrQueryParam.nRow1, mnTab, rDoc.MaxCol(), rDoc.MaxRow(), mnTab),
401
0
            PaintPartFlags::Grid | PaintPartFlags::Left);
402
0
    aModificator.SetDocumentModified();
403
404
0
    return true;
405
0
}
406
}
407
408
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */