Don't keep compiling/run if something failed.
[kdevelopdvcssupport.git] / plugins / teamwork / editpatch.cpp
blob8921d2365f41e59e76f2c023346ac096c898efc0
1 /***************************************************************************
2 Copyright 2006 David Nolden <david.nolden.kdevelop@art-master.de>
3 ***************************************************************************/
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 ***************************************************************************/
14 #include "editpatch.h"
16 #include <kmimetype.h>
17 #include <klineedit.h>
18 #include <kmimetypechooser.h>
19 #include <kmimetypetrader.h>
20 #include <k3process.h>
21 #include <krandom.h>
22 #include <QTabWidget>
23 #include <QMenu>
24 #include <QFile>
25 #include <QTimer>
26 #include <QMutexLocker>
27 #include <QPersistentModelIndex>
28 //#include <lib/kdevappintterface.h>
29 #include "patchesmanager.h"
30 #include <kfiledialog.h>
31 #include "kdevteamwork.h"
32 #include <interfaces/idocument.h>
33 //#include "kdevdiffinterface.h"
34 #include "kdevteamworkplugin.h"
35 #include "collaborationmanager.h"
36 #include <QStandardItemModel>
37 #include <interfaces/icore.h>
38 #include "serializationutils.h"
39 #include "kdevteamwork_user.h"
40 #include "messagemanager.h"
41 #include "lib/network/messagetypeset.h"
42 #include "kdevteamwork_helpers.h"
43 #include "teamworkfoldermanager.h"
44 #include <kde_terminal_interface.h>
45 #include <kparts/part.h>
46 #include <kparts/factory.h>
47 #include <kdialog.h>
49 #include "lib/libdiff2/komparemodellist.h"
50 #include "lib/libdiff2/kompare.h"
51 #include <kmessagebox.h>
52 #include <QMetaType>
53 #include <QVariant>
54 #include "lib/libdiff2/diffsettings.h"
55 #include <ktexteditor/cursor.h>
56 #include <ktexteditor/document.h>
57 #include <ktexteditor/view.h>
58 #include <ktexteditor/markinterface.h>
59 #include <ktexteditor/smartinterface.h>
60 #include <interfaces/idocumentcontroller.h>
62 #include "kdevteamwork_client.h"
64 /* Exclude this file from doublequote_chars check as krazy doesn't understand
65 std::string*/
66 //krazy:excludeall=doublequote_chars
68 ///Whether arbitrary exceptions that occurred while diff-parsing within the library should be caught
69 #define CATCHLIBDIFF
71 using namespace KDevelop;
73 QString getDefaultExtension( const QStringList& patterns );
75 Q_DECLARE_METATYPE( const Diff2::DiffModel* )
77 EditPatch::EditPatch( PatchesManager* parent, LocalPatchSourcePointer patch, bool local ) : QObject( parent ), SafeLogger( parent->teamwork() ->logger(), "EditPatch: " ), m_actionState( LocalPatchSource::Unknown ), m_parent( parent ), m_editingPatch( patch ), m_editPatchLocal( local ), m_editDlg( 0 ), m_reversed( false ), m_started( false ), m_isSource( false ) {
78 m_updateKompareTimer = new QTimer( this );
79 m_updateKompareTimer->setSingleShot( true );
80 connect( m_updateKompareTimer, SIGNAL( timeout() ), this, SLOT( updateKompareModel() ) );
81 showEditDialog();
82 if ( m_editingPatch )
83 fillEditFromPatch();
84 m_started = true;
87 EditPatch::~EditPatch() {
88 removeHighlighting();
91 void EditPatch::slotEditOk() {
93 LocalPatchSourcePointer p = patchFromEdit();
95 if ( p && !m_parent->hasPatch( p ) )
96 m_parent->addPatch( p );
97 //emit dialogClosed( this );
98 emit stateChanged( this );
101 void EditPatch::slotEditCancel() {
102 //emit dialogClosed( this );
105 LocalPatchSourcePointer EditPatch::patchFromEdit() {
106 if ( !m_editDlg )
107 return 0;
108 LocalPatchSourcePointer::Locked ps;
110 if ( m_editingPatch )
111 ps = m_editingPatch;
112 else {
113 ps = new LocalPatchSource();
114 ps->userIdentity = m_parent->teamwork() ->currentUserIdentity();
117 if ( !ps ) {
118 err() << "could not lock edited patch";
119 return 0;
122 LocalPatchSource& ls( *ps );
123 ls.name = ~m_editPatch.name->text();
124 ls.unApplyCommand = ~m_editPatch.unapplyCommand->text();
125 ls.type = ~m_editPatch.type->text();
126 if( !m_editPatch.filename->url().toLocalFile().isEmpty() )
127 ls.filename = ~TeamworkFolderManager::teamworkRelative( ( m_editPatch.filename->url() ) );
128 else
129 ls.filename = "";
130 ls.description = ~m_editPatch.description->toPlainText();
131 ls.dependencies = ~m_editPatch.dependencies->text();
132 ls.command = ~m_editPatch.command->text();
133 ls.applyCommand = ~m_editPatch.applyCommand->text();
134 ls.state = editState();
135 ls.author = ~m_editPatch.author->text();
136 QString txt = m_editPatch.accessRights->currentText();
138 ls.access = LocalPatchSource::accessFromString( ~txt );
140 m_editingPatch = ps;
141 return ps;
144 void EditPatch::fillEditFromPatch() {
145 out( Logger::Debug ) << "filling edit from patch";
147 LocalPatchSourcePointer _patch = m_editingPatch;
148 bool local = m_editPatchLocal;
149 LocalPatchSourcePointer::Locked l = _patch;
150 if ( !m_editDlg || !_patch || !l )
151 return ;
152 LocalPatchSource& patch( *l );
153 m_editPatchLocal = local;
155 m_editPatch.name->setText( ~patch.name );
156 m_editPatch.unapplyCommand->setText( ~patch.unApplyCommand );
157 m_editPatch.type->setText( ~patch.type );
158 if( !patch.filename.empty() )
159 m_editPatch.filename->setUrl( TeamworkFolderManager::teamworkAbsolute( ~patch.filename ) );
160 else
161 m_editPatch.filename->setUrl( KUrl() );
162 m_editPatch.description->setText( ~patch.description );
163 m_editPatch.dependencies->setText( ~patch.dependencies );
164 m_editPatch.command->setText( ~patch.command );
165 m_editPatch.applyCommand->setText( ~patch.applyCommand );
166 m_editPatch.author->setText( ~patch.author );
168 int stateIndex = 0;
170 switch ( patch.state ) {
171 case LocalPatchSource::Applied:
172 stateIndex = 1;
173 break;
174 case LocalPatchSource::NotApplied:
175 stateIndex = 2;
176 break;
177 default:
178 stateIndex = 0;
181 m_editPatch.state->setCurrentIndex( stateIndex );
182 slotStateChanged();
184 if ( !patch.filename.empty() )
185 m_editPatch.tabWidget->setCurrentIndex( m_editPatch.tabWidget->indexOf( m_editPatch.fileTab ) );
186 else
187 m_editPatch.tabWidget->setCurrentIndex( m_editPatch.tabWidget->indexOf( m_editPatch.commandTab ) );
189 m_editPatch.accessRights->setEditText( ~patch.accessAsString() );
191 UserIdentity id = l->userIdentity;
192 if ( id ) {
193 m_editPatch.userButton->setEnabled( true );
194 m_editPatch.userButton->setText( ~id.name() );
195 } else {
196 m_editPatch.userButton->setEnabled( false );
199 updateByType();
200 m_updateKompareTimer->start( 400 );
203 void EditPatch::slotFileNameEdited() {
204 m_updateKompareTimer->start( 100 );
207 void EditPatch::slotCommandEdited() {
208 m_updateKompareTimer->start( 100 );
211 void EditPatch::slotEditMimeType( const QString& /*str*/ ) {
212 if ( !m_started )
213 return ;
214 m_editingPatch = patchFromEdit();
215 updateByType();
216 m_updateKompareTimer->start( 100 );
219 void EditPatch::slotEditCommandChanged( const QString& /*str*/ ) {
220 if ( !m_started )
221 return ;
222 m_editPatch.filename->setUrl( QString( "" ) );
223 emit stateChanged( this );
225 //m_updateKompareTimer->start( 100 );
228 void EditPatch::slotUserButton() {
229 LocalPatchSourcePointer::Locked patch = patchFromEdit();
230 try {
231 if ( !patch )
232 throw "could not lock patch";
233 UserPointer u;
235 TeamworkClientPointer::Locked client = m_parent->teamwork() ->client();
236 if ( !client )
237 throw "no client";
238 u = client->findUser( patch->userIdentity );
240 if ( !u )
241 throw "could not get user";
242 if ( !u.cast<KDevTeamworkUser>() )
243 throw "user has wrong type";
245 m_parent->teamwork() ->showUserInfo( u.cast<KDevTeamworkUser>() );
246 } catch ( const QString & str ) {
247 err() << "slotUserButton():" << str;
248 } catch ( const char * str ) {
249 err() << "slotUserButton():" << str;
253 void EditPatch::slotApplyEditPatch() {
254 LocalPatchSourcePointer patch = patchFromEdit();
255 if ( m_editPatchLocal ) {
256 LocalPatchSourcePointer::Locked l = m_editingPatch;
257 if ( !m_parent->hasPatch( patch ) )
258 m_parent->addPatch( patch );
260 if ( l && l->filename.empty() ) {
261 ///The patch has to be serialized first before it can be applied
263 SafeSharedPtr<PatchMessage>::Locked l = getPatchMessage( PatchRequestData::Apply );
264 if ( !l )
265 throw "could not get the patch-message";
266 m_parent->processMessage( l.data() );
267 } else {
268 apply();
269 fillEditFromPatch();
271 } else {
272 ///Request the patch from the peer and apply it
273 try {
274 LocalPatchSourcePointer::Locked lpatch = patch;
275 if ( !lpatch )
276 throw "could not lock patch-source";
278 UserPointer::Locked user = lpatch->user();
279 if ( !user )
280 throw "the patch has no associated user";
281 if ( !user->online() )
282 throw "the user is not online";
284 SessionPointer session = user->online().session();
285 if ( !session )
286 throw "the session could not be acquired";
288 MessagePointer::Locked mp = new PatchRequestMessage( globalMessageTypeSet(), lpatch, m_parent->teamwork(), PatchRequestData::Apply );
289 session.unsafe() ->send( mp );
290 m_parent->teamwork() ->addMessageToList( mp );
291 } catch ( const char * str ) {
292 err() << "error in slotApplyEditPatch:" << str;
295 emit stateChanged( this );
298 void EditPatch::slotUnapplyEditPatch() {
299 if ( !m_editPatchLocal )
300 return ;
302 LocalPatchSourcePointer patch = patchFromEdit();
303 if ( !m_parent->hasPatch( patch ) )
304 m_parent->addPatch( patch );
305 LocalPatchSourcePointer::Locked l = patch;
306 if ( l ) {
307 if ( !l->command.empty() ) {
308 int result = KMessageBox::warningContinueCancel( m_editDlg, i18n( "You are about to unapply a dynamic patch created by the command \"%1\". That can be very dangerous. If the same command will not produce the same text after the unapply, the patch cannot be reapplied and all data may be lost. \nRecommendation: Make the patch a file-patch before unapplying it." ), i18n("Warning") );
309 if ( result != KMessageBox::Continue )
310 return ;
313 apply( true );
314 fillEditFromPatch();
316 emit stateChanged( this );
319 SafeSharedPtr<PatchMessage> EditPatch::getPatchMessage( PatchRequestData::RequestType /*type*/ ) {
320 try {
321 LocalPatchSourcePointer::Locked lpatch = patchFromEdit();
322 if ( !lpatch )
323 throw "could not lock patch";
325 UserPointer::Locked fakeUser( new KDevTeamworkUser( UserPointer::Locked( new User( "local" ) ) ) );
326 SafeSharedPtr<Teamwork::FakeSession>::Locked fakeSession( new Teamwork::FakeSession( fakeUser, m_parent->teamwork() ->logger(), globalMessageTypeSet(), 0 ) );
327 fakeUser->setSession( ( SessionInterface* ) fakeSession );
329 SafeSharedPtr<PatchRequestMessage>::Locked request = new PatchRequestMessage( globalMessageTypeSet(), lpatch, m_parent->teamwork() );
331 request->info().setSession( ( SessionInterface* ) fakeSession );
333 m_parent->processMessage( request.data() );
335 MessagePointer::Locked msg = fakeSession->getFirstMessage();
336 if ( !msg.cast<PatchMessage>() ) {
337 if ( KDevSystemMessagePointer::Locked l = msg.cast<KDevSystemMessage>() ) {
338 throw QString( "Requesting the message from self failed: %1: %2" ).arg( l->messageAsString() ).arg( l->text() );
339 } else {
340 throw QString( "answer-message is not of type PatchMessage: " ) + ( msg ? msg->name() : "" );
344 return msg.cast<PatchMessage>().data();
345 } catch ( const char * str ) {
346 err() << "getPatchMessage: error:" << str;
347 } catch ( const QString & str ) {
348 err() << "getPatchMessage: error:" << str;
349 } catch ( const TeamworkError & error ) {
350 err() << "getPatchMessage: failed to serialize and deserialize the patch locally:" << error.what();
352 return 0;
355 KUrl EditPatch::getPatchFile( bool temp ) {
356 try {
357 LocalPatchSourcePointer patch = patchFromEdit();
358 if ( !m_parent->hasPatch( patch ) )
359 m_parent->addPatch( patch );
360 SafeSharedPtr<PatchMessage> smsg = getPatchMessage( PatchRequestData::View );
361 SafeSharedPtr<PatchMessage>::Locked msg = smsg;
362 if ( !msg )
363 throw QString( "could not serialize patch-message" );
364 LocalPatchSourcePointer::Locked patchInfo = m_editingPatch;
365 if ( !patchInfo )
366 throw QString( "could not lock patchi-info" );
368 QString mimeName = ~patchInfo->type;
369 if ( mimeName.trimmed().isEmpty() )
370 mimeName = "text/x-diff";
372 KMimeType::Ptr mime = KMimeType::mimeType( mimeName );
373 if ( !mime )
374 throw QString( "Error in kdelibs: could not create mime-type" ); ///according to the documentation this should never happen, but it does
376 QString fileName = ~patchInfo->name;
377 if ( !patchInfo->filename.empty() )
378 fileName += "_" + QFileInfo( ~patchInfo->filename ).fileName();
379 else
380 fileName += getDefaultExtension( mime->patterns() );
382 out( Logger::Debug ) << "getPatchFile() creating file for patch:" << fileName;
384 QString subFolder = "local";
385 if ( temp )
386 subFolder = "temp";
388 KUrl filePath = TeamworkFolderManager::createUniqueFile( "patches/" + subFolder, fileName );
389 if ( temp )
390 TeamworkFolderManager::registerTempItem( filePath );
393 ///@todo use NetAccess
394 QFile file( filePath.toLocalFile() );
396 file.open( QIODevice::WriteOnly );
397 if ( !file.isOpen() )
398 throw QString( "could not open %1" ).arg( filePath.toLocalFile() );
400 if ( file.write( msg->data() ) != msg->data().size() )
401 throw "writing the file " + filePath.toLocalFile() + " failed";
403 return filePath;
404 } catch ( const QString & str ) {
405 err() << "getPatchFile(" << temp << ") failed:" << str;
406 return QString();
407 } catch ( const char * str ) {
408 err() << "getPatchFile(" << temp << ") failed:" << str;
409 return QString();
412 void EditPatch::slotShowEditPatch() {
413 LocalPatchSourcePointer patch = patchFromEdit();
414 if ( !m_parent->hasPatch( patch ) )
415 m_parent->addPatch( patch );
416 try {
418 if ( m_editPatchLocal ) {
419 SafeSharedPtr<PatchMessage>::Locked l = getPatchMessage( PatchRequestData::View );
420 if ( !l )
421 throw "could not get the patch-message";
422 m_parent->processMessage( l.data() );
423 } else {
424 ///Request the patch from the peer and show it
425 LocalPatchSourcePointer::Locked lpatch = patch;
426 if ( !lpatch )
427 throw "could not lock patch-source";
429 UserPointer::Locked user = lpatch->user();
430 if ( !user )
431 throw "the patch has no associated user";
432 if ( !user->online() )
433 throw "the user is not online";
435 SessionPointer session = user->online().session();
436 if ( !session )
437 throw "the session could not be acquired";
439 MessagePointer::Locked mp = new PatchRequestMessage( globalMessageTypeSet(), lpatch, m_parent->teamwork() );
440 session.unsafe() ->send( mp );
441 m_parent->teamwork() ->addMessageToList( mp );
443 } catch ( const QString & str ) {
444 err() << "error in slotShowEditPatch:" << str;
445 } catch ( const char * str ) {
446 err() << "error in slotShowEditPatch:" << str;
450 void EditPatch::slotStateChanged() {
451 if ( !m_started )
452 return ;
453 if ( !m_editDlg )
454 return ;
455 patchFromEdit();
457 switch ( editState() ) {
458 case LocalPatchSource::Unknown:
459 m_editPatch.applyButton->setEnabled( true );
460 m_editPatch.unApplyButton->setEnabled( true );
461 break;
462 case LocalPatchSource::Applied:
463 m_editPatch.applyButton->setEnabled( false );
464 m_editPatch.unApplyButton->setEnabled( true );
465 break;
466 case LocalPatchSource::NotApplied:
467 m_editPatch.applyButton->setEnabled( true );
468 m_editPatch.unApplyButton->setEnabled( false );
469 break;
472 if ( !m_editPatchLocal ) {
473 m_editPatch.unApplyButton->setEnabled( false );
476 m_updateKompareTimer->start( 100 );
478 updateByType();
479 emit stateChanged( this );
482 void EditPatch::slotEditFileNameChanged( const QString& str ) {
483 if ( !m_editDlg || !m_started )
484 return ;
485 QString txt;
487 KSharedPtr<KMimeType> mt = KMimeType::findByUrl( KUrl( str ) );
489 if ( !str.isEmpty() ) {
490 txt = "mime-type: " + mt->name();
491 m_editPatch.type->setText( mt->name() );
494 m_editPatch.mimetype->setPixmap( m_parent->teamwork() ->icons().getIcon( mt->iconName(), KIconLoader::Desktop ).pixmap( 16, 16 ) );
496 m_editPatch.mimetype->setText( txt );
497 m_editPatch.command->setText( "" );
498 emit stateChanged( this );
500 //m_updateKompareTimer->start( 100 );
503 void EditPatch::slotEditDialogFinished( int /*result*/ ) {
504 if ( !m_started )
505 return ;
506 LocalPatchSourcePointer patch = patchFromEdit();
508 m_editDlg = 0;
509 emit dialogClosed( this );
512 void EditPatch::slotChooseType() {
513 if ( !m_started )
514 return ;
515 if ( !m_editDlg )
516 return ;
518 QString text = "Select the MimeType for this item.";
519 QStringList list;
520 list << m_editPatch.type->text();
521 KMimeTypeChooserDialog *d = new KMimeTypeChooserDialog( text, "", list , "text", QStringList(), KMimeTypeChooser::Comments | KMimeTypeChooser::Patterns | KMimeTypeChooser::EditButton, m_editDlg );
522 if ( d->exec() == QDialog::Accepted ) {
523 if ( !d->chooser() ->mimeTypes().isEmpty() ) {
524 ///It would be perfect to have a mime-type-chooser that allows only one choice.
525 if ( d->chooser() ->mimeTypes().back() == m_editPatch.type->text() ) {
526 m_editPatch.type->setText( d->chooser() ->mimeTypes().front() );
527 } else {
528 m_editPatch.type->setText( d->chooser() ->mimeTypes().back() );
533 delete d;
534 updateByType();
537 std::string EditPatch::logPrefix() {
538 LocalPatchSourcePointer::Locked l = m_editingPatch;
539 if ( !l )
540 return "EditPatch: ";
541 else
542 return "EditPatch(" + l->name + "): ";
545 void EditPatch::slotToFile() {
546 LocalPatchSourcePointer::Locked l = patchFromEdit();
547 if ( !l ) {
548 err() << "slotToFile() could not lock edited patch";
549 return ;
551 KUrl f = getPatchFile();
552 if ( f.isEmpty() ) {
553 err() << "slotToFile() could not get create patch-file";
554 return ;
556 out( Logger::Debug ) << "converting patch with command \"" << l->command << "\" to file \"" << f.prettyUrl() << "\"";
557 if( !f.isEmpty() )
558 l->setFileName( ~TeamworkFolderManager::teamworkRelative( f ) );
559 else
560 l->setFileName( "" );
562 m_editPatch.tabWidget->setCurrentIndex( 0 );
563 m_editPatch.filename->setUrl( f );
564 fillEditFromPatch();
568 void EditPatch::slotDetermineState() {
569 m_parent->determineState( patchFromEdit() );
570 fillEditFromPatch();
571 emit stateChanged( this );
574 void EditPatch::dialogDestroyed() {
575 m_editDlg = 0;
576 EditPatchPointer p = this;
577 emit dialogClosed( this );
580 void EditPatch::updateByType() {
581 LocalPatchSourcePointer::Locked l = m_editingPatch;
582 if ( !l )
583 return ;
584 l->applyCommand = ~m_editPatch.applyCommand->text();
585 l->unApplyCommand = ~m_editPatch.unapplyCommand->text();
586 l->type = ~m_editPatch.type->text();
588 if ( l->type == "text/x-diff" ) {
589 if ( l->state == LocalPatchSource::Applied ) {
590 m_editPatch.filesGroup->show();
591 } else {
592 m_editPatch.filesGroup->hide();
594 if ( l->patchTool( false ) == "patch" && l->patchTool( true ) == "patch" ) {
595 m_editPatch.determineState->setEnabled( true );
596 } else {
597 m_editPatch.determineState->setEnabled( false );
599 } else {
600 m_editPatch.determineState->setEnabled( false );
601 m_editPatch.filesGroup->hide();
605 void EditPatch::showEditDialog() {
606 if ( m_editDlg ) {
607 out( Logger::Warning ) << "there is still another edit-dialog open";
608 return ;
612 m_editDlg = new QDialog( m_parent->teamwork() ->widget() );
614 connect( m_editDlg, SIGNAL( destroyed( QObject* ) ), this, SLOT( dialogDestroyed() ) );
615 m_editPatch.setupUi( m_editDlg );
617 m_filesModel = new QStandardItemModel( m_editPatch.filesList );
618 m_editPatch.filesList->setModel( m_filesModel );
620 connect( m_editPatch.previousHunk, SIGNAL( clicked( bool ) ), this, SLOT( prevHunk() ) );
621 connect( m_editPatch.nextHunk, SIGNAL( clicked( bool ) ), this, SLOT( nextHunk() ) );
622 connect( m_editPatch.filesList, SIGNAL( doubleClicked( const QModelIndex& ) ), this, SLOT( fileDoubleClicked( const QModelIndex& ) ) );
623 connect( m_editPatch.filesList->selectionModel(), SIGNAL( selectionChanged( const QItemSelection&, const QItemSelection& ) ), this, SLOT( fileSelectionChanged() ) );
624 connect( m_editPatch.okButton, SIGNAL( pressed() ), this, SLOT( slotEditOk() ) );
625 //connect( m_editPatch.cancelButton, SIGNAL( pressed() ), this, SLOT( slotEditCancel() ) );
627 connect( m_editPatch.highlightFiles, SIGNAL( clicked( bool ) ), this, SLOT( highlightFile() ) );
628 connect( m_editPatch.applyButton, SIGNAL( pressed() ), this, SLOT( slotApplyEditPatch() ) );
629 connect( m_editPatch.unApplyButton, SIGNAL( pressed() ), this, SLOT( slotUnapplyEditPatch() ) );
630 connect( m_editPatch.showButton, SIGNAL( pressed() ), this, SLOT( slotShowEditPatch() ) );
632 connect( m_editPatch.type, SIGNAL( textChanged( const QString& ) ), this, SLOT( slotEditMimeType( const QString& ) ) );
633 connect( m_editPatch.chooseType, SIGNAL( pressed() ), this, SLOT( slotChooseType() ) );
634 connect( m_editDlg, SIGNAL( finished( int ) ), this, SLOT( slotEditDialogFinished( int ) ) );
635 connect( m_editPatch.filename, SIGNAL( textChanged( const QString& ) ), this, SLOT( slotEditFileNameChanged( const QString& ) ) );
636 connect( m_editPatch.command, SIGNAL( textChanged( const QString& ) ), this, SLOT( slotEditCommandChanged( const QString& ) ) );
637 connect( m_editPatch.state, SIGNAL( currentIndexChanged( int ) ), this, SLOT( slotStateChanged() ) );
638 connect( m_editPatch.determineState, SIGNAL( clicked( bool ) ), this, SLOT( slotDetermineState() ) );
639 connect( m_editPatch.commandToFile, SIGNAL( clicked( bool ) ), this, SLOT( slotToFile() ) );
641 connect( m_editPatch.filename->lineEdit(), SIGNAL( returnPressed() ), this, SLOT( slotFileNameEdited() ) );
642 connect( m_editPatch.filename->lineEdit(), SIGNAL( editingFinished() ), this, SLOT( slotFileNameEdited() ) );
643 connect( m_editPatch.filename, SIGNAL( urlSelected( const QString& ) ), this, SLOT( slotCommandEdited() ) );
644 connect( m_editPatch.command, SIGNAL( editingFinished() ), this, SLOT( slotCommandEdited() ) );
645 connect( m_editPatch.command, SIGNAL( returnPressed() ), this, SLOT( slotCommandEdited() ) );
646 connect( m_editPatch.command, SIGNAL( () ), this, SLOT( slotCommandEdited() ) );
648 connect( m_editPatch.userButton, SIGNAL( clicked( bool ) ), this, SLOT( slotUserButton() ) );
649 connect( m_editPatch.applyCommand, SIGNAL( textEdited( const QString& ) ), this, SLOT( updateByType() ) );
650 connect( m_editPatch.unapplyCommand, SIGNAL( textEdited( const QString& ) ), this, SLOT( updateByType() ) );
652 bool blocked = blockSignals( true );
653 m_editPatch.accessRights->insertItem( 0, ~LocalPatchSource::accessToString( Public ) );
654 m_editPatch.accessRights->insertItem( 1, ~LocalPatchSource::accessToString( ConnectedOnly ) );
655 m_editPatch.accessRights->insertItem( 2, ~LocalPatchSource::accessToString( Ask ) );
656 m_editPatch.accessRights->insertItem( 3, ~LocalPatchSource::accessToString( Private ) );
658 m_editPatch.state->insertItem( 0, "Unknown" );
659 m_editPatch.state->insertItem( 1, "Applied" );
660 m_editPatch.state->insertItem( 2, "Not Applied" );
663 m_editPatch.filename->fileDialog() ->setUrl( TeamworkFolderManager::workspaceDirectory() );
665 m_editPatch.type->setText( "text/x-diff" );
667 m_editDlg->show();
668 blockSignals( blocked );
671 void EditPatch::hideEditDialog() {
672 if ( !m_editDlg )
673 return ;
674 m_editDlg->close();
675 delete m_editDlg;
676 m_editDlg = 0;
679 void EditPatch::editPatchReadOnly() {
680 if ( !m_editDlg )
681 return ;
683 m_editPatch.applyCommand->setReadOnly( true );
684 m_editPatch.unapplyCommand->setReadOnly( true );
685 m_editPatch.name->setReadOnly( true );
686 m_editPatch.dependencies->setReadOnly( true );
687 m_editPatch.command->setReadOnly( true );
688 m_editPatch.accessRights->setEditable( false );
689 if ( m_editPatch.accessRights->lineEdit() )
690 m_editPatch.accessRights->lineEdit() ->setReadOnly( true );
691 m_editPatch.type->setReadOnly( true );
692 /*if( m_editPatch.type->lineEdit() )
693 m_editPatch.type->lineEdit()->setReadOnly( true );*/
694 m_editPatch.description->setReadOnly( true );
695 if ( m_editPatch.filename->lineEdit() )
696 m_editPatch.filename->lineEdit() ->setReadOnly( true );
697 if ( m_editPatch.filename->comboBox() )
698 m_editPatch.filename->comboBox() ->setEditable( false );
699 if ( m_editPatch.filename->lineEdit() )
700 m_editPatch.filename->lineEdit() ->setReadOnly( true );
701 /*m_editPatch.applyButton->setEnabled( false );
702 m_editPatch.unApplyButton->setEnabled( false );
703 m_editPatch.showButton->setEnabled( false );*/
704 if ( !m_editPatchLocal ) {
705 m_editPatch.unApplyButton->setEnabled( true );
706 m_editPatch.applyButton->setEnabled( true );
707 m_editPatch.showButton->setEnabled( true );
709 // m_editPatch.cancelButton->setEnabled( false );
712 LocalPatchSource::State EditPatch::editState() {
713 if ( !m_editDlg )
714 return LocalPatchSource::Unknown;
715 switch ( m_editPatch.state->currentIndex() ) {
716 case 0:
717 return LocalPatchSource::Unknown;
718 case 1:
719 return LocalPatchSource::Applied;
720 default:
721 return LocalPatchSource::NotApplied;
725 LocalPatchSourcePointer EditPatch::patch() const {
726 return m_editingPatch;
729 const QString terminalSuccessMarker = "TERMINAL_ACTION_SUCCESSFUL";
730 const QString reversedMarker = "Reversed (or previously applied) patch detected";
732 void EditPatch::apply( bool reverse, const QString& _fileName ) {
733 try {
734 if ( m_lastDataTime.isValid() ) {
735 if ( m_lastDataTime.msecsTo( QTime::currentTime() ) < 100 ) {
736 m_editPatch.konsoleDock->showNormal();
737 throw "terminal is still in use";
740 LocalPatchSourcePointer::Locked patch = patchFromEdit();
741 if ( !patch )
742 throw "could not lock patch";
744 QString fileName = _fileName;
745 if ( fileName.trimmed().isEmpty() )
746 fileName = ( ~patch->filename ).trimmed();
748 if ( fileName.isEmpty() )
749 throw "cannot apply patch without a filename";
751 out() << "Applying patch" << patch->name << "from file" << fileName;
753 if ( ( !reverse && patch->state == LocalPatchSource::Applied ) )
754 throw QString( "tried to apply an already applied patch: %1" ).arg( ~patch->name );
755 if ( ( reverse && patch->state == LocalPatchSource::NotApplied ) )
756 throw QString( "tried to unapply a not applied patch: %1" ).arg( ~patch->name );
758 KLibFactory* factory = KLibLoader::self() ->factory( "libkonsolepart" );
759 if ( factory == 0L )
760 throw "no factory for libkonsolepart available";
762 KParts::Part* p;
763 if ( !m_konsolePart ) {
764 p = static_cast<KParts::Part*>( factory->create( ( QObject* ) m_editDlg ) );
765 } else {
766 p = m_konsolePart;
769 if ( !p )
770 throw "could not create konsole-part";
772 p->widget() ->setFocusPolicy( Qt::WheelFocus );
773 p->widget() ->setFocus();
775 if ( QFrame * frame = qobject_cast<QFrame*>( p->widget() ) )
776 frame->setFrameStyle( QFrame::Panel | QFrame::Sunken );
778 m_konsolePart = p;
780 TerminalInterface* terminal = static_cast<TerminalInterface*>( p->qt_metacast( "TerminalInterface" ) );
781 if ( !terminal )
782 throw "could not get terminal-interface";
784 m_editPatch.konsoleDock->show();
785 m_editPatch.konsoleDock->setWidget( p->widget() );
786 p->widget() ->show();
787 m_editPatch.konsoleDock->showNormal();
789 m_reversed = false;
791 QString params = ~patch->patchParams( reverse );
793 bool hadFile = params.indexOf( "$FILE" ) != -1;
794 //params.replace( "$FILE", fileName );
796 if ( !hadFile ) {
797 if ( patch->patchTool( reverse ) == "patch" )
798 params += " -i";
799 params += " " + fileName;
801 QString command = "FILE=" + fileName + " " + ~patch->patchTool( reverse ) + " " + params + " && echo " + QString( terminalSuccessMarker ) + "\n";
803 ///@todo not working with remove directories
804 terminal->showShellInDir( TeamworkFolderManager::workspaceDirectory().toLocalFile() );
806 if ( reverse )
807 m_actionState = LocalPatchSource::NotApplied;
808 else
809 m_actionState = LocalPatchSource::Applied;
811 connect( p, SIGNAL( receivedData( const QString& ) ), this, SLOT( receivedTerminalData( const QString& ) ) );
813 patch->state = LocalPatchSource::Unknown;
815 m_lastTerminalData.clear();
816 terminal->sendInput( command );
817 //terminal->startProgram( ~patch->patchTool( reverse ), l );
819 fillEditFromPatch();
820 } catch ( const QString & str ) {
821 err() << QString( "error in applyPatch(reverse = %1): %2" ).arg( reverse ).arg( str );
822 } catch ( const char * str ) {
823 err() << QString( "error in applyPatch(reverse = %1): %2" ).arg( reverse ).arg( str );
827 ///This ugly hacking is necerssary because the status-output cannot be retrieved anymore
828 void EditPatch::receivedTerminalData( const QString& s ) {
829 m_lastDataTime = QTime::currentTime();
830 m_lastTerminalData += s;
831 if ( m_lastTerminalData.contains( reversedMarker ) )
832 m_reversed = true;
834 if ( m_lastTerminalData.contains( terminalSuccessMarker ) ) {
835 m_lastTerminalData.replace( "echo " + terminalSuccessMarker, "" );
836 if ( m_lastTerminalData.contains( terminalSuccessMarker ) ) {
837 LocalPatchSourcePointer::Locked l = patchFromEdit();
838 if ( !l )
839 return ;
840 m_editPatch.konsoleDock->hide();
841 if ( m_reversed ) {
842 if ( m_actionState == LocalPatchSource::Applied )
843 m_actionState = LocalPatchSource::NotApplied;
844 else if ( m_actionState == LocalPatchSource::NotApplied )
845 m_actionState = LocalPatchSource::Applied;
847 l->state = m_actionState;
848 fillEditFromPatch();
849 emit stateChanged( this );
853 m_lastTerminalData = m_lastTerminalData.right( terminalSuccessMarker.length() + reversedMarker.length() );
856 void EditPatch::nextHunk() {
857 updateKompareModel();
858 seekHunk( true, m_isSource );
861 void EditPatch::prevHunk() {
862 updateKompareModel();
863 seekHunk( false, m_isSource );
866 void EditPatch::seekHunk( bool forwards, bool isSource, const QString& fileName ) {
867 try {
868 QModelIndexList il = m_editPatch.filesList->selectionModel() ->selectedIndexes();
869 if ( il.isEmpty() )
870 throw "selection is empty";
871 if ( !m_modelList.get() )
872 throw "no model";
874 for ( QModelIndexList::iterator it = il.begin(); it != il.end(); ++it ) {
875 QVariant v = m_filesModel->data( *it, Qt::UserRole );
876 if ( !v.canConvert<const Diff2::DiffModel*>() )
877 continue;
878 const Diff2::DiffModel* model = v.value<const Diff2::DiffModel*>();
879 if ( !model || !model->differences() )
880 continue;
882 KUrl file = TeamworkFolderManager::workspaceDirectory();
883 if ( isSource ) {
884 file.addPath( model->sourcePath() );
885 file.addPath( model->sourceFile() );
886 } else {
887 file.addPath( model->destinationPath() );
888 file.addPath( model->destinationFile() );
890 if ( !fileName.isEmpty() && fileName != file.toLocalFile() )
891 continue;
893 //out( Logger::Debug ) << "highlighting" << file.toLocalFile();
895 IDocument* doc = KDevTeamworkPlugin::staticDocumentController() ->documentForUrl( file );
897 if ( doc ) {
898 KDevTeamworkPlugin::staticDocumentController()->activateDocument( doc );
899 if ( doc->textDocument() ) {
900 KTextEditor::View * v = doc->textDocument() ->activeView();
901 int bestLine = -1;
902 if ( v ) {
903 KTextEditor::Cursor c = v->cursorPosition();
904 for ( Diff2::DifferenceList::const_iterator it = model->differences() ->begin(); it != model->differences() ->end(); ++it ) {
905 int line;
906 Diff2::Difference* diff = *it;
907 if ( isSource )
908 line = diff->sourceLineNumber();
909 else
910 line = diff->destinationLineNumber();
911 if ( line > 0 )
912 line -= 1;
914 if ( forwards ) {
915 if ( line > c.line() && ( bestLine == -1 || line < bestLine ) )
916 bestLine = line;
917 } else {
918 if ( line < c.line() && ( bestLine == -1 || line > bestLine ) )
919 bestLine = line;
922 if ( bestLine != -1 ) {
923 v->setCursorPosition( KTextEditor::Cursor( bestLine, 0 ) );
924 return ;
931 } catch ( const QString & str ) {
932 err() << "seekHunk():" << str;
933 } catch ( const char * str ) {
934 err() << "seekHunk():" << str;
936 out() << "no matching hunk found";
939 void EditPatch::highlightFile() {
940 updateKompareModel();
941 try {
942 QModelIndexList il = m_editPatch.filesList->selectionModel() ->selectedIndexes();
943 if ( il.isEmpty() )
944 throw "selection is empty";
945 if ( !m_modelList.get() )
946 throw "no model";
948 for ( QModelIndexList::iterator it = il.begin(); it != il.end(); ++it ) {
949 QVariant v = m_filesModel->data( *it, Qt::UserRole );
950 if ( !v.canConvert<const Diff2::DiffModel*>() )
951 continue;
952 const Diff2::DiffModel* model = v.value<const Diff2::DiffModel*>();
953 if ( !model )
954 continue;
956 KUrl file = TeamworkFolderManager::workspaceDirectory();
957 if ( m_isSource ) {
958 file.addPath( model->sourcePath() );
959 file.addPath( model->sourceFile() );
960 } else {
961 file.addPath( model->destinationPath() );
962 file.addPath( model->destinationFile() );
965 out( Logger::Debug ) << "highlighting" << file.toLocalFile();
967 IDocument* doc = KDevTeamworkPlugin::staticDocumentController() ->documentForUrl( file );
969 if ( !doc ) {
970 doc = KDevTeamworkPlugin::staticDocumentController() ->openDocument( file, KTextEditor::Cursor() );
971 seekHunk( true, m_isSource, file.toLocalFile() );
973 removeHighlighting( file.toLocalFile() );
975 m_highlighters[ file.toLocalFile() ] = new DocumentHighlighter( model, doc, m_isSource );
978 } catch ( const QString & str ) {
979 err() << "highlightFile():" << str;
980 } catch ( const char * str ) {
981 err() << "highlightFile():" << str;
985 void EditPatch::fileDoubleClicked( const QModelIndex& i ) {
986 try {
987 if ( !m_modelList.get() )
988 throw "no model";
989 QVariant v = m_filesModel->data( i, Qt::UserRole );
990 if ( !v.canConvert<const Diff2::DiffModel*>() )
991 throw "cannot convert";
992 const Diff2::DiffModel* model = v.value<const Diff2::DiffModel*>();
993 if ( !model )
994 throw "bad model-value";
996 KUrl file = TeamworkFolderManager::workspaceDirectory();
997 if ( m_isSource ) {
998 file.addPath( model->sourcePath() );
999 file.addPath( model->sourceFile() );
1000 } else {
1001 file.addPath( model->destinationPath() );
1002 file.addPath( model->destinationFile() );
1005 out( Logger::Debug ) << "opening" << file.toLocalFile();
1007 KDevTeamworkPlugin::staticDocumentController() ->openDocument( file, KTextEditor::Cursor() );
1009 seekHunk( true, m_isSource, file.toLocalFile() );
1010 } catch ( const QString & str ) {
1011 err() << "fileDoubleClicked():" << str;
1012 } catch ( const char * str ) {
1013 err() << "fileDoubleClicked():" << str;
1017 void EditPatch::fileSelectionChanged() {
1018 QModelIndexList i = m_editPatch.filesList->selectionModel() ->selectedIndexes();
1019 m_editPatch.nextHunk->setEnabled( false );
1020 m_editPatch.previousHunk->setEnabled( false );
1021 m_editPatch.highlightFiles->setEnabled( false );
1022 if ( !m_modelList.get() )
1023 return ;
1024 for ( QModelIndexList::iterator it = i.begin(); it != i.end(); ++it ) {
1025 QVariant v = m_filesModel->data( *it, Qt::UserRole );
1026 if ( v.canConvert<const Diff2::DiffModel*>() ) {
1027 const Diff2::DiffModel * model = v.value<const Diff2::DiffModel*>();
1028 if ( model ) {
1029 if ( model->differenceCount() != 0 ) {
1030 m_editPatch.nextHunk->setEnabled( true );
1031 m_editPatch.previousHunk->setEnabled( true );
1032 m_editPatch.highlightFiles->setEnabled( true );
1039 DocumentHighlighter::DocumentHighlighter( const Diff2::DiffModel* model, IDocument* kdoc, bool isSource ) throw( QString ) : m_doc( kdoc ) {
1040 // connect( kdoc, SIGNAL( destroyed( QObject* ) ), this, SLOT( documentDestroyed() ) );
1041 connect( kdoc->textDocument(), SIGNAL( destroyed( QObject* ) ), this, SLOT( documentDestroyed() ) );
1042 connect( model, SIGNAL( destroyed( QObject* ) ), this, SLOT( documentDestroyed() ) );
1044 KTextEditor::Document* doc = kdoc->textDocument();
1045 if ( doc->lines() == 0 )
1046 return ;
1047 if( doc->activeView() == 0 ) return;
1049 if ( !model->differences() )
1050 return ;
1051 KTextEditor::SmartInterface* smart = dynamic_cast<KTextEditor::SmartInterface*>( doc );
1052 if ( !smart )
1053 throw QString( "no smart-interface" );
1055 QMutexLocker lock(smart->smartMutex());
1057 KTextEditor::SmartRange* topRange = smart->newSmartRange(doc->documentRange(), 0, KTextEditor::SmartRange::ExpandLeft | KTextEditor::SmartRange::ExpandRight);
1059 for ( Diff2::DifferenceList::const_iterator it = model->differences() ->begin(); it != model->differences() ->end(); ++it ) {
1060 Diff2::Difference* diff = *it;
1061 int line, lineCount;
1062 if ( isSource ) {
1063 line = diff->sourceLineNumber();
1064 lineCount = diff->sourceLineCount();
1065 } else {
1066 line = diff->destinationLineNumber();
1067 lineCount = diff->destinationLineCount();
1069 if ( line > 0 )
1070 line -= 1;
1072 KTextEditor::Cursor c( line, 0 );
1073 KTextEditor::Cursor endC( line + lineCount, 0 );
1074 if ( doc->lines() <= c.line() )
1075 c.setLine( doc->lines() - 1 );
1076 if ( doc->lines() <= endC.line() )
1077 endC.setLine( doc->lines() - 1 );
1078 endC.setColumn( doc->lineLength( endC.line() ) ) ;
1080 if ( endC.isValid() && c.isValid() ) {
1081 KTextEditor::SmartRange * r = smart->newSmartRange( c, endC );
1082 r->setParentRange(topRange);
1083 KSharedPtr<KTextEditor::Attribute> t( new KTextEditor::Attribute() );
1085 t->setProperty( QTextFormat::BackgroundBrush, QBrush( QColor( 200, 200, 255 ) ) );
1086 r->setAttribute( t );
1090 m_ranges << topRange;
1092 smart->addHighlightToDocument(topRange);
1095 DocumentHighlighter::~DocumentHighlighter() {
1096 for ( QList<KTextEditor::SmartRange*>::iterator it = m_ranges.begin(); it != m_ranges.end(); ++it )
1097 delete *it;
1099 m_ranges.clear();
1102 IDocument* DocumentHighlighter::doc() {
1103 return m_doc;
1106 void DocumentHighlighter::documentDestroyed() {
1107 m_ranges.clear();
1110 void EditPatch::removeHighlighting( const QString& file ) {
1111 if ( file.isEmpty() ) {
1112 ///Remove all highlighting
1113 for ( HighlightMap::iterator it = m_highlighters.begin(); it != m_highlighters.end(); ++it ) {
1114 delete *it;
1116 m_highlighters.clear();
1117 } else {
1118 HighlightMap::iterator it = m_highlighters.find( file );
1119 if ( it != m_highlighters.end() ) {
1120 delete * it;
1121 m_highlighters.erase( it );
1127 void EditPatch::updateKompareModel() {
1128 try {
1129 LocalPatchSourcePointer::Locked l = m_editingPatch;
1130 if ( l ) {
1131 if ( l->state != LocalPatchSource::Applied )
1132 return ;
1133 if ( l->type != "text/x-diff" )
1134 return ;
1135 if ( l->command == ~m_lastModelCommand && l->filename == ~m_lastModelFile && m_modelList.get() )
1136 return ; ///We already have the correct model
1137 m_lastModelCommand = ~l->command;
1138 m_lastModelFile = ~l->filename;
1142 m_modelList.reset( 0 );
1143 qRegisterMetaType<const Diff2::DiffModel*>( "const Diff2::DiffModel*" );
1144 if ( m_diffSettings )
1145 delete m_diffSettings;
1146 m_diffSettings = new DiffSettings( m_editDlg );
1147 m_kompareInfo.reset( new Kompare::Info() );
1148 removeHighlighting();
1149 m_modelList.reset( new Diff2::KompareModelList( m_diffSettings, *m_kompareInfo, ( QObject* ) this ) );
1150 KUrl diffFile = getPatchFile( true );
1151 if ( diffFile.isEmpty() )
1152 return ;
1153 try {
1154 ///@todo does not work with remote URLs
1155 if ( !m_modelList->openDirAndDiff( TeamworkFolderManager::workspaceDirectory().toLocalFile(), diffFile.toLocalFile() ) )
1156 throw "could not open diff " + diffFile.toLocalFile();
1157 } catch ( const QString & str ) {
1158 throw;
1159 } catch ( ... ) {
1160 throw QString( "lib/libdiff2 crashed, memory may be corrupted. Please restart kdevelop." );
1162 m_filesModel->clear();
1163 m_filesModel->insertColumns( 0, 1 );
1165 const Diff2::DiffModelList* models = m_modelList->models();
1166 if ( !models )
1167 throw "no diff-models";
1168 Diff2::DiffModelList::const_iterator it = models->begin();
1169 while ( it != models->end() ) {
1170 Diff2::DifferenceList * diffs = ( *it ) ->differences();
1171 int cnt = 0;
1172 if ( diffs )
1173 cnt = diffs->count();
1175 KUrl file;
1176 if ( m_isSource ) {
1177 file.addPath( ( *it ) ->sourcePath() );
1178 file.addPath( ( *it ) ->sourceFile() );
1179 } else {
1180 file.addPath( ( *it ) ->destinationPath() );
1181 file.addPath( ( *it ) ->destinationFile() );
1184 m_filesModel->insertRow( 0 );
1185 QModelIndex i = m_filesModel->index( 0, 0 );
1186 if ( i.isValid() ) {
1187 //m_filesModel->setData( i, file, Qt::DisplayRole );
1188 m_filesModel->setData( i, QString( "%1 (%2 hunks)" ).arg( file.toLocalFile() ).arg( cnt ), Qt::DisplayRole );
1189 QVariant v;
1190 v.setValue<const Diff2::DiffModel*>( *it );
1191 m_filesModel->setData( i, v, Qt::UserRole );
1193 ++it;
1195 fileSelectionChanged();
1196 return ;
1197 } catch ( const QString & str ) {
1198 err() << "updateKompareModel:" << str;
1199 } catch ( const char * str ) {
1200 err() << "updateKompareModel:" << str;
1202 m_modelList.reset( 0 );
1203 delete m_diffSettings;
1204 m_kompareInfo.reset( 0 );
1207 #include "editpatch.moc"
1209 // kate: space-indent on; indent-width 2; tab-width 2; replace-tabs on