Coverage Report

Created: 2026-03-31 11:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/sc/source/ui/view/spelleng.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
 * This file incorporates work covered by the following license notice:
10
 *
11
 *   Licensed to the Apache Software Foundation (ASF) under one or more
12
 *   contributor license agreements. See the NOTICE file distributed
13
 *   with this work for additional information regarding copyright
14
 *   ownership. The ASF licenses this file to you under the Apache
15
 *   License, Version 2.0 (the "License"); you may not use this file
16
 *   except in compliance with the License. You may obtain a copy of
17
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18
 */
19
20
#include <spelleng.hxx>
21
#include <com/sun/star/i18n/TextConversionOption.hpp>
22
23
#include <scitems.hxx>
24
25
#include <editeng/langitem.hxx>
26
#include <editeng/editobj.hxx>
27
#include <editeng/editview.hxx>
28
#include <sfx2/viewfrm.hxx>
29
#include <utility>
30
#include <vcl/settings.hxx>
31
#include <vcl/svapp.hxx>
32
#include <vcl/weld/MessageDialog.hxx>
33
#include <vcl/weld/weld.hxx>
34
#include <osl/diagnose.h>
35
36
#include <spelldialog.hxx>
37
#include <tabvwsh.hxx>
38
#include <docsh.hxx>
39
#include <cellvalue.hxx>
40
#include <cellform.hxx>
41
#include <patattr.hxx>
42
#include <globstr.hrc>
43
#include <scresid.hxx>
44
#include <markdata.hxx>
45
46
#include <memory>
47
48
using namespace ::com::sun::star;
49
50
ScConversionEngineBase::ScConversionEngineBase(
51
        SfxItemPool* pEnginePoolP, ScViewData& rViewData,
52
        ScDocument* pUndoDoc, ScDocument* pRedoDoc ) :
53
0
    ScEditEngineDefaulter( pEnginePoolP ),
54
0
    mrViewData( rViewData ),
55
0
    mrDocShell( *rViewData.GetDocShell() ),
56
0
    mrDoc( rViewData.GetDocShell()->GetDocument() ),
57
0
    maSelState( rViewData ),
58
0
    mpUndoDoc( pUndoDoc ),
59
0
    mpRedoDoc( pRedoDoc ),
60
0
    meCurrLang( LANGUAGE_ENGLISH_US ),
61
0
    mbIsAnyModified( false ),
62
0
    mbInitialState( true ),
63
0
    mbWrappedInTable( false ),
64
0
    mbFinished( false )
