3 Copyright (c) 2003-2007 Clarence Dang <dang@kde.org>
6 Redistribution and use in source and binary forms, with or without
7 modification, are permitted provided that the following conditions
10 1. Redistributions of source code must retain the above copyright
11 notice, this list of conditions and the following disclaimer.
12 2. Redistributions in binary form must reproduce the above copyright
13 notice, this list of conditions and the following disclaimer in the
14 documentation and/or other materials provided with the distribution.
16 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 #define DEBUG_KP_COMMAND_HISTORY 0
32 #include <kpCommandHistoryBase.h>
36 #include <qdatetime.h>
37 #include <qlinkedlist.h>
39 #include <kapplication.h>
45 #include <kstandardshortcut.h>
46 #include <kstandardaction.h>
47 #include <ktoolbarpopupaction.h>
48 #include <kactioncollection.h>
49 #include <kconfiggroup.h>
51 #include <kpCommand.h>
52 #include <kpCommandEnvironment.h>
54 #include <kpDocument.h>
55 #include <kpMainWindow.h>
59 //template <typename T>
60 static void ClearPointerList (QLinkedList
<kpCommand
*> *listPtr
)
65 qDeleteAll (listPtr
->begin (), listPtr
->end ());
71 struct kpCommandHistoryBasePrivate
76 kpCommandHistoryBase::kpCommandHistoryBase (bool doReadConfig
,
77 KActionCollection
*ac
)
78 : d (new kpCommandHistoryBasePrivate ())
80 m_actionUndo
= new KToolBarPopupAction (KIcon ("edit-undo"), undoActionText (), this);
81 ac
->addAction (KStandardAction::name (KStandardAction::Undo
), m_actionUndo
);
82 m_actionUndo
->setShortcuts (KStandardShortcut::shortcut (KStandardShortcut::Undo
));
83 connect (m_actionUndo
, SIGNAL(triggered(bool)), this, SLOT (undo ()));
85 m_actionRedo
= new KToolBarPopupAction (KIcon ("edit-redo"), redoActionText (), this);
86 ac
->addAction (KStandardAction::name (KStandardAction::Redo
), m_actionRedo
);
87 m_actionRedo
->setShortcuts (KStandardShortcut::shortcut (KStandardShortcut::Redo
));
88 connect (m_actionRedo
, SIGNAL(triggered(bool)), this, SLOT (redo ()));
91 m_actionUndo
->setEnabled (false);
92 m_actionRedo
->setEnabled (false);
95 connect (m_actionUndo
->menu (), SIGNAL (triggered (QAction
*)),
96 this, SLOT (undoUpToNumber (QAction
*)));
97 connect (m_actionRedo
->menu (), SIGNAL (triggered (QAction
*)),
98 this, SLOT (redoUpToNumber (QAction
*)));
102 m_undoMaxLimit
= 500;
103 m_undoMaxLimitSizeLimit
= 16 * 1048576;
106 m_documentRestoredPosition
= 0;
113 kpCommandHistoryBase::~kpCommandHistoryBase ()
115 ::ClearPointerList (&m_undoCommandList
);
116 ::ClearPointerList (&m_redoCommandList
);
123 int kpCommandHistoryBase::undoLimit () const
125 return undoMinLimit ();
129 void kpCommandHistoryBase::setUndoLimit (int limit
)
131 setUndoMinLimit (limit
);
136 int kpCommandHistoryBase::undoMinLimit () const
138 return m_undoMinLimit
;
142 void kpCommandHistoryBase::setUndoMinLimit (int limit
)
144 #if DEBUG_KP_COMMAND_HISTORY
145 kDebug () << "kpCommandHistoryBase::setUndoMinLimit("
150 if (limit
< 1 || limit
> 5000/*"ought to be enough for anybody"*/)
152 kError () << "kpCommandHistoryBase::setUndoMinLimit("
158 if (limit
== m_undoMinLimit
)
161 m_undoMinLimit
= limit
;
162 trimCommandListsUpdateActions ();
167 int kpCommandHistoryBase::undoMaxLimit () const
169 return m_undoMaxLimit
;
173 void kpCommandHistoryBase::setUndoMaxLimit (int limit
)
175 #if DEBUG_KP_COMMAND_HISTORY
176 kDebug () << "kpCommandHistoryBase::setUndoMaxLimit("
181 if (limit
< 1 || limit
> 5000/*"ought to be enough for anybody"*/)
183 kError () << "kpCommandHistoryBase::setUndoMaxLimit("
189 if (limit
== m_undoMaxLimit
)
192 m_undoMaxLimit
= limit
;
193 trimCommandListsUpdateActions ();
198 kpCommandSize::SizeType
kpCommandHistoryBase::undoMaxLimitSizeLimit () const
200 return m_undoMaxLimitSizeLimit
;
204 void kpCommandHistoryBase::setUndoMaxLimitSizeLimit (kpCommandSize::SizeType sizeLimit
)
206 #if DEBUG_KP_COMMAND_HISTORY
207 kDebug () << "kpCommandHistoryBase::setUndoMaxLimitSizeLimit("
213 sizeLimit
> (500 * 1048576)/*"ought to be enough for anybody"*/)
215 kError () << "kpCommandHistoryBase::setUndoMaxLimitSizeLimit("
221 if (sizeLimit
== m_undoMaxLimitSizeLimit
)
224 m_undoMaxLimitSizeLimit
= sizeLimit
;
225 trimCommandListsUpdateActions ();
230 void kpCommandHistoryBase::readConfig ()
232 #if DEBUG_KP_COMMAND_HISTORY
233 kDebug () << "kpCommandHistoryBase::readConfig()";
235 KConfigGroup
cfg (KGlobal::config (), kpSettingsGroupUndoRedo
);
237 setUndoMinLimit (cfg
.readEntry (kpSettingUndoMinLimit
, undoMinLimit ()));
238 setUndoMaxLimit (cfg
.readEntry (kpSettingUndoMaxLimit
, undoMaxLimit ()));
239 setUndoMaxLimitSizeLimit (
240 cfg
.readEntry
<kpCommandSize::SizeType
> (kpSettingUndoMaxLimitSizeLimit
,
241 undoMaxLimitSizeLimit ()));
243 trimCommandListsUpdateActions ();
247 void kpCommandHistoryBase::writeConfig ()
249 #if DEBUG_KP_COMMAND_HISTORY
250 kDebug () << "kpCommandHistoryBase::writeConfig()";
252 KConfigGroup
cfg (KGlobal::config (), kpSettingsGroupUndoRedo
);
254 cfg
.writeEntry (kpSettingUndoMinLimit
, undoMinLimit ());
255 cfg
.writeEntry (kpSettingUndoMaxLimit
, undoMaxLimit ());
256 cfg
.writeEntry
<kpCommandSize::SizeType
> (
257 kpSettingUndoMaxLimitSizeLimit
, undoMaxLimitSizeLimit ());
264 void kpCommandHistoryBase::addCommand (kpCommand
*command
, bool execute
)
266 #if DEBUG_KP_COMMAND_HISTORY
267 kDebug () << "kpCommandHistoryBase::addCommand("
269 << ",execute=" << execute
<< ")"
276 m_undoCommandList
.push_front (command
);
277 ::ClearPointerList (&m_redoCommandList
);
279 #if DEBUG_KP_COMMAND_HISTORY
280 kDebug () << "\tdocumentRestoredPosition=" << m_documentRestoredPosition
283 if (m_documentRestoredPosition
!= INT_MAX
)
285 if (m_documentRestoredPosition
> 0)
286 m_documentRestoredPosition
= INT_MAX
;
288 m_documentRestoredPosition
--;
289 #if DEBUG_KP_COMMAND_HISTORY
290 kDebug () << "\t\tdocumentRestoredPosition=" << m_documentRestoredPosition
295 trimCommandListsUpdateActions ();
299 void kpCommandHistoryBase::clear ()
301 #if DEBUG_KP_COMMAND_HISTORY
302 kDebug () << "kpCommandHistoryBase::clear()";
305 ::ClearPointerList (&m_undoCommandList
);
306 ::ClearPointerList (&m_redoCommandList
);
308 m_documentRestoredPosition
= 0;
315 void kpCommandHistoryBase::undoInternal ()
317 #if DEBUG_KP_COMMAND_HISTORY
318 kDebug () << "kpCommandHistoryBase::undoInternal()";
321 kpCommand
*undoCommand
= nextUndoCommand ();
325 undoCommand
->unexecute ();
328 m_undoCommandList
.erase (m_undoCommandList
.begin ());
329 m_redoCommandList
.push_front (undoCommand
);
332 #if DEBUG_KP_COMMAND_HISTORY
333 kDebug () << "\tdocumentRestoredPosition=" << m_documentRestoredPosition
336 if (m_documentRestoredPosition
!= INT_MAX
)
338 m_documentRestoredPosition
++;
339 if (m_documentRestoredPosition
== 0)
340 emit
documentRestored ();
341 #if DEBUG_KP_COMMAND_HISTORY
342 kDebug () << "\t\tdocumentRestoredPosition=" << m_documentRestoredPosition
349 void kpCommandHistoryBase::redoInternal ()
351 #if DEBUG_KP_COMMAND_HISTORY
352 kDebug () << "kpCommandHistoryBase::redoInternal()";
355 kpCommand
*redoCommand
= nextRedoCommand ();
359 redoCommand
->execute ();
362 m_redoCommandList
.erase (m_redoCommandList
.begin ());
363 m_undoCommandList
.push_front (redoCommand
);
366 #if DEBUG_KP_COMMAND_HISTORY
367 kDebug () << "\tdocumentRestoredPosition=" << m_documentRestoredPosition
370 if (m_documentRestoredPosition
!= INT_MAX
)
372 m_documentRestoredPosition
--;
373 if (m_documentRestoredPosition
== 0)
374 emit
documentRestored ();
375 #if DEBUG_KP_COMMAND_HISTORY
376 kDebug () << "\t\tdocumentRestoredPosition=" << m_documentRestoredPosition
383 // public slot virtual
384 void kpCommandHistoryBase::undo ()
386 #if DEBUG_KP_COMMAND_HISTORY
387 kDebug () << "kpCommandHistoryBase::undo()";
391 trimCommandListsUpdateActions ();
394 // public slot virtual
395 void kpCommandHistoryBase::redo ()
397 #if DEBUG_KP_COMMAND_HISTORY
398 kDebug () << "kpCommandHistoryBase::redo()";
402 trimCommandListsUpdateActions ();
406 // public slot virtual
407 void kpCommandHistoryBase::undoUpToNumber (QAction
*which
)
409 #if DEBUG_KP_COMMAND_HISTORY
410 kDebug () << "kpCommandHistoryBase::undoUpToNumber(" << which
<< ")";
414 i
<= which
->data().toInt() && !m_undoCommandList
.isEmpty ();
420 trimCommandListsUpdateActions ();
423 // public slot virtual
424 void kpCommandHistoryBase::redoUpToNumber (QAction
*which
)
426 #if DEBUG_KP_COMMAND_HISTORY
427 kDebug () << "kpCommandHistoryBase::redoUpToNumber(" << which
<< ")";
431 i
<= which
->data().toInt() && !m_redoCommandList
.isEmpty ();
437 trimCommandListsUpdateActions ();
442 QString
kpCommandHistoryBase::undoActionText () const
444 kpCommand
*undoCommand
= nextUndoCommand ();
447 return i18n ("&Undo: %1", undoCommand
->name ());
449 return i18n ("&Undo");
453 QString
kpCommandHistoryBase::redoActionText () const
455 kpCommand
*redoCommand
= nextRedoCommand ();
458 return i18n ("&Redo: %1", redoCommand
->name ());
460 return i18n ("&Redo");
465 QString
kpCommandHistoryBase::undoActionToolTip () const
467 kpCommand
*undoCommand
= nextUndoCommand ();
470 return i18n ("Undo: %1", undoCommand
->name ());
472 return i18n ("Undo");
476 QString
kpCommandHistoryBase::redoActionToolTip () const
478 kpCommand
*redoCommand
= nextRedoCommand ();
481 return i18n ("Redo: %1", redoCommand
->name ());
483 return i18n ("Redo");
488 void kpCommandHistoryBase::trimCommandListsUpdateActions ()
490 #if DEBUG_KP_COMMAND_HISTORY
491 kDebug () << "kpCommandHistoryBase::trimCommandListsUpdateActions()";
499 void kpCommandHistoryBase::trimCommandList (QLinkedList
<kpCommand
*> *commandList
)
501 #if DEBUG_KP_COMMAND_HISTORY
502 kDebug () << "kpCommandHistoryBase::trimCommandList()";
503 QTime timer
; timer
.start ();
508 kError () << "kpCommandHistoryBase::trimCommandList() passed 0 commandList"
514 #if DEBUG_KP_COMMAND_HISTORY
515 kDebug () << "\tsize=" << commandList
->size ()
516 << " undoMinLimit=" << m_undoMinLimit
517 << " undoMaxLimit=" << m_undoMaxLimit
518 << " undoMaxLimitSizeLimit=" << m_undoMaxLimitSizeLimit
521 if ((int) commandList
->size () <= m_undoMinLimit
)
523 #if DEBUG_KP_COMMAND_HISTORY
524 kDebug () << "\t\tsize under undoMinLimit - done";
530 #if DEBUG_KP_COMMAND_HISTORY && 0
531 kDebug () << "\tsize over undoMinLimit - iterating thru cmds:";
534 QLinkedList
<kpCommand
*>::iterator it
= commandList
->begin ();
537 kpCommandSize::SizeType sizeSoFar
= 0;
539 while (it
!= commandList
->end ())
541 bool advanceIt
= true;
543 if (sizeSoFar
<= m_undoMaxLimitSizeLimit
)
545 sizeSoFar
+= (*it
)->size ();
548 #if DEBUG_KP_COMMAND_HISTORY && 0
549 kDebug () << "\t\t" << upto
<< ":"
550 << " name='" << (*it
)->name ()
551 << "' size=" << (*it
)->size ()
552 << " sizeSoFar=" << sizeSoFar
556 if (upto
>= m_undoMinLimit
)
558 if (upto
>= m_undoMaxLimit
||
559 sizeSoFar
> m_undoMaxLimitSizeLimit
)
561 #if DEBUG_KP_COMMAND_HISTORY && 0
562 kDebug () << "\t\t\tkill";
565 it
= m_undoCommandList
.erase (it
);
575 #if DEBUG_KP_COMMAND_HISTORY
576 kDebug () << "\ttook " << timer
.elapsed () << "ms";
581 void kpCommandHistoryBase::trimCommandLists ()
583 #if DEBUG_KP_COMMAND_HISTORY
584 kDebug () << "kpCommandHistoryBase::trimCommandLists()";
587 trimCommandList (&m_undoCommandList
);
588 trimCommandList (&m_redoCommandList
);
590 #if DEBUG_KP_COMMAND_HISTORY
591 kDebug () << "\tdocumentRestoredPosition=" << m_documentRestoredPosition
594 if (m_documentRestoredPosition
!= INT_MAX
)
596 #if DEBUG_KP_COMMAND_HISTORY
597 kDebug () << "\t\tundoCmdList.size=" << m_undoCommandList
.size ()
598 << " redoCmdList.size=" << m_redoCommandList
.size ()
601 if (m_documentRestoredPosition
> (int) m_redoCommandList
.size () ||
602 -m_documentRestoredPosition
> (int) m_undoCommandList
.size ())
604 #if DEBUG_KP_COMMAND_HISTORY
605 kDebug () << "\t\t\tinvalidate documentRestoredPosition";
607 m_documentRestoredPosition
= INT_MAX
;
613 static void populatePopupMenu (KMenu
*popupMenu
,
614 const QString
&undoOrRedo
,
615 const QLinkedList
<kpCommand
*> &commandList
)
622 QLinkedList
<kpCommand
*>::const_iterator it
= commandList
.begin ();
624 while (i
< 10 && it
!= commandList
.end ())
626 QAction
*action
= new QAction(i18n ("%1: %2", undoOrRedo
, (*it
)->name ()), popupMenu
);
628 popupMenu
->addAction (action
);
632 if (it
!= commandList
.end ())
634 // TODO: maybe have a scrollview show all the items instead, like KOffice in KDE 3
635 // LOCOMPAT: should be centered text.
636 popupMenu
->addTitle (i18np ("%1 more item", "%1 more items",
637 commandList
.size () - i
));
643 void kpCommandHistoryBase::updateActions ()
645 #if DEBUG_KP_COMMAND_HISTORY
646 kDebug () << "kpCommandHistoryBase::updateActions()";
649 m_actionUndo
->setEnabled ((bool) nextUndoCommand ());
650 // Don't want to keep changing toolbar text.
651 // TODO: As a bad side-effect, the menu doesn't have "Undo: <action>"
652 // anymore. In any case, the KDE4 KToolBarPopupAction
653 // sucks in menus as it forces the clicking of a submenu. IMO,
654 // there should be no submenu in the menu.
655 //m_actionUndo->setText (undoActionText ());
657 // But in icon mode, a tooltip with context is useful.
658 m_actionUndo
->setToolTip (undoActionToolTip ());
659 #if DEBUG_KP_COMMAND_HISTORY
660 QTime timer
; timer
.start ();
662 populatePopupMenu (qobject_cast
<KMenu
*> (m_actionUndo
->menu ()),
665 #if DEBUG_KP_COMMAND_HISTORY
666 kDebug () << "\tpopuplatePopupMenu undo=" << timer
.elapsed ()
670 m_actionRedo
->setEnabled ((bool) nextRedoCommand ());
671 // Don't want to keep changing toolbar text.
672 // TODO: As a bad side-effect, the menu doesn't have "Undo: <action>"
673 // anymore. In any case, the KDE4 KToolBarPopupAction
674 // sucks in menus as it forces the clicking of a submenu. IMO,
675 // there should be no submenu in the menu.
676 //m_actionRedo->setText (redoActionText ());
678 // But in icon mode, a tooltip with context is useful.
679 m_actionRedo
->setToolTip (redoActionToolTip ());
680 #if DEBUG_KP_COMMAND_HISTORY
683 populatePopupMenu (qobject_cast
<KMenu
*> (m_actionRedo
->menu ()),
686 #if DEBUG_KP_COMMAND_HISTORY
687 kDebug () << "\tpopuplatePopupMenu redo=" << timer
.elapsed ()
694 kpCommand
*kpCommandHistoryBase::nextUndoCommand () const
696 if (m_undoCommandList
.isEmpty ())
699 return m_undoCommandList
.first ();
703 kpCommand
*kpCommandHistoryBase::nextRedoCommand () const
705 if (m_redoCommandList
.isEmpty ())
708 return m_redoCommandList
.first ();
713 void kpCommandHistoryBase::setNextUndoCommand (kpCommand
*command
)
715 #if DEBUG_KP_COMMAND_HISTORY
716 kDebug () << "kpCommandHistoryBase::setNextUndoCommand("
722 if (m_undoCommandList
.isEmpty ())
726 delete *m_undoCommandList
.begin ();
727 *m_undoCommandList
.begin () = command
;
730 trimCommandListsUpdateActions ();
734 // public slot virtual
735 void kpCommandHistoryBase::documentSaved ()
737 #if DEBUG_KP_COMMAND_HISTORY
738 kDebug () << "kpCommandHistoryBase::documentSaved()";
741 m_documentRestoredPosition
= 0;
745 #include <kpCommandHistoryBase.moc>