cleanup code and fix pesky 'no such signal QObject::patchChanged
[kdevelop:kdevplatform.git] / plugins / patchreview / patchreview.cpp
1 /***************************************************************************
2 Copyright 2006-2009 David Nolden <david.nolden.kdevelop@art-master.de>
3 ***************************************************************************/
4
5 /***************************************************************************
6  *                                                                         *
7  *   This program is free software; you can redistribute it and/or modify  *
8  *   it under the terms of the GNU General Public License as published by  *
9  *   the Free Software Foundation; either version 2 of the License, or     *
10  *   (at your option) any later version.                                   *
11  *                                                                         *
12  ***************************************************************************/
13
14 #include "patchreview.h"
15
16 #include <kmimetype.h>
17 #include <klineedit.h>
18 #include <kmimetypechooser.h>
19 #include <kmimetypetrader.h>
20 #include <krandom.h>
21 #include <QTabWidget>
22 #include <QMenu>
23 #include <QFile>
24 #include <QTimer>
25 #include <QPersistentModelIndex>
26 #include <kfiledialog.h>
27 #include <interfaces/idocument.h>
28 #include <QStandardItemModel>
29 #include <interfaces/icore.h>
30 #include <kde_terminal_interface.h>
31 #include <kparts/part.h>
32 #include <kparts/factory.h>
33 #include <kdialog.h>
34 #include <ktemporaryfile.h>
35
36
37 #include "libdiff2/komparemodellist.h"
38 #include "libdiff2/kompare.h"
39 #include <kmessagebox.h>
40 #include <QMetaType>
41 #include <QVariant>
42 #include <ktexteditor/cursor.h>
43 #include <ktexteditor/document.h>
44 #include <ktexteditor/view.h>
45 #include <ktexteditor/markinterface.h>
46 #include <ktexteditor/movinginterface.h>
47 #include <interfaces/idocumentcontroller.h>
48 #include <kprocess.h>
49 #include <interfaces/iuicontroller.h>
50 #include <kaboutdata.h>
51
52 ///Whether arbitrary exceptions that occurred while diff-parsing within the library should be caught
53 #define CATCHLIBDIFF
54
55 /* Exclude this file from doublequote_chars check as krazy doesn't understand
56 std::string*/
57 //krazy:excludeall=doublequote_chars
58 #include <krun.h>
59 #include <kparts/mainwindow.h>
60 #include <qtextdocument.h>
61 #include <util/activetooltip.h>
62 #include <ktextbrowser.h>
63 #include <kiconeffect.h>
64 #include <kcolorutils.h>
65 #include <kcolorscheme.h>
66 #include <sublime/controller.h>
67 #include <sublime/mainwindow.h>
68 #include <sublime/area.h>
69 #include <interfaces/iprojectcontroller.h>
70 #include "diffsettings.h"
71 #include <interfaces/iplugincontroller.h>
72 #include <interfaces/ipatchexporter.h>
73 #include "standardpatchexport.h"
74 #include <language/highlighting/colorcache.h>
75
76 using namespace KDevelop;
77
78 namespace {
79   // Maximum number of files to open directly within a tab when the review is started
80   const int maximumFilesToOpenDirectly = 15;
81 }
82
83 Q_DECLARE_METATYPE( const Diff2::DiffModel* )
84
85 PatchReviewToolView::PatchReviewToolView( QWidget* parent, PatchReviewPlugin* plugin ) 
86 : QWidget( parent ), m_reversed( false ), m_plugin( plugin ) 
87 {
88     connect( plugin, SIGNAL(patchChanged()), SLOT(patchChanged()) );
89     connect( ICore::self()->documentController(), SIGNAL(documentActivated(KDevelop::IDocument*)), this, SLOT(documentActivated(KDevelop::IDocument*)) );
90     
91     showEditDialog();
92     patchChanged();
93 }
94
95 void PatchReviewToolView::patchChanged()
96 {
97     fillEditFromPatch();
98     kompareModelChanged();
99 }
100
101 PatchReviewToolView::~PatchReviewToolView() {
102 }
103
104 void PatchReviewToolView::updatePatchFromEdit() {
105
106     IPatchSource::Ptr ips = m_plugin->patch();
107
108     if ( !ips )
109         return;
110     LocalPatchSource* lpatch = dynamic_cast<LocalPatchSource*>(ips.data());
111     if(!lpatch)
112       return;
113
114     lpatch->m_command = m_editPatch.command->text();
115     lpatch->m_filename = m_editPatch.filename->url();
116     lpatch->m_baseDir = m_editPatch.baseDir->url();
117 //     lpatch->m_depth = m_editPatch.depth->value();
118
119     m_plugin->notifyPatchChanged();
120 }
121
122 void PatchReviewToolView::fillEditFromPatch() {
123
124     IPatchSource::Ptr ipatch = m_plugin->patch();
125     if ( !ipatch )
126         return ;
127     
128     disconnect( m_editPatch.patchSelection, SIGNAL(currentIndexChanged(int)), this, SLOT(patchSelectionChanged(int)));
129     
130     m_editPatch.patchSelection->clear();
131     foreach(IPatchSource::Ptr patch, m_plugin->knownPatches())
132     {
133       if(!patch)
134         continue;
135       m_editPatch.patchSelection->addItem(patch->icon(), patch->name());
136       if(patch == ipatch)
137         m_editPatch.patchSelection->setCurrentIndex(m_editPatch.patchSelection->count()-1);
138     }
139     
140     connect( m_editPatch.patchSelection, SIGNAL(currentIndexChanged(int)), this, SLOT(patchSelectionChanged(int)));
141
142     m_editPatch.cancelReview->setVisible(ipatch->canCancel());
143
144     QString finishText = i18n("Finish Review");
145     if(!ipatch->finishReviewCustomText().isEmpty())
146       finishText = ipatch->finishReviewCustomText();
147     kDebug() << "finish-text: " << finishText;
148     m_editPatch.finishReview->setText(finishText);
149     
150     if(m_customWidget) {
151       kDebug() << "removing custom widget";
152       m_customWidget->hide();
153       m_editPatch.verticalLayout->removeWidget(m_customWidget);
154     }
155
156     m_customWidget = ipatch->customWidget();
157     if(m_customWidget) {
158       m_editPatch.verticalLayout->insertWidget(0, m_customWidget);
159       m_customWidget->show();
160       kDebug() << "got custom widget";
161     }
162     
163     LocalPatchSource* lpatch = dynamic_cast<LocalPatchSource*>(ipatch.data());
164     if(!lpatch) {
165       m_editPatch.tabWidget->hide();
166       m_editPatch.baseDir->hide();
167       m_editPatch.label->hide();
168       return;
169     }else{
170       m_editPatch.tabWidget->show();
171       m_editPatch.baseDir->show();
172       m_editPatch.label->show();
173     }
174     
175     m_editPatch.command->setText( lpatch->m_command );
176     m_editPatch.filename->setUrl( lpatch->m_filename );
177     m_editPatch.baseDir->setUrl( lpatch->m_baseDir );
178 //     m_editPatch.depth->setValue( lpatch->m_depth );
179
180     if ( lpatch->m_command.isEmpty() )
181         m_editPatch.tabWidget->setCurrentIndex( m_editPatch.tabWidget->indexOf( m_editPatch.fileTab ) );
182     else
183         m_editPatch.tabWidget->setCurrentIndex( m_editPatch.tabWidget->indexOf( m_editPatch.commandTab ) );
184 }
185
186 void PatchReviewToolView::patchSelectionChanged(int selection)
187 {
188   m_editPatch.filesList->clear();
189     if(selection >= 0 && selection < m_plugin->knownPatches().size()) {
190       m_plugin->setPatch(m_plugin->knownPatches()[selection]);
191     }
192 }
193
194 void PatchReviewToolView::slotEditCommandChanged() {
195 //     m_editPatch.filename->lineEdit()->setText( "" );
196     updatePatchFromEdit();
197 }
198
199 void PatchReviewToolView::slotEditFileNameChanged() {
200 //     m_editPatch.command->setText( "" );
201     updatePatchFromEdit();
202 }
203
204 void PatchReviewToolView::showEditDialog() {
205
206     m_editPatch.setupUi( this );
207
208     m_editPatch.filesList->header()->hide();
209     m_editPatch.filesList->setRootIsDecorated(false);
210     
211     m_editPatch.previousHunk->setIcon(KIcon("arrow-up"));
212     m_editPatch.nextHunk->setIcon(KIcon("arrow-down"));
213     m_editPatch.cancelReview->setIcon(KIcon("dialog-cancel"));
214     m_editPatch.finishReview->setIcon(KIcon("dialog-ok"));
215     
216     QMenu* exportMenu = new QMenu(m_editPatch.exportReview);
217     StandardPatchExport* stdactions = new StandardPatchExport(m_plugin, this);
218     stdactions->addActions(exportMenu);
219     connect(exportMenu, SIGNAL(triggered(QAction*)), m_plugin, SLOT(exporterSelected(QAction*)));
220     
221     IPluginController* pluginManager = ICore::self()->pluginController();
222     foreach( IPlugin* p, pluginManager->allPluginsForExtension( "org.kdevelop.IPatchExporter" ) )
223     {
224         KPluginInfo info=pluginManager->pluginInfo(p);
225         QAction* action=exportMenu->addAction(KIcon(info.icon()), info.name());
226         action->setData(qVariantFromValue<QObject*>(p));
227     }
228     
229     m_editPatch.exportReview->setMenu(exportMenu);
230     
231     connect( m_editPatch.previousHunk, SIGNAL( clicked( bool ) ), this, SLOT( prevHunk() ) );
232     connect( m_editPatch.nextHunk, SIGNAL( clicked( bool ) ), this, SLOT( nextHunk() ) );
233     connect( m_editPatch.filesList, SIGNAL( doubleClicked( const QModelIndex& ) ), this, SLOT( fileDoubleClicked( const QModelIndex& ) ) );
234     
235     connect( m_editPatch.cancelReview, SIGNAL(clicked(bool)), m_plugin, SLOT(cancelReview()) );
236     connect( m_editPatch.finishReview, SIGNAL(clicked(bool)), this, SLOT(finishReview()) );
237     //connect( m_editPatch.cancelButton, SIGNAL( pressed() ), this, SLOT( slotEditCancel() ) );
238
239     //connect( this, SIGNAL( finished( int ) ), this, SLOT( slotEditDialogFinished( int ) ) );
240
241 //     connect( m_editPatch.depth, SIGNAL(valueChanged(int)), SLOT(updatePatchFromEdit()) );
242     connect( m_editPatch.filename, SIGNAL( textChanged( const QString& ) ), SLOT(slotEditFileNameChanged()) );
243     connect( m_editPatch.baseDir, SIGNAL(textChanged(QString)), SLOT(updatePatchFromEdit()) );
244
245     
246     
247     m_editPatch.baseDir->setMode(KFile::Directory);
248
249     connect( m_editPatch.command, SIGNAL( textChanged( const QString& ) ), this, SLOT(slotEditCommandChanged()) );
250 //   connect( m_editPatch.commandToFile, SIGNAL( clicked( bool ) ), this, SLOT( slotToFile() ) );
251
252     connect( m_editPatch.filename->lineEdit(), SIGNAL( returnPressed() ), this, SLOT(slotEditFileNameChanged()) );
253     connect( m_editPatch.filename->lineEdit(), SIGNAL( editingFinished() ), this, SLOT(slotEditFileNameChanged()) );
254     connect( m_editPatch.filename, SIGNAL( urlSelected( const KUrl& ) ), this, SLOT(slotEditFileNameChanged()) );
255     connect( m_editPatch.command, SIGNAL(textChanged(QString)), this, SLOT(slotEditCommandChanged()) );
256 //     connect( m_editPatch.commandToFile, SIGNAL(clicked(bool)), m_plugin, SLOT(commandToFile()) );
257
258     connect( m_editPatch.patchSelection, SIGNAL(currentIndexChanged(int)), this, SLOT(patchSelectionChanged(int)));
259     
260     connect( m_editPatch.updateButton, SIGNAL(clicked(bool)), m_plugin, SLOT(forceUpdate()) );
261
262     connect( m_editPatch.showButton, SIGNAL(clicked(bool)), m_plugin, SLOT(showPatch()) );
263     
264     bool blocked = blockSignals( true );
265
266     blockSignals( blocked );
267 }
268
269 void PatchReviewToolView::nextHunk() {
270 //   updateKompareModel();
271     m_plugin->seekHunk( true );
272 }
273
274 void PatchReviewToolView::prevHunk() {
275 //   updateKompareModel();
276     m_plugin->seekHunk( false );
277 }
278
279 KUrl PatchReviewPlugin::diffFile()
280 {
281     return m_patch->file();
282 }
283
284 void PatchReviewPlugin::seekHunk( bool forwards, const KUrl& fileName ) {
285     try {
286         if ( !m_modelList.get() )
287             throw "no model";
288
289         for (int a = 0; a < m_modelList->modelCount(); ++a) {
290
291             const Diff2::DiffModel* model = m_modelList->modelAt(a);
292             if ( !model || !model->differences() )
293                 continue;
294
295             KUrl file = m_patch->baseDir();
296             
297             file.addPath( model->destinationPath() );
298             file.addPath( model->destinationFile() );
299             
300             if ( !fileName.isEmpty() && fileName != file )
301                 continue;
302
303             IDocument* doc = ICore::self()->documentController()->documentForUrl( file );
304
305             if ( doc && doc == ICore::self()->documentController()->activeDocument() && m_highlighters.contains(doc->url()) && m_highlighters[doc->url()] ) {
306               
307                 ICore::self()->documentController()->activateDocument( doc );
308                 if ( doc->textDocument() ) {
309                   
310                     KTextEditor::MovingInterface* moving = dynamic_cast<KTextEditor::MovingInterface*>( doc->textDocument() );
311                     Q_ASSERT(moving);
312                     const QList< KTextEditor::MovingRange* > ranges = m_highlighters[doc->url()]->ranges();
313                     
314                     KTextEditor::View * v = doc->textDocument() ->activeView();
315                     int bestLine = -1;
316                     if ( v ) {
317                         KTextEditor::Cursor c = v->cursorPosition();
318                         for ( QList< KTextEditor::MovingRange* >::const_iterator it = ranges.begin(); it != ranges.end(); ++it ) {
319                             int line;
320                             
321                             line = (*it)->start().line();
322
323                             if ( forwards ) {
324                                 if ( line > c.line() && ( bestLine == -1 || line < bestLine ) )
325                                     bestLine = line;
326                             } else {
327                                 if ( line < c.line() && ( bestLine == -1 || line > bestLine ) )
328                                     bestLine = line;
329                             }
330                         }
331                         if ( bestLine != -1 ) {
332                             v->setCursorPosition( KTextEditor::Cursor( bestLine, 0 ) );
333                             return ;
334                         }
335                     }
336                 }
337             }
338         }
339
340     } catch ( const QString & str ) {
341         kDebug() << "seekHunk():" << str;
342     } catch ( const char * str ) {
343         kDebug() << "seekHunk():" << str;
344     }
345     kDebug() << "no matching hunk found";
346 }
347
348
349
350 void PatchReviewPlugin::addHighlighting(const KUrl& highlightFile, IDocument* document)
351 {
352     try {
353         if ( !modelList() )
354             throw "no model";
355
356         for (int a = 0; a < modelList()->modelCount(); ++a) {
357             const Diff2::DiffModel* model = modelList()->modelAt(a);
358             if ( !model )
359                 continue;
360
361             KUrl file = m_patch->baseDir();
362             
363             file.addPath( model->destinationPath() );
364             file.addPath( model->destinationFile() );
365
366             if (file != highlightFile)
367                 continue;
368
369             kDebug() << "highlighting" << file.prettyUrl();
370
371             IDocument* doc = document;
372             if(!doc)
373               doc = ICore::self()->documentController()->documentForUrl( file );
374
375             kDebug() << "highlighting file" << file << "with doc" << doc;
376             
377             if ( !doc || !doc->textDocument() )
378                 continue;
379
380             removeHighlighting( file );
381
382             m_highlighters[ file ] = new PatchHighlighter( model, doc, this );
383         }
384
385     } catch ( const QString & str ) {
386         kDebug() << "highlightFile():" << str;
387     } catch ( const char * str ) {
388         kDebug() << "highlightFile():" << str;
389     }
390 }
391
392 void PatchReviewPlugin::highlightPatch() {
393     try {
394         if ( !modelList() )
395             throw "no model";
396
397         for (int a = 0; a < modelList()->modelCount(); ++a) {
398             const Diff2::DiffModel* model = modelList()->modelAt(a);
399             if ( !model )
400                 continue;
401
402             KUrl file = m_patch->baseDir();
403             
404             file.addPath( model->destinationPath() );
405             file.addPath( model->destinationFile() );
406
407             addHighlighting(file);
408         }
409
410     } catch ( const QString & str ) {
411         kDebug() << "highlightFile():" << str;
412     } catch ( const char * str ) {
413         kDebug() << "highlightFile():" << str;
414     }
415 }
416
417
418 void PatchReviewToolView::finishReview()
419 {
420     QList<KUrl> selectedUrls;
421     for(int a = 0; a< m_editPatch.filesList->topLevelItemCount(); ++a) {
422       QTreeWidgetItem* item = m_editPatch.filesList->topLevelItem(a);
423       if(item && item->checkState(0) == Qt::Checked) {
424         QVariant v = item->data(0, Qt::UserRole);
425         
426         if( v.canConvert<KUrl>() ) {
427           selectedUrls << v.value<KUrl>();
428         }else if ( v.canConvert<const Diff2::DiffModel*>() ) {
429           const Diff2::DiffModel* model = v.value<const Diff2::DiffModel*>();
430
431           KUrl file = m_plugin->patch()->baseDir();
432           
433           file.addPath( model->destinationPath() );
434           file.addPath( model->destinationFile() );
435           
436           selectedUrls << file;
437         }
438       }
439     }
440     kDebug() << "finishing review with" << selectedUrls;
441     m_plugin->finishReview(selectedUrls);
442 }
443
444 void PatchReviewToolView::fileDoubleClicked( const QModelIndex& i ) {
445     try {
446         if ( !m_plugin->modelList() )
447             throw "no model";
448         
449         QVariant v = i.data( Qt::UserRole );
450         
451         if( v.canConvert<KUrl>() ) {
452           KUrl u = v.value<KUrl>();
453           ICore::self()->documentController()->openDocument( u, KTextEditor::Cursor() );
454           return;
455         }
456         
457         if ( !v.canConvert<const Diff2::DiffModel*>() )
458             throw "cannot convert";
459         const Diff2::DiffModel* model = v.value<const Diff2::DiffModel*>();
460         if ( !model )
461             throw "bad model-value";
462
463         KUrl file = m_plugin->patch()->baseDir();
464         
465         file.addPath( model->destinationPath() );
466         file.addPath( model->destinationFile() );
467
468         kDebug() << "opening" << file.toLocalFile();
469
470         ICore::self()->documentController()->openDocument( file, KTextEditor::Cursor() );
471
472         m_plugin->seekHunk( true, file );
473     } catch ( const QString & str ) {
474         kDebug() << "fileDoubleClicked():" << str;
475     } catch ( const char * str ) {
476         kDebug() << "fileDoubleClicked():" << str;
477     }
478 }
479
480 KUrl PatchReviewToolView::urlForFileModel(const Diff2::DiffModel* model)
481 {
482   KUrl file = m_plugin->patch()->baseDir();
483   
484   file.addPath( model->destinationPath() );
485   file.addPath( model->destinationFile() );
486   
487   return file;
488 }
489
490 static QString stateToString(KDevelop::VcsStatusInfo::State state)
491 {
492     switch(state)
493     {
494       case KDevelop::VcsStatusInfo::ItemAdded:
495           return i18n("Added");
496       case KDevelop::VcsStatusInfo::ItemDeleted:
497           return i18n("Deleted");
498       case KDevelop::VcsStatusInfo::ItemHasConflicts:
499           return i18n("Has Conflicts");
500       case KDevelop::VcsStatusInfo::ItemModified:
501           return i18n("Modified");
502       case KDevelop::VcsStatusInfo::ItemUpToDate:
503           return i18n("Up To Date");
504       case KDevelop::VcsStatusInfo::ItemUnknown:
505       case KDevelop::VcsStatusInfo::ItemUserState:
506           return i18n("Unknown");
507     }
508     return i18nc("Unknown VCS file status, probably a backend error", "?");
509 }
510
511 static KIcon stateToIcon(KDevelop::VcsStatusInfo::State state)
512 {
513     switch(state)
514     {
515       case KDevelop::VcsStatusInfo::ItemAdded:
516           return KIcon("vcs-added");
517       case KDevelop::VcsStatusInfo::ItemDeleted:
518           return KIcon("vcs-removed");
519       case KDevelop::VcsStatusInfo::ItemHasConflicts:
520           return KIcon("vcs-conflicting");
521       case KDevelop::VcsStatusInfo::ItemModified:
522           return KIcon("vcs-locally-modified");
523       case KDevelop::VcsStatusInfo::ItemUpToDate:
524           return KIcon("vcs-normal");
525       case KDevelop::VcsStatusInfo::ItemUnknown:
526       case KDevelop::VcsStatusInfo::ItemUserState:
527           return KIcon("unknown");
528     }
529     return KIcon("dialog-error");
530 }
531
532 void PatchReviewToolView::kompareModelChanged()
533 {
534     m_editPatch.filesList->clear();
535     m_editPatch.filesList->setColumnCount(1);
536
537     if (!m_plugin->modelList())
538         return;
539
540     QMap<KUrl, KDevelop::VcsStatusInfo::State> additionalUrls = m_plugin->patch()->additionalSelectableFiles();
541
542     QSet<KUrl> haveUrls;
543     
544     const Diff2::DiffModelList* models = m_plugin->modelList()->models();
545     if( models )
546     {
547       Diff2::DiffModelList::const_iterator it = models->constBegin();
548       for(; it != models->constEnd(); ++it) {
549           Diff2::DifferenceList * diffs = ( *it ) ->differences();
550           int cnt = 0;
551           if ( diffs )
552               cnt = diffs->count();
553
554           KUrl file = urlForFileModel(*it);
555           haveUrls.insert(file);
556
557           if(!QFileInfo(file.toLocalFile()).isReadable())
558             continue;
559             
560           QTreeWidgetItem* item = new QTreeWidgetItem(m_editPatch.filesList);
561           
562           m_editPatch.filesList->insertTopLevelItem(0, item);
563
564           const QString filenameArgument = ICore::self()->projectController()->prettyFileName(file, KDevelop::IProjectController::FormatPlain);
565
566           QString text;
567           QIcon icon;
568           if(additionalUrls.contains(file)) {
569               text = i18np("%2 (1 hunk, %3)", "%2 (%1 hunks, %3)", cnt, filenameArgument, stateToString(additionalUrls[file]));
570               icon = stateToIcon(additionalUrls[file]);
571           } else {
572               text = i18np("%2 (1 hunk)", "%2 (%1 hunks)", cnt, filenameArgument);
573           }
574
575           item->setData( 0, Qt::DisplayRole, text );
576           item->setIcon( 0, icon );
577           item->setData( 0, Qt::UserRole, qVariantFromValue<const Diff2::DiffModel*>(*it));
578           item->setCheckState( 0, Qt::Checked );
579       }
580     }
581     
582     // Maps the _really_ useful items (with VCS state) to index 0,
583     // the items that have at least a project found to 1,
584     // and the probably really useless items without project found to 2.
585     // The project-manager filters useless stuff like backups out so they get index 2.
586     QMap<int, QList< QPair<KUrl, KDevelop::VcsStatusInfo::State> > > newItems;
587     
588     for(QMap<KUrl, KDevelop::VcsStatusInfo::State>::const_iterator it = additionalUrls.constBegin(); it != additionalUrls.constEnd(); ++it)
589     {
590       KUrl url = it.key();
591       
592       if(!haveUrls.contains(url))
593       {
594         haveUrls.insert(url);
595         
596         if(*it != KDevelop::VcsStatusInfo::ItemUnknown)
597         {
598           newItems[0] << qMakePair(url, *it);
599         }else{
600           if(((bool)ICore::self()->projectController()->findProjectForUrl(url)))
601           {
602             newItems[1] << qMakePair(url, *it);
603           }else{
604             newItems[2] << qMakePair(url, *it);
605           }
606         }
607       }
608     }
609     
610     for(int a = 0; a < 3; ++a)
611     {
612       for(QList< QPair< KUrl, KDevelop::VcsStatusInfo::State > >::iterator itemIt = newItems[a].begin(); itemIt != newItems[a].end(); ++itemIt)
613       {
614         KUrl url = itemIt->first;
615         KDevelop::VcsStatusInfo::State state = itemIt->second;
616         
617         QTreeWidgetItem* item = new QTreeWidgetItem(m_editPatch.filesList);
618         
619         QString text = ICore::self()->projectController()->prettyFileName(url, KDevelop::IProjectController::FormatPlain);
620         text += " (" + stateToString(state) + ")";
621         
622         item->setData( 0, Qt::DisplayRole, text );
623         QVariant v;
624         v.setValue<KUrl>( url );
625         item->setData( 0, Qt::UserRole, v );
626         item->setIcon( 0, stateToIcon(state) );
627         item->setCheckState( 0, Qt::Unchecked );
628
629         if(a == 0)
630           item->setCheckState( 0, Qt::Checked );
631         
632         m_editPatch.filesList->addTopLevelItem(item);
633       }
634     }
635 }
636
637
638 void PatchReviewToolView::documentActivated(IDocument* doc)
639 {
640     QModelIndexList i = m_editPatch.filesList->selectionModel() ->selectedIndexes();
641     if ( !m_plugin->modelList() )
642         return ;
643     for(int a = 0; a < m_editPatch.filesList->topLevelItemCount(); ++a) {
644       
645         QTreeWidgetItem* item = m_editPatch.filesList->topLevelItem(a);
646       
647         QVariant v = item->data( 0, Qt::UserRole );
648         if ( v.canConvert<const Diff2::DiffModel*>() ) {
649             const Diff2::DiffModel * model = v.value<const Diff2::DiffModel*>();
650             
651             KUrl file = urlForFileModel(model);
652             
653             if(file == doc->url()) {
654               m_editPatch.filesList->setCurrentItem(item);
655               return;
656             }
657         }
658     }
659     m_editPatch.filesList->setCurrentIndex(QModelIndex());
660 }
661
662 void PatchHighlighter::aboutToDeleteMovingInterfaceContent(KTextEditor::Document* )
663 {
664     kDebug() << "about to delete";
665     clear();
666 }
667
668 QSize sizeHintForHtml(QString html, QSize maxSize) {
669   QTextDocument doc;
670   doc.setHtml(html);
671
672   QSize ret;
673   if(doc.idealWidth() > maxSize.width()) {
674     doc.setPageSize( QSize(maxSize.width(), 30) );
675     ret.setWidth(maxSize.width());
676   }else{
677     ret.setWidth(doc.idealWidth());    
678   }
679   ret.setHeight(doc.size().height());
680   if(ret.height() > maxSize.height())
681     ret.setHeight(maxSize.height());
682   return ret;
683 }
684
685 namespace {
686   QPointer<QWidget> currentTooltip;
687   KTextEditor::MovingRange* currentTooltipMark;
688 }
689
690 void PatchHighlighter::showToolTipForMark(QPoint pos, KTextEditor::MovingRange* markRange, QPair< int, int > highlightMark)
691 {
692   if(currentTooltipMark == markRange && currentTooltip)
693     return;
694   delete currentTooltip;
695   
696   //Got the difference
697   Diff2::Difference* diff = m_differencesForRanges[markRange];
698   
699   QString html;
700 #if 0
701   if(diff->hasConflict())
702     html += i18n("<b><span style=\"color:red\">Conflict</span></b><br/>");
703 #endif
704   
705   Diff2::DifferenceStringList lines;
706   
707   if(m_plugin->patch()->isAlreadyApplied() && !diff->applied())
708     html += i18n("<b>Reverted.</b><br/>");
709   else if(!m_plugin->patch()->isAlreadyApplied() && diff->applied())
710     html += i18n("<b>Applied.</b><br/>");
711   
712   if(diff->applied()) {
713     if(isInsertion(diff))
714     {
715       html += i18n("<b>Insertion</b><br/>");
716     }else{
717       if(isRemoval(diff))
718         html += i18n("<b>Removal</b><br/>");
719       html += i18n("<b>Previous:</b><br/>");
720       lines = diff->sourceLines();
721     }
722   }else{
723     if(isRemoval(diff)) {
724       html += i18n("<b>Removal</b><br/>");
725     }else{
726       if(isInsertion(diff))
727         html += i18n("<b>Insertion</b><br/>");
728       
729       html += i18n("<b>Alternative:</b><br/>");
730       
731       lines = diff->destinationLines();
732     }
733   }
734
735   for(int a = 0; a < lines.size(); ++a) {
736     Diff2::DifferenceString* line = lines[a];
737     uint currentPos = 0;
738     QString string = line->string();
739     
740     Diff2::MarkerList markers = line->markerList();
741
742     for(int b = 0; b < markers.size(); ++b) {
743       QString spanText = Qt::escape(string.mid(currentPos, markers[b]->offset() - currentPos));
744       if(markers[b]->type() == Diff2::Marker::End && (currentPos != 0 || markers[b]->offset() != string.size()))
745       {
746         if(a == highlightMark.first && b == highlightMark.second)
747           html += "<b><span style=\"background:#FF5555\">" + spanText + "</span></b>";
748         else
749           html += "<b><span style=\"background:#FFBBBB\">" + spanText + "</span></b>";
750       }else{
751         html += spanText;
752       }
753       currentPos = markers[b]->offset();
754     }
755
756     html += Qt::escape(string.mid(currentPos, string.length()-currentPos));
757     html += "<br/>";
758   }
759
760   KTextBrowser* browser = new KTextBrowser;
761   browser->setPalette( QApplication::palette() );
762   browser->setHtml(html);
763   
764   int maxHeight = 500;
765   
766   browser->setMinimumSize(sizeHintForHtml(html, QSize((ICore::self()->uiController()->activeMainWindow()->width()*2)/3, maxHeight)));
767   browser->setMaximumSize(browser->minimumSize() + QSize(10, 10));
768   if(browser->minimumHeight() != maxHeight)
769     browser->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
770   
771   QVBoxLayout* layout = new QVBoxLayout;
772   layout->setMargin(0);
773   layout->addWidget(browser);
774
775   KDevelop::ActiveToolTip* tooltip = new KDevelop::ActiveToolTip(ICore::self()->uiController()->activeMainWindow(), pos + QPoint(5, -browser->sizeHint().height() - 30));
776   tooltip->setLayout(layout);
777   tooltip->resize( tooltip->sizeHint() + QSize(10, 10) );
778   tooltip->move(pos - QPoint(0, 20 + tooltip->height()));
779   tooltip->addExtendRect(QRect(pos - QPoint(15, 15), pos + QPoint(15, 15)));
780   
781   currentTooltip = tooltip;
782   currentTooltipMark = markRange;
783   
784   ActiveToolTip::showToolTip(tooltip);
785 }
786
787 void PatchHighlighter::markClicked(KTextEditor::Document* doc, KTextEditor::Mark mark, bool& handled)
788 {
789   if(handled)
790     return;
791   
792   handled = true;
793   
794   if(doc->activeView()) ///This is a workaround, if the cursor is somewhere else, the editor will always jump there when a mark was clicked
795     doc->activeView()->setCursorPosition(KTextEditor::Cursor(mark.line, 0));
796
797   KTextEditor::MovingRange* range = rangeForMark(mark);
798   
799   if(range) {
800     KTextEditor::MovingInterface* moving = dynamic_cast<KTextEditor::MovingInterface*>( doc );
801     Q_ASSERT(moving);
802     
803     QString currentText = doc->text(range->toRange());
804     Diff2::Difference* diff = m_differencesForRanges[range];
805     
806     removeLineMarker(range, diff);
807     
808     QString sourceText;
809     QString targetText;
810     
811     for(int a = 0; a < diff->sourceLineCount(); ++a) {
812       sourceText += diff->sourceLineAt(a)->string();
813       if(!sourceText.endsWith("\n"))
814         sourceText += "\n";
815     }
816     
817     for(int a = 0; a < diff->destinationLineCount(); ++a) {
818       targetText += diff->destinationLineAt(a)->string();
819       if(!targetText.endsWith("\n"))
820         targetText += "\n";
821     }
822     
823     QString replace;
824     QString replaceWith;
825     
826     if(!diff->applied()) {
827       replace = sourceText;
828       replaceWith = targetText;
829     }else {
830       replace = targetText;
831       replaceWith = sourceText;
832     }
833     
834     if(currentText.simplified() != replace.simplified()) {
835       KMessageBox::error(ICore::self()->uiController()->activeMainWindow(), i18n("Could not apply the change: Text should be \"%1\", but is \"%2\".", replace, currentText));
836       return;
837     }
838     
839     diff->apply(!diff->applied());
840     
841     KTextEditor::Cursor start = range->start().toCursor();
842     range->document()->replaceText(range->toRange(), replaceWith);
843     KTextEditor::Range newRange(start, start);
844     
845     uint replaceWithLines = replaceWith.count('\n');
846     newRange.end().setLine(newRange.end().line() +  replaceWithLines);
847     range->setRange(newRange);
848     
849     addLineMarker(range, diff);
850   }
851   
852   {
853     // After applying the change, show the tooltip again, mainly to update an old tooltip
854     delete currentTooltip;
855     bool h = false;
856     markToolTipRequested(doc, mark, QCursor::pos(), h);
857   }
858 }
859
860 KTextEditor::MovingRange* PatchHighlighter::rangeForMark(KTextEditor::Mark mark)
861 {
862     for(QMap< KTextEditor::MovingRange*, Diff2::Difference* >::const_iterator it = m_differencesForRanges.constBegin(); it != m_differencesForRanges.constEnd(); ++it) {
863       if(it.key()->start().line() == mark.line)
864       {
865         return it.key();
866       }
867     }
868     
869     return 0;
870 }
871
872 void PatchHighlighter::markToolTipRequested(KTextEditor::Document* , KTextEditor::Mark mark, QPoint pos, bool& handled)
873 {
874   if(handled)
875     return;
876   
877   handled = true;
878   
879   int myMarksPattern = KTextEditor::MarkInterface::markType22 | KTextEditor::MarkInterface::markType23 | KTextEditor::MarkInterface::markType24 | KTextEditor::MarkInterface::markType25 | KTextEditor::MarkInterface::markType26 | KTextEditor::MarkInterface::markType27;
880   if(mark.type & myMarksPattern) {
881     //There is a mark in this line. Show the old text.
882     KTextEditor::MovingRange* range = rangeForMark(mark);
883     if(range)
884         showToolTipForMark(pos, range);
885   }
886 }
887
888
889 bool PatchHighlighter::isInsertion(Diff2::Difference* diff)
890 {
891     return diff->sourceLineCount() == 0;
892 }
893
894
895 bool PatchHighlighter::isRemoval(Diff2::Difference* diff)
896 {
897     return diff->destinationLineCount() == 0;
898 }
899
900 void PatchHighlighter::textInserted(KTextEditor::Document* doc, KTextEditor::Range range)
901 {
902     if(range == doc->documentRange())
903     {
904       kWarning() << "re-doing";
905       //The document was loaded / reloaded
906     if ( !m_model->differences() )
907         return ;
908     KTextEditor::MovingInterface* moving = dynamic_cast<KTextEditor::MovingInterface*>( doc );
909     if ( !moving )
910         return;
911
912     KTextEditor::MarkInterface* markIface = dynamic_cast<KTextEditor::MarkInterface*>( doc );
913     if( !markIface )
914       return;
915     
916     clear();
917     
918     QColor activeIconColor = QApplication::palette().color(QPalette::Active, QPalette::Highlight);
919     QColor inActiveIconColor = QApplication::palette().color(QPalette::Active, QPalette::Base);
920     
921     KColorScheme scheme(QPalette::Active);
922     
923     QImage tintedInsertion = KIcon("insert-text").pixmap(16, 16).toImage();
924     KIconEffect::colorize(tintedInsertion, scheme.foreground(KColorScheme::NegativeText).color(), 1.0);
925     QImage tintedRemoval = KIcon("edit-delete").pixmap(16, 16).toImage();
926     KIconEffect::colorize(tintedRemoval, scheme.foreground(KColorScheme::NegativeText).color(), 1.0);
927     QImage tintedChange = KIcon("text-field").pixmap(16, 16).toImage();
928     KIconEffect::colorize(tintedChange, scheme.foreground(KColorScheme::NegativeText).color(), 1.0);
929     
930     markIface->setMarkDescription(KTextEditor::MarkInterface::markType22, i18n("Insertion"));
931     markIface->setMarkPixmap(KTextEditor::MarkInterface::markType22, QPixmap::fromImage(tintedInsertion));
932     markIface->setMarkDescription(KTextEditor::MarkInterface::markType23, i18n("Removal"));
933     markIface->setMarkPixmap(KTextEditor::MarkInterface::markType23, QPixmap::fromImage(tintedRemoval));
934     markIface->setMarkDescription(KTextEditor::MarkInterface::markType24, i18n("Change"));
935     markIface->setMarkPixmap(KTextEditor::MarkInterface::markType24, QPixmap::fromImage(tintedChange));
936     
937     markIface->setMarkDescription(KTextEditor::MarkInterface::markType25, i18n("Insertion"));
938     markIface->setMarkPixmap(KTextEditor::MarkInterface::markType25, KIcon("insert-text").pixmap(16, 16));
939     markIface->setMarkDescription(KTextEditor::MarkInterface::markType26, i18n("Removal"));
940     markIface->setMarkPixmap(KTextEditor::MarkInterface::markType26, KIcon("edit-delete").pixmap(16, 16));
941     markIface->setMarkDescription(KTextEditor::MarkInterface::markType27, i18n("Change"));
942     markIface->setMarkPixmap(KTextEditor::MarkInterface::markType27, KIcon("text-field").pixmap(16, 16));
943
944     for ( Diff2::DifferenceList::const_iterator it = m_model->differences() ->constBegin(); it != m_model->differences() ->constEnd(); ++it ) {
945         Diff2::Difference* diff = *it;
946         int line, lineCount;
947         Diff2::DifferenceStringList lines ;
948         
949         if(diff->applied()) {
950           line = diff->destinationLineNumber();
951           lineCount = diff->destinationLineCount();
952           lines = diff->destinationLines();
953         } else {
954           line = diff->sourceLineNumber();
955           lineCount = diff->sourceLineCount();
956           lines = diff->sourceLines();
957         }
958
959         if ( line > 0 )
960             line -= 1;
961         
962         KTextEditor::Cursor c( line, 0 );
963         KTextEditor::Cursor endC( line + lineCount, 0 );
964         if ( doc->lines() <= c.line() )
965             c.setLine( doc->lines() - 1 );
966         if ( doc->lines() <= endC.line() )
967             endC.setLine( doc->lines() );
968
969         if ( endC.isValid() && c.isValid() ) {
970             KTextEditor::MovingRange * r = moving->newMovingRange( KTextEditor::Range(c, endC) );
971             m_ranges << r;
972             
973             m_differencesForRanges[r] = *it;
974
975             addLineMarker(r, diff);
976         }
977     }
978
979     }
980 }
981
982 PatchHighlighter::PatchHighlighter( const Diff2::DiffModel* model, IDocument* kdoc, PatchReviewPlugin* plugin ) throw( QString )
983   : m_doc( kdoc ), m_plugin(plugin), m_model(model)
984 {
985 //     connect( kdoc, SIGNAL( destroyed( QObject* ) ), this, SLOT( documentDestroyed() ) );
986     connect( kdoc->textDocument(), SIGNAL(textInserted(KTextEditor::Document*,KTextEditor::Range)), this, SLOT(textInserted(KTextEditor::Document*,KTextEditor::Range)) );
987     connect( kdoc->textDocument(), SIGNAL( destroyed( QObject* ) ), this, SLOT( documentDestroyed() ) );
988
989     KTextEditor::Document* doc = kdoc->textDocument();
990     if ( doc->lines() == 0 )
991         return ;
992
993     connect(doc, SIGNAL(markToolTipRequested(KTextEditor::Document*,KTextEditor::Mark,QPoint,bool&)), this, SLOT(markToolTipRequested(KTextEditor::Document*,KTextEditor::Mark,QPoint,bool&)));
994     connect(doc, SIGNAL(markClicked(KTextEditor::Document*,KTextEditor::Mark,bool&)), this, SLOT(markClicked(KTextEditor::Document*,KTextEditor::Mark,bool&)));
995     connect(doc, SIGNAL(aboutToDeleteMovingInterfaceContent (KTextEditor::Document*)), this, SLOT(aboutToDeleteMovingInterfaceContent(KTextEditor::Document*)));
996     
997     textInserted(kdoc->textDocument(), kdoc->textDocument()->documentRange());
998 }
999
1000 void PatchHighlighter::removeLineMarker(KTextEditor::MovingRange* range, Diff2::Difference* difference)
1001 {
1002     KTextEditor::MovingInterface* moving = dynamic_cast<KTextEditor::MovingInterface*>( range->document() );
1003     if ( !moving )
1004         return;
1005
1006     KTextEditor::MarkInterface* markIface = dynamic_cast<KTextEditor::MarkInterface*>( range->document() );
1007     if( !markIface )
1008       return;
1009     
1010     markIface->removeMark(range->start().line(), KTextEditor::MarkInterface::markType22);
1011     markIface->removeMark(range->start().line(), KTextEditor::MarkInterface::markType23);
1012     markIface->removeMark(range->start().line(), KTextEditor::MarkInterface::markType24);
1013     markIface->removeMark(range->start().line(), KTextEditor::MarkInterface::markType25);
1014     markIface->removeMark(range->start().line(), KTextEditor::MarkInterface::markType26);
1015     markIface->removeMark(range->start().line(), KTextEditor::MarkInterface::markType27);
1016     
1017     // Remove all ranges that are in the same line (the line markers)
1018     foreach(KTextEditor::MovingRange* r, m_ranges)
1019     {
1020       if(r != range && range->contains(r->toRange()))
1021       {
1022         delete r;
1023         m_ranges.remove(r);
1024       }
1025     }
1026 }
1027
1028 void PatchHighlighter::addLineMarker(KTextEditor::MovingRange* range, Diff2::Difference* diff)
1029 {
1030     KTextEditor::MovingInterface* moving = dynamic_cast<KTextEditor::MovingInterface*>( range->document() );
1031     if ( !moving )
1032         return;
1033
1034     KTextEditor::MarkInterface* markIface = dynamic_cast<KTextEditor::MarkInterface*>( range->document() );
1035     if( !markIface )
1036       return;
1037     
1038     KSharedPtr<KTextEditor::Attribute> t( new KTextEditor::Attribute() );
1039     
1040     bool isOriginalState = diff->applied() == m_plugin->patch()->isAlreadyApplied();
1041     
1042     if(isOriginalState) {
1043       t->setProperty( QTextFormat::BackgroundBrush, QBrush( ColorCache::self()->blendBackground( QColor( 0, 255, 255), 20 ) ) );
1044     }else{
1045       t->setProperty( QTextFormat::BackgroundBrush, QBrush( ColorCache::self()->blendBackground( QColor( 255, 0, 255), 20 ) ) );
1046     }
1047     range->setAttribute( t );
1048     range->setZDepth( -500 );
1049     
1050     
1051     KTextEditor::MarkInterface::MarkTypes mark;
1052     
1053     if(isOriginalState) {
1054       mark = KTextEditor::MarkInterface::markType27;
1055       
1056       if(isInsertion(diff))
1057         mark = KTextEditor::MarkInterface::markType25;
1058       if(isRemoval(diff))
1059         mark = KTextEditor::MarkInterface::markType26;
1060     }else{
1061       mark = KTextEditor::MarkInterface::markType24;
1062       
1063       if(isInsertion(diff))
1064         mark = KTextEditor::MarkInterface::markType22;
1065       if(isRemoval(diff))
1066         mark = KTextEditor::MarkInterface::markType23;
1067     }
1068     
1069     markIface->addMark(range->start().line(), mark);
1070     
1071     
1072     Diff2::DifferenceStringList lines;
1073     if(diff->applied())
1074       lines = diff->destinationLines();
1075     else
1076       lines = diff->sourceLines();
1077     
1078     for(int a = 0; a < lines.size(); ++a) {
1079       Diff2::DifferenceString* line = lines[a];
1080       int currentPos = 0;
1081       QString string = line->string();
1082       
1083       Diff2::MarkerList markers = line->markerList();
1084
1085       for(int b = 0; b < markers.size(); ++b) { 
1086         if(markers[b]->type() == Diff2::Marker::End)
1087         {
1088           if(currentPos != 0 || markers[b]->offset() != string.size())
1089           {
1090             KTextEditor::MovingRange * r2 = moving->newMovingRange( KTextEditor::Range( KTextEditor::Cursor(a + range->start().line(), currentPos), KTextEditor::Cursor(a + range->start().line(), markers[b]->offset()) ) );
1091             m_ranges << r2;
1092             
1093             KSharedPtr<KTextEditor::Attribute> t( new KTextEditor::Attribute() );
1094
1095             t->setProperty( QTextFormat::BackgroundBrush, QBrush( ColorCache::self()->blendBackground( QColor( 255, 0, 0), 70 ) ) );
1096             r2->setAttribute( t );
1097             r2->setZDepth( -600 );
1098           }
1099         }
1100         currentPos = markers[b]->offset();
1101       }
1102     }
1103 }
1104
1105 void PatchHighlighter::clear()
1106 {
1107     if(m_ranges.empty())
1108       return;
1109
1110     KTextEditor::MovingInterface* moving = dynamic_cast<KTextEditor::MovingInterface*>( m_doc->textDocument() );
1111     if ( !moving )
1112         return;
1113
1114     KTextEditor::MarkInterface* markIface = dynamic_cast<KTextEditor::MarkInterface*>( m_doc->textDocument() );
1115     if( !markIface )
1116       return;
1117
1118     QHash< int, KTextEditor::Mark* > marks = markIface->marks();
1119     foreach(int line, marks.keys()) {
1120       markIface->removeMark(line, KTextEditor::MarkInterface::markType22);
1121       markIface->removeMark(line, KTextEditor::MarkInterface::markType23);
1122       markIface->removeMark(line, KTextEditor::MarkInterface::markType24);
1123       markIface->removeMark(line, KTextEditor::MarkInterface::markType25);
1124       markIface->removeMark(line, KTextEditor::MarkInterface::markType26);
1125       markIface->removeMark(line, KTextEditor::MarkInterface::markType27);
1126     }
1127     
1128     qDeleteAll(m_ranges);
1129     m_ranges.clear();
1130     m_differencesForRanges.clear();
1131 }
1132
1133 PatchHighlighter::~PatchHighlighter()
1134 {
1135     clear();
1136 }
1137
1138 IDocument* PatchHighlighter::doc() {
1139     return m_doc;
1140 }
1141
1142 void PatchHighlighter::documentDestroyed() {
1143     kDebug() << "document destroyed";
1144     m_ranges.clear();
1145     m_differencesForRanges.clear();
1146 }
1147
1148 void PatchReviewPlugin::removeHighlighting( const KUrl& file ) {
1149     if ( file.isEmpty() ) {
1150         ///Remove all highlighting
1151         qDeleteAll(m_highlighters);
1152         m_highlighters.clear();
1153     } else {
1154         HighlightMap::iterator it = m_highlighters.find( file );
1155         if ( it != m_highlighters.end() ) {
1156             delete * it;
1157             m_highlighters.erase( it );
1158         }
1159     }
1160 }
1161
1162 void PatchReviewPlugin::notifyPatchChanged()
1163 {
1164     kDebug() << "notifying patch change: " << m_patch->file();
1165     m_updateKompareTimer->start(500);
1166 }
1167
1168 void PatchReviewPlugin::showPatch()
1169 {
1170     startReview(m_patch, OpenAndRaise);
1171 }
1172
1173 void PatchReviewPlugin::forceUpdate()
1174 {
1175   m_patch->update();
1176   
1177   notifyPatchChanged();
1178 }
1179
1180 void PatchReviewPlugin::updateKompareModel() {
1181   
1182     kDebug() << "updating model";
1183     try {
1184         m_modelList.reset( 0 );
1185         delete m_diffSettings;
1186         removeHighlighting();
1187         
1188         emit patchChanged();
1189
1190         if ( m_patch->file().isEmpty() )
1191             return;
1192         
1193         qRegisterMetaType<const Diff2::DiffModel*>( "const Diff2::DiffModel*" );
1194         m_diffSettings = new DiffSettings( 0 );
1195         m_kompareInfo.reset( new Kompare::Info() );
1196         m_kompareInfo->localDestination=m_patch->file().toLocalFile();
1197         m_kompareInfo->localSource=m_patch->baseDir().toLocalFile();
1198         
1199         m_modelList.reset(new Diff2::KompareModelList( m_diffSettings.data(), new QWidget, this ));
1200         m_modelList->slotKompareInfo(m_kompareInfo.get());
1201         
1202         try {
1203             if ( !m_modelList->openDirAndDiff() )
1204             {
1205 #if 0
1206                 // Don't error out on empty files, as those are valid diffs too
1207                 if(QFileInfo(m_patch->file().toLocalFile()).size() != 0)
1208                     throw "could not open diff " + m_patch->file().prettyUrl() + " on " + m_patch->baseDir().prettyUrl();
1209 #endif
1210             }
1211          } catch ( const QString & str ) {
1212             throw;
1213         } catch ( ... ) {
1214             throw QString( "lib/libdiff2 crashed, memory may be corrupted. Please restart kdevelop." );
1215         }
1216
1217         emit patchChanged();
1218         
1219         for(int i=0; i<m_modelList->modelCount(); i++) {
1220             const Diff2::DiffModel* model=m_modelList->modelAt(i);
1221             for(int j=0; j<model->differences()->count(); j++) {
1222                 model->differences()->at(j)->apply(m_patch->isAlreadyApplied());
1223             }
1224         }
1225
1226         highlightPatch();
1227
1228         return;
1229     } catch ( const QString & str ) {
1230         KMessageBox::error(0, str, i18n("Kompare Model Update"));
1231     } catch ( const char * str ) {
1232         KMessageBox::error(0, str, i18n("Kompare Model Update"));
1233     }
1234     m_modelList.reset( 0 );
1235     m_kompareInfo.reset( 0 );
1236     delete m_diffSettings;
1237     
1238     emit patchChanged();
1239 }
1240
1241 K_PLUGIN_FACTORY(KDevProblemReporterFactory, registerPlugin<PatchReviewPlugin>(); )
1242 K_EXPORT_PLUGIN(KDevProblemReporterFactory(KAboutData("kdevpatchreview","kdevpatchreview", ki18n("Patch Review"), "0.1", ki18n("Highlights code affected by a patch"), KAboutData::License_GPL)))
1243
1244 class PatchReviewToolViewFactory : public KDevelop::IToolViewFactory
1245 {
1246 public:
1247     PatchReviewToolViewFactory(PatchReviewPlugin *plugin): m_plugin(plugin) {}
1248
1249     virtual QWidget* create(QWidget *parent = 0)
1250     {
1251         return m_plugin->createToolView(parent);
1252     }
1253
1254     virtual Qt::DockWidgetArea defaultPosition()
1255     {
1256         return Qt::BottomDockWidgetArea;
1257     }
1258
1259     virtual QString id() const
1260     {
1261         return "org.kdevelop.PatchReview";
1262     }
1263
1264 private:
1265     PatchReviewPlugin *m_plugin;
1266 };
1267
1268 PatchReviewPlugin::~PatchReviewPlugin()
1269 {
1270     removeHighlighting();
1271     delete m_patch;
1272 }
1273
1274 void PatchReviewPlugin::registerPatch(IPatchSource::Ptr patch)
1275 {
1276   if(!m_knownPatches.contains(patch)) {
1277     m_knownPatches << patch;
1278     connect(patch, SIGNAL(destroyed(QObject*)), SLOT(clearPatch(QObject*)));
1279   }
1280 }
1281
1282 void PatchReviewPlugin::clearPatch(QObject* _patch)
1283 {
1284   kDebug() << "clearing patch" << _patch << "current:" << (QObject*)m_patch;
1285   IPatchSource::Ptr patch((IPatchSource*)_patch);
1286   m_knownPatches.removeAll(patch);
1287   m_knownPatches.removeAll(0);
1288   
1289   if(patch == m_patch) {
1290     kDebug() << "is current patch";
1291     if(!m_knownPatches.empty())
1292       setPatch(m_knownPatches.first());
1293     else
1294       setPatch(IPatchSource::Ptr(new LocalPatchSource));
1295   }
1296 }
1297
1298 #if 0
1299 #if HAVE_KOMPARE
1300 void showDiff(const KDevelop::VcsDiff& d)
1301 {
1302     ICore::self()->uiController()->switchToArea("review", KDevelop::IUiController::ThisWindow);
1303     foreach(const VcsLocation& l, d.leftTexts().keys())
1304     {
1305         KUrl to;
1306         if(d.rightTexts().contains(l))
1307         {
1308             KTemporaryFile temp2;
1309             temp2.setSuffix("2.patch");
1310             //FIXME: don't leak
1311             temp2.setAutoRemove(false);
1312             temp2.open();
1313             QTextStream t2(&temp2);
1314             t2 << d.rightTexts()[l];
1315             temp2.close();
1316             to=temp2.fileName();
1317         }
1318         else
1319             to=l.localUrl();
1320         
1321         KUrl fakeUrl(to);
1322         fakeUrl.setScheme("kdevpatch");
1323         
1324         IDocumentFactory* docf=ICore::self()->documentController()->factory("text/x-patch");
1325         IDocument* doc=docf->create(fakeUrl, ICore::self());
1326         IPatchDocument* pdoc=dynamic_cast<IPatchDocument*>(doc);
1327         
1328         Q_ASSERT(pdoc);
1329         ICore::self()->documentController()->openDocument(doc);
1330         pdoc->setDiff(d.leftTexts()[l], to);
1331     }
1332 }
1333 #endif
1334 #endif
1335
1336 void PatchReviewPlugin::cancelReview()
1337 {
1338   if(m_patch) {
1339     m_modelList.reset( 0 );
1340     m_patch->cancelReview();
1341
1342     emit patchChanged();
1343     
1344     delete m_patch;
1345     
1346     Sublime::MainWindow* w = dynamic_cast<Sublime::MainWindow*>(ICore::self()->uiController()->activeMainWindow());
1347     if(w->area()->objectName() == "review") {
1348       w->area()->clearViews();
1349       ICore::self()->uiController()->switchToArea("code", KDevelop::IUiController::ThisWindow);
1350     }
1351   }
1352 }
1353
1354 void PatchReviewPlugin::finishReview(QList< KUrl > selection)
1355 {
1356   if(m_patch) {
1357     if(!m_patch->finishReview(selection))
1358       return;
1359     m_modelList.reset( 0 );
1360     
1361     emit patchChanged();
1362     
1363     if(!dynamic_cast<LocalPatchSource*>(m_patch.data()))
1364       delete m_patch;
1365     
1366     Sublime::MainWindow* w = dynamic_cast<Sublime::MainWindow*>(ICore::self()->uiController()->activeMainWindow());
1367     if(w->area()->objectName() == "review") {
1368       w->area()->clearViews();
1369       ICore::self()->uiController()->switchToArea("code", KDevelop::IUiController::ThisWindow);
1370     }
1371   }
1372 }
1373
1374 void PatchReviewPlugin::startReview(IPatchSource* patch, IPatchReview::ReviewMode mode)
1375 {
1376   Q_UNUSED(mode);
1377   setPatch(patch);
1378   QMetaObject::invokeMethod(this, "updateReview", Qt::QueuedConnection);
1379 }
1380
1381 void PatchReviewPlugin::updateReview()
1382 {
1383   if(!m_patch)
1384     return;
1385   
1386   m_updateKompareTimer->stop();
1387   updateKompareModel();
1388
1389   Sublime::MainWindow* w = dynamic_cast<Sublime::MainWindow*>(ICore::self()->uiController()->activeMainWindow());
1390   if (w->area()->objectName() != "review")
1391     ICore::self()->uiController()->switchToArea("review", KDevelop::IUiController::ThisWindow);
1392
1393   if(!w->area()->workingSet().startsWith("review"))
1394     w->area()->setWorkingSet("review");
1395   
1396   if(!m_modelList.get())
1397     return;
1398
1399   // list of opened documents to prevent flicker
1400   QMap<KUrl, IDocument*> documents;
1401   foreach(IDocument* doc, ICore::self()->documentController()->openDocuments()) {
1402     documents[doc->url()] = doc;
1403   }
1404   
1405   IDocument* futureActiveDoc = 0;
1406   //Open the diff itself
1407 #ifdef HAVE_KOMPARE
1408   KUrl fakeUrl(m_patch->file());
1409   fakeUrl.setScheme("kdevpatch");
1410   IDocumentFactory* docf=ICore::self()->documentController()->factory("text/x-patch");
1411   IDocument* doc=docf->create(fakeUrl, ICore::self());
1412   IPatchDocument* pdoc=dynamic_cast<IPatchDocument*>(doc);
1413   
1414   Q_ASSERT(pdoc);
1415   futureActiveDoc = ICore::self()->documentController()->openDocument(doc);
1416   //TODO: close kompare doc if available
1417 #else
1418   if (!documents.contains(m_patch->file())) {
1419     futureActiveDoc = ICore::self()->documentController()->openDocument(m_patch->file());
1420   } else {
1421     documents.remove(m_patch->file());
1422   }
1423 #endif
1424
1425   if(m_modelList->modelCount() < maximumFilesToOpenDirectly) {
1426     //Open all relates files
1427     for(int a = 0; a < m_modelList->modelCount(); ++a) {
1428       
1429       KUrl absoluteUrl = m_patch->baseDir();
1430       KUrl url(m_modelList->modelAt(a)->destination());
1431       
1432       if(url.isRelative())
1433         absoluteUrl.addPath(url.path());
1434       else
1435         absoluteUrl = url;
1436       
1437       if(QFileInfo(absoluteUrl.path()).exists() && absoluteUrl.path() != "/dev/null")
1438       {
1439         if (!documents.contains(absoluteUrl)) {
1440           ICore::self()->documentController()->openDocument(absoluteUrl);
1441         } else {
1442           documents.remove(absoluteUrl);
1443         }
1444         seekHunk(true, absoluteUrl); //Jump to the first changed position
1445       }else{
1446         // Maybe the file was deleted
1447         kDebug() << "could not open" << absoluteUrl << "because it doesn't exist";
1448       }
1449     }
1450   }
1451
1452   // close documents we didn't open again
1453   foreach(IDocument* doc, documents.values()) {
1454     doc->close();
1455   }
1456
1457   Q_ASSERT(futureActiveDoc);
1458   ICore::self()->documentController()->activateDocument(futureActiveDoc);
1459
1460   bool b = ICore::self()->uiController()->findToolView(i18n("Patch Review"), m_factory);
1461   Q_ASSERT(b);
1462 }
1463
1464 void PatchReviewPlugin::setPatch(IPatchSource* patch)
1465 {
1466   if(m_patch) {
1467     disconnect(m_patch, SIGNAL(patchChanged()), this, SLOT(notifyPatchChanged()));
1468   }
1469   m_patch = patch;
1470
1471   if(m_patch) {
1472     kDebug() << "setting new patch" << patch->name() << "with file" << patch->file();
1473     registerPatch(patch);
1474
1475     connect(m_patch, SIGNAL(patchChanged()), this, SLOT(notifyPatchChanged()));
1476   }
1477   
1478   notifyPatchChanged();
1479 }
1480
1481 PatchReviewPlugin::PatchReviewPlugin(QObject *parent, const QVariantList &)
1482 : KDevelop::IPlugin(KDevProblemReporterFactory::componentData(), parent), 
1483   m_patch(0), m_factory(new PatchReviewToolViewFactory(this))
1484 {
1485     KDEV_USE_EXTENSION_INTERFACE( KDevelop::IPatchReview )
1486   
1487     core()->uiController()->addToolView(i18n("Patch Review"), m_factory);
1488     setXMLFile("kdevpatchreview.rc");
1489
1490     connect(ICore::self()->documentController(), SIGNAL(documentClosed(KDevelop::IDocument*)), this, SLOT(documentClosed(KDevelop::IDocument*)));
1491     connect(ICore::self()->documentController(), SIGNAL(textDocumentCreated(KDevelop::IDocument*)), this, SLOT(textDocumentCreated(KDevelop::IDocument*)));
1492
1493     m_updateKompareTimer = new QTimer( this );
1494     m_updateKompareTimer->setSingleShot( true );
1495     connect( m_updateKompareTimer, SIGNAL( timeout() ), this, SLOT( updateKompareModel() ) );
1496     
1497     setPatch(IPatchSource::Ptr(new LocalPatchSource));
1498 }
1499
1500 void PatchReviewPlugin::documentClosed(IDocument* doc)
1501 {
1502     removeHighlighting(doc->url());
1503 }
1504
1505 void PatchReviewPlugin::textDocumentCreated(IDocument* doc)
1506 {
1507     addHighlighting( doc->url(), doc );
1508 }
1509
1510 void PatchReviewPlugin::unload()
1511 {
1512     core()->uiController()->removeToolView(m_factory);
1513
1514     KDevelop::IPlugin::unload();
1515 }
1516
1517 QWidget* PatchReviewPlugin::createToolView(QWidget* parent)
1518 {
1519     return new PatchReviewToolView(parent, this);
1520 }
1521
1522 void PatchReviewPlugin::exporterSelected(QAction* action)
1523 {
1524     IPlugin* exporter = qobject_cast<IPlugin*>(action->data().value<QObject*>());
1525     
1526     if(exporter) {
1527         qDebug() << "exporting patch" << exporter << action->text();
1528         exporter->extension<IPatchExporter>()->exportPatch(patch());
1529     }
1530 }
1531
1532
1533 #if 0
1534 void PatchReviewPlugin::determineState() {
1535     LocalPatchSourcePointer lpatch = m_patch;
1536     if ( !lpatch ) {
1537         kDebug() <<"determineState(..) could not lock patch";
1538     }
1539     try {
1540         if ( lpatch->filename.isEmpty() )
1541             throw "state can only be determined for file-patches";
1542
1543         KUrl fileUrl = lpatch->filename;
1544
1545         {
1546             K3Process proc;                                                                                                        
1547             ///Try to apply, if it works, the patch is not applied                                                                 
1548             QString cmd =  "patch --dry-run -s -f -i " + fileUrl.toLocalFile();                                                    
1549             proc << splitArgs( cmd );                                                                                              
1550                                                                                                                                    
1551             kDebug() << "calling " << cmd;                                                                                         
1552                                                                                                                                    
1553             if ( !proc.start( K3Process::Block ) )                                                                                 
1554                 throw "could not start process";                                                                                   
1555                                                                                                                                    
1556             if ( !proc.normalExit() )                                                                                              
1557                 throw "process did not exit normally";                                                                             
1558                                                                                                                                    
1559             kDebug() << "exit-status:" << proc.exitStatus();                                                                       
1560
1561             if ( proc.exitStatus() == 0 ) {
1562 //                 lpatch->state = LocalPatchSource::NotApplied;
1563                 return;
1564             }
1565         }
1566
1567         {
1568             ///Try to revert, of it works, the patch is applied
1569             K3Process proc;
1570             QString cmd =  "patch --dry-run -s -f -i --reverse " + fileUrl.toLocalFile();
1571             proc << splitArgs( cmd );
1572
1573             kDebug() << "calling " << cmd;
1574
1575             if ( !proc.start( K3Process::Block ) )
1576                 throw "could not start process";
1577
1578             if ( !proc.normalExit() )
1579                 throw "process did not exit normally";
1580
1581             kDebug() << "exit-status:" << proc.exitStatus();
1582
1583             if ( proc.exitStatus() == 0 ) {
1584 //                 lpatch->state = LocalPatchSource::Applied;
1585                 return;
1586             }
1587         }
1588     } catch ( const QString& str ) {
1589         kWarning() <<"Error:" << str;
1590     } catch ( const char* str ) {
1591         kWarning() << "Error:" << str;
1592     }
1593
1594 //     lpatch->state = LocalPatchSource::Unknown;
1595 }
1596 #endif
1597 #include "patchreview.moc"
1598
1599 // kate: space-indent on; indent-width 2; tab-width 2; replace-tabs on