65
0
{
66
0
    maSelState.GetCellCursor().GetVars( mnStartCol, mnStartRow, mnStartTab );
67
    // start with cell A1 in cell/range/multi-selection, will seek to first selected
68
0
    if( maSelState.GetSelectionType() == SC_SELECTTYPE_SHEET )
69
0
    {
70
0
        mnStartCol = 0;
71
0
        mnStartRow = 0;
72
0
    }
73
0
    mnCurrCol = mnStartCol;
74
0
    mnCurrRow = mnStartRow;
75
0
}
76
77
ScConversionEngineBase::~ScConversionEngineBase()
78
0
{
79
0
}
80
81
bool ScConversionEngineBase::FindNextConversionCell()
82
0
{
83
0
    ScMarkData& rMark = mrViewData.GetMarkData();
84
0
    ScTabViewShell* pViewShell = mrViewData.GetViewShell();
85
0
    const ScPatternAttr* pPattern = nullptr;
86
0
    const ScPatternAttr* pLastPattern = nullptr;
87
88
0
    SfxItemSet aEditDefaults(GetEmptyItemSet());
89
90
0
    if( IsModified() )
91
0
    {
92
0
        mbIsAnyModified = true;
93
94
0
        OUString aNewStr = GetText();
95
96
        // Check if the user has changed the language. If the new language is
97
        // applied to the entire string length, we will set the language as cell
98
        // attribute. Otherwise we will commit this as an edit-engine string.
99
0
        editeng::LanguageSpan aLang = GetLanguage(0, 0);
100
101
0
        bool bSimpleString = GetParagraphCount() == 1 &&
102
0
            aLang.nLang != LANGUAGE_DONTKNOW &&
103
0
            aLang.nStart == 0 &&
104
0
            aLang.nEnd == aNewStr.getLength();
105
106
0
        bool bMultiTab = (rMark.GetSelectCount() > 1);
107
108
0
        OUString aVisibleStr;
109
0
        if( bMultiTab )
110
0
            aVisibleStr = mrDoc.GetString(mnCurrCol, mnCurrRow, mnStartTab);
111
112
0
        for( SCTAB nTab = 0, nTabCount = mrDoc.GetTableCount(); nTab < nTabCount; ++nTab )
113
0
        {
114
            //  always change the cell on the visible tab,
115
            //  on the other selected tabs only if they contain the same text
116
117
0
            if ((nTab == mnStartTab) ||
118
0
                (bMultiTab && rMark.GetTableSelect(nTab) && mrDoc.GetString(mnCurrCol, mnCurrRow, nTab) == aVisibleStr))
119
0
            {
120
0
                ScAddress aPos( mnCurrCol, mnCurrRow, nTab );
121
0
                CellType eCellType = mrDoc.GetCellType( aPos );
122
0
                bool bEmptyCell = eCellType == CELLTYPE_NONE;
123
124
0
                if (mpUndoDoc && !bEmptyCell)
125
0
                    mrDoc.CopyCellToDocument(aPos, aPos, *mpUndoDoc);
126
127
0
                if (!bSimpleString || eCellType == CELLTYPE_EDIT)
128
0
                {
129
0
                    std::unique_ptr<EditTextObject> pEditObj(CreateTextObject());
130
0
                    mrDoc.SetEditText(aPos, *pEditObj, GetItemPool());
131
0
                }
132
0
                else
133
0
                {
134
                    // Set the new string and update the language with the cell.
135
0
                    mrDoc.SetString(aPos, aNewStr);
136
137
0
                    const ScPatternAttr* pAttr(mrDoc.GetPattern(aPos));
138
0
                    ScPatternAttr* pNewAttr(nullptr);
139
140
0
                    if (nullptr != pAttr)
141
0
                        pNewAttr = new ScPatternAttr(*pAttr);
142
0
                    else
143
0
                        pNewAttr = new ScPatternAttr(mrDoc.getCellAttributeHelper());
144
145
0
                    pNewAttr->ItemSetPut(SvxLanguageItem(aLang.nLang, ATTR_FONT_LANGUAGE));
146
0
                    mrDoc.SetPattern(aPos, CellAttributeHolder(pNewAttr, true));
147
0
                }
148
149
0
                if (mpRedoDoc && !bEmptyCell)
150
0
                    mrDoc.CopyCellToDocument(aPos, aPos, *mpRedoDoc);
151
152
0
                mrDocShell.PostPaintCell(aPos);
153
0
            }
154
0
        }
155
0
    }
156
157
0
    SCCOL nNewCol = mnCurrCol;
158
0
    SCROW nNewRow = mnCurrRow;
159
160
0
    if( mbInitialState )
161
0
    {
162
        /*  On very first call, decrement row to let GetNextSpellingCell() find
163
            the first cell of current range. */
164
0
        mbInitialState = false;
165
0
        --nNewRow;
166
0
    }
167
168
0
    bool bSheetSel = maSelState.GetSelectionType() == SC_SELECTTYPE_SHEET;
169
0
    bool bLoop = true;
170
0
    bool bFound = false;
171
0
    while( bLoop && !bFound )
172
0
    {
173
0
        bLoop = mrDoc.GetNextSpellingCell( nNewCol, nNewRow, mnStartTab, bSheetSel, rMark );
174
0
        if( bLoop )
175
0
        {
176
0
            FillFromCell( mnCurrCol, mnCurrRow, mnStartTab );
177
178
0
            if( mbWrappedInTable && ((nNewCol > mnStartCol) || ((nNewCol == mnStartCol) && (nNewRow >= mnStartRow))) )
179
0
            {
180
0
                ShowFinishDialog();
181
0
                bLoop = false;
182
0
                mbFinished = true;
183
0
            }
184
0
            else if( nNewCol >= mrDoc.GetAllocatedColumnsCount(mnStartTab) )
185
0
            {
186
                // no more cells in the sheet - try to restart at top of sheet
187
188
0
                if( bSheetSel || ((mnStartCol == 0) && (mnStartRow == 0)) )
189
0
                {
190
                    // conversion started at cell A1 or in selection, do not query to restart at top
191
0
                    ShowFinishDialog();
192
0
                    bLoop = false;
193
0
                    mbFinished = true;
194
0
                }
195
0
                else if( ShowTableWrapDialog() )
196
0
                {
197
                    // conversion started anywhere but in cell A1, user wants to restart
198
0
                    nNewRow = mrDoc.MaxRow() + 2;
199
0
                    mbWrappedInTable = true;
200
0
                }
201
0
                else
202
0
                {
203
0
                    bLoop = false;
204
0
                    mbFinished = true;
205
0
                }
206
0
            }
207
0
            else
208
0
            {
209
                // GetPattern may implicitly allocates the column if not exists,
210
0
                pPattern = mrDoc.GetPattern( nNewCol, nNewRow, mnStartTab );
211
0
                if( pPattern && !ScPatternAttr::areSame(pPattern, pLastPattern) )
212
0
                {
213
0
                    pPattern->FillEditItemSet( &aEditDefaults );
214
0
                    SetDefaults( aEditDefaults );
215
0
                    pLastPattern = pPattern;
216
0
                }
217
218
                // language changed?
219
0
                const SfxPoolItem& rItem = mrDoc.GetAttr( nNewCol, nNewRow, mnStartTab, ATTR_FONT_LANGUAGE );
220
0
                if (const SvxLanguageItem* pLangItem = dynamic_cast<const SvxLanguageItem*>(&rItem))
221
0
                {
222
0
                    LanguageType eLang = pLangItem->GetValue();
223
0
                    if( eLang == LANGUAGE_SYSTEM )
224
0
                        eLang = Application::GetSettings().GetLanguageTag().getLanguageType();   // never use SYSTEM for spelling
225
0
                    if( eLang != meCurrLang )
226
0
                    {
227
0
                        meCurrLang = eLang;
228
0
                        SetDefaultLanguage( eLang );
229
0
                    }
230
0
                }
231
232
0
                FillFromCell( nNewCol, nNewRow, mnStartTab );
233
234
0
                bFound = bLoop && NeedsConversion();
235
0
            }
236
0
        }
237
0
    }
238
239
0
    if( bFound )
240
0
    {
241
0
        pViewShell->AlignToCursor( nNewCol, nNewRow, SC_FOLLOW_JUMP );
242
0
        pViewShell->SetCursor( nNewCol, nNewRow, true );
243
0
        mrViewData.GetView()->MakeEditView(*this, nNewCol, nNewRow);
244
0
        EditView* pEditView = mrViewData.GetSpellingView();
245
        // maSelState.GetEditSelection() returns (0,0) if not in edit mode -> ok
246
0
        pEditView->SetSelection( maSelState.GetEditSelection() );
247
248
0
        ClearModifyFlag();
249
0
        mnCurrCol = nNewCol;
250
0
        mnCurrRow = nNewRow;
251
0
    }
252
253
0
    return bFound;
254
0
}
255
256
void ScConversionEngineBase::RestoreCursorPos()
257
0
{
258
0
    const ScAddress& rPos = maSelState.GetCellCursor();
259
0
    mrViewData.GetViewShell()->SetCursor( rPos.Col(), rPos.Row() );
260
0
}
261
262
bool ScConversionEngineBase::ShowTableWrapDialog()
263
0
{
264
    // default: no dialog, always restart at top
265
0
    return true;
266
0
}
267
268
void ScConversionEngineBase::ShowFinishDialog()
269
0
{
270
    // default: no dialog
271
0
}
272
273
// private --------------------------------------------------------------------
274
275
void ScConversionEngineBase::FillFromCell( SCCOL nCol, SCROW nRow, SCTAB nTab )
276
0
{
277
0
    ScAddress aPos(nCol, nRow, nTab);
278
279
0
    ScRefCellValue aCell(mrDoc, aPos);
280
0
    switch (aCell.getType())
281
0
    {
282
0
        case CELLTYPE_STRING:
283
0
        {
284
0
            sal_uInt32 nNumFmt = mrDoc.GetNumberFormat(ScRange(aPos));
285
0
            const Color* pColor;
286
0
            OUString aText = ScCellFormat::GetString(aCell, nNumFmt, &pColor, nullptr, mrDoc);
287
288
0
            SetTextCurrentDefaults(aText);
289
0
        }
290
0
        break;
291
0
        case CELLTYPE_EDIT:
292
0
        {
293
0
            const EditTextObject* pNewEditObj = aCell.getEditText();
294
0
            SetTextCurrentDefaults(*pNewEditObj);
295
0
        }
296
0
        break;
297
0
        default:
298
0
            SetTextCurrentDefaults(OUString());
299
0
    }
300
0
}
301
302
ScSpellingEngine::ScSpellingEngine(
303
        SfxItemPool* pEnginePoolP, ScViewData& rViewData,
304
        ScDocument* pUndoDoc, ScDocument* pRedoDoc,
305
        css::uno::Reference< css::linguistic2::XSpellChecker1 > const & xSpeller ) :
306
0
    ScConversionEngineBase( pEnginePoolP, rViewData, pUndoDoc, pRedoDoc )
307
0
{
308
0
    SetSpeller( xSpeller );
309
0
}
310
311
void ScSpellingEngine::ConvertAll(weld::Widget* pDialogParent, EditView& rEditView)
312
0
{
313
0
    EESpellState eState = EESpellState::Ok;
314
0
    if( FindNextConversionCell() )
315
0
        eState = rEditView.StartSpeller(pDialogParent, true);
316
317
0
    OSL_ENSURE( eState != EESpellState::NoSpeller, "ScSpellingEngine::Convert - no spell checker" );
318
0
}
319
320
bool ScSpellingEngine::SpellNextDocument()
321
0
{
322
0
    return FindNextConversionCell();
323
0
}
324
325
bool ScSpellingEngine::NeedsConversion()
326
0
{
327
0
    return HasSpellErrors() != EESpellState::Ok;
328
0
}
329
330
bool ScSpellingEngine::ShowTableWrapDialog()
331
0
{
332
0
    weld::Widget* pParent = GetDialogParent();
333
0
    weld::WaitObject aWaitOff(pParent);
334
335
0
    std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(pParent,
336
0
                                              VclMessageType::Question, VclButtonsType::YesNo,
337
0
                                              ScResId(STR_SPELLING_BEGIN_TAB))); // "delete data?"
