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 SdrUndoManager::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 if(!isTextEditActive()) 98 { 99 EnableUndo(bDoUndo); 100 101 SdrModel *const pSdrModel = m_rDrawModelAccess.GetDrawModel(); 102 if( pSdrModel ) 103 { 104 pSdrModel->EnableUndo(bDoUndo); 105 } 106 } 107 } 108 109 bool UndoManager::DoesUndo() const 110 { 111 if(isTextEditActive()) 112 { 113 return false; 114 } 115 else 116 { 117 return IsUndoEnabled(); 118 } 119 } 120 121 void UndoManager::DoGroupUndo(bool const bDoUndo) 122 { 123 m_bGroupUndo = bDoUndo; 124 } 125 126 bool UndoManager::DoesGroupUndo() const 127 { 128 return m_bGroupUndo; 129 } 130 131 void UndoManager::DoDrawUndo(bool const bDoUndo) 132 { 133 m_bDrawUndo = bDoUndo; 134 } 135 136 bool UndoManager::DoesDrawUndo() const 137 { 138 return m_bDrawUndo; 139 } 140 141 142 bool UndoManager::IsUndoNoResetModified() const 143 { 144 return MARK_INVALID == m_UndoSaveMark; 145 } 146 147 void UndoManager::SetUndoNoResetModified() 148 { 149 if (MARK_INVALID != m_UndoSaveMark) 150 { 151 RemoveMark(m_UndoSaveMark); 152 m_UndoSaveMark = MARK_INVALID; 153 } 154 } 155 156 void UndoManager::SetUndoNoModifiedPosition() 157 { 158 if (!m_bLockUndoNoModifiedPosition) 159 { 160 m_UndoSaveMark = MarkTopUndoAction(); 161 } 162 } 163 164 void UndoManager::LockUndoNoModifiedPosition() 165 { 166 m_bLockUndoNoModifiedPosition = true; 167 } 168 169 void UndoManager::UnLockUndoNoModifiedPosition() 170 { 171 m_bLockUndoNoModifiedPosition = false; 172 } 173 174 175 SwUndo* UndoManager::GetLastUndo() 176 { 177 if (!SdrUndoManager::GetUndoActionCount(CurrentLevel)) 178 { 179 return 0; 180 } 181 SfxUndoAction *const pAction( SdrUndoManager::GetUndoAction(0) ); 182 return dynamic_cast<SwUndo*>(pAction); 183 } 184 185 void UndoManager::AppendUndo(SwUndo *const pUndo) 186 { 187 AddUndoAction(pUndo); 188 } 189 190 void UndoManager::ClearRedo() 191 { 192 return SdrUndoManager::ImplClearRedo_NoLock(TopLevel); 193 } 194 195 void UndoManager::DelAllUndoObj() 196 { 197 ::sw::UndoGuard const undoGuard(*this); 198 199 SdrUndoManager::ClearAllLevels(); 200 201 m_UndoSaveMark = MARK_INVALID; 202 } 203 204 205 /**************** UNDO ******************/ 206 207 SwUndoId 208 UndoManager::StartUndo(SwUndoId const i_eUndoId, 209 SwRewriter const*const pRewriter) 210 { 211 if (!IsUndoEnabled()) 212 { 213 return UNDO_EMPTY; 214 } 215 216 SwUndoId const eUndoId( (0 == i_eUndoId) ? UNDO_START : i_eUndoId ); 217 218 OSL_ASSERT(UNDO_END != eUndoId); 219 String comment( (UNDO_START == eUndoId) 220 ? String("??", RTL_TEXTENCODING_ASCII_US) 221 : String(SW_RES(UNDO_BASE + eUndoId)) ); 222 if (pRewriter) 223 { 224 OSL_ASSERT(UNDO_START != eUndoId); 225 comment = pRewriter->Apply(comment); 226 } 227 228 SdrUndoManager::EnterListAction(comment, comment, eUndoId); 229 230 return eUndoId; 231 } 232 233 234 SwUndoId 235 UndoManager::EndUndo(SwUndoId const i_eUndoId, SwRewriter const*const pRewriter) 236 { 237 if (!IsUndoEnabled()) 238 { 239 return UNDO_EMPTY; 240 } 241 242 SwUndoId const eUndoId( ((0 == i_eUndoId) || (UNDO_START == i_eUndoId)) 243 ? UNDO_END : i_eUndoId ); 244 OSL_ENSURE(!((UNDO_END == eUndoId) && pRewriter), 245 "EndUndo(): no Undo ID, but rewriter given?"); 246 247 SfxUndoAction *const pLastUndo( 248 (0 == SdrUndoManager::GetUndoActionCount(CurrentLevel)) 249 ? 0 : SdrUndoManager::GetUndoAction(0) ); 250 251 int const nCount = LeaveListAction(); 252 253 if (nCount) // otherwise: empty list action not inserted! 254 { 255 OSL_ASSERT(pLastUndo); 256 OSL_ASSERT(UNDO_START != eUndoId); 257 SfxUndoAction *const pUndoAction(SdrUndoManager::GetUndoAction(0)); 258 SfxListUndoAction *const pListAction( 259 dynamic_cast<SfxListUndoAction*>(pUndoAction)); 260 OSL_ASSERT(pListAction); 261 if (pListAction) 262 { 263 if (UNDO_END != eUndoId) 264 { 265 OSL_ENSURE(pListAction->GetId() == eUndoId, 266 "EndUndo(): given ID different from StartUndo()"); 267 // comment set by caller of EndUndo 268 String comment = String(SW_RES(UNDO_BASE + eUndoId)); 269 if (pRewriter) 270 { 271 comment = pRewriter->Apply(comment); 272 } 273 pListAction->SetComment(comment); 274 } 275 else if ((UNDO_START != pListAction->GetId())) 276 { 277 // comment set by caller of StartUndo: nothing to do here 278 } 279 else if (pLastUndo) 280 { 281 // comment was not set at StartUndo or EndUndo: 282 // take comment of last contained action 283 // (note that this works recursively, i.e. the last contained 284 // action may be a list action created by StartUndo/EndUndo) 285 String const comment(pLastUndo->GetComment()); 286 pListAction->SetComment(comment); 287 } 288 else 289 { 290 OSL_ENSURE(false, "EndUndo(): no comment?"); 291 } 292 } 293 } 294 295 return eUndoId; 296 } 297 298 bool 299 UndoManager::GetLastUndoInfo( 300 ::rtl::OUString *const o_pStr, SwUndoId *const o_pId) const 301 { 302 // this is actually expected to work on the current level, 303 // but that was really not obvious from the previous implementation... 304 if (!SdrUndoManager::GetUndoActionCount(CurrentLevel)) 305 { 306 return false; 307 } 308 309 SfxUndoAction *const pAction( SdrUndoManager::GetUndoAction(0) ); 310 311 if (o_pStr) 312 { 313 *o_pStr = pAction->GetComment(); 314 } 315 if (o_pId) 316 { 317 sal_uInt16 const nId(pAction->GetId()); 318 *o_pId = static_cast<SwUndoId>(nId); 319 } 320 321 return true; 322 } 323 324 SwUndoComments_t UndoManager::GetUndoComments() const 325 { 326 OSL_ENSURE(!SdrUndoManager::IsInListAction(), 327 "GetUndoComments() called while in list action?"); 328 329 SwUndoComments_t ret; 330 sal_uInt16 const nUndoCount(SdrUndoManager::GetUndoActionCount(TopLevel)); 331 for (sal_uInt16 n = 0; n < nUndoCount; ++n) 332 { 333 ::rtl::OUString const comment( 334 SdrUndoManager::GetUndoActionComment(n, TopLevel)); 335 ret.push_back(comment); 336 } 337 338 return ret; 339 } 340 341 342 /**************** REDO ******************/ 343 344 bool UndoManager::GetFirstRedoInfo(::rtl::OUString *const o_pStr) const 345 { 346 if (!SdrUndoManager::GetRedoActionCount(CurrentLevel)) 347 { 348 return false; 349 } 350 351 if (o_pStr) 352 { 353 *o_pStr = SdrUndoManager::GetRedoActionComment(0, CurrentLevel); 354 } 355 356 return true; 357 } 358 359 360 SwUndoComments_t UndoManager::GetRedoComments() const 361 { 362 OSL_ENSURE(!SdrUndoManager::IsInListAction(), 363 "GetRedoComments() called while in list action?"); 364 365 SwUndoComments_t ret; 366 sal_uInt16 const nRedoCount(SdrUndoManager::GetRedoActionCount(TopLevel)); 367 for (sal_uInt16 n = 0; n < nRedoCount; ++n) 368 { 369 ::rtl::OUString const comment( 370 SdrUndoManager::GetRedoActionComment(n, TopLevel)); 371 ret.push_back(comment); 372 } 373 374 return ret; 375 } 376 377 /**************** REPEAT ******************/ 378 379 SwUndoId UndoManager::GetRepeatInfo(::rtl::OUString *const o_pStr) const 380 { 381 SwUndoId nRepeatId(UNDO_EMPTY); 382 GetLastUndoInfo(o_pStr, & nRepeatId); 383 if( REPEAT_START <= nRepeatId && REPEAT_END > nRepeatId ) 384 { 385 return nRepeatId; 386 } 387 if (o_pStr) // not repeatable -> clear comment 388 { 389 *o_pStr = String(); 390 } 391 return UNDO_EMPTY; 392 } 393 394 SwUndo * UndoManager::RemoveLastUndo() 395 { 396 if (SdrUndoManager::GetRedoActionCount(CurrentLevel) || 397 SdrUndoManager::GetRedoActionCount(TopLevel)) 398 { 399 OSL_ENSURE(false, "RemoveLastUndoAction(): there are Redo actions?"); 400 return 0; 401 } 402 if (!SdrUndoManager::GetUndoActionCount(CurrentLevel)) 403 { 404 OSL_ENSURE(false, "RemoveLastUndoAction(): no Undo actions"); 405 return 0; 406 } 407 SfxUndoAction *const pLastUndo(GetUndoAction(0)); 408 SdrUndoManager::RemoveLastUndoAction(); 409 return dynamic_cast<SwUndo *>(pLastUndo); 410 } 411 412 // svl::IUndoManager ///////////////////////////////////////////////////// 413 414 void UndoManager::EnableUndo(bool bEnable) 415 { 416 // SdrUndoManager does not have a counter anymore, but reverted to the old behavior of 417 // having a simple boolean flag for locking. So, simply forward. 418 SdrUndoManager::EnableUndo(bEnable); 419 } 420 421 void UndoManager::AddUndoAction(SfxUndoAction *pAction, sal_Bool bTryMerge) 422 { 423 SwUndo *const pUndo( dynamic_cast<SwUndo *>(pAction) ); 424 if (pUndo) 425 { 426 if (nsRedlineMode_t::REDLINE_NONE == pUndo->GetRedlineMode()) 427 { 428 pUndo->SetRedlineMode( m_rRedlineAccess.GetRedlineMode() ); 429 } 430 } 431 SdrUndoManager::AddUndoAction(pAction, bTryMerge); 432 // if the undo nodes array is too large, delete some actions 433 while (UNDO_ACTION_LIMIT < GetUndoNodes().Count()) 434 { 435 RemoveOldestUndoActions(1); 436 } 437 } 438 439 class CursorGuard 440 { 441 public: 442 CursorGuard(SwEditShell & rShell, bool const bSave) 443 : m_rShell(rShell) 444 , m_bSaveCursor(bSave) 445 { 446 if (m_bSaveCursor) 447 { 448 m_rShell.Push(); // prevent modification of current cursor 449 } 450 } 451 ~CursorGuard() 452 { 453 if (m_bSaveCursor) 454 { 455 m_rShell.Pop(); 456 } 457 } 458 private: 459 SwEditShell & m_rShell; 460 bool const m_bSaveCursor; 461 }; 462 463 bool UndoManager::impl_DoUndoRedo(UndoOrRedo_t const undoOrRedo) 464 { 465 SwDoc & rDoc(*GetUndoNodes().GetDoc()); 466 467 UnoActionContext c(& rDoc); // exception-safe StartAllAction/EndAllAction 468 469 SwEditShell *const pEditShell( rDoc.GetEditShell() ); 470 471 OSL_ENSURE(pEditShell, "sw::UndoManager needs a SwEditShell!"); 472 if (!pEditShell) 473 { 474 throw uno::RuntimeException(); 475 } 476 477 // in case the model has controllers locked, the Undo should not 478 // change the view cursors! 479 bool const bSaveCursors(pEditShell->CursorsLocked()); 480 CursorGuard(*pEditShell, bSaveCursors); 481 if (!bSaveCursors) 482 { 483 // (in case Undo was called via API) clear the cursors: 484 pEditShell->KillPams(); 485 pEditShell->SetMark(); 486 pEditShell->ClearMark(); 487 } 488 489 bool bRet(false); 490 491 ::sw::UndoRedoContext context(rDoc, *pEditShell); 492 493 // N.B. these may throw! 494 if (UNDO == undoOrRedo) 495 { 496 bRet = SdrUndoManager::UndoWithContext(context); 497 } 498 else 499 { 500 bRet = SdrUndoManager::RedoWithContext(context); 501 } 502 503 if (bRet) 504 { 505 // if we are at the "last save" position, the document is not modified 506 if (SdrUndoManager::HasTopUndoActionMark(m_UndoSaveMark)) 507 { 508 m_rState.ResetModified(); 509 } 510 else 511 { 512 m_rState.SetModified(); 513 } 514 } 515 516 pEditShell->HandleUndoRedoContext(context); 517 518 return bRet; 519 } 520 521 sal_Bool UndoManager::Undo() 522 { 523 if(isTextEditActive()) 524 { 525 return SdrUndoManager::Undo(); 526 } 527 else 528 { 529 return impl_DoUndoRedo(UNDO); 530 } 531 } 532 533 sal_Bool UndoManager::Redo() 534 { 535 if(isTextEditActive()) 536 { 537 return SdrUndoManager::Redo(); 538 } 539 else 540 { 541 return impl_DoUndoRedo(REDO); 542 } 543 } 544 545 /** N.B.: this does _not_ call SdrUndoManager::Repeat because it is not 546 possible to wrap a list action around it: 547 calling EnterListAction here will cause SdrUndoManager::Repeat 548 to repeat the list action! 549 */ 550 bool 551 UndoManager::Repeat(::sw::RepeatContext & rContext, 552 sal_uInt16 const nRepeatCount) 553 { 554 if (SdrUndoManager::IsInListAction()) 555 { 556 OSL_ENSURE(false, "repeat in open list action???"); 557 return false; 558 } 559 if (!SdrUndoManager::GetUndoActionCount(TopLevel)) 560 { 561 return false; 562 } 563 SfxUndoAction *const pRepeatAction(GetUndoAction(0)); 564 OSL_ASSERT(pRepeatAction); 565 if (!pRepeatAction || !pRepeatAction->CanRepeat(rContext)) 566 { 567 return false; 568 } 569 570 ::rtl::OUString const comment(pRepeatAction->GetComment()); 571 ::rtl::OUString const rcomment(pRepeatAction->GetRepeatComment(rContext)); 572 sal_uInt16 const nId(pRepeatAction->GetId()); 573 if (DoesUndo()) 574 { 575 EnterListAction(comment, rcomment, nId); 576 } 577 578 SwPaM *const pFirstCursor(& rContext.GetRepeatPaM()); 579 do { // iterate over ring 580 for (sal_uInt16 nRptCnt = nRepeatCount; nRptCnt > 0; --nRptCnt) 581 { 582 pRepeatAction->Repeat(rContext); 583 } 584 rContext.m_bDeleteRepeated = false; // reset for next PaM 585 rContext.m_pCurrentPaM = 586 static_cast<SwPaM*>(rContext.m_pCurrentPaM->GetNext()); 587 } while (pFirstCursor != & rContext.GetRepeatPaM()); 588 589 if (DoesUndo()) 590 { 591 LeaveListAction(); 592 } 593 return true; 594 } 595 596 } // namespace sw 597 598