1 /************************************************************** 2 * 3 * Licensed to the Apache Software Foundation (ASF) under one 4 * or more contributor license agreements. See the NOTICE file 5 * distributed with this work for additional information 6 * regarding copyright ownership. The ASF licenses this file 7 * to you under the Apache License, Version 2.0 (the 8 * "License"); you may not use this file except in compliance 9 * with the License. You may obtain a copy of the License at 10 * 11 * http://www.apache.org/licenses/LICENSE-2.0 12 * 13 * Unless required by applicable law or agreed to in writing, 14 * software distributed under the License is distributed on an 15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 * KIND, either express or implied. See the License for the 17 * specific language governing permissions and limitations 18 * under the License. 19 * 20 *************************************************************/ 21 22 23 24 // MARKER(update_precomp.py): autogen include statement, do not remove 25 #include "precompiled_sw.hxx" 26 27 #include <UndoManager.hxx> 28 29 #include <unotools/undoopt.hxx> 30 31 #include <vcl/wrkwin.hxx> 32 33 #include <svx/svdmodel.hxx> 34 35 #include <swmodule.hxx> 36 #include <doc.hxx> 37 #include <ndarr.hxx> 38 #include <pam.hxx> 39 #include <ndtxt.hxx> 40 #include <swundo.hxx> 41 #include <UndoCore.hxx> 42 #include <rolbck.hxx> 43 #include <undo.hrc> 44 #include <editsh.hxx> 45 #include <unobaseclass.hxx> 46 #include <limits> 47 48 #include <limits> 49 50 using namespace ::com::sun::star; 51 52 53 // the undo array should never grow beyond this limit: 54 #define UNDO_ACTION_LIMIT (USHRT_MAX - 1000) 55 56 57 // UndoManager /////////////////////////////////////////////////////////// 58 59 namespace sw { 60 61 UndoManager::UndoManager(::std::auto_ptr<SwNodes> pUndoNodes, 62 IDocumentDrawModelAccess & rDrawModelAccess, 63 IDocumentRedlineAccess & rRedlineAccess, 64 IDocumentState & rState) 65 : m_rDrawModelAccess(rDrawModelAccess) 66 , m_rRedlineAccess(rRedlineAccess) 67 , m_rState(rState) 68 , m_pUndoNodes(pUndoNodes) 69 , m_bGroupUndo(true) 70 , m_bDrawUndo(true) 71 , m_bLockUndoNoModifiedPosition(false) 72 , m_UndoSaveMark(MARK_INVALID) 73 { 74 OSL_ASSERT(m_pUndoNodes.get()); 75 // writer expects it to be disabled initially 76 // Undo is enabled by SwEditShell constructor 77 SfxUndoManager::EnableUndo(false); 78 } 79 80 SwNodes const& UndoManager::GetUndoNodes() const 81 { 82 return *m_pUndoNodes; 83 } 84 85 SwNodes & UndoManager::GetUndoNodes() 86 { 87 return *m_pUndoNodes; 88 } 89 90 bool UndoManager::IsUndoNodes(SwNodes const& rNodes) const 91 { 92 return & rNodes == m_pUndoNodes.get(); 93 } 94 95 void UndoManager::DoUndo(bool const bDoUndo) 96 { 97 EnableUndo(bDoUndo); 98 99 SdrModel *const pSdrModel = m_rDrawModelAccess.GetDrawModel(); 100 if( pSdrModel ) 101 { 102 pSdrModel->EnableUndo(bDoUndo); 103 } 104 } 105 106 bool UndoManager::DoesUndo() const 107 { 108 return IsUndoEnabled(); 109 } 110 111 void UndoManager::DoGroupUndo(bool const bDoUndo) 112 { 113 m_bGroupUndo = bDoUndo; 114 } 115 116 bool UndoManager::DoesGroupUndo() const 117 { 118 return m_bGroupUndo; 119 } 120 121 void UndoManager::DoDrawUndo(bool const bDoUndo) 122 { 123 m_bDrawUndo = bDoUndo; 124 } 125 126 bool UndoManager::DoesDrawUndo() const 127 { 128 return m_bDrawUndo; 129 } 130 131 132 bool UndoManager::IsUndoNoResetModified() const 133 { 134 return MARK_INVALID == m_UndoSaveMark; 135 } 136 137 void UndoManager::SetUndoNoResetModified() 138 { 139 if (MARK_INVALID != m_UndoSaveMark) 140 { 141 RemoveMark(m_UndoSaveMark); 142 m_UndoSaveMark = MARK_INVALID; 143 } 144 } 145 146 void UndoManager::SetUndoNoModifiedPosition() 147 { 148 if (!m_bLockUndoNoModifiedPosition) 149 { 150 m_UndoSaveMark = MarkTopUndoAction(); 151 } 152 } 153 154 void UndoManager::LockUndoNoModifiedPosition() 155 { 156 m_bLockUndoNoModifiedPosition = true; 157 } 158 159 void UndoManager::UnLockUndoNoModifiedPosition() 160 { 161 m_bLockUndoNoModifiedPosition = false; 162 } 163 164 165 SwUndo* UndoManager::GetLastUndo() 166 { 167 if (!SfxUndoManager::GetUndoActionCount(CurrentLevel)) 168 { 169 return 0; 170 } 171 SfxUndoAction *const pAction( SfxUndoManager::GetUndoAction(0) ); 172 return dynamic_cast<SwUndo*>(pAction); 173 } 174 175 void UndoManager::AppendUndo(SwUndo *const pUndo) 176 { 177 AddUndoAction(pUndo); 178 } 179 180 void UndoManager::ClearRedo() 181 { 182 return SfxUndoManager::ImplClearRedo_NoLock(TopLevel); 183 } 184 185 void UndoManager::DelAllUndoObj() 186 { 187 ::sw::UndoGuard const undoGuard(*this); 188 189 SfxUndoManager::ClearAllLevels(); 190 191 m_UndoSaveMark = MARK_INVALID; 192 } 193 194 195 /**************** UNDO ******************/ 196 197 SwUndoId 198 UndoManager::StartUndo(SwUndoId const i_eUndoId, 199 SwRewriter const*const pRewriter) 200 { 201 if (!IsUndoEnabled()) 202 { 203 return UNDO_EMPTY; 204 } 205 206 SwUndoId const eUndoId( (0 == i_eUndoId) ? UNDO_START : i_eUndoId ); 207 208 OSL_ASSERT(UNDO_END != eUndoId); 209 String comment( (UNDO_START == eUndoId) 210 ? String("??", RTL_TEXTENCODING_ASCII_US) 211 : String(SW_RES(UNDO_BASE + eUndoId)) ); 212 if (pRewriter) 213 { 214 OSL_ASSERT(UNDO_START != eUndoId); 215 comment = pRewriter->Apply(comment); 216 } 217 218 SfxUndoManager::EnterListAction(comment, comment, eUndoId); 219 220 return eUndoId; 221 } 222 223 224 SwUndoId 225 UndoManager::EndUndo(SwUndoId const i_eUndoId, SwRewriter const*const pRewriter) 226 { 227 if (!IsUndoEnabled()) 228 { 229 return UNDO_EMPTY; 230 } 231 232 SwUndoId const eUndoId( ((0 == i_eUndoId) || (UNDO_START == i_eUndoId)) 233 ? UNDO_END : i_eUndoId ); 234 OSL_ENSURE(!((UNDO_END == eUndoId) && pRewriter), 235 "EndUndo(): no Undo ID, but rewriter given?"); 236 237 SfxUndoAction *const pLastUndo( 238 (0 == SfxUndoManager::GetUndoActionCount(CurrentLevel)) 239 ? 0 : SfxUndoManager::GetUndoAction(0) ); 240 241 int const nCount = LeaveListAction(); 242 243 if (nCount) // otherwise: empty list action not inserted! 244 { 245 OSL_ASSERT(pLastUndo); 246 OSL_ASSERT(UNDO_START != eUndoId); 247 SfxUndoAction *const pUndoAction(SfxUndoManager::GetUndoAction(0)); 248 SfxListUndoAction *const pListAction( 249 dynamic_cast<SfxListUndoAction*>(pUndoAction)); 250 OSL_ASSERT(pListAction); 251 if (pListAction) 252 { 253 if (UNDO_END != eUndoId) 254 { 255 OSL_ENSURE(pListAction->GetId() == eUndoId, 256 "EndUndo(): given ID different from StartUndo()"); 257 // comment set by caller of EndUndo 258 String comment = String(SW_RES(UNDO_BASE + eUndoId)); 259 if (pRewriter) 260 { 261 comment = pRewriter->Apply(comment); 262 } 263 pListAction->SetComment(comment); 264 } 265 else if ((UNDO_START != pListAction->GetId())) 266 { 267 // comment set by caller of StartUndo: nothing to do here 268 } 269 else if (pLastUndo) 270 { 271 // comment was not set at StartUndo or EndUndo: 272 // take comment of last contained action 273 // (note that this works recursively, i.e. the last contained 274 // action may be a list action created by StartUndo/EndUndo) 275 String const comment(pLastUndo->GetComment()); 276 pListAction->SetComment(comment); 277 } 278 else 279 { 280 OSL_ENSURE(false, "EndUndo(): no comment?"); 281 } 282 } 283 } 284 285 return eUndoId; 286 } 287 288 bool 289 UndoManager::GetLastUndoInfo( 290 ::rtl::OUString *const o_pStr, SwUndoId *const o_pId) const 291 { 292 // this is actually expected to work on the current level, 293 // but that was really not obvious from the previous implementation... 294 if (!SfxUndoManager::GetUndoActionCount(CurrentLevel)) 295 { 296 return false; 297 } 298 299 SfxUndoAction *const pAction( SfxUndoManager::GetUndoAction(0) ); 300 301 if (o_pStr) 302 { 303 *o_pStr = pAction->GetComment(); 304 } 305 if (o_pId) 306 { 307 sal_uInt16 const nId(pAction->GetId()); 308 *o_pId = static_cast<SwUndoId>(nId); 309 } 310 311 return true; 312 } 313 314 SwUndoComments_t UndoManager::GetUndoComments() const 315 { 316 OSL_ENSURE(!SfxUndoManager::IsInListAction(), 317 "GetUndoComments() called while in list action?"); 318 319 SwUndoComments_t ret; 320 sal_uInt16 const nUndoCount(SfxUndoManager::GetUndoActionCount(TopLevel)); 321 for (sal_uInt16 n = 0; n < nUndoCount; ++n) 322 { 323 ::rtl::OUString const comment( 324 SfxUndoManager::GetUndoActionComment(n, TopLevel)); 325 ret.push_back(comment); 326 } 327 328 return ret; 329 } 330 331 332 /**************** REDO ******************/ 333 334 bool UndoManager::GetFirstRedoInfo(::rtl::OUString *const o_pStr) const 335 { 336 if (!SfxUndoManager::GetRedoActionCount(CurrentLevel)) 337 { 338 return false; 339 } 340 341 if (o_pStr) 342 { 343 *o_pStr = SfxUndoManager::GetRedoActionComment(0, CurrentLevel); 344 } 345 346 return true; 347 } 348 349 350 SwUndoComments_t UndoManager::GetRedoComments() const 351 { 352 OSL_ENSURE(!SfxUndoManager::IsInListAction(), 353 "GetRedoComments() called while in list action?"); 354 355 SwUndoComments_t ret; 356 sal_uInt16 const nRedoCount(SfxUndoManager::GetRedoActionCount(TopLevel)); 357 for (sal_uInt16 n = 0; n < nRedoCount; ++n) 358 { 359 ::rtl::OUString const comment( 360 SfxUndoManager::GetRedoActionComment(n, TopLevel)); 361 ret.push_back(comment); 362 } 363 364 return ret; 365 } 366 367 /**************** REPEAT ******************/ 368 369 SwUndoId UndoManager::GetRepeatInfo(::rtl::OUString *const o_pStr) const 370 { 371 SwUndoId nRepeatId(UNDO_EMPTY); 372 GetLastUndoInfo(o_pStr, & nRepeatId); 373 if( REPEAT_START <= nRepeatId && REPEAT_END > nRepeatId ) 374 { 375 return nRepeatId; 376 } 377 if (o_pStr) // not repeatable -> clear comment 378 { 379 *o_pStr = String(); 380 } 381 return UNDO_EMPTY; 382 } 383 384 SwUndo * UndoManager::RemoveLastUndo() 385 { 386 if (SfxUndoManager::GetRedoActionCount(CurrentLevel) || 387 SfxUndoManager::GetRedoActionCount(TopLevel)) 388 { 389 OSL_ENSURE(false, "RemoveLastUndoAction(): there are Redo actions?"); 390 return 0; 391 } 392 if (!SfxUndoManager::GetUndoActionCount(CurrentLevel)) 393 { 394 OSL_ENSURE(false, "RemoveLastUndoAction(): no Undo actions"); 395 return 0; 396 } 397 SfxUndoAction *const pLastUndo(GetUndoAction(0)); 398 SfxUndoManager::RemoveLastUndoAction(); 399 return dynamic_cast<SwUndo *>(pLastUndo); 400 } 401 402 // svl::IUndoManager ///////////////////////////////////////////////////// 403 404 void UndoManager::EnableUndo(bool bEnable) 405 { 406 // SfxUndoManager does not have a counter anymore, but reverted to the old behavior of 407 // having a simple boolean flag for locking. So, simply forward. 408 SfxUndoManager::EnableUndo(bEnable); 409 } 410 411 void UndoManager::AddUndoAction(SfxUndoAction *pAction, sal_Bool bTryMerge) 412 { 413 SwUndo *const pUndo( dynamic_cast<SwUndo *>(pAction) ); 414 if (pUndo) 415 { 416 if (nsRedlineMode_t::REDLINE_NONE == pUndo->GetRedlineMode()) 417 { 418 pUndo->SetRedlineMode( m_rRedlineAccess.GetRedlineMode() ); 419 } 420 } 421 SfxUndoManager::AddUndoAction(pAction, bTryMerge); 422 // if the undo nodes array is too large, delete some actions 423 while (UNDO_ACTION_LIMIT < GetUndoNodes().Count()) 424 { 425 RemoveOldestUndoActions(1); 426 } 427 } 428 429 class CursorGuard 430 { 431 public: 432 CursorGuard(SwEditShell & rShell, bool const bSave) 433 : m_rShell(rShell) 434 , m_bSaveCursor(bSave) 435 { 436 if (m_bSaveCursor) 437 { 438 m_rShell.Push(); // prevent modification of current cursor 439 } 440 } 441 ~CursorGuard() 442 { 443 if (m_bSaveCursor) 444 { 445 m_rShell.Pop(); 446 } 447 } 448 private: 449 SwEditShell & m_rShell; 450 bool const m_bSaveCursor; 451 }; 452 453 bool UndoManager::impl_DoUndoRedo(UndoOrRedo_t const undoOrRedo) 454 { 455 SwDoc & rDoc(*GetUndoNodes().GetDoc()); 456 457 UnoActionContext c(& rDoc); // exception-safe StartAllAction/EndAllAction 458 459 SwEditShell *const pEditShell( rDoc.GetEditShell() ); 460 461 OSL_ENSURE(pEditShell, "sw::UndoManager needs a SwEditShell!"); 462 if (!pEditShell) 463 { 464 throw uno::RuntimeException(); 465 } 466 467 // in case the model has controllers locked, the Undo should not 468 // change the view cursors! 469 bool const bSaveCursors(pEditShell->CursorsLocked()); 470 CursorGuard(*pEditShell, bSaveCursors); 471 if (!bSaveCursors) 472 { 473 // (in case Undo was called via API) clear the cursors: 474 pEditShell->KillPams(); 475 pEditShell->SetMark(); 476 pEditShell->ClearMark(); 477 } 478 479 bool bRet(false); 480 481 ::sw::UndoRedoContext context(rDoc, *pEditShell); 482 483 // N.B. these may throw! 484 if (UNDO == undoOrRedo) 485 { 486 bRet = SfxUndoManager::UndoWithContext(context); 487 } 488 else 489 { 490 bRet = SfxUndoManager::RedoWithContext(context); 491 } 492 493 if (bRet) 494 { 495 // if we are at the "last save" position, the document is not modified 496 if (SfxUndoManager::HasTopUndoActionMark(m_UndoSaveMark)) 497 { 498 m_rState.ResetModified(); 499 } 500 else 501 { 502 m_rState.SetModified(); 503 } 504 } 505 506 pEditShell->HandleUndoRedoContext(context); 507 508 return bRet; 509 } 510 511 sal_Bool UndoManager::Undo() 512 { 513 bool const bRet = impl_DoUndoRedo(UNDO); 514 return bRet; 515 } 516 517 sal_Bool UndoManager::Redo() 518 { 519 bool const bRet = impl_DoUndoRedo(REDO); 520 return bRet; 521 } 522 523 /** N.B.: this does _not_ call SfxUndoManager::Repeat because it is not 524 possible to wrap a list action around it: 525 calling EnterListAction here will cause SfxUndoManager::Repeat 526 to repeat the list action! 527 */ 528 bool 529 UndoManager::Repeat(::sw::RepeatContext & rContext, 530 sal_uInt16 const nRepeatCount) 531 { 532 if (SfxUndoManager::IsInListAction()) 533 { 534 OSL_ENSURE(false, "repeat in open list action???"); 535 return false; 536 } 537 if (!SfxUndoManager::GetUndoActionCount(TopLevel)) 538 { 539 return false; 540 } 541 SfxUndoAction *const pRepeatAction(GetUndoAction(0)); 542 OSL_ASSERT(pRepeatAction); 543 if (!pRepeatAction || !pRepeatAction->CanRepeat(rContext)) 544 { 545 return false; 546 } 547 548 ::rtl::OUString const comment(pRepeatAction->GetComment()); 549 ::rtl::OUString const rcomment(pRepeatAction->GetRepeatComment(rContext)); 550 sal_uInt16 const nId(pRepeatAction->GetId()); 551 if (DoesUndo()) 552 { 553 EnterListAction(comment, rcomment, nId); 554 } 555 556 SwPaM *const pFirstCursor(& rContext.GetRepeatPaM()); 557 do { // iterate over ring 558 for (sal_uInt16 nRptCnt = nRepeatCount; nRptCnt > 0; --nRptCnt) 559 { 560 pRepeatAction->Repeat(rContext); 561 } 562 rContext.m_bDeleteRepeated = false; // reset for next PaM 563 rContext.m_pCurrentPaM = 564 static_cast<SwPaM*>(rContext.m_pCurrentPaM->GetNext()); 565 } while (pFirstCursor != & rContext.GetRepeatPaM()); 566 567 if (DoesUndo()) 568 { 569 LeaveListAction(); 570 } 571 return true; 572 } 573 574 } // namespace sw 575 576