338
0
    xBox->set_title(ScResId(STR_MSSG_DOSUBTOTALS_0));
339
0
    xBox->set_default_response(RET_YES);
340
0
    return xBox->run() == RET_YES;
341
0
}
342
343
void ScSpellingEngine::ShowFinishDialog()
344
0
{
345
0
    weld::Widget* pParent = GetDialogParent();
346
0
    weld::WaitObject aWaitOff(pParent);
347
0
    std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(pParent,
348
0
                                                  VclMessageType::Info, VclButtonsType::Ok,
349
0
                                                  ScResId(STR_SPELLING_STOP_OK)));
350
0
    xInfoBox->run();
351
0
}
352
353
weld::Widget* ScSpellingEngine::GetDialogParent()
354
0
{
355
0
    sal_uInt16 nWinId = ScSpellDialogChildWindow::GetChildWindowId();
356
0
    SfxViewFrame& rViewFrm = mrViewData.GetViewShell()->GetViewFrame();
357
0
    if( rViewFrm.HasChildWindow( nWinId ) )
358
0
    {
359
0
        if( SfxChildWindow* pChild = rViewFrm.GetChildWindow( nWinId ) )
360
0
        {
361
0
            auto xController = pChild->GetController();
362
0
            if (xController)
363
0
            {
364
0
                if (weld::Window *pRet = xController->getDialog())
365
0
                {
366
0
                    if (pRet->get_visible())
367
0
                        return pRet;
368
0
                }
369
0
            }
370
0
        }
371
0
    }
372
373
    // fall back to standard dialog parent
374
0
    return ScDocShell::GetActiveDialogParent();
375
0
}
376
377
ScConversionParam::ScConversionParam( ScConversionType eConvType ) :
378
0
    meConvType( eConvType ),
