]> cloud.milkyroute.net Git - dolphin.git/blob - src/undomanager.cpp
Applied some patches found by EBN (thanks to Nicolas Lécureuil for the patches!).
[dolphin.git] / src / undomanager.cpp
1 /***************************************************************************
2 * Copyright (C) 2006 by Peter Penz *
3 * peter.penz@gmx.at *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
19 ***************************************************************************/
20
21 #include "undomanager.h"
22 #include <klocale.h>
23 #include <kio/netaccess.h>
24 #include <qtimer.h>
25 #include <assert.h>
26
27 #include "dolphinmainwindow.h"
28 #include "dolphinstatusbar.h"
29 #include "progressindicator.h"
30
31 DolphinCommand::DolphinCommand() :
32 m_type(Copy),
33 m_macroIndex(-1)
34 {
35 // Implementation note: DolphinCommands are stored in a QValueList, whereas
36 // QValueList requires a default constructor of the added class.
37 // Instead of expressing this implementation detail to the interface by adding a
38 // Type::Undefined just Type::Copy is used to assure that all members have
39 // a defined state.
40 //
41 // KDE4TODO: QList doesn't require a default constructor iirc - so remove this
42 }
43
44 DolphinCommand::DolphinCommand(Type type,
45 const KUrl::List& source,
46 const KUrl& dest) :
47 m_type(type),
48 m_macroIndex(-1),
49 m_source(source),
50 m_dest(dest)
51 {
52 }
53
54 DolphinCommand::~DolphinCommand()
55 {
56 }
57
58 DolphinCommand& DolphinCommand::operator = (const DolphinCommand& command)
59 {
60 m_type = command.m_type;
61 m_source = command.m_source;
62 m_dest = command.m_dest;
63 return *this;
64 }
65
66 UndoManager& UndoManager::instance()
67 {
68 static UndoManager* instance = 0;
69 if (instance == 0) {
70 instance = new UndoManager();
71 }
72 return *instance;
73 }
74
75 void UndoManager::addCommand(const DolphinCommand& command)
76 {
77 ++m_historyIndex;
78
79 QList<DolphinCommand>::iterator it = m_history.begin();
80 it += m_historyIndex;
81 if (m_recordMacro) {
82 DolphinCommand macroCommand = command;
83 macroCommand.m_macroIndex = m_macroCounter;
84 m_history.insert(it, macroCommand);
85 }
86 else {
87 m_history.insert(it, command);
88 }
89
90 emit undoAvailable(true);
91 emit undoTextChanged(i18n("Undo: %1",commandText(command)));
92
93 // prevent an endless growing of the Undo history
94 if (m_historyIndex > 10000) {
95 m_history.erase(m_history.begin());
96 --m_historyIndex;
97 }
98 }
99
100 void UndoManager::beginMacro()
101 {
102 assert(!m_recordMacro);
103 m_recordMacro = true;
104 ++m_macroCounter;
105 }
106
107 void UndoManager::endMacro()
108 {
109 assert(m_recordMacro);
110 m_recordMacro = false;
111 }
112
113 // KDE4 TODO: consider switching to KCommandHistory (kdeui) for the command history, and to
114 // KonqCommandRecorder etc. from libkonq/konq_undo.*
115 void UndoManager::undo(DolphinMainWindow* mainWindow)
116 {
117 if (m_recordMacro) {
118 endMacro();
119 }
120
121 if (m_historyIndex < 0) {
122 return;
123 }
124
125 int progressCount = 0;
126 int macroCount = 1;
127 calcStepsCount(macroCount, progressCount);
128
129 /*
130 * KDE4, ### TODO Only here to avoid possible crash
131 */
132 ProgressIndicator progressIndicator(mainWindow, i18n("Executing undo operation..."),
133 i18n("Executed undo operation."),
134 progressCount);
135
136 for (int i = 0; i < macroCount; ++i) {
137 const DolphinCommand command = m_history[m_historyIndex];
138
139 --m_historyIndex;
140 if (m_historyIndex < 0) {
141 emit undoAvailable(false);
142 emit undoTextChanged(i18n("Undo"));
143 }
144 else {
145 emit undoTextChanged(i18n("Undo: %1",commandText(m_history[m_historyIndex])));
146 }
147
148 if (m_historyIndex < static_cast<int>(m_history.count()) - 1) {
149 emit redoAvailable(true);
150 emit redoTextChanged(i18n("Redo: %1",commandText(command)));
151 }
152 else {
153 emit redoAvailable(false);
154 emit redoTextChanged(i18n("Redo"));
155 }
156
157 KUrl::List sourceUrls = command.source();
158 KUrl::List::Iterator it = sourceUrls.begin();
159 const KUrl::List::Iterator end = sourceUrls.end();
160 const QString destUrl(command.destination().prettyUrl(KUrl::AddTrailingSlash));
161
162 KIO::Job* job = 0;
163 switch (command.type()) {
164 case DolphinCommand::Link:
165 case DolphinCommand::Copy: {
166 KUrl::List list;
167 while (it != end) {
168 const KUrl deleteUrl(destUrl + (*it).fileName());
169 list.append(deleteUrl);
170 ++it;
171 }
172 job = KIO::del(list, false, false);
173 break;
174 }
175
176 case DolphinCommand::Move: {
177 KUrl::List list;
178 const KUrl newDestUrl((*it).directory());
179 while (it != end) {
180 const KUrl newSourceUrl(destUrl + (*it).fileName());
181 list.append(newSourceUrl);
182 ++it;
183 }
184 job = KIO::move(list, newDestUrl, false);
185 break;
186 }
187
188 case DolphinCommand::Rename: {
189 assert(sourceUrls.count() == 1);
190 KIO::NetAccess::move(command.destination(), (*it));
191 break;
192 }
193
194 case DolphinCommand::Trash: {
195 while (it != end) {
196 // TODO: use KIO::special for accessing the trash protocol. See
197 // also Dolphin::slotJobResult() for further details.
198 const QString originalFileName((*it).fileName().section('-', 1));
199 KUrl newDestUrl(destUrl + originalFileName);
200 KIO::NetAccess::move(*it, newDestUrl);
201 ++it;
202
203 progressIndicator.execOperation();
204 }
205 break;
206 }
207
208 case DolphinCommand::CreateFolder:
209 case DolphinCommand::CreateFile: {
210 KIO::NetAccess::del(command.destination(), mainWindow);
211 break;
212 }
213 }
214
215 if (job != 0) {
216 // Execute the jobs in a synchronous manner and forward the progress
217 // information to the Dolphin statusbar.
218 connect(job, SIGNAL(percent(KIO::Job*, unsigned long)),
219 this, SLOT(slotPercent(KIO::Job*, unsigned long)));
220 KIO::NetAccess::synchronousRun(job, mainWindow);
221 }
222
223 progressIndicator.execOperation();
224 }
225 }
226
227 void UndoManager::redo(DolphinMainWindow *mainWindow)
228 {
229 if (m_recordMacro) {
230 endMacro();
231 }
232
233 const int maxHistoryIndex = m_history.count() - 1;
234 if (m_historyIndex >= maxHistoryIndex) {
235 return;
236 }
237 ++m_historyIndex;
238
239 int progressCount = 0;
240 int macroCount = 1;
241 calcStepsCount(macroCount, progressCount);
242
243 ProgressIndicator progressIndicator(mainWindow, i18n("Executing redo operation..."),
244 i18n("Executed redo operation."),
245 progressCount);
246
247 for (int i = 0; i < macroCount; ++i) {
248 const DolphinCommand command = m_history[m_historyIndex];
249 if (m_historyIndex >= maxHistoryIndex) {
250 emit redoAvailable(false);
251 emit redoTextChanged(i18n("Redo"));
252 }
253 else {
254 emit redoTextChanged(i18n("Redo: %1",commandText(m_history[m_historyIndex + 1])));
255 }
256
257 emit undoAvailable(true);
258 emit undoTextChanged(i18n("Undo: %1",commandText(command)));
259
260 KUrl::List sourceUrls = command.source();
261 KUrl::List::Iterator it = sourceUrls.begin();
262 const KUrl::List::Iterator end = sourceUrls.end();
263
264 KIO::Job* job = 0;
265 switch (command.type()) {
266 case DolphinCommand::Link: {
267 job = KIO::link(sourceUrls, command.destination(), false);
268 break;
269 }
270
271 case DolphinCommand::Copy: {
272 job = KIO::copy(sourceUrls, command.destination(), false);
273 break;
274 }
275
276 case DolphinCommand::Rename:
277 case DolphinCommand::Move: {
278 job = KIO::move(sourceUrls, command.destination(), false);
279 break;
280 }
281
282 case DolphinCommand::Trash: {
283 const QString destUrl(command.destination().prettyUrl());
284 while (it != end) {
285 // TODO: use KIO::special for accessing the trash protocol. See
286 // also Dolphin::slotJobResult() for further details.
287 const QString originalFileName((*it).fileName().section('-', 1));
288 KUrl originalSourceUrl(destUrl + "/" + originalFileName);
289 KIO::Job* moveToTrashJob = KIO::trash(originalSourceUrl);
290 KIO::NetAccess::synchronousRun(moveToTrashJob, mainWindow);
291 ++it;
292
293 progressIndicator.execOperation();
294 }
295 break;
296 }
297
298 case DolphinCommand::CreateFolder: {
299 KIO::NetAccess::mkdir(command.destination(), mainWindow);
300 break;
301 }
302
303 case DolphinCommand::CreateFile: {
304 progressIndicator.execOperation();
305 KUrl::List::Iterator it = sourceUrls.begin();
306 assert(sourceUrls.count() == 1);
307 KIO::CopyJob* copyJob = KIO::copyAs(*it, command.destination(), false);
308 copyJob->setDefaultPermissions(true);
309 job = copyJob;
310 break;
311 }
312 }
313
314 if (job != 0) {
315 // Execute the jobs in a synchronous manner and forward the progress
316 // information to the Dolphin statusbar.
317 connect(job, SIGNAL(percent(KJob*, unsigned long)),
318 this, SLOT(slotPercent(KJob*, unsigned long)));
319 KIO::NetAccess::synchronousRun(job, mainWindow);
320 }
321
322 ++m_historyIndex;
323 progressIndicator.execOperation();
324 }
325
326 --m_historyIndex;
327
328 }
329
330 UndoManager::UndoManager() :
331 m_recordMacro(false),
332 m_historyIndex(-1),
333 m_macroCounter(0)
334 {
335 }
336
337 UndoManager::~UndoManager()
338 {
339 }
340
341 QString UndoManager::commandText(const DolphinCommand& command) const
342 {
343 QString text;
344 switch (command.type()) {
345 case DolphinCommand::Copy: text = i18n("Copy"); break;
346 case DolphinCommand::Move: text = i18n("Move"); break;
347 case DolphinCommand::Link: text = i18n("Link"); break;
348 case DolphinCommand::Rename: text = i18n("Rename"); break;
349 case DolphinCommand::Trash: text = i18n("Move to Trash"); break;
350 case DolphinCommand::CreateFolder: text = i18n("Create New Folder"); break;
351 case DolphinCommand::CreateFile: text = i18n("Create New File"); break;
352 default: break;
353 }
354 return text;
355 }
356
357 void UndoManager::slotPercent(KJob* /* job */, unsigned long /* percent */)
358 {
359 // It is not allowed to update the progress indicator in the context
360 // of this slot, hence do an asynchronous triggering.
361 QTimer::singleShot(0, this, SLOT(updateProgress()));
362 }
363
364 void UndoManager::updateProgress()
365 {
366 /*
367 * ### XXX, TODO, KDE4 make this work when switchting to KonqUndoManager
368 */
369 //m_progressIndicator->execOperation();
370 }
371
372 void UndoManager::calcStepsCount(int& macroCount, int& progressCount)
373 {
374 progressCount = 0;
375 macroCount = 0;
376
377 const int macroIndex = m_history[m_historyIndex].m_macroIndex;
378 if (macroIndex < 0) {
379 // default use case: no macro has been recorded
380 macroCount = 1;
381 progressCount = m_history[m_historyIndex].source().count();
382 return;
383 }
384
385 // iterate backward for undo...
386 int i = m_historyIndex;
387 while ((i >= 0) && (m_history[i].m_macroIndex == macroIndex)) {
388 ++macroCount;
389 progressCount += m_history[i].source().count();
390 --i;
391 }
392
393 // iterate forward for redo...
394 const int max = m_history.count() - 1;
395 i = m_historyIndex + 1;
396 while ((i <= max) && (m_history[i].m_macroIndex == macroIndex)) {
397 ++macroCount;
398 progressCount += m_history[i].source().count();
399 ++i;
400 }
401 }
402
403 #include "undomanager.moc"
404
405