lok: Don't attempt to select the exact text after a failed search.
[LibreOffice.git] / cui / source / dialogs / SpellDialog.cxx
blobf8952df4c2baee62b50b838ed89851f0cffb04c0
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include <vcl/wrkwin.hxx>
21 #include <vcl/menu.hxx>
22 #include <vcl/layout.hxx>
23 #include <vcl/scrbar.hxx>
24 #include <vcl/settings.hxx>
25 #include <SpellAttrib.hxx>
26 #include <sfx2/dispatch.hxx>
27 #include <sfx2/bindings.hxx>
28 #include <svl/undo.hxx>
29 #include <unotools/lingucfg.hxx>
30 #include <vcl/textdata.hxx>
31 #include <vcl/graphicfilter.hxx>
32 #include <editeng/unolingu.hxx>
33 #include <editeng/splwrap.hxx>
34 #include <linguistic/lngprops.hxx>
35 #include <linguistic/misc.hxx>
36 #include <comphelper/processfactory.hxx>
37 #include <com/sun/star/lang/XMultiServiceFactory.hpp>
38 #include <com/sun/star/lang/XServiceInfo.hpp>
39 #include <com/sun/star/lang/XServiceDisplayName.hpp>
40 #include <com/sun/star/linguistic2/SpellFailure.hpp>
41 #include <com/sun/star/frame/XStorable.hpp>
42 #include <com/sun/star/system/SystemShellExecuteFlags.hpp>
43 #include <com/sun/star/system/SystemShellExecute.hpp>
44 #include <sfx2/app.hxx>
45 #include <vcl/help.hxx>
46 #include <vcl/graph.hxx>
47 #include <vcl/builderfactory.hxx>
48 #include <osl/file.hxx>
49 #include <cuires.hrc>
50 #include <editeng/optitems.hxx>
51 #include <editeng/svxenum.hxx>
52 #include <svx/SpellDialogChildWindow.hxx>
53 #include "SpellDialog.hxx"
54 #include <svx/dlgutil.hxx>
55 #include "optlingu.hxx"
56 #include <dialmgr.hxx>
57 #include <svx/svxerr.hxx>
58 #include "treeopt.hxx"
59 #include <svtools/langtab.hxx>
60 #include <comphelper/anytostring.hxx>
61 #include <cppuhelper/exc_hlp.hxx>
62 #include <boost/scoped_ptr.hpp>
64 using namespace ::com::sun::star;
65 using namespace ::com::sun::star::uno;
66 using namespace ::com::sun::star::beans;
67 using namespace ::com::sun::star::linguistic2;
68 using namespace linguistic;
71 // struct SpellDialog_Impl ---------------------------------------------
73 struct SpellDialog_Impl
75 Sequence< Reference< XDictionary > > aDics;
79 #define SPELLUNDO_START 200
81 #define SPELLUNDO_CHANGE_LANGUAGE (SPELLUNDO_START + 1)
82 #define SPELLUNDO_CHANGE_TEXTENGINE (SPELLUNDO_START + 2)
83 #define SPELLUNDO_CHANGE_NEXTERROR (SPELLUNDO_START + 3)
84 #define SPELLUNDO_CHANGE_ADD_TO_DICTIONARY (SPELLUNDO_START + 4)
85 #define SPELLUNDO_CHANGE_GROUP (SPELLUNDO_START + 5) //undo list
86 #define SPELLUNDO_MOVE_ERROREND (SPELLUNDO_START + 6)
87 #define SPELLUNDO_UNDO_EDIT_MODE (SPELLUNDO_START + 7)
88 #define SPELLUNDO_ADD_IGNORE_RULE (SPELLUNDO_START + 8)
90 namespace svx{
91 class SpellUndoAction_Impl : public SfxUndoAction
93 sal_uInt16 m_nId;
94 const Link<>& m_rActionLink;
95 //undo of button enabling
96 bool m_bEnableChangePB;
97 bool m_bEnableChangeAllPB;
98 //undo of MarkNextError - used in change and change all, ignore and ignore all
99 long m_nNewErrorStart;
100 long m_nNewErrorEnd;
101 long m_nOldErrorStart;
102 long m_nOldErrorEnd;
103 bool m_bIsErrorLanguageSelected;
104 //undo of AddToDictionary
105 Reference<XDictionary> m_xDictionary;
106 OUString m_sAddedWord;
107 //move end of error - ::ChangeMarkedWord()
108 long m_nOffset;
110 public:
111 SpellUndoAction_Impl(sal_uInt16 nId, const Link<>& rActionLink) :
112 m_nId(nId),
113 m_rActionLink( rActionLink),
114 m_bEnableChangePB(false),
115 m_bEnableChangeAllPB(false),
116 m_nNewErrorStart(-1),
117 m_nNewErrorEnd(-1),
118 m_nOldErrorStart(-1),
119 m_nOldErrorEnd(-1),
120 m_bIsErrorLanguageSelected(false),
121 m_nOffset(0)
124 virtual ~SpellUndoAction_Impl();
126 virtual void Undo() SAL_OVERRIDE;
127 virtual sal_uInt16 GetId() const SAL_OVERRIDE;
129 void SetEnableChangePB(){m_bEnableChangePB = true;}
130 bool IsEnableChangePB(){return m_bEnableChangePB;}
132 void SetEnableChangeAllPB(){m_bEnableChangeAllPB = true;}
133 bool IsEnableChangeAllPB(){return m_bEnableChangeAllPB;}
135 void SetErrorMove(long nNewStart, long nNewEnd, long nOldStart, long nOldEnd)
137 m_nNewErrorStart = nNewStart;
138 m_nNewErrorEnd = nNewEnd;
139 m_nOldErrorStart = nOldStart;
140 m_nOldErrorEnd = nOldEnd;
142 long GetOldErrorStart() { return m_nOldErrorStart;}
143 long GetOldErrorEnd() { return m_nOldErrorEnd;}
145 void SetErrorLanguageSelected(bool bSet){ m_bIsErrorLanguageSelected = bSet;}
146 bool IsErrorLanguageSelected() const {return m_bIsErrorLanguageSelected;}
148 void SetDictionary(Reference<XDictionary> xDict) { m_xDictionary = xDict; }
149 Reference<XDictionary> GetDictionary() const {return m_xDictionary;}
150 void SetAddedWord(const OUString& rWord) {m_sAddedWord = rWord;}
151 const OUString& GetAddedWord() const { return m_sAddedWord;}
153 void SetOffset(long nSet) {m_nOffset = nSet;}
154 long GetOffset() const {return m_nOffset;}
156 }//namespace svx
157 using namespace ::svx;
160 SpellUndoAction_Impl::~SpellUndoAction_Impl()
165 void SpellUndoAction_Impl::Undo()
167 m_rActionLink.Call(this);
171 sal_uInt16 SpellUndoAction_Impl::GetId()const
173 return m_nId;
176 // class SvxSpellCheckDialog ---------------------------------------------
178 SpellDialog::SpellDialog(SpellDialogChildWindow* pChildWindow,
179 vcl::Window * pParent, SfxBindings* _pBindings)
180 : SfxModelessDialog (_pBindings, pChildWindow,
181 pParent, "SpellingDialog", "cui/ui/spellingdialog.ui")
182 , aDialogUndoLink(LINK (this, SpellDialog, DialogUndoHdl))
183 , bModified(false)
184 , bFocusLocked(true)
185 , rParent(*pChildWindow)
187 m_sTitleSpellingGrammar = GetText();
188 m_sTitleSpelling = get<FixedText>("alttitleft")->GetText();
190 // fdo#68794 set initial title for cases where no text has been processed
191 // yet to show its language attributes
192 OUString sTitle = rParent.HasGrammarChecking() ? m_sTitleSpellingGrammar : m_sTitleSpelling;
193 SetText(sTitle.replaceFirst("$LANGUAGE ($LOCATION)", ""));
195 m_sResumeST = get<FixedText>("resumeft")->GetText();
196 m_sNoSuggestionsST = get<FixedText>("nosuggestionsft")->GetText();
198 get(m_pLanguageFT, "languageft");
199 get(m_pLanguageLB, "languagelb");
200 get(m_pExplainFT, "explain");
201 get(m_pExplainLink, "explainlink");
202 get(m_pNotInDictFT, "notindictft");
203 get(m_pSentenceED, "sentence");
204 Size aEdSize(LogicToPixel(Size(197, 48), MAP_APPFONT));
205 m_pSentenceED->set_width_request(aEdSize.Width());
206 m_pSentenceED->set_height_request(aEdSize.Height());
207 get(m_pSuggestionFT, "suggestionsft");
208 get(m_pSuggestionLB, "suggestionslb");
209 m_pSuggestionLB->SetDropDownLineCount(5);
210 m_pSuggestionLB->set_width_request(aEdSize.Width());
211 get(m_pIgnorePB, "ignore");
212 m_sIgnoreOnceST = m_pIgnorePB->GetText();
213 get(m_pIgnoreAllPB, "ignoreall");
214 get(m_pIgnoreRulePB, "ignorerule");
215 get(m_pAddToDictPB, "add");
216 get(m_pAddToDictMB, "addmb");
217 m_pAddToDictMB->SetHelpId(m_pAddToDictPB->GetHelpId());
218 get(m_pChangePB, "change");
219 get(m_pChangeAllPB, "changeall");
220 get(m_pAutoCorrPB, "autocorrect");
221 get(m_pCheckGrammarCB, "checkgrammar");
222 get(m_pOptionsPB, "options");
223 get(m_pUndoPB, "undo");
224 get(m_pClosePB, "close");
225 xSpell = LinguMgr::GetSpellChecker();
226 pImpl = new SpellDialog_Impl;
228 const StyleSettings& rSettings = GetSettings().GetStyleSettings();
229 Color aCol = rSettings.GetHelpColor();
230 Wallpaper aWall( aCol );
231 m_pExplainLink->SetBackground( aWall );
232 m_pExplainFT->SetBackground( aWall );
234 Init_Impl();
236 // disable controls if service is missing
237 Enable(xSpell.is());
239 //InitHdl wants to use virtual methods, so it
240 //can't be called during the ctor, so init
241 //it on next event cycle post-ctor
242 Application::PostUserEvent(
243 LINK( this, SpellDialog, InitHdl ), NULL, true );
248 SpellDialog::~SpellDialog()
250 disposeOnce();
253 void SpellDialog::dispose()
255 if (pImpl)
257 // save possibly modified user-dictionaries
258 Reference< XSearchableDictionaryList > xDicList( SvxGetDictionaryList() );
259 if (xDicList.is())
260 SaveDictionaries( xDicList );
262 delete pImpl;
263 pImpl = NULL;
265 m_pLanguageFT.clear();
266 m_pLanguageLB.clear();
267 m_pExplainFT.clear();
268 m_pExplainLink.clear();
269 m_pNotInDictFT.clear();
270 m_pSentenceED.clear();
271 m_pSuggestionFT.clear();
272 m_pSuggestionLB.clear();
273 m_pIgnorePB.clear();
274 m_pIgnoreAllPB.clear();
275 m_pIgnoreRulePB.clear();
276 m_pAddToDictPB.clear();
277 m_pAddToDictMB.clear();
278 m_pChangePB.clear();
279 m_pChangeAllPB.clear();
280 m_pAutoCorrPB.clear();
281 m_pCheckGrammarCB.clear();
282 m_pOptionsPB.clear();
283 m_pUndoPB.clear();
284 m_pClosePB.clear();
285 SfxModelessDialog::dispose();
288 void SpellDialog::Init_Impl()
290 // initialize handler
291 m_pClosePB->SetClickHdl(LINK( this, SpellDialog, CancelHdl ) );
292 m_pChangePB->SetClickHdl(LINK( this, SpellDialog, ChangeHdl ) );
293 m_pChangeAllPB->SetClickHdl(LINK( this, SpellDialog, ChangeAllHdl ) );
294 m_pIgnorePB->SetClickHdl(LINK( this, SpellDialog, IgnoreHdl ) );
295 m_pIgnoreAllPB->SetClickHdl(LINK( this, SpellDialog, IgnoreAllHdl ) );
296 m_pIgnoreRulePB->SetClickHdl(LINK( this, SpellDialog, IgnoreAllHdl ) );
297 m_pUndoPB->SetClickHdl(LINK( this, SpellDialog, UndoHdl ) );
299 m_pAutoCorrPB->SetClickHdl( LINK( this, SpellDialog, ExtClickHdl ) );
300 m_pCheckGrammarCB->SetClickHdl( LINK( this, SpellDialog, CheckGrammarHdl ));
301 m_pOptionsPB->SetClickHdl( LINK( this, SpellDialog, ExtClickHdl ) );
303 m_pSuggestionLB->SetDoubleClickHdl( LINK( this, SpellDialog, ChangeHdl ) );
305 m_pSentenceED->SetModifyHdl(LINK ( this, SpellDialog, ModifyHdl) );
307 m_pAddToDictMB->SetSelectHdl(LINK ( this, SpellDialog, AddToDictSelectHdl ) );
308 m_pAddToDictPB->SetClickHdl(LINK ( this, SpellDialog, AddToDictClickHdl ) );
310 m_pLanguageLB->SetSelectHdl(LINK( this, SpellDialog, LanguageSelectHdl ) );
312 m_pExplainLink->SetClickHdl( LINK( this, SpellDialog, HandleHyperlink ) );
314 // initialize language ListBox
315 m_pLanguageLB->SetLanguageList( SvxLanguageListFlags::SPELL_USED, false, false, true );
317 m_pSentenceED->ClearModifyFlag();
318 SvxGetChangeAllList()->clear();
323 void SpellDialog::UpdateBoxes_Impl()
325 sal_Int32 i;
326 m_pSuggestionLB->Clear();
328 const SpellErrorDescription* pSpellErrorDescription = m_pSentenceED->GetAlternatives();
330 LanguageType nAltLanguage = LANGUAGE_NONE;
331 Sequence< OUString > aNewWords;
332 bool bIsGrammarError = false;
333 if( pSpellErrorDescription )
335 nAltLanguage = LanguageTag::convertToLanguageType( pSpellErrorDescription->aLocale );
336 aNewWords = pSpellErrorDescription->aSuggestions;
337 bIsGrammarError = pSpellErrorDescription->bIsGrammarError;
338 m_pExplainLink->SetURL( pSpellErrorDescription->sExplanationURL );
339 m_pExplainFT->SetText( pSpellErrorDescription->sExplanation );
341 if( pSpellErrorDescription && !pSpellErrorDescription->sDialogTitle.isEmpty() )
343 // use this function to apply the correct image to be used...
344 SetTitle_Impl( nAltLanguage );
345 // then change the title to the one to be actually used
346 SetText( pSpellErrorDescription->sDialogTitle );
348 else
349 SetTitle_Impl( nAltLanguage );
350 SetSelectedLang_Impl( nAltLanguage );
351 int nDicts = InitUserDicts();
353 // enter alternatives
354 const OUString *pNewWords = aNewWords.getConstArray();
355 const sal_Int32 nSize = aNewWords.getLength();
356 for ( i = 0; i < nSize; ++i )
358 OUString aTmp( pNewWords[i] );
359 if ( LISTBOX_ENTRY_NOTFOUND == m_pSuggestionLB->GetEntryPos( aTmp ) )
361 m_pSuggestionLB->InsertEntry( aTmp );
362 m_pSuggestionLB->SetEntryFlags(m_pSuggestionLB->GetEntryCount() - 1, ListBoxEntryFlags::MultiLine);
365 if(!nSize)
366 m_pSuggestionLB->InsertEntry(m_sNoSuggestionsST);
367 m_pAutoCorrPB->Enable( nSize > 0 );
369 m_pSuggestionFT->Enable(nSize > 0);
370 m_pSuggestionLB->Enable(nSize > 0);
371 if( nSize )
373 m_pSuggestionLB->SelectEntryPos(0);
375 m_pChangePB->Enable( nSize > 0);
376 m_pChangeAllPB->Enable(nSize > 0);
377 bool bShowChangeAll = !bIsGrammarError;
378 m_pChangeAllPB->Show( bShowChangeAll );
379 m_pExplainFT->Show( !bShowChangeAll );
380 m_pLanguageLB->Enable( bShowChangeAll );
381 m_pIgnoreAllPB->Show( bShowChangeAll );
383 m_pAddToDictMB->Show( bShowChangeAll && nDicts > 1);
384 m_pAddToDictPB->Show( bShowChangeAll && nDicts <= 1);
385 m_pIgnoreRulePB->Show( !bShowChangeAll );
386 m_pIgnoreRulePB->Enable(pSpellErrorDescription && !pSpellErrorDescription->sRuleId.isEmpty());
387 m_pAutoCorrPB->Show( bShowChangeAll && rParent.HasAutoCorrection() );
389 bool bOldShowGrammar = m_pCheckGrammarCB->IsVisible();
390 bool bOldShowExplain = m_pExplainLink->IsVisible();
392 m_pCheckGrammarCB->Show(rParent.HasGrammarChecking());
393 m_pExplainLink->Show(!m_pExplainLink->GetURL().isEmpty());
394 if (m_pExplainFT->GetText().isEmpty())
396 m_pExplainFT->Hide();
397 m_pExplainLink->Hide();
400 if (bOldShowExplain != (bool) m_pExplainLink->IsVisible() || bOldShowGrammar != (bool) m_pCheckGrammarCB->IsVisible())
401 setOptimalLayoutSize();
405 void SpellDialog::SpellContinue_Impl(bool bUseSavedSentence, bool bIgnoreCurrentError )
407 //initially or after the last error of a sentence MarkNextError will fail
408 //then GetNextSentence() has to be called followed again by MarkNextError()
409 //MarkNextError is not initially called if the UndoEdit mode is active
410 bool bNextSentence = false;
411 if((!m_pSentenceED->IsUndoEditMode() && m_pSentenceED->MarkNextError( bIgnoreCurrentError, xSpell )) ||
412 ( bNextSentence = GetNextSentence_Impl(bUseSavedSentence, m_pSentenceED->IsUndoEditMode()) && m_pSentenceED->MarkNextError( false, xSpell )))
414 const SpellErrorDescription* pSpellErrorDescription = m_pSentenceED->GetAlternatives();
415 if( pSpellErrorDescription )
417 UpdateBoxes_Impl();
418 Control* aControls[] =
420 m_pNotInDictFT,
421 m_pSentenceED,
422 m_pLanguageFT,
425 sal_Int32 nIdx = 0;
428 aControls[nIdx]->Enable(true);
430 while(aControls[++nIdx]);
433 if( bNextSentence )
435 //remove undo if a new sentence is active
436 m_pSentenceED->ResetUndo();
437 m_pUndoPB->Enable(false);
441 /* Initialize, asynchronous to prevent virtial calls
442 from a constructor
444 IMPL_LINK( SpellDialog, InitHdl, SpellDialog *, )
446 SetUpdateMode( false );
447 //show or hide AutoCorrect depending on the modules abilities
448 m_pAutoCorrPB->Show(rParent.HasAutoCorrection());
449 SpellContinue_Impl();
450 m_pSentenceED->ResetUndo();
451 m_pUndoPB->Enable(false);
453 // get current language
454 UpdateBoxes_Impl();
456 // fill dictionary PopupMenu
457 InitUserDicts();
459 LockFocusChanges(true);
460 if( m_pChangePB->IsEnabled() )
461 m_pChangePB->GrabFocus();
462 else if( m_pIgnorePB->IsEnabled() )
463 m_pIgnorePB->GrabFocus();
464 else if( m_pClosePB->IsEnabled() )
465 m_pClosePB->GrabFocus();
466 LockFocusChanges(false);
467 //show grammar CheckBox depending on the modules abilities
468 m_pCheckGrammarCB->Check( rParent.IsGrammarChecking() );
469 SetUpdateMode( true );
470 Show();
471 return 0;
476 IMPL_LINK( SpellDialog, ExtClickHdl, Button *, pBtn )
478 if (m_pOptionsPB == pBtn)
479 StartSpellOptDlg_Impl();
480 else if (m_pAutoCorrPB == pBtn)
482 //get the currently selected wrong word
483 OUString sCurrentErrorText = m_pSentenceED->GetErrorText();
484 //get the wrong word from the XSpellAlternative
485 const SpellErrorDescription* pSpellErrorDescription = m_pSentenceED->GetAlternatives();
486 if( pSpellErrorDescription )
488 OUString sWrong(pSpellErrorDescription->sErrorText);
489 //if the word has not been edited in the MultiLineEdit then
490 //the current suggestion should be used
491 //if it's not the 'no suggestions' entry
492 if(sWrong == sCurrentErrorText &&
493 m_pSuggestionLB->IsEnabled() && m_pSuggestionLB->GetSelectEntryCount() > 0 &&
494 !m_sNoSuggestionsST.equals(m_pSuggestionLB->GetSelectEntry()))
496 sCurrentErrorText = m_pSuggestionLB->GetSelectEntry();
498 if(sWrong != sCurrentErrorText)
500 SvxPrepareAutoCorrect( sWrong, sCurrentErrorText );
501 LanguageType eLang = GetSelectedLang_Impl();
502 rParent.AddAutoCorrection( sWrong, sCurrentErrorText, eLang );
506 return 0;
509 IMPL_LINK( SpellDialog, CheckGrammarHdl, CheckBox*, pBox )
511 rParent.SetGrammarChecking( pBox->IsChecked() );
512 Impl_Restore();
513 return 0;
516 void SpellDialog::StartSpellOptDlg_Impl()
518 sal_uInt16 aSpellInfos[] =
520 SID_ATTR_SPELL,SID_ATTR_SPELL,
521 SID_SPELL_MODIFIED, SID_SPELL_MODIFIED,
522 SID_AUTOSPELL_CHECK, SID_AUTOSPELL_CHECK,
525 SfxItemSet aSet( SfxGetpApp()->GetPool(), aSpellInfos);
526 aSet.Put(SfxSpellCheckItem( xSpell, SID_ATTR_SPELL ));
527 VclPtr<SfxSingleTabDialog> pDlg(
528 VclPtr<SfxSingleTabDialog>::Create(this, aSet, "SpellOptionsDialog", "cui/ui/spelloptionsdialog.ui"));
529 VclPtr<SfxTabPage> pPage = SvxLinguTabPage::Create( pDlg->get_content_area(), &aSet );
530 static_cast<SvxLinguTabPage*>(pPage.get())->HideGroups( GROUP_MODULES );
531 pDlg->SetTabPage( pPage );
532 if(RET_OK == pDlg->Execute())
534 InitUserDicts();
535 const SfxItemSet* pOutSet = pDlg->GetOutputItemSet();
536 if(pOutSet)
537 OfaTreeOptionsDialog::ApplyLanguageOptions(*pOutSet);
541 namespace
543 OUString getDotReplacementString(const OUString &rErrorText, const OUString &rSuggestedReplacement)
545 OUString aString = rErrorText;
547 //dots are sometimes part of the spelled word but they are not necessarily part of the replacement
548 bool bDot = aString.endsWith(".");
550 aString = rSuggestedReplacement;
552 if(bDot && (aString.isEmpty() || !aString.endsWith(".")))
553 aString += ".";
555 return aString;
560 OUString SpellDialog::getReplacementString() const
562 OUString sOrigString = m_pSentenceED->GetErrorText();
564 OUString sReplacement(sOrigString);
566 if(m_pSuggestionLB->IsEnabled() &&
567 m_pSuggestionLB->GetSelectEntryCount()>0 &&
568 !m_sNoSuggestionsST.equals(m_pSuggestionLB->GetSelectEntry()))
569 sReplacement = m_pSuggestionLB->GetSelectEntry();
571 return getDotReplacementString(sOrigString, sReplacement);
576 IMPL_LINK_NOARG(SpellDialog, ChangeHdl)
578 if(m_pSentenceED->IsUndoEditMode())
580 SpellContinue_Impl();
582 else
584 m_pSentenceED->UndoActionStart( SPELLUNDO_CHANGE_GROUP );
585 OUString aString = getReplacementString();
586 m_pSentenceED->ChangeMarkedWord(aString, GetSelectedLang_Impl());
587 SpellContinue_Impl();
588 bModified = false;
589 m_pSentenceED->UndoActionEnd();
591 if(!m_pChangePB->IsEnabled())
592 m_pIgnorePB->GrabFocus();
593 return 1;
598 IMPL_LINK_NOARG(SpellDialog, ChangeAllHdl)
600 m_pSentenceED->UndoActionStart( SPELLUNDO_CHANGE_GROUP );
601 OUString aString = getReplacementString();
602 LanguageType eLang = GetSelectedLang_Impl();
604 // add new word to ChangeAll list
605 OUString aOldWord( m_pSentenceED->GetErrorText() );
606 SvxPrepareAutoCorrect( aOldWord, aString );
607 Reference<XDictionary> aXDictionary( SvxGetChangeAllList(), UNO_QUERY );
608 DictionaryError nAdded = AddEntryToDic( aXDictionary,
609 aOldWord, true,
610 aString, eLang );
612 if(nAdded == DictionaryError::NONE)
614 SpellUndoAction_Impl* pAction = new SpellUndoAction_Impl(
615 SPELLUNDO_CHANGE_ADD_TO_DICTIONARY, aDialogUndoLink);
616 pAction->SetDictionary(aXDictionary);
617 pAction->SetAddedWord(aOldWord);
618 m_pSentenceED->AddUndoAction(pAction);
621 m_pSentenceED->ChangeMarkedWord(aString, eLang);
622 SpellContinue_Impl();
623 bModified = false;
624 m_pSentenceED->UndoActionEnd();
625 return 1;
629 IMPL_LINK( SpellDialog, IgnoreAllHdl, Button *, pButton )
631 m_pSentenceED->UndoActionStart( SPELLUNDO_CHANGE_GROUP );
632 // add word to IgnoreAll list
633 Reference< XDictionary > aXDictionary( SvxGetIgnoreAllList(), UNO_QUERY );
634 //in case the error has been changed manually it has to be restored
635 m_pSentenceED->RestoreCurrentError();
636 if (pButton == m_pIgnoreRulePB)
638 const SpellErrorDescription* pSpellErrorDescription = m_pSentenceED->GetAlternatives();
641 if( pSpellErrorDescription && pSpellErrorDescription->xGrammarChecker.is() )
643 pSpellErrorDescription->xGrammarChecker->ignoreRule( pSpellErrorDescription->sRuleId,
644 pSpellErrorDescription->aLocale );
645 // refresh the layout (workaround to launch a dictionary event)
646 aXDictionary->setActive(sal_False);
647 aXDictionary->setActive(sal_True);
650 catch( const uno::Exception& )
654 else
656 OUString sErrorText(m_pSentenceED->GetErrorText());
657 DictionaryError nAdded = AddEntryToDic( aXDictionary,
658 sErrorText, false,
659 OUString(), LANGUAGE_NONE );
660 if(nAdded == DictionaryError::NONE)
662 SpellUndoAction_Impl* pAction = new SpellUndoAction_Impl(
663 SPELLUNDO_CHANGE_ADD_TO_DICTIONARY, aDialogUndoLink);
664 pAction->SetDictionary(aXDictionary);
665 pAction->SetAddedWord(sErrorText);
666 m_pSentenceED->AddUndoAction(pAction);
670 SpellContinue_Impl();
671 bModified = false;
672 m_pSentenceED->UndoActionEnd();
673 return 1;
677 IMPL_LINK_NOARG(SpellDialog, UndoHdl)
679 m_pSentenceED->Undo();
680 if(!m_pSentenceED->GetUndoActionCount())
681 m_pUndoPB->Enable(false);
682 return 0;
686 IMPL_LINK( SpellDialog, DialogUndoHdl, SpellUndoAction_Impl*, pAction )
688 switch(pAction->GetId())
690 case SPELLUNDO_CHANGE_TEXTENGINE:
692 if(pAction->IsEnableChangePB())
693 m_pChangePB->Enable(false);
694 if(pAction->IsEnableChangeAllPB())
695 m_pChangeAllPB->Enable(false);
697 break;
698 case SPELLUNDO_CHANGE_NEXTERROR:
700 m_pSentenceED->MoveErrorMarkTo((sal_uInt16)pAction->GetOldErrorStart(), (sal_uInt16)pAction->GetOldErrorEnd(), false);
701 if(pAction->IsErrorLanguageSelected())
703 UpdateBoxes_Impl();
706 break;
707 case SPELLUNDO_CHANGE_ADD_TO_DICTIONARY:
709 if(pAction->GetDictionary().is())
710 pAction->GetDictionary()->remove(pAction->GetAddedWord());
712 break;
713 case SPELLUNDO_MOVE_ERROREND :
715 if(pAction->GetOffset() != 0)
716 m_pSentenceED->MoveErrorEnd(pAction->GetOffset());
718 break;
719 case SPELLUNDO_UNDO_EDIT_MODE :
721 //refill the dialog with the currently spelled sentence - throw away all changes
722 SpellContinue_Impl(true);
724 break;
725 case SPELLUNDO_ADD_IGNORE_RULE:
726 //undo of ignored rules is not supported
727 break;
730 return 0;
733 void SpellDialog::Impl_Restore()
735 //clear the "ChangeAllList"
736 SvxGetChangeAllList()->clear();
737 //get a new sentence
738 m_pSentenceED->SetText(OUString());
739 m_pSentenceED->ResetModified();
740 //Resolves: fdo#39348 refill the dialog with the currently spelled sentence
741 SpellContinue_Impl(true);
742 m_pIgnorePB->SetText(m_sIgnoreOnceST);
745 IMPL_LINK_NOARG(SpellDialog, IgnoreHdl)
747 if (m_sResumeST.equals(m_pIgnorePB->GetText()))
749 Impl_Restore();
751 else
753 //in case the error has been changed manually it has to be restored,
754 // since the users choice now was to ignore the error
755 m_pSentenceED->RestoreCurrentError();
757 // the word is being ignored
758 SpellContinue_Impl( false, true );
760 return 1;
765 bool SpellDialog::Close()
767 // We have to call ToggleChildWindow directly; calling SfxDispatcher's
768 // Execute() does not work here when we are in a document with protected
769 // section - in that case, the cursor can move from the editable field to
770 // the protected area, and the slots get disabled because of
771 // SW_DISABLE_ON_PROTECTED_CURSOR (see FN_SPELL_GRAMMAR_DIALOG in .sdi).
772 SfxViewFrame::Current()->ToggleChildWindow(rParent.GetType());
774 return true;
778 void SpellDialog::SetSelectedLang_Impl( LanguageType nLang )
780 m_pLanguageLB->SelectLanguage( nLang );
785 LanguageType SpellDialog::GetSelectedLang_Impl() const
787 sal_Int16 nLang = m_pLanguageLB->GetSelectLanguage();
788 return nLang;
792 IMPL_LINK(SpellDialog, LanguageSelectHdl, SvxLanguageBox*, pBox)
794 //If selected language changes, then add->list should be regenerated to
795 //match
796 InitUserDicts();
798 //if currently an error is selected then search for alternatives for
799 //this word and fill the alternatives ListBox accordingly
800 OUString sError = m_pSentenceED->GetErrorText();
801 m_pSuggestionLB->Clear();
802 if(!sError.isEmpty())
804 LanguageType eLanguage = pBox->GetSelectLanguage();
805 Reference <XSpellAlternatives> xAlt = xSpell->spell( sError, eLanguage,
806 Sequence< PropertyValue >() );
807 if( xAlt.is() )
808 m_pSentenceED->SetAlternatives( xAlt );
809 else
811 m_pSentenceED->ChangeMarkedWord( sError, eLanguage );
812 SpellContinue_Impl();
815 m_pSentenceED->AddUndoAction(new SpellUndoAction_Impl(SPELLUNDO_CHANGE_LANGUAGE, aDialogUndoLink));
817 SpellDialog::UpdateBoxes_Impl();
818 return 0;
822 void SpellDialog::SetLanguage( sal_uInt16 nLang )
824 Description:
825 If the language has been changed in thesaurus,
826 it must be changed here, too.
829 SetTitle_Impl( nLang );
830 m_pLanguageLB->SelectLanguage( nLang );
833 void SpellDialog::SetTitle_Impl(LanguageType nLang)
835 OUString sTitle = rParent.HasGrammarChecking() ? m_sTitleSpellingGrammar : m_sTitleSpelling;
836 sTitle = sTitle.replaceFirst( "$LANGUAGE ($LOCATION)", SvtLanguageTable::GetLanguageString(nLang) );
837 SetText( sTitle );
840 int SpellDialog::InitUserDicts()
842 const LanguageType nLang = m_pLanguageLB->GetSelectLanguage();
844 const Reference< XDictionary > *pDic = 0;
846 // get list of dictionaries
847 Reference< XSearchableDictionaryList > xDicList( SvxGetDictionaryList() );
848 if (xDicList.is())
850 // add active, positive dictionary to dic-list (if not already done).
851 // This is to ensure that there is at least on dictionary to which
852 // words could be added.
853 Reference< XDictionary > xDic( SvxGetOrCreatePosDic( xDicList ) );
854 if (xDic.is())
855 xDic->setActive( sal_True );
857 pImpl->aDics = xDicList->getDictionaries();
860 SvtLinguConfig aCfg;
862 // list suitable dictionaries
863 bool bEnable = false;
864 const sal_Int32 nSize = pImpl->aDics.getLength();
865 pDic = pImpl->aDics.getConstArray();
866 PopupMenu* pMenu = m_pAddToDictMB->GetPopupMenu();
867 assert(pMenu);
868 pMenu->Clear();
869 pMenu->SetMenuFlags(MenuFlags::NoAutoMnemonics);
870 sal_uInt16 nItemId = 1; // menu items should be enumerated from 1 and not 0
871 for (sal_Int32 i = 0; i < nSize; ++i)
873 uno::Reference< linguistic2::XDictionary > xDicTmp( pDic[i], uno::UNO_QUERY );
874 if (!xDicTmp.is() || SvxGetIgnoreAllList() == xDicTmp)
875 continue;
877 uno::Reference< frame::XStorable > xStor( xDicTmp, uno::UNO_QUERY );
878 LanguageType nActLanguage = LanguageTag( xDicTmp->getLocale() ).getLanguageType();
879 if( xDicTmp->isActive()
880 && xDicTmp->getDictionaryType() != linguistic2::DictionaryType_NEGATIVE
881 && (nLang == nActLanguage || LANGUAGE_NONE == nActLanguage )
882 && (!xStor.is() || !xStor->isReadonly()) )
884 pMenu->InsertItem( nItemId, xDicTmp->getName() );
885 bEnable = true;
887 uno::Reference< lang::XServiceInfo > xSvcInfo( xDicTmp, uno::UNO_QUERY );
888 if (xSvcInfo.is())
890 OUString aDictionaryImageUrl( aCfg.GetSpellAndGrammarContextDictionaryImage(
891 xSvcInfo->getImplementationName()) );
892 if (!aDictionaryImageUrl.isEmpty())
894 Image aImage( aDictionaryImageUrl );
895 pMenu->SetItemImage( nItemId, aImage );
899 ++nItemId;
902 m_pAddToDictMB->Enable( bEnable );
903 m_pAddToDictPB->Enable( bEnable );
905 int nDicts = nItemId-1;
907 m_pAddToDictMB->Show( nDicts > 1 );
908 m_pAddToDictPB->Show( nDicts <= 1 );
910 return nDicts;
914 IMPL_LINK(SpellDialog, AddToDictClickHdl, PushButton*, )
916 return AddToDictionaryExecute(1, m_pAddToDictMB->GetPopupMenu());
920 IMPL_LINK_TYPED(SpellDialog, AddToDictSelectHdl, MenuButton*, pButton, void )
922 AddToDictionaryExecute(pButton->GetCurItemId(), pButton->GetPopupMenu());
926 int SpellDialog::AddToDictionaryExecute( sal_uInt16 nItemId, PopupMenu *pMenu )
928 m_pSentenceED->UndoActionStart( SPELLUNDO_CHANGE_GROUP );
930 //GetErrorText() returns the current error even if the text is already
931 //manually changed
932 const OUString aNewWord = m_pSentenceED->GetErrorText();
934 OUString aDicName ( pMenu->GetItemText( nItemId ) );
936 uno::Reference< linguistic2::XDictionary > xDic;
937 uno::Reference< linguistic2::XSearchableDictionaryList > xDicList( SvxGetDictionaryList() );
938 if (xDicList.is())
939 xDic = xDicList->getDictionaryByName( aDicName );
941 DictionaryError nAddRes = DictionaryError::UNKNOWN;
942 if (xDic.is())
944 nAddRes = AddEntryToDic( xDic, aNewWord, false, OUString(), LANGUAGE_NONE );
945 // save modified user-dictionary if it is persistent
946 uno::Reference< frame::XStorable > xSavDic( xDic, uno::UNO_QUERY );
947 if (xSavDic.is())
948 xSavDic->store();
950 if (nAddRes == DictionaryError::NONE)
952 SpellUndoAction_Impl* pAction = new SpellUndoAction_Impl(
953 SPELLUNDO_CHANGE_ADD_TO_DICTIONARY, aDialogUndoLink);
954 pAction->SetDictionary( xDic );
955 pAction->SetAddedWord( aNewWord );
956 m_pSentenceED->AddUndoAction( pAction );
958 // failed because there is already an entry?
959 if (DictionaryError::NONE != nAddRes && xDic->getEntry( aNewWord ).is())
960 nAddRes = DictionaryError::NONE;
962 if (DictionaryError::NONE != nAddRes)
964 SvxDicError( this, nAddRes );
965 return 0; // don't continue
968 // go on
969 SpellContinue_Impl();
970 m_pSentenceED->UndoActionEnd();
971 return 0;
975 IMPL_LINK(SpellDialog, ModifyHdl, SentenceEditWindow_Impl*, pEd)
977 if (m_pSentenceED == pEd)
979 bModified = true;
980 m_pSuggestionLB->SetNoSelection();
981 m_pSuggestionLB->Disable();
982 OUString sNewText( m_pSentenceED->GetText() );
983 m_pAutoCorrPB->Enable( sNewText != m_pSentenceED->GetText() );
984 SpellUndoAction_Impl* pSpellAction = new SpellUndoAction_Impl(SPELLUNDO_CHANGE_TEXTENGINE, aDialogUndoLink);
985 if(!m_pChangeAllPB->IsEnabled())
987 m_pChangeAllPB->Enable();
988 pSpellAction->SetEnableChangeAllPB();
990 if(!m_pChangePB->IsEnabled())
992 m_pChangePB->Enable();
993 pSpellAction->SetEnableChangePB();
995 m_pSentenceED->AddUndoAction(pSpellAction);
997 return 0;
1001 IMPL_LINK_NOARG(SpellDialog, CancelHdl)
1003 //apply changes and ignored text parts first - if there are any
1004 rParent.ApplyChangedSentence(m_pSentenceED->CreateSpellPortions(true), false);
1005 Close();
1006 return 0;
1010 bool SpellDialog::Notify( NotifyEvent& rNEvt )
1012 /* #i38338#
1013 * FIXME: LoseFocus and GetFocus are signals from vcl that
1014 * a window actually got/lost the focus, it never should be
1015 * forwarded from another window, that is simply wrong.
1016 * FIXME: overriding the virtual methods GetFocus and LoseFocus
1017 * in SpellDialogChildWindow by making them pure is at least questionable.
1018 * The only sensible thing would be to call the new Method differently,
1019 * e.g. DialogGot/LostFocus or so.
1021 if( IsVisible() && !bFocusLocked )
1023 if( rNEvt.GetType() == MouseNotifyEvent::GETFOCUS )
1025 //notify the child window of the focus change
1026 rParent.GetFocus();
1028 else if( rNEvt.GetType() == MouseNotifyEvent::LOSEFOCUS )
1030 //notify the child window of the focus change
1031 rParent.LoseFocus();
1034 return SfxModelessDialog::Notify(rNEvt);
1038 void SpellDialog::InvalidateDialog()
1040 if( bFocusLocked )
1041 return;
1042 m_pIgnorePB->SetText(m_sResumeST);
1043 vcl::Window* aDisableArr[] =
1045 m_pNotInDictFT,
1046 m_pSentenceED,
1047 m_pSuggestionFT,
1048 m_pSuggestionLB,
1049 m_pLanguageFT,
1050 m_pLanguageLB,
1051 m_pIgnoreAllPB,
1052 m_pIgnoreRulePB,
1053 m_pAddToDictMB,
1054 m_pAddToDictPB,
1055 m_pChangePB,
1056 m_pChangeAllPB,
1057 m_pAutoCorrPB,
1058 m_pUndoPB,
1061 sal_Int16 i = 0;
1062 while(aDisableArr[i])
1064 aDisableArr[i]->Enable(false);
1065 i++;
1067 SfxModelessDialog::Deactivate();
1071 bool SpellDialog::GetNextSentence_Impl(bool bUseSavedSentence, bool bRecheck)
1073 bool bRet = false;
1074 if(!bUseSavedSentence)
1076 //apply changes and ignored text parts
1077 rParent.ApplyChangedSentence(m_pSentenceED->CreateSpellPortions(true), bRecheck);
1079 m_pSentenceED->ResetIgnoreErrorsAt();
1080 m_pSentenceED->ResetModified();
1081 SpellPortions aSentence = bUseSavedSentence ? m_aSavedSentence : rParent.GetNextWrongSentence( bRecheck );
1082 if(!bUseSavedSentence)
1083 m_aSavedSentence = aSentence;
1084 bool bHasReplaced = false;
1085 while(!aSentence.empty())
1087 //apply all changes that are already part of the "ChangeAllList"
1088 //returns true if the list still contains errors after the changes have been applied
1090 if(!ApplyChangeAllList_Impl(aSentence, bHasReplaced))
1092 rParent.ApplyChangedSentence(aSentence, bRecheck);
1093 aSentence = rParent.GetNextWrongSentence( bRecheck );
1095 else
1096 break;
1099 if(!aSentence.empty())
1101 SpellPortions::iterator aStart = aSentence.begin();
1102 OUString sText;
1103 while(aStart != aSentence.end())
1105 // hidden text has to be ignored
1106 if(!aStart->bIsHidden)
1107 sText += aStart->sText;
1108 ++aStart;
1110 m_pSentenceED->SetText(sText);
1111 aStart = aSentence.begin();
1112 sal_Int32 nStartPosition = 0;
1113 sal_Int32 nEndPosition = 0;
1115 while(aStart != aSentence.end())
1117 // hidden text has to be ignored
1118 if(!aStart->bIsHidden)
1120 nEndPosition += aStart->sText.getLength();
1121 if(aStart->xAlternatives.is())
1123 uno::Reference< container::XNamed > xNamed( aStart->xAlternatives, uno::UNO_QUERY );
1124 OUString sServiceName;
1125 if( xNamed.is() )
1126 sServiceName = xNamed->getName();
1127 SpellErrorDescription aDesc( false, aStart->xAlternatives->getWord(),
1128 aStart->xAlternatives->getLocale(), aStart->xAlternatives->getAlternatives(), 0, sServiceName);
1129 m_pSentenceED->SetAttrib( SpellErrorAttrib(aDesc), 0, (sal_uInt16) nStartPosition, (sal_uInt16) nEndPosition );
1131 else if(aStart->bIsGrammarError )
1133 beans::PropertyValues aProperties = aStart->aGrammarError.aProperties;
1134 OUString sFullCommentURL;
1135 sal_Int32 i = 0;
1136 while ( sFullCommentURL.isEmpty() && i < aProperties.getLength() )
1138 if ( aProperties[i].Name == "FullCommentURL" )
1140 uno::Any aValue = aProperties[i].Value;
1141 aValue >>= sFullCommentURL;
1143 ++i;
1146 uno::Reference< lang::XServiceInfo > xInfo( aStart->xGrammarChecker, uno::UNO_QUERY );
1147 SpellErrorDescription aDesc( true,
1148 aStart->sText,
1149 LanguageTag::convertToLocale( aStart->eLanguage ),
1150 aStart->aGrammarError.aSuggestions,
1151 aStart->xGrammarChecker,
1152 xInfo->getImplementationName(),
1153 &aStart->sDialogTitle,
1154 &aStart->aGrammarError.aFullComment,
1155 &aStart->aGrammarError.aRuleIdentifier,
1156 &sFullCommentURL );
1157 m_pSentenceED->SetAttrib( SpellErrorAttrib(aDesc), 0, (sal_uInt16) nStartPosition, (sal_uInt16) nEndPosition );
1159 if(aStart->bIsField)
1160 m_pSentenceED->SetAttrib( SpellBackgroundAttrib(COL_LIGHTGRAY), 0, (sal_uInt16) nStartPosition, (sal_uInt16) nEndPosition );
1161 m_pSentenceED->SetAttrib( SpellLanguageAttrib(aStart->eLanguage), 0, (sal_uInt16) nStartPosition, (sal_uInt16) nEndPosition );
1162 nStartPosition = nEndPosition;
1164 ++aStart;
1166 //the edit field needs to be modified to apply the change from the ApplyChangeAllList
1167 if(!bHasReplaced)
1168 m_pSentenceED->ClearModifyFlag();
1169 m_pSentenceED->ResetUndo();
1170 m_pUndoPB->Enable(false);
1171 bRet = nStartPosition > 0;
1173 return bRet;
1175 /*-------------------------------------------------------------------------
1176 replace errors that have a replacement in the ChangeAllList
1177 returns false if the result doesn't contain errors after the replacement
1178 -----------------------------------------------------------------------*/
1179 bool SpellDialog::ApplyChangeAllList_Impl(SpellPortions& rSentence, bool &bHasReplaced)
1181 bHasReplaced = false;
1182 bool bRet = true;
1183 SpellPortions::iterator aStart = rSentence.begin();
1184 Reference<XDictionary> xChangeAll( SvxGetChangeAllList(), UNO_QUERY );
1185 if(!xChangeAll->getCount())
1186 return bRet;
1187 bRet = false;
1188 while(aStart != rSentence.end())
1190 if(aStart->xAlternatives.is())
1192 const OUString &rString = aStart->sText;
1194 Reference<XDictionaryEntry> xEntry = xChangeAll->getEntry(rString);
1196 if(xEntry.is())
1198 aStart->sText = getDotReplacementString(rString, xEntry->getReplacementText());
1199 aStart->xAlternatives = 0;
1200 bHasReplaced = true;
1202 else
1203 bRet = true;
1205 else if( aStart->bIsGrammarError )
1206 bRet = true;
1207 ++aStart;
1209 return bRet;
1213 SentenceEditWindow_Impl::SentenceEditWindow_Impl(vcl::Window * pParent, WinBits nBits)
1214 : VclMultiLineEdit(pParent, nBits)
1215 , m_nErrorStart(0)
1216 , m_nErrorEnd(0)
1217 , m_bIsUndoEditMode(false)
1219 DisableSelectionOnFocus();
1222 VCL_BUILDER_DECL_FACTORY(SentenceEditWindow)
1224 (void)rMap;
1225 rRet = VclPtr<SentenceEditWindow_Impl>::Create(pParent, WB_BORDER|WB_VSCROLL|WB_IGNORETAB);
1229 /*-------------------------------------------------------------------------
1230 The selection before inputting a key may have a range or not
1231 and it may be inside or outside of field or error attributes.
1232 A range may include the attribute partially, completely or together
1233 with surrounding text. It may also contain more than one attribute
1234 or no attribute at all.
1235 Depending on this starting conditions some actions are necessary:
1236 Attempts to delete a field are only allowed if the selection is the same
1237 as the field's selection. Otherwise the field has to be selected and the key
1238 input action has to be skipped.
1239 Input of text at the start of the field requires the field attribute to be
1240 corrected - it is not allowed to grow.
1242 In case of errors the appending of text should grow the error attribute because
1243 that is what the user usually wants to do.
1245 Backspace at the start of the attribute requires to find out if a field ends
1246 directly in front of the cursor position. In case of a field this attribute has to be
1247 selected otherwise the key input method is allowed.
1249 All changes outside of the error attributes switch the dialog mode to a "Undo edit" state that
1250 removes all visible attributes and switches off further attribute checks.
1251 Undo in this restarts the dialog with a current sentence newly presented.
1252 All changes to the sentence are undone including the ones before the "Undo edit state" has been reached
1254 We end up with 9 types of selection
1255 1 (LEFT_NO) - no range, start of attribute - can also be 3 at the same time
1256 2 (INSIDE_NO) - no range, inside of attribute
1257 3 (RIGHT_NO) - no range, end of attribute - can also be 1 at the same time
1258 4 (FULL) - range, same as attribute
1259 5 (INSIDE_YES) - range, inside of the attribute
1260 6 (BRACE)- range, from outside of the attribute to the inside or
1261 including the complete attribute and something outside,
1262 maybe more than one attribute
1263 7 (OUTSIDE_NO) - no range, not at an attribute
1264 8 (OUTSIDE_YES) - range, completely outside of all attributes
1266 What has to be done depending on the attribute type involved
1267 possible actions: UE - Undo edit mode
1268 CO - Continue, no additional action is required
1269 FS - Field has to be completely selected
1270 EX - The attribute has to be expanded to include the added text
1272 1 - backspace delete any other
1273 UE on field FS on error CO on field FS on error CO
1275 2 - on field FS on error C
1276 3 - backspace delete any other
1277 on field FS on error CO UE on field UE on error EX
1279 if 1 and 3 happen to apply both then backspace and other handling is 1 delete is 3
1281 4 - on field UE and on error CO
1282 5 - on field FS and on error CO
1283 6 - on field FS and on error UE
1284 7 - UE
1285 8 - UE
1286 -----------------------------------------------------------------------*/
1287 #define INVALID 0
1288 #define LEFT_NO 1
1289 #define INSIDE_NO 2
1290 #define RIGHT_NO 3
1291 #define FULL 4
1292 #define INSIDE_YES 5
1293 #define BRACE 6
1294 #define OUTSIDE_NO 7
1295 #define OUTSIDE_YES 8
1297 #define ACTION_UNDOEDIT 0
1298 #define ACTION_CONTINUE 1
1299 #define ACTION_SELECTFIELD 2
1300 #define ACTION_EXPAND 3
1302 bool SentenceEditWindow_Impl::PreNotify( NotifyEvent& rNEvt )
1304 bool bChange = false;
1305 if(rNEvt.GetType() == MouseNotifyEvent::KEYINPUT)
1307 const KeyEvent& rKeyEvt = *rNEvt.GetKeyEvent();
1308 bChange = TextEngine::DoesKeyChangeText( rKeyEvt );
1309 if(bChange && !IsUndoEditMode() &&
1310 rKeyEvt.GetKeyCode().GetCode() != KEY_TAB)
1312 TextEngine* pTextEngine = GetTextEngine();
1313 TextView* pTextView = pTextEngine->GetActiveView();
1314 const TextSelection& rCurrentSelection = pTextView->GetSelection();
1315 //determine if the selection contains a field
1316 bool bHasField = false;
1317 bool bHasError = false;
1318 bool bHasFieldLeft = false;
1319 bool bHasErrorLeft = false;
1321 bool bHasRange = rCurrentSelection.HasRange();
1322 sal_uInt8 nSelectionType = 0; // invalid type!
1324 TextPaM aCursor(rCurrentSelection.GetStart());
1325 const TextCharAttrib* pBackAttr = pTextEngine->FindCharAttrib( aCursor, TEXTATTR_SPELL_BACKGROUND );
1326 const TextCharAttrib* pErrorAttr = pTextEngine->FindCharAttrib( aCursor, TEXTATTR_SPELL_ERROR );
1327 const TextCharAttrib* pBackAttrLeft = 0;
1328 const TextCharAttrib* pErrorAttrLeft = 0;
1330 bHasField = pBackAttr != 0 && (bHasRange || pBackAttr->GetEnd() > aCursor.GetIndex());
1331 bHasError = pErrorAttr != 0 && (bHasRange || pErrorAttr->GetEnd() > aCursor.GetIndex());
1332 if(bHasRange)
1334 if(pBackAttr &&
1335 pBackAttr->GetStart() == rCurrentSelection.GetStart().GetIndex() &&
1336 pBackAttr->GetEnd() == rCurrentSelection.GetEnd().GetIndex())
1338 nSelectionType = FULL;
1340 else if(pErrorAttr &&
1341 pErrorAttr->GetStart() <= rCurrentSelection.GetStart().GetIndex() &&
1342 pErrorAttr->GetEnd() >= rCurrentSelection.GetEnd().GetIndex())
1344 nSelectionType = INSIDE_YES;
1346 else
1348 nSelectionType = bHasField||bHasError ? BRACE : OUTSIDE_NO;
1349 while(aCursor.GetIndex() < rCurrentSelection.GetEnd().GetIndex())
1351 ++aCursor.GetIndex();
1352 const TextCharAttrib* pIntBackAttr = pTextEngine->FindCharAttrib( aCursor, TEXTATTR_SPELL_BACKGROUND );
1353 const TextCharAttrib* pIntErrorAttr = pTextEngine->FindCharAttrib( aCursor, TEXTATTR_SPELL_ERROR );
1354 //if any attr has been found then BRACE
1355 if(pIntBackAttr || pIntErrorAttr)
1356 nSelectionType = BRACE;
1357 //the field has to be selected
1358 if(pIntBackAttr && !pBackAttr)
1359 pBackAttr = pIntBackAttr;
1360 bHasField |= pIntBackAttr != 0;
1364 else
1366 //no range selection: then 1 2 3 and 8 are possible
1367 const TextCharAttrib* pCurAttr = pBackAttr ? pBackAttr : pErrorAttr;
1368 if(pCurAttr)
1370 nSelectionType = pCurAttr->GetStart() == rCurrentSelection.GetStart().GetIndex() ?
1371 LEFT_NO : pCurAttr->GetEnd() == rCurrentSelection.GetEnd().GetIndex() ? RIGHT_NO : INSIDE_NO;
1373 else
1374 nSelectionType = OUTSIDE_NO;
1376 bHasFieldLeft = pBackAttr && pBackAttr->GetEnd() == aCursor.GetIndex();
1377 if(bHasFieldLeft)
1379 pBackAttrLeft = pBackAttr;
1380 pBackAttr = 0;
1382 bHasErrorLeft = pErrorAttr && pErrorAttr->GetEnd() == aCursor.GetIndex();
1383 if(bHasErrorLeft)
1385 pErrorAttrLeft = pErrorAttr;
1386 pErrorAttr = 0;
1389 //check previous position if this exists
1390 //that is a redundant in the case the attribute found above already is on the left cursor side
1391 //but it's o.k. for two errors/fields side by side
1392 if(aCursor.GetIndex())
1394 --aCursor.GetIndex();
1395 pBackAttrLeft = pTextEngine->FindCharAttrib( aCursor, TEXTATTR_SPELL_BACKGROUND );
1396 pErrorAttrLeft = pTextEngine->FindCharAttrib( aCursor, TEXTATTR_SPELL_ERROR );
1397 bHasFieldLeft = pBackAttrLeft !=0;
1398 bHasErrorLeft = pErrorAttrLeft != 0;
1399 ++aCursor.GetIndex();
1402 //Here we have to determine if the error found is the one currently active
1403 bool bIsErrorActive = (pErrorAttr && pErrorAttr->GetStart() == m_nErrorStart) ||
1404 (pErrorAttrLeft && pErrorAttrLeft->GetStart() == m_nErrorStart);
1406 SAL_WARN_IF(
1407 nSelectionType == INVALID, "cui.dialogs",
1408 "selection type not set");
1410 const vcl::KeyCode& rKeyCode = rKeyEvt.GetKeyCode();
1411 bool bDelete = rKeyCode.GetCode() == KEY_DELETE;
1412 bool bBackspace = rKeyCode.GetCode() == KEY_BACKSPACE;
1414 sal_Int8 nAction = ACTION_CONTINUE;
1415 switch(nSelectionType)
1417 // 1 - backspace delete any other
1418 // UE on field FS on error CO on field FS on error CO
1419 case LEFT_NO :
1420 if(bBackspace)
1422 nAction = bHasFieldLeft ? ACTION_SELECTFIELD : ACTION_UNDOEDIT;
1423 //to force the use of pBackAttrLeft
1424 pBackAttr = 0;
1426 else if(bDelete)
1427 nAction = bHasField ? ACTION_SELECTFIELD : ACTION_CONTINUE;
1428 else
1429 nAction = bHasError && !aCursor.GetIndex() ? ACTION_CONTINUE :
1430 bHasError ? ACTION_EXPAND : bHasErrorLeft ? ACTION_CONTINUE : ACTION_UNDOEDIT;
1431 break;
1432 // 2 - on field FS on error C
1433 case INSIDE_NO :
1434 nAction = bHasField ? ACTION_SELECTFIELD :
1435 bIsErrorActive ? ACTION_CONTINUE : ACTION_UNDOEDIT;
1436 break;
1437 // 3 - backspace delete any other
1438 // on field FS on error CO UE on field UE on error EX
1439 case RIGHT_NO :
1440 if(bBackspace)
1441 nAction = bHasFieldLeft ? ACTION_SELECTFIELD : ACTION_CONTINUE;
1442 else if(bDelete)
1443 nAction = bHasFieldLeft && bHasError ? ACTION_CONTINUE : ACTION_UNDOEDIT;
1444 else
1445 nAction = bHasFieldLeft && bHasError ? ACTION_EXPAND :
1446 bHasError ? ACTION_CONTINUE : bHasErrorLeft ? ACTION_EXPAND :ACTION_UNDOEDIT;
1447 break;
1448 // 4 - on field UE and on error CO
1449 case FULL :
1450 nAction = ACTION_UNDOEDIT;
1451 break;
1452 // 5 - on field FS and on error CO
1453 case INSIDE_YES :
1454 nAction = bHasField ? ACTION_SELECTFIELD : ACTION_CONTINUE;
1455 break;
1456 // 6 - on field FS and on error UE
1457 case BRACE :
1458 nAction = bHasField ? ACTION_SELECTFIELD : ACTION_UNDOEDIT;;
1459 break;
1460 // 7 - UE
1461 // 8 - UE
1462 case OUTSIDE_NO :
1463 case OUTSIDE_YES:
1464 nAction = ACTION_UNDOEDIT;
1465 break;
1467 //save the current paragraph
1468 sal_Int32 nCurrentLen = GetText().getLength();
1469 if(nAction != ACTION_SELECTFIELD)
1470 pTextView->GetWindow()->KeyInput(rKeyEvt);
1471 else
1473 const TextCharAttrib* pCharAttr = pBackAttr ? pBackAttr : pBackAttrLeft;
1474 if(pCharAttr)
1476 TextPaM aStart(0, pCharAttr->GetStart());
1477 TextPaM aEnd(0, pCharAttr->GetEnd());
1478 TextSelection aNewSel(aStart, aEnd);
1479 pTextView->SetSelection( aNewSel);
1482 if(nAction == ACTION_EXPAND)
1484 DBG_ASSERT(pErrorAttrLeft || pErrorAttr, "where is the error");
1485 //text has been added on the right and only the 'error attribute has to be corrected
1486 if(pErrorAttrLeft)
1488 boost::scoped_ptr<TextAttrib> pNewError(pErrorAttrLeft->GetAttr().Clone());
1489 sal_uInt16 nStart = pErrorAttrLeft->GetStart();
1490 sal_uInt16 nEnd = pErrorAttrLeft->GetEnd();
1491 pTextEngine->RemoveAttrib( 0, *pErrorAttrLeft );
1492 SetAttrib( *pNewError, 0, nStart, ++nEnd );
1493 //only active errors move the mark
1494 if(bIsErrorActive)
1496 bool bGrammar = static_cast<const SpellErrorAttrib&>(*pNewError).GetErrorDescription().bIsGrammarError;
1497 MoveErrorMarkTo(nStart, nEnd, bGrammar);
1500 //text has been added on the left then the error attribute has to be expanded and the
1501 //field attribute on the right - if any - has to be contracted
1502 else if(pErrorAttr)
1504 //determine the change
1505 sal_Int32 nAddedChars = GetText().getLength() - nCurrentLen;
1507 boost::scoped_ptr<TextAttrib> pNewError(pErrorAttr->GetAttr().Clone());
1508 sal_Int32 nStart = pErrorAttr->GetStart();
1509 sal_Int32 nEnd = pErrorAttr->GetEnd();
1510 pTextEngine->RemoveAttrib( 0, *pErrorAttr );
1511 nStart = nStart - nAddedChars;
1512 SetAttrib( *pNewError, 0, nStart - nAddedChars, nEnd );
1513 //only if the error is active the mark is moved here
1514 if(bIsErrorActive)
1516 bool bGrammar = static_cast<const SpellErrorAttrib&>(*pNewError).GetErrorDescription().bIsGrammarError;
1517 MoveErrorMarkTo(nStart, nEnd, bGrammar);
1519 pNewError.reset();
1521 if(pBackAttrLeft)
1523 boost::scoped_ptr<TextAttrib> pNewBack(pBackAttrLeft->GetAttr().Clone());
1524 sal_uInt16 _nStart = pBackAttrLeft->GetStart();
1525 sal_uInt16 _nEnd = pBackAttrLeft->GetEnd();
1526 pTextEngine->RemoveAttrib( 0, *pBackAttrLeft );
1527 SetAttrib( *pNewBack, 0, _nStart, _nEnd - nAddedChars);
1531 else if(nAction == ACTION_UNDOEDIT)
1533 SetUndoEditMode(true);
1535 //make sure the error positions are correct after text changes
1536 //the old attribute may have been deleted
1537 //all changes inside of the current error leave the error attribute at the current
1538 //start position
1539 if(!IsUndoEditMode() && bIsErrorActive)
1541 const TextCharAttrib* pFontColor = pTextEngine->FindCharAttrib( aCursor, TEXTATTR_FONTCOLOR );
1542 const TextCharAttrib* pErrorAttrib = pTextEngine->FindCharAttrib( TextPaM(0, m_nErrorStart), TEXTATTR_SPELL_ERROR );
1543 if(pFontColor && pErrorAttrib )
1545 m_nErrorStart = pFontColor->GetStart();
1546 m_nErrorEnd = pFontColor->GetEnd();
1547 if(pErrorAttrib->GetStart() != m_nErrorStart || pErrorAttrib->GetEnd() != m_nErrorEnd)
1549 boost::scoped_ptr<TextAttrib> pNewError(pErrorAttrib->GetAttr().Clone());
1550 pTextEngine->RemoveAttrib( 0, *pErrorAttr );
1551 SetAttrib( *pNewError, 0, m_nErrorStart, m_nErrorEnd );
1555 //this is not a modification anymore
1556 if(nAction != ACTION_SELECTFIELD && !m_bIsUndoEditMode)
1557 CallModifyLink();
1559 else
1560 bChange = false;
1562 return bChange || VclMultiLineEdit::PreNotify(rNEvt);
1566 bool SentenceEditWindow_Impl::MarkNextError( bool bIgnoreCurrentError, com::sun::star::uno::Reference<com::sun::star::linguistic2::XSpellChecker1> xSpell )
1568 if (bIgnoreCurrentError)
1569 m_aIgnoreErrorsAt.insert( m_nErrorStart );
1570 ExtTextEngine* pTextEngine = GetTextEngine();
1571 sal_uInt16 nTextLen = pTextEngine->GetTextLen(0);
1572 if(m_nErrorEnd >= nTextLen - 1)
1573 return false;
1574 //if it's not already modified the modified flag has to be reset at the end of the marking
1575 bool bModified = IsModified();
1576 bool bRet = false;
1577 const sal_uInt16 nOldErrorStart = m_nErrorStart;
1578 const sal_uInt16 nOldErrorEnd = m_nErrorEnd;
1580 //create a cursor behind the end of the last error
1581 //- or at 0 at the start of the sentence
1582 TextPaM aCursor(0, m_nErrorEnd ? m_nErrorEnd + 1 : 0);
1583 //search for SpellErrorAttrib
1585 const TextCharAttrib* pNextError = 0;
1586 //iterate over the text and search for the next error that maybe has
1587 //to be replace by a ChangeAllList replacement
1588 bool bGrammarError = false;
1589 while(aCursor.GetIndex() < nTextLen)
1591 while(aCursor.GetIndex() < nTextLen &&
1592 0 == (pNextError = pTextEngine->FindCharAttrib( aCursor, TEXTATTR_SPELL_ERROR)))
1594 ++aCursor.GetIndex();
1596 // maybe the error found here is already in the ChangeAllList and has to be replaced
1598 Reference<XDictionary> xChangeAll( SvxGetChangeAllList(), UNO_QUERY );
1599 Reference<XDictionaryEntry> xEntry;
1601 const SpellErrorDescription* pSpellErrorDescription = 0;
1602 if(pNextError)
1604 pSpellErrorDescription = &static_cast<const SpellErrorAttrib&>(pNextError->GetAttr()).GetErrorDescription();
1605 bGrammarError = pSpellErrorDescription->bIsGrammarError;
1606 m_nErrorStart = pNextError->GetStart();
1607 m_nErrorEnd = pNextError->GetEnd();
1609 if(xChangeAll->getCount() && pSpellErrorDescription &&
1610 (xEntry = xChangeAll->getEntry( pSpellErrorDescription->sErrorText )).is())
1613 OUString sReplacement(getDotReplacementString(GetErrorText(), xEntry->getReplacementText()));
1615 ChangeMarkedWord(sReplacement, LanguageTag::convertToLanguageType( pSpellErrorDescription->aLocale ));
1617 aCursor.GetIndex() = aCursor.GetIndex() + (sal_uInt16)(xEntry->getReplacementText().getLength());
1618 // maybe the error found here is already added to the dictionary and has to be ignored
1619 } else if(pSpellErrorDescription && !bGrammarError && xSpell->isValid( GetErrorText(), LanguageTag::convertToLanguageType( pSpellErrorDescription->aLocale ), Sequence< PropertyValue >() )) {
1620 aCursor.GetIndex() = aCursor.GetIndex() + 1;
1622 else
1623 break;
1626 //if an attrib has been found search for the end of the error string
1627 if(aCursor.GetIndex() < nTextLen)
1629 m_nErrorStart = aCursor.GetIndex();
1630 m_nErrorEnd = pNextError->GetEnd();
1631 MoveErrorMarkTo(m_nErrorStart, m_nErrorEnd, bGrammarError);
1632 bRet = true;
1633 //add an undo action
1634 SpellUndoAction_Impl* pAction = new SpellUndoAction_Impl(
1635 SPELLUNDO_CHANGE_NEXTERROR, GetSpellDialog()->aDialogUndoLink);
1636 pAction->SetErrorMove(m_nErrorStart, m_nErrorEnd, nOldErrorStart, nOldErrorEnd);
1637 const SpellErrorAttrib* pOldAttrib = static_cast<const SpellErrorAttrib*>(
1638 pTextEngine->FindAttrib( TextPaM(0, nOldErrorStart), TEXTATTR_SPELL_ERROR ));
1639 pAction->SetErrorLanguageSelected(pOldAttrib && pOldAttrib->GetErrorDescription().aSuggestions.getLength() &&
1640 LanguageTag( pOldAttrib->GetErrorDescription().aLocale).getLanguageType() ==
1641 GetSpellDialog()->m_pLanguageLB->GetSelectLanguage());
1642 AddUndoAction(pAction);
1644 else
1645 m_nErrorStart = m_nErrorEnd = nTextLen;
1646 if( !bModified )
1647 ClearModifyFlag();
1648 SpellDialog* pSpellDialog = GetSpellDialog();
1649 pSpellDialog->m_pIgnorePB->Enable(bRet);
1650 pSpellDialog->m_pIgnoreAllPB->Enable(bRet);
1651 pSpellDialog->m_pAutoCorrPB->Enable(bRet);
1652 pSpellDialog->m_pAddToDictMB->Enable(bRet);
1653 pSpellDialog->m_pAddToDictPB->Enable(bRet);
1654 return bRet;
1658 void SentenceEditWindow_Impl::MoveErrorMarkTo(sal_uInt16 nStart, sal_uInt16 nEnd, bool bGrammarError)
1660 TextEngine* pTextEngine = GetTextEngine();
1661 pTextEngine->RemoveAttribs( 0, (sal_uInt16)TEXTATTR_FONTCOLOR, true );
1662 pTextEngine->RemoveAttribs( 0, (sal_uInt16)TEXTATTR_FONTWEIGHT, true );
1663 pTextEngine->SetAttrib( TextAttribFontWeight(WEIGHT_BOLD), 0, nStart, nEnd );
1664 pTextEngine->SetAttrib( TextAttribFontColor(bGrammarError ? COL_LIGHTBLUE : COL_LIGHTRED), 0, nStart, nEnd );
1665 m_nErrorStart = nStart;
1666 m_nErrorEnd = nEnd;
1670 void SentenceEditWindow_Impl::ChangeMarkedWord(const OUString& rNewWord, LanguageType eLanguage)
1672 //calculate length changes
1673 long nDiffLen = rNewWord.getLength() - m_nErrorEnd + m_nErrorStart;
1674 TextSelection aSel(TextPaM(0, m_nErrorStart), TextPaM(0, m_nErrorEnd));
1675 //Remove spell error attribute
1676 ExtTextEngine* pTextEngine = GetTextEngine();
1677 pTextEngine->UndoActionStart();
1678 const TextCharAttrib* pErrorAttrib = pTextEngine->FindCharAttrib( TextPaM(0, m_nErrorStart), TEXTATTR_SPELL_ERROR );
1679 DBG_ASSERT(pErrorAttrib, "no error attribute found");
1680 const SpellErrorDescription* pSpellErrorDescription = 0;
1681 if(pErrorAttrib)
1683 pTextEngine->RemoveAttrib(0, *pErrorAttrib);
1684 pSpellErrorDescription = &static_cast<const SpellErrorAttrib&>(pErrorAttrib->GetAttr()).GetErrorDescription();
1686 const TextCharAttrib* pBackAttrib = pTextEngine->FindCharAttrib( TextPaM(0, m_nErrorStart), TEXTATTR_SPELL_BACKGROUND );
1687 pTextEngine->ReplaceText( aSel, rNewWord );
1689 if(!m_nErrorStart)
1691 //attributes following an error at the start of the text are not moved but expanded from the
1692 //text engine - this is done to keep full-paragraph-attributes
1693 //in the current case that handling is not desired
1694 const TextCharAttrib* pLangAttrib =
1695 pTextEngine->FindCharAttrib(
1696 TextPaM(0, m_nErrorEnd), TEXTATTR_SPELL_LANGUAGE );
1697 sal_uInt16 nTextLen = pTextEngine->GetTextLen( 0 );
1698 if(pLangAttrib && !pLangAttrib->GetStart() && pLangAttrib->GetEnd() ==
1699 nTextLen)
1701 SpellLanguageAttrib aNewLangAttrib( static_cast<const SpellLanguageAttrib&>(pLangAttrib->GetAttr()).GetLanguage());
1702 pTextEngine->RemoveAttrib(0, *pLangAttrib);
1703 pTextEngine->SetAttrib( aNewLangAttrib, 0, (sal_uInt16)(m_nErrorEnd + nDiffLen) , nTextLen );
1706 // undo expanded attributes!
1707 if( pBackAttrib && pBackAttrib->GetStart() < m_nErrorStart && pBackAttrib->GetEnd() == m_nErrorEnd + nDiffLen)
1709 boost::scoped_ptr<TextAttrib> pNewBackground(pBackAttrib->GetAttr().Clone());
1710 sal_uInt16 nStart = pBackAttrib->GetStart();
1711 pTextEngine->RemoveAttrib(0, *pBackAttrib);
1712 pTextEngine->SetAttrib(*pNewBackground, 0, nStart, m_nErrorStart);
1714 pTextEngine->SetModified(true);
1716 //adjust end position
1717 long nEndTemp = m_nErrorEnd;
1718 nEndTemp += nDiffLen;
1719 m_nErrorEnd = (sal_uInt16)nEndTemp;
1721 SpellUndoAction_Impl* pAction = new SpellUndoAction_Impl(
1722 SPELLUNDO_MOVE_ERROREND, GetSpellDialog()->aDialogUndoLink);
1723 pAction->SetOffset(nDiffLen);
1724 AddUndoAction(pAction);
1725 if(pSpellErrorDescription)
1726 SetAttrib( SpellErrorAttrib(*pSpellErrorDescription), 0, m_nErrorStart, m_nErrorEnd );
1727 SetAttrib( SpellLanguageAttrib(eLanguage), 0, m_nErrorStart, m_nErrorEnd );
1728 pTextEngine->UndoActionEnd();
1732 OUString SentenceEditWindow_Impl::GetErrorText() const
1734 return GetTextEngine()->GetText(TextSelection(TextPaM(0, m_nErrorStart), TextPaM(0, m_nErrorEnd) ));
1738 const SpellErrorDescription* SentenceEditWindow_Impl::GetAlternatives()
1740 TextPaM aCursor(0, m_nErrorStart);
1741 const SpellErrorAttrib* pAttrib = static_cast<const SpellErrorAttrib*>(
1742 GetTextEngine()->FindAttrib( aCursor, TEXTATTR_SPELL_ERROR));
1743 return pAttrib ? &pAttrib->GetErrorDescription() : 0;
1747 void SentenceEditWindow_Impl::RestoreCurrentError()
1749 TextPaM aCursor(0, m_nErrorStart);
1750 const SpellErrorAttrib* pAttrib = static_cast<const SpellErrorAttrib*>(
1751 GetTextEngine()->FindAttrib( aCursor, TEXTATTR_SPELL_ERROR));
1752 if( pAttrib )
1754 const SpellErrorDescription& rDesc = pAttrib->GetErrorDescription();
1755 if( !rDesc.sErrorText.equals( GetErrorText() ) )
1756 ChangeMarkedWord(rDesc.sErrorText, LanguageTag::convertToLanguageType( rDesc.aLocale ));
1761 void SentenceEditWindow_Impl::SetAlternatives( Reference< XSpellAlternatives> xAlt )
1763 TextPaM aCursor(0, m_nErrorStart);
1764 DBG_ASSERT(static_cast<const SpellErrorAttrib*>(
1765 GetTextEngine()->FindAttrib( aCursor, TEXTATTR_SPELL_ERROR)), "no error set?");
1767 OUString aWord;
1768 lang::Locale aLocale;
1769 uno::Sequence< OUString > aAlts;
1770 OUString sServiceName;
1771 if (xAlt.is())
1773 aWord = xAlt->getWord();
1774 aLocale = xAlt->getLocale();
1775 aAlts = xAlt->getAlternatives();
1776 uno::Reference< container::XNamed > xNamed( xAlt, uno::UNO_QUERY );
1777 if (xNamed.is())
1778 sServiceName = xNamed->getName();
1780 SpellErrorDescription aDesc( false, aWord, aLocale, aAlts, 0, sServiceName);
1781 GetTextEngine()->SetAttrib( SpellErrorAttrib(aDesc), 0, m_nErrorStart, m_nErrorEnd );
1785 void SentenceEditWindow_Impl::SetAttrib( const TextAttrib& rAttr, sal_uLong nPara, sal_uInt16 nStart, sal_uInt16 nEnd )
1787 GetTextEngine()->SetAttrib(rAttr, nPara, nStart, nEnd);
1791 void SentenceEditWindow_Impl::SetText( const OUString& rStr )
1793 m_nErrorStart = m_nErrorEnd = 0;
1794 GetTextEngine()->SetText(rStr);
1798 struct LanguagePosition_Impl
1800 sal_uInt16 nPosition;
1801 LanguageType eLanguage;
1803 LanguagePosition_Impl(sal_uInt16 nPos, LanguageType eLang) :
1804 nPosition(nPos),
1805 eLanguage(eLang)
1808 typedef std::vector<LanguagePosition_Impl> LanguagePositions_Impl;
1810 static void lcl_InsertBreakPosition_Impl(
1811 LanguagePositions_Impl& rBreakPositions, sal_uInt16 nInsert, LanguageType eLanguage)
1813 LanguagePositions_Impl::iterator aStart = rBreakPositions.begin();
1814 while(aStart != rBreakPositions.end())
1816 if(aStart->nPosition == nInsert)
1818 //the language of following starts has to overwrite
1819 //the one of previous ends
1820 aStart->eLanguage = eLanguage;
1821 return;
1823 else if(aStart->nPosition > nInsert)
1826 rBreakPositions.insert(aStart, LanguagePosition_Impl(nInsert, eLanguage));
1827 return;
1829 else
1830 ++aStart;
1832 rBreakPositions.push_back(LanguagePosition_Impl(nInsert, eLanguage));
1834 /*-------------------------------------------------------------------------
1835 Returns the text in spell portions. Each portion contains text with an
1836 equal language and attribute. The spell alternatives are empty.
1837 -----------------------------------------------------------------------*/
1838 svx::SpellPortions SentenceEditWindow_Impl::CreateSpellPortions( bool bSetIgnoreFlag ) const
1840 svx::SpellPortions aRet;
1841 ExtTextEngine* pTextEngine = GetTextEngine();
1842 const sal_uInt16 nTextLen = pTextEngine->GetTextLen(0);
1843 if(nTextLen)
1845 TextPaM aCursor(0, 0);
1846 LanguagePositions_Impl aBreakPositions;
1847 const TextCharAttrib* pLastLang = 0;
1848 const TextCharAttrib* pLastError = 0;
1849 LanguageType eLang = LANGUAGE_DONTKNOW;
1850 const TextCharAttrib* pError = 0;
1851 while(aCursor.GetIndex() < nTextLen)
1853 const TextCharAttrib* pLang = pTextEngine->FindCharAttrib( aCursor, TEXTATTR_SPELL_LANGUAGE);
1854 if(pLang && pLang != pLastLang)
1856 eLang = static_cast<const SpellLanguageAttrib&>(pLang->GetAttr()).GetLanguage();
1857 lcl_InsertBreakPosition_Impl(aBreakPositions, pLang->GetStart(), eLang);
1858 lcl_InsertBreakPosition_Impl(aBreakPositions, pLang->GetEnd(), eLang);
1859 pLastLang = pLang;
1861 pError = pTextEngine->FindCharAttrib( aCursor, TEXTATTR_SPELL_ERROR);
1862 if(pError && pLastError != pError)
1864 lcl_InsertBreakPosition_Impl(aBreakPositions, pError->GetStart(), eLang);
1865 lcl_InsertBreakPosition_Impl(aBreakPositions, pError->GetEnd(), eLang);
1866 pLastError = pError;
1869 aCursor.GetIndex()++;
1872 if(nTextLen && aBreakPositions.empty())
1874 //if all content has been overwritten the attributes may have been removed, too
1875 svx::SpellPortion aPortion1;
1876 aPortion1.eLanguage = GetSpellDialog()->GetSelectedLang_Impl();
1877 aPortion1.sText = pTextEngine->GetText(
1878 TextSelection(TextPaM(0, 0), TextPaM(0, nTextLen)));
1880 aRet.push_back(aPortion1);
1883 else if(!aBreakPositions.empty())
1885 LanguagePositions_Impl::iterator aStart = aBreakPositions.begin();
1886 //start should always be Null
1887 eLang = aStart->eLanguage;
1888 sal_uInt16 nStart = aStart->nPosition;
1889 DBG_ASSERT(!nStart, "invalid start position - language attribute missing?");
1890 ++aStart;
1892 while(aStart != aBreakPositions.end())
1894 svx::SpellPortion aPortion1;
1895 aPortion1.eLanguage = eLang;
1896 aPortion1.sText = pTextEngine->GetText(
1897 TextSelection(TextPaM(0, nStart), TextPaM(0, aStart->nPosition)));
1898 bool bIsIgnoreError = m_aIgnoreErrorsAt.find( nStart ) != m_aIgnoreErrorsAt.end();
1899 if( bSetIgnoreFlag && bIsIgnoreError )
1901 aPortion1.bIgnoreThisError = true;
1903 aRet.push_back(aPortion1);
1904 nStart = aStart->nPosition;
1905 eLang = aStart->eLanguage;
1906 ++aStart;
1910 // quick partly fix of #i71318. Correct fix needs to patch the TextEngine itself...
1911 // this one will only prevent text from disappearing. It may to not have the
1912 // correct language and will probably not spell checked...
1913 sal_uLong nPara = pTextEngine->GetParagraphCount();
1914 if (nPara > 1)
1916 OUString aLeftOverText;
1917 for (sal_uLong i = 1; i < nPara; ++i)
1919 aLeftOverText += "\x0a"; // the manual line break...
1920 aLeftOverText += pTextEngine->GetText(i);
1922 if (pError)
1923 { // we need to add a new portion containing the left-over text
1924 svx::SpellPortion aPortion2;
1925 aPortion2.eLanguage = eLang;
1926 aPortion2.sText = aLeftOverText;
1927 aRet.push_back( aPortion2 );
1929 else
1930 { // we just need to append the left-over text to the last portion (which had no errors)
1931 aRet[ aRet.size() - 1 ].sText += aLeftOverText;
1935 return aRet;
1939 void SentenceEditWindow_Impl::Undo()
1941 ::svl::IUndoManager& rUndoMgr = GetTextEngine()->GetUndoManager();
1942 DBG_ASSERT(GetUndoActionCount(), "no undo actions available" );
1943 if(!GetUndoActionCount())
1944 return;
1945 bool bSaveUndoEdit = IsUndoEditMode();
1946 sal_uInt16 nId;
1947 //if the undo edit mode is active then undo all changes until the UNDO_EDIT_MODE action has been found
1950 nId = rUndoMgr.GetUndoActionId();
1951 rUndoMgr.Undo();
1952 }while(bSaveUndoEdit && SPELLUNDO_UNDO_EDIT_MODE != nId && GetUndoActionCount());
1954 if(bSaveUndoEdit || SPELLUNDO_CHANGE_GROUP == nId)
1955 GetSpellDialog()->UpdateBoxes_Impl();
1959 void SentenceEditWindow_Impl::ResetUndo()
1961 GetTextEngine()->ResetUndo();
1965 void SentenceEditWindow_Impl::AddUndoAction( SfxUndoAction *pAction, bool bTryMerg )
1967 ::svl::IUndoManager& rUndoMgr = GetTextEngine()->GetUndoManager();
1968 rUndoMgr.AddUndoAction(pAction, bTryMerg);
1969 GetSpellDialog()->m_pUndoPB->Enable();
1973 sal_uInt16 SentenceEditWindow_Impl::GetUndoActionCount()
1975 return GetTextEngine()->GetUndoManager().GetUndoActionCount();
1979 void SentenceEditWindow_Impl::UndoActionStart( sal_uInt16 nId )
1981 GetTextEngine()->UndoActionStart(nId);
1985 void SentenceEditWindow_Impl::UndoActionEnd()
1987 GetTextEngine()->UndoActionEnd();
1991 void SentenceEditWindow_Impl::MoveErrorEnd(long nOffset)
1993 if(nOffset > 0)
1994 m_nErrorEnd = m_nErrorEnd - (sal_uInt16)nOffset;
1995 else
1996 m_nErrorEnd = m_nErrorEnd -(sal_uInt16)- nOffset;
2000 void SentenceEditWindow_Impl::SetUndoEditMode(bool bSet)
2002 DBG_ASSERT(!bSet || m_bIsUndoEditMode != bSet, "SetUndoEditMode with equal values?");
2003 m_bIsUndoEditMode = bSet;
2004 //disable all buttons except the Change
2005 SpellDialog* pSpellDialog = GetSpellDialog();
2006 Control* aControls[] =
2008 pSpellDialog->m_pChangeAllPB,
2009 pSpellDialog->m_pExplainFT,
2010 pSpellDialog->m_pIgnoreAllPB,
2011 pSpellDialog->m_pIgnoreRulePB,
2012 pSpellDialog->m_pIgnorePB,
2013 pSpellDialog->m_pSuggestionLB,
2014 pSpellDialog->m_pSuggestionFT,
2015 pSpellDialog->m_pLanguageFT,
2016 pSpellDialog->m_pLanguageLB,
2017 pSpellDialog->m_pAddToDictMB,
2018 pSpellDialog->m_pAddToDictPB,
2019 pSpellDialog->m_pAutoCorrPB,
2022 sal_Int32 nIdx = 0;
2025 aControls[nIdx]->Enable(false);
2027 while(aControls[++nIdx]);
2029 //remove error marks
2030 TextEngine* pTextEngine = GetTextEngine();
2031 pTextEngine->RemoveAttribs( 0, (sal_uInt16)TEXTATTR_FONTCOLOR, true );
2032 pTextEngine->RemoveAttribs( 0, (sal_uInt16)TEXTATTR_FONTWEIGHT, true );
2034 //put the appropriate action on the Undo-stack
2035 SpellUndoAction_Impl* pAction = new SpellUndoAction_Impl(
2036 SPELLUNDO_UNDO_EDIT_MODE, GetSpellDialog()->aDialogUndoLink);
2037 AddUndoAction(pAction);
2038 pSpellDialog->m_pChangePB->Enable();
2041 IMPL_LINK( SpellDialog, HandleHyperlink, FixedHyperlink*, pHyperlink )
2043 OUString sURL=pHyperlink->GetURL();
2044 OUString sTitle=GetText();
2046 if ( sURL.isEmpty() ) // Nothing to do, when the URL is empty
2047 return 1;
2050 uno::Reference< com::sun::star::system::XSystemShellExecute > xSystemShellExecute(
2051 com::sun::star::system::SystemShellExecute::create(::comphelper::getProcessComponentContext()) );
2052 xSystemShellExecute->execute( sURL, OUString(), com::sun::star::system::SystemShellExecuteFlags::URIS_ONLY );
2054 catch ( uno::Exception& )
2056 uno::Any exc( ::cppu::getCaughtException() );
2057 OUString msg( ::comphelper::anyToString( exc ) );
2058 const SolarMutexGuard guard;
2059 ScopedVclPtrInstance< MessageDialog > aErrorBox(nullptr, msg);
2060 aErrorBox->SetText(sTitle);
2061 aErrorBox->Execute();
2064 return 1;
2067 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */