/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: */ |