/*************************************************************************** * Copyright (C) 2006 by Peter Penz * * peter.penz@gmx.at * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "undomanager.h" #include #include #include #include #include "dolphin.h" #include "dolphinstatusbar.h" #include "progressindicator.h" DolphinCommand::DolphinCommand() : m_type(Copy), m_macroIndex(-1) { // Implementation note: DolphinCommands are stored in a QValueList, whereas // QValueList requires a default constructor of the added class. // Instead of expressing this implementation detail to the interface by adding a // Type::Undefined just Type::Copy is used to assure that all members have // a defined state. } DolphinCommand::DolphinCommand(Type type, const KURL::List& source, const KURL& dest) : m_type(type), m_macroIndex(-1), m_source(source), m_dest(dest) { } DolphinCommand::~DolphinCommand() { } DolphinCommand& DolphinCommand::operator = (const DolphinCommand& command) { m_type = command.m_type; m_source = command.m_source; m_dest = command.m_dest; return *this; } UndoManager& UndoManager::instance() { static UndoManager* instance = 0; if (instance == 0) { instance = new UndoManager(); } return *instance; } void UndoManager::addCommand(const DolphinCommand& command) { ++m_historyIndex; if (m_recordMacro) { DolphinCommand macroCommand = command; macroCommand.m_macroIndex = m_macroCounter; m_history.insert(m_history.at(m_historyIndex), macroCommand); } else { m_history.insert(m_history.at(m_historyIndex), command); } emit undoAvailable(true); emit undoTextChanged(i18n("Undo: %1").arg(commandText(command))); // prevent an endless growing of the Undo history if (m_historyIndex > 10000) { m_history.erase(m_history.begin()); --m_historyIndex; } } void UndoManager::beginMacro() { assert(!m_recordMacro); m_recordMacro = true; ++m_macroCounter; } void UndoManager::endMacro() { assert(m_recordMacro); m_recordMacro = false; } void UndoManager::undo() { if (m_recordMacro) { endMacro(); } if (m_historyIndex < 0) { return; } int progressCount = 0; int macroCount = 1; calcStepsCount(macroCount, progressCount); m_progressIndicator = new ProgressIndicator(i18n("Executing undo operation..."), i18n("Executed undo operation."), progressCount); for (int i = 0; i < macroCount; ++i) { const DolphinCommand command = m_history[m_historyIndex]; --m_historyIndex; if (m_historyIndex < 0) { emit undoAvailable(false); emit undoTextChanged(i18n("Undo")); } else { emit undoTextChanged(i18n("Undo: %1").arg(commandText(m_history[m_historyIndex]))); } if (m_historyIndex < static_cast(m_history.count()) - 1) { emit redoAvailable(true); emit redoTextChanged(i18n("Redo: %1").arg(commandText(command))); } else { emit redoAvailable(false); emit redoTextChanged(i18n("Redo")); } KURL::List sourceURLs = command.source(); KURL::List::Iterator it = sourceURLs.begin(); const KURL::List::Iterator end = sourceURLs.end(); const QString destURL(command.destination().prettyURL(+1)); KIO::Job* job = 0; switch (command.type()) { case DolphinCommand::Link: case DolphinCommand::Copy: { KURL::List list; while (it != end) { const KURL deleteURL(destURL + (*it).filename()); list.append(deleteURL); ++it; } job = KIO::del(list, false, false); break; } case DolphinCommand::Move: { KURL::List list; const KURL newDestURL((*it).directory()); while (it != end) { const KURL newSourceURL(destURL + (*it).filename()); list.append(newSourceURL); ++it; } job = KIO::move(list, newDestURL, false); break; } case DolphinCommand::Rename: { assert(sourceURLs.count() == 1); KIO::NetAccess::move(command.destination(), (*it)); break; } case DolphinCommand::Trash: { while (it != end) { // TODO: use KIO::special for accessing the trash protocol. See // also Dolphin::slotJobResult() for further details. const QString originalFileName((*it).filename().section('-', 1)); KURL newDestURL(destURL + originalFileName); KIO::NetAccess::move(*it, newDestURL); ++it; m_progressIndicator->execOperation(); } break; } case DolphinCommand::CreateFolder: case DolphinCommand::CreateFile: { KIO::NetAccess::del(command.destination(), &Dolphin::mainWin()); break; } } if (job != 0) { // Execute the jobs in a synchronous manner and forward the progress // information to the Dolphin statusbar. connect(job, SIGNAL(percent(KIO::Job*, unsigned long)), this, SLOT(slotPercent(KIO::Job*, unsigned long))); KIO::NetAccess::synchronousRun(job, &Dolphin::mainWin()); } m_progressIndicator->execOperation(); } delete m_progressIndicator; m_progressIndicator = 0; } void UndoManager::redo() { if (m_recordMacro) { endMacro(); } const int maxHistoryIndex = m_history.count() - 1; if (m_historyIndex >= maxHistoryIndex) { return; } ++m_historyIndex; int progressCount = 0; int macroCount = 1; calcStepsCount(macroCount, progressCount); m_progressIndicator = new ProgressIndicator(i18n("Executing redo operation..."), i18n("Executed redo operation."), progressCount); for (int i = 0; i < macroCount; ++i) { const DolphinCommand command = m_history[m_historyIndex]; if (m_historyIndex >= maxHistoryIndex) { emit redoAvailable(false); emit redoTextChanged(i18n("Redo")); } else { emit redoTextChanged(i18n("Redo: %1").arg(commandText(m_history[m_historyIndex + 1]))); } emit undoAvailable(true); emit undoTextChanged(i18n("Undo: %1").arg(commandText(command))); Dolphin& dolphin = Dolphin::mainWin(); KURL::List sourceURLs = command.source(); KURL::List::Iterator it = sourceURLs.begin(); const KURL::List::Iterator end = sourceURLs.end(); KIO::Job* job = 0; switch (command.type()) { case DolphinCommand::Link: { job = KIO::link(sourceURLs, command.destination(), false); break; } case DolphinCommand::Copy: { job = KIO::copy(sourceURLs, command.destination(), false); break; } case DolphinCommand::Rename: case DolphinCommand::Move: { job = KIO::move(sourceURLs, command.destination(), false); break; } case DolphinCommand::Trash: { const QString destURL(command.destination().prettyURL()); while (it != end) { // TODO: use KIO::special for accessing the trash protocol. See // also Dolphin::slotJobResult() for further details. const QString originalFileName((*it).filename().section('-', 1)); KURL originalSourceURL(destURL + "/" + originalFileName); KIO::Job* moveToTrashJob = KIO::trash(originalSourceURL); KIO::NetAccess::synchronousRun(moveToTrashJob, &dolphin); ++it; m_progressIndicator->execOperation(); } break; } case DolphinCommand::CreateFolder: { KIO::NetAccess::mkdir(command.destination(), &dolphin); break; } case DolphinCommand::CreateFile: { m_progressIndicator->execOperation(); KURL::List::Iterator it = sourceURLs.begin(); assert(sourceURLs.count() == 1); KIO::CopyJob* copyJob = KIO::copyAs(*it, command.destination(), false); copyJob->setDefaultPermissions(true); job = copyJob; break; } } if (job != 0) { // Execute the jobs in a synchronous manner and forward the progress // information to the Dolphin statusbar. connect(job, SIGNAL(percent(KIO::Job*, unsigned long)), this, SLOT(slotPercent(KIO::Job*, unsigned long))); KIO::NetAccess::synchronousRun(job, &dolphin); } ++m_historyIndex; m_progressIndicator->execOperation(); } --m_historyIndex; delete m_progressIndicator; m_progressIndicator = 0; } UndoManager::UndoManager() : m_recordMacro(false), m_historyIndex(-1), m_macroCounter(0), m_progressIndicator(0) { } UndoManager::~UndoManager() { delete m_progressIndicator; m_progressIndicator = 0; } QString UndoManager::commandText(const DolphinCommand& command) const { QString text; switch (command.type()) { case DolphinCommand::Copy: text = i18n("Copy"); break; case DolphinCommand::Move: text = i18n("Move"); break; case DolphinCommand::Link: text = i18n("Link"); break; case DolphinCommand::Rename: text = i18n("Rename"); break; case DolphinCommand::Trash: text = i18n("Move to Trash"); break; case DolphinCommand::CreateFolder: text = i18n("Create New Folder"); break; case DolphinCommand::CreateFile: text = i18n("Create New File"); break; default: break; } return text; } void UndoManager::slotPercent(KIO::Job* /* job */, unsigned long /* percent */) { // It is not allowed to update the progress indicator in the context // of this slot, hence do an asynchronous triggering. QTimer::singleShot(0, this, SLOT(updateProgress())); } void UndoManager::updateProgress() { m_progressIndicator->execOperation(); } void UndoManager::calcStepsCount(int& macroCount, int& progressCount) { progressCount = 0; macroCount = 0; const int macroIndex = m_history[m_historyIndex].m_macroIndex; if (macroIndex < 0) { // default use case: no macro has been recorded macroCount = 1; progressCount = m_history[m_historyIndex].source().count(); return; } // iterate backward for undo... int i = m_historyIndex; while ((i >= 0) && (m_history[i].m_macroIndex == macroIndex)) { ++macroCount; progressCount += m_history[i].source().count(); --i; } // iterate forward for redo... const int max = m_history.count() - 1; i = m_historyIndex + 1; while ((i <= max) && (m_history[i].m_macroIndex == macroIndex)) { ++macroCount; progressCount += m_history[i].source().count(); ++i; } } #include "undomanager.moc"