379
0
    meSourceLang( LANGUAGE_NONE ),
380
0
    meTargetLang( LANGUAGE_NONE ),
381
0
    mnOptions( 0 ),
382
0
    mbUseTargetFont( false ),
383
0
    mbIsInteractive( false )
384
0
{
385
0
}
386
387
ScConversionParam::ScConversionParam( ScConversionType eConvType,
388
        LanguageType eLang, sal_Int32 nOptions, bool bIsInteractive ) :
389
0
    meConvType( eConvType ),
390
0
    meSourceLang( eLang ),
391
0
    meTargetLang( eLang ),
392
0
    mnOptions( nOptions ),
393
0
    mbUseTargetFont( false ),
394
0
    mbIsInteractive( bIsInteractive )
395
0
{
396
0
    if (LANGUAGE_KOREAN == eLang)
397
0
        mnOptions = i18n::TextConversionOption::CHARACTER_BY_CHARACTER;
398
0
}
399
400
ScConversionParam::ScConversionParam( ScConversionType eConvType,
401
        LanguageType eSourceLang, LanguageType eTargetLang, vcl::Font aTargetFont,
402
        sal_Int32 nOptions, bool bIsInteractive ) :
403
0
    meConvType( eConvType ),
404
0
    meSourceLang( eSourceLang ),
405
0
    meTargetLang( eTargetLang ),
406
0
    maTargetFont(std::move( aTargetFont )),
407
0
    mnOptions( nOptions ),
408
0
    mbUseTargetFont( true ),
409
0
    mbIsInteractive( bIsInteractive )
410
0
{
411
0
    if (LANGUAGE_KOREAN == meSourceLang && LANGUAGE_KOREAN == meTargetLang)
412
0
        mnOptions = i18n::TextConversionOption::CHARACTER_BY_CHARACTER;
413
0
}
414
415
ScTextConversionEngine::ScTextConversionEngine(
416
        SfxItemPool* pEnginePoolP, ScViewData& rViewData,
417
        ScConversionParam aConvParam,
418
        ScDocument* pUndoDoc, ScDocument* pRedoDoc ) :
419
0
    ScConversionEngineBase( pEnginePoolP, rViewData, pUndoDoc, pRedoDoc ),
420
0
    maConvParam(std::move( aConvParam ))
421
0
{
422
0
}
423
424
void ScTextConversionEngine::ConvertAll(weld::Widget* pDialogParent, EditView& rEditView)
425
0
{
426
0
    if( FindNextConversionCell() )
427
0
    {
428
0
        rEditView.StartTextConversion(pDialogParent,
429
0
            maConvParam.GetSourceLang(), maConvParam.GetTargetLang(), maConvParam.GetTargetFont(),
430
0
            maConvParam.GetOptions(), maConvParam.IsInteractive(), true );
431
        // #i34769# restore initial cursor position
432
0
        RestoreCursorPos();
433
0
    }
434
0
}
435
436
bool ScTextConversionEngine::ConvertNextDocument()
437
0
{
438
0
    return FindNextConversionCell();
439
0
}
440
441
bool ScTextConversionEngine::NeedsConversion()
442
0
{
443
0
    return HasConvertibleTextPortion( maConvParam.GetSourceLang() );
444
0
}
445
446
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */