lib

KoCommandHistory.cpp

00001 /* This file is part of the KDE project
00002    Copyright (C) 2000 Werner Trobin <trobin@kde.org>
00003    Copyright (C) 2000 David Faure <faure@kde.org>
00004 
00005    This library is free software; you can redistribute it and/or
00006    modify it under the terms of the GNU Library General Public
00007    License as published by the Free Software Foundation; either
00008    version 2 of the License, or (at your option) any later version.
00009 
00010    This library is distributed in the hope that it will be useful,
00011    but WITHOUT ANY WARRANTY; without even the implied warranty of
00012    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00013    Library General Public License for more details.
00014 
00015    You should have received a copy of the GNU Library General Public License
00016    along with this library; see the file COPYING.LIB.  If not, write to
00017    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00018  * Boston, MA 02110-1301, USA.
00019 */
00020 
00021 #include <qlabel.h>
00022 #include <qlayout.h>
00023 #include <qlistbox.h>
00024 
00025 #include <kaction.h>
00026 #include <kcommand.h>
00027 #include <kdebug.h>
00028 #include <klocale.h>
00029 #include <kpopupmenu.h>
00030 #include <kshortcutlist.h>
00031 #include <kstdaccel.h>
00032 #include <kstdaction.h>
00033 
00034 #include "KoCommandHistory.h"
00035 
00036 KoListBox::KoListBox( QWidget *parent , const char *name , WFlags f)
00037     : QListBox( parent, name, f)
00038 {
00039     setVScrollBarMode( AlwaysOn );
00040 }
00041 
00042 void KoListBox::contentsMouseMoveEvent ( QMouseEvent * e)
00043 {
00044     QListBoxItem *item_p = itemAt( contentsToViewport(e->pos()));
00045     if ( item_p )
00046     {
00047         int itemIndex = index( item_p );
00048         for ( int i = 0; i<=itemIndex;i++)
00049             setSelected ( i, true );
00050         for ( int i = itemIndex+1; i<(int)count();i++)
00051             setSelected ( i, false );
00052         emit changeNumberOfSelectedItem( itemIndex);
00053     }
00054 }
00055 
00056 QSize KoListBox::sizeHint() const
00057 {
00058   return QSize(QMIN(maxItemWidth() + verticalScrollBar()->width() + 4, 400),
00059                QMIN(count() * itemHeight() + horizontalScrollBar()->height() + 4,300));
00060 }
00061 
00062 class KoCommandHistory::KoCommandHistoryPrivate {
00063 public:
00064     KoCommandHistoryPrivate() {
00065         m_savedAt=-1;
00066         m_present=0;
00067     }
00068     ~KoCommandHistoryPrivate() {}
00069     int m_savedAt;
00070     KCommand *m_present;
00071     KoListBox *m_undoListBox;
00072     KoListBox *m_redoListBox;
00073     QLabel *m_undoLabel;
00074     QLabel *m_redoLabel;
00075 };
00076 
00078 
00079 KoCommandHistory::KoCommandHistory() :
00080     m_undo(0), m_redo(0), m_undoLimit(50), m_redoLimit(30), m_first(false)
00081 {
00082     d=new KoCommandHistoryPrivate();
00083     m_commands.setAutoDelete(true);
00084     clear();
00085 }
00086 
00087 KoCommandHistory::KoCommandHistory(KActionCollection * actionCollection, bool withMenus) :
00088     m_undoLimit(50), m_redoLimit(30), m_first(false)
00089 {
00090     d=new KoCommandHistoryPrivate();
00091     if (withMenus)
00092     {
00093         KToolBarPopupAction * undo = new KToolBarPopupAction( i18n("&Undo"), "undo",
00094                                                               KStdAccel::undo(), this, SLOT( undo() ),
00095                                                               actionCollection, /*KStdAction::stdName( KStdAction::Undo )*/"koffice_undo" );
00096         connect( undo->popupMenu(), SIGNAL( aboutToShow() ), this, SLOT( slotUndoAboutToShow() ) );
00097         connect( undo->popupMenu(), SIGNAL( activated( int ) ), this, SLOT( slotUndoActivated( int ) ) );
00098         m_undo = undo;
00099         m_undoPopup = undo->popupMenu();
00100         d->m_undoListBox = new KoListBox( m_undoPopup );
00101         d->m_undoListBox->setSelectionMode( QListBox::Multi );
00102 
00103         m_undoPopup->insertItem(d->m_undoListBox);
00104         d->m_undoLabel = new QLabel( m_undoPopup);
00105         m_undoPopup->insertItem(d->m_undoLabel);
00106 
00107         connect( d->m_undoListBox, SIGNAL( selected( int ) ), this, SLOT( slotUndoActivated( int ) ) );
00108         connect( d->m_undoListBox, SIGNAL(clicked ( QListBoxItem *)), this, SLOT( slotUndoActivated( QListBoxItem * ) ) );
00109 
00110         connect( d->m_undoListBox, SIGNAL( changeNumberOfSelectedItem( int )), this, SLOT( slotChangeUndoNumberOfSelectedItem( int )));
00111 
00112         KToolBarPopupAction * redo = new KToolBarPopupAction( i18n("&Redo"), "redo",
00113                                                               KStdAccel::redo(), this, SLOT( redo() ),
00114                                                               actionCollection, /*KStdAction::stdName( KStdAction::Redo )*/
00115                                                               "koffice_redo");
00116         connect( redo->popupMenu(), SIGNAL( aboutToShow() ), this, SLOT( slotRedoAboutToShow() ) );
00117         connect( redo->popupMenu(), SIGNAL( activated( int ) ), this, SLOT( slotRedoActivated( int ) ) );
00118         m_redo = redo;
00119         m_redoPopup = redo->popupMenu();
00120         d->m_redoListBox = new KoListBox( m_redoPopup );
00121         d->m_redoListBox->setSelectionMode( QListBox::Multi );
00122         m_redoPopup->insertItem(d->m_redoListBox);
00123 
00124         d->m_redoLabel = new QLabel( m_redoPopup);
00125         m_redoPopup->insertItem(d->m_redoLabel);
00126 
00127 
00128         connect( d->m_redoListBox, SIGNAL( selected( int ) ), this, SLOT( slotRedoActivated( int ) ) );
00129         connect( d->m_redoListBox, SIGNAL(clicked ( QListBoxItem *)), this, SLOT( slotRedoActivated( QListBoxItem * ) ) );
00130         connect( d->m_redoListBox, SIGNAL( changeNumberOfSelectedItem( int )), this, SLOT( slotChangeRedoNumberOfSelectedItem( int )));
00131 
00132     }
00133     else
00134     {
00135         m_undo = KStdAction::undo( this, SLOT( undo() ), actionCollection, "koffice_undo");
00136         m_redo = KStdAction::redo( this, SLOT( redo() ), actionCollection, "koffice_redo");
00137         m_undoPopup = 0L;
00138         m_redoPopup = 0L;
00139         d->m_redoListBox = 0L;
00140         d->m_undoListBox = 0L;
00141         d->m_redoLabel = 0L;
00142         d->m_undoLabel = 0L;
00143     }
00144     m_commands.setAutoDelete(true);
00145     clear();
00146 }
00147 
00148 KoCommandHistory::~KoCommandHistory() {
00149     delete d;
00150 }
00151 
00152 KCommand * KoCommandHistory::presentCommand ( )
00153 {   return d->m_present;
00154 }
00155 
00156 
00157 void KoCommandHistory::clear() {
00158     if (m_undo != 0) {
00159         m_undo->setEnabled(false);
00160         m_undo->setText(i18n("&Undo"));
00161     }
00162     if (m_redo != 0) {
00163         m_redo->setEnabled(false);
00164         m_redo->setText(i18n("&Redo"));
00165     }
00166     d->m_present = 0L;
00167     d->m_savedAt=-42;
00168 }
00169 
00170 void KoCommandHistory::addCommand(KCommand *command, bool execute) {
00171 
00172     if(command==0L)
00173         return;
00174 
00175     int index;
00176     if(d->m_present!=0L && (index=m_commands.findRef(d->m_present))!=-1) {
00177         if (m_first)
00178             --index;
00179         m_commands.insert(index+1, command);
00180         // truncate history
00181         unsigned int count=m_commands.count();
00182         for(unsigned int i=index+2; i<count; ++i)
00183             m_commands.removeLast();
00184         // check whether we still can reach savedAt
00185         if(index<d->m_savedAt)
00186             d->m_savedAt=-1;
00187         d->m_present=command;
00188         m_first=false;
00189         if (m_undo != 0) {
00190             m_undo->setEnabled(true);
00191             m_undo->setText(i18n("&Undo: %1").arg(d->m_present->name()));
00192         }
00193         if((m_redo != 0) && m_redo->isEnabled()) {
00194             m_redo->setEnabled(false);
00195             m_redo->setText(i18n("&Redo"));
00196         }
00197         clipCommands();
00198     }
00199     else { // either this is the first time we add a Command or something has gone wrong
00200         kdDebug(230) << "KoCommandHistory: Initializing the Command History" << endl;
00201         m_commands.clear();
00202         m_commands.append(command);
00203         d->m_present=command;
00204         if (m_undo != 0) {
00205             m_undo->setEnabled(true);
00206             m_undo->setText(i18n("&Undo: %1").arg(d->m_present->name()));
00207         }
00208         if (m_redo != 0) {
00209             m_redo->setEnabled(false);
00210             m_redo->setText(i18n("&Redo"));
00211         }
00212         m_first=false;    // Michael B: yes, that *is* correct :-)
00213     }
00214     if ( execute )
00215     {
00216         command->execute();
00217         emit commandExecuted();
00218         emit commandExecuted(command);
00219     }
00220 }
00221 
00222 void KoCommandHistory::undo() {
00223 
00224     if (m_first || (d->m_present == 0L))
00225         return;
00226 
00227     d->m_present->unexecute();
00228     KCommand *commandUndone = d->m_present;
00229 
00230     if (m_redo != 0) {
00231         m_redo->setEnabled(true);
00232         m_redo->setText(i18n("&Redo: %1").arg(d->m_present->name()));
00233     }
00234     int index;
00235     if((index=m_commands.findRef(d->m_present))!=-1 && m_commands.prev()!=0) {
00236         d->m_present=m_commands.current();
00237         emit commandExecuted();
00238         emit commandExecuted(commandUndone);
00239         if (m_undo != 0) {
00240             m_undo->setEnabled(true);
00241             m_undo->setText(i18n("&Undo: %1").arg(d->m_present->name()));
00242         }
00243         --index;
00244         if(index==d->m_savedAt)
00245             emit documentRestored();
00246     }
00247     else {
00248         emit commandExecuted();
00249         emit commandExecuted(commandUndone);
00250         if (m_undo != 0) {
00251             m_undo->setEnabled(false);
00252             m_undo->setText(i18n("&Undo"));
00253         }
00254         if(d->m_savedAt==-42)
00255             emit documentRestored();
00256         m_first=true;
00257     }
00258     clipCommands(); // only needed here and in addCommand, NOT in redo
00259 }
00260 
00261 void KoCommandHistory::redo() {
00262 
00263     int index;
00264     if(m_first) {
00265         d->m_present->execute();
00266         emit commandExecuted();
00267         emit commandExecuted(d->m_present);
00268         m_first=false;
00269         m_commands.first();
00270         if(d->m_savedAt==0)
00271             emit documentRestored();
00272     }
00273     else if((index=m_commands.findRef(d->m_present))!=-1 && m_commands.next()!=0) {
00274         d->m_present=m_commands.current();
00275         d->m_present->execute();
00276         emit commandExecuted();
00277         emit commandExecuted(d->m_present);
00278         ++index;
00279         if(index==d->m_savedAt)
00280             emit documentRestored();
00281     }
00282 
00283     if (m_undo != 0) {
00284         m_undo->setEnabled(true);
00285         m_undo->setText(i18n("&Undo: %1").arg(d->m_present->name()));
00286     }
00287 
00288     if(m_commands.next()!=0) {
00289         if (m_redo != 0) {
00290             m_redo->setEnabled(true);
00291             m_redo->setText(i18n("&Redo: %1").arg(m_commands.current()->name()));
00292         }
00293     }
00294     else {
00295         if((m_redo != 0) && m_redo->isEnabled()) {
00296             m_redo->setEnabled(false);
00297             m_redo->setText(i18n("&Redo"));
00298         }
00299     }
00300 }
00301 
00302 void KoCommandHistory::documentSaved() {
00303     if(d->m_present!=0 && !m_first)
00304         d->m_savedAt=m_commands.findRef(d->m_present);
00305     else if(d->m_present==0 && !m_first)
00306         d->m_savedAt=-42;  // this value signals that the document has
00307                         // been saved with an empty history.
00308     else if(m_first)
00309         d->m_savedAt=-42;
00310 }
00311 
00312 void KoCommandHistory::setUndoLimit(int limit) {
00313 
00314     if(limit>0 && limit!=m_undoLimit) {
00315         m_undoLimit=limit;
00316         clipCommands();
00317     }
00318 }
00319 
00320 void KoCommandHistory::setRedoLimit(int limit) {
00321 
00322     if(limit>0 && limit!=m_redoLimit) {
00323         m_redoLimit=limit;
00324         clipCommands();
00325     }
00326 }
00327 
00328 void KoCommandHistory::clipCommands() {
00329 
00330     int count=m_commands.count();
00331     if(count<=m_undoLimit && count<=m_redoLimit)
00332         return;
00333 
00334     int index=m_commands.findRef(d->m_present);
00335     if(index>=m_undoLimit) {
00336         for(int i=0; i<=(index-m_undoLimit); ++i) {
00337             m_commands.removeFirst();
00338             --d->m_savedAt;
00339             if(d->m_savedAt==-1)
00340                 d->m_savedAt=-42;
00341         }
00342         index=m_commands.findRef(d->m_present); // calculate the new
00343         count=m_commands.count();            // values (for the redo-branch :)
00344         // make it easier for us... d->m_savedAt==-1 -> invalid
00345         if(d->m_savedAt!=-42 && d->m_savedAt<-1)
00346             d->m_savedAt=-1;
00347     }
00348     // adjust the index if it's the first command
00349     if(m_first)
00350         index=-1;
00351     if((index+m_redoLimit+1)<count) {
00352         if(d->m_savedAt>(index+m_redoLimit))
00353             d->m_savedAt=-1;
00354         for(int i=0; i<(count-(index+m_redoLimit+1)); ++i)
00355             m_commands.removeLast();
00356     }
00357 }
00358 
00359 void KoCommandHistory::slotUndoAboutToShow()
00360 {
00361     d->m_undoListBox->clear();
00362     slotChangeUndoNumberOfSelectedItem( -1 );
00363     int i = 0;
00364     QStringList lst;
00365     if (m_commands.findRef(d->m_present)!=-1)
00366         while ( m_commands.current() && i<10 ) // TODO make number of items configurable ?
00367         {
00368             lst.append(i18n("Undo: %1").arg(m_commands.current()->name()));
00369             m_commands.prev();
00370         }
00371     d->m_undoListBox->insertStringList( lst );
00372 }
00373 
00374 void KoCommandHistory::slotUndoActivated( int pos )
00375 {
00376     kdDebug(230) << "KoCommandHistory::slotUndoActivated " << pos << endl;
00377     for ( int i = 0 ; i < pos+1; ++i )
00378         undo();
00379     m_undoPopup->hide();
00380 }
00381 
00382 void KoCommandHistory::slotUndoActivated( QListBoxItem * item)
00383 {
00384     if ( item )
00385         slotUndoActivated( item->listBox()->index(item));
00386 }
00387 
00388 void KoCommandHistory::slotRedoActivated( QListBoxItem * item)
00389 {
00390     if ( item )
00391         slotRedoActivated( item->listBox()->index(item));
00392 }
00393 
00394 void KoCommandHistory::slotChangeUndoNumberOfSelectedItem( int pos)
00395 {
00396     d->m_undoLabel->setText( i18n("Undo %n action", "Undo %n actions", pos+1) );
00397 }
00398 
00399 void KoCommandHistory::slotChangeRedoNumberOfSelectedItem( int pos)
00400 {
00401     d->m_redoLabel->setText( i18n("Redo %n action", "Redo %n actions", pos+1) );
00402 }
00403 
00404 
00405 void KoCommandHistory::slotRedoAboutToShow()
00406 {
00407     d->m_redoListBox->clear();
00408     slotChangeRedoNumberOfSelectedItem( -1 );
00409     QStringList lst;
00410     int i = 0;
00411     if (m_first)
00412     {
00413         d->m_present = m_commands.first();
00414         lst.append(i18n("Redo: %1").arg(d->m_present->name()));
00415     }
00416     if (m_commands.findRef(d->m_present)!=-1 && m_commands.next())
00417         while ( m_commands.current() && i<10 ) // TODO make number of items configurable ?
00418         {
00419             lst.append(i18n("Redo: %1").arg(m_commands.current()->name()));
00420             m_commands.next();
00421         }
00422     d->m_redoListBox->insertStringList( lst );
00423 
00424 }
00425 
00426 void KoCommandHistory::slotRedoActivated( int pos )
00427 {
00428     kdDebug(230) << "KoCommandHistory::slotRedoActivated " << pos << endl;
00429     for ( int i = 0 ; i < pos+1; ++i )
00430         redo();
00431     m_redoPopup->hide();
00432 }
00433 
00434 void KoCommandHistory::updateActions()
00435 {
00436     if ( m_undo && m_redo )
00437     {
00438         m_undo->setEnabled( !m_first && ( d->m_present != 0L ) );
00439         m_redo->setEnabled(m_first || (m_commands.findRef(d->m_present)!=-1 && m_commands.next()!=0));
00440     }
00441 }
00442 
00443 void KoCommandHistory::virtual_hook( int, void* )
00444 { /*BASE::virtual_hook( id, data );*/ }
00445 
00446 #include "KoCommandHistory.moc"
KDE Home | KDE Accessibility Home | Description of Access Keys