1 /***************************************************************************
2 Copyright 2006 David Nolden <david.nolden.kdevelop@art-master.de>
3 ***************************************************************************/
5 /***************************************************************************
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. *
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>
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>
49 #include "lib/libdiff2/komparemodellist.h"
50 #include "lib/libdiff2/kompare.h"
51 #include <kmessagebox.h>
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
66 //krazy:excludeall=doublequote_chars
68 ///Whether arbitrary exceptions that occurred while diff-parsing within the library should be caught
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() ) );
87 EditPatch::~EditPatch() {
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() {
108 LocalPatchSourcePointer::Locked ps
;
110 if ( m_editingPatch
)
113 ps
= new LocalPatchSource();
114 ps
->userIdentity
= m_parent
->teamwork() ->currentUserIdentity();
118 err() << "could not lock edited patch";
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() ) );
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
);
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
)
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
) );
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
);
170 switch ( patch
.state
) {
171 case LocalPatchSource::Applied
:
174 case LocalPatchSource::NotApplied
:
181 m_editPatch
.state
->setCurrentIndex( stateIndex
);
184 if ( !patch
.filename
.empty() )
185 m_editPatch
.tabWidget
->setCurrentIndex( m_editPatch
.tabWidget
->indexOf( m_editPatch
.fileTab
) );
187 m_editPatch
.tabWidget
->setCurrentIndex( m_editPatch
.tabWidget
->indexOf( m_editPatch
.commandTab
) );
189 m_editPatch
.accessRights
->setEditText( ~patch
.accessAsString() );
191 UserIdentity id
= l
->userIdentity
;
193 m_editPatch
.userButton
->setEnabled( true );
194 m_editPatch
.userButton
->setText( ~id
.name() );
196 m_editPatch
.userButton
->setEnabled( false );
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*/ ) {
214 m_editingPatch
= patchFromEdit();
216 m_updateKompareTimer
->start( 100 );
219 void EditPatch::slotEditCommandChanged( const QString
& /*str*/ ) {
222 m_editPatch
.filename
->setUrl( QString( "" ) );
223 emit
stateChanged( this );
225 //m_updateKompareTimer->start( 100 );
228 void EditPatch::slotUserButton() {
229 LocalPatchSourcePointer::Locked patch
= patchFromEdit();
232 throw "could not lock patch";
235 TeamworkClientPointer::Locked client
= m_parent
->teamwork() ->client();
238 u
= client
->findUser( patch
->userIdentity
);
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
);
265 throw "could not get the patch-message";
266 m_parent
->processMessage( l
.data() );
272 ///Request the patch from the peer and apply it
274 LocalPatchSourcePointer::Locked lpatch
= patch
;
276 throw "could not lock patch-source";
278 UserPointer::Locked user
= lpatch
->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();
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
)
302 LocalPatchSourcePointer patch
= patchFromEdit();
303 if ( !m_parent
->hasPatch( patch
) )
304 m_parent
->addPatch( patch
);
305 LocalPatchSourcePointer::Locked l
= patch
;
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
)
316 emit
stateChanged( this );
319 SafeSharedPtr
<PatchMessage
> EditPatch::getPatchMessage( PatchRequestData::RequestType
/*type*/ ) {
321 LocalPatchSourcePointer::Locked lpatch
= patchFromEdit();
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() );
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();
355 KUrl
EditPatch::getPatchFile( bool temp
) {
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
;
363 throw QString( "could not serialize patch-message" );
364 LocalPatchSourcePointer::Locked patchInfo
= m_editingPatch
;
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
);
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();
380 fileName
+= getDefaultExtension( mime
->patterns() );
382 out( Logger::Debug
) << "getPatchFile() creating file for patch:" << fileName
;
384 QString subFolder
= "local";
388 KUrl filePath
= TeamworkFolderManager::createUniqueFile( "patches/" + subFolder
, fileName
);
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";
404 } catch ( const QString
& str
) {
405 err() << "getPatchFile(" << temp
<< ") failed:" << str
;
407 } catch ( const char * str
) {
408 err() << "getPatchFile(" << temp
<< ") failed:" << str
;
412 void EditPatch::slotShowEditPatch() {
413 LocalPatchSourcePointer patch
= patchFromEdit();
414 if ( !m_parent
->hasPatch( patch
) )
415 m_parent
->addPatch( patch
);
418 if ( m_editPatchLocal
) {
419 SafeSharedPtr
<PatchMessage
>::Locked l
= getPatchMessage( PatchRequestData::View
);
421 throw "could not get the patch-message";
422 m_parent
->processMessage( l
.data() );
424 ///Request the patch from the peer and show it
425 LocalPatchSourcePointer::Locked lpatch
= patch
;
427 throw "could not lock patch-source";
429 UserPointer::Locked user
= lpatch
->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();
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() {
457 switch ( editState() ) {
458 case LocalPatchSource::Unknown
:
459 m_editPatch
.applyButton
->setEnabled( true );
460 m_editPatch
.unApplyButton
->setEnabled( true );
462 case LocalPatchSource::Applied
:
463 m_editPatch
.applyButton
->setEnabled( false );
464 m_editPatch
.unApplyButton
->setEnabled( true );
466 case LocalPatchSource::NotApplied
:
467 m_editPatch
.applyButton
->setEnabled( true );
468 m_editPatch
.unApplyButton
->setEnabled( false );
472 if ( !m_editPatchLocal
) {
473 m_editPatch
.unApplyButton
->setEnabled( false );
476 m_updateKompareTimer
->start( 100 );
479 emit
stateChanged( this );
482 void EditPatch::slotEditFileNameChanged( const QString
& str
) {
483 if ( !m_editDlg
|| !m_started
)
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*/ ) {
506 LocalPatchSourcePointer patch
= patchFromEdit();
509 emit
dialogClosed( this );
512 void EditPatch::slotChooseType() {
518 QString text
= "Select the MimeType for this item.";
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() );
528 m_editPatch
.type
->setText( d
->chooser() ->mimeTypes().back() );
537 std::string
EditPatch::logPrefix() {
538 LocalPatchSourcePointer::Locked l
= m_editingPatch
;
540 return "EditPatch: ";
542 return "EditPatch(" + l
->name
+ "): ";
545 void EditPatch::slotToFile() {
546 LocalPatchSourcePointer::Locked l
= patchFromEdit();
548 err() << "slotToFile() could not lock edited patch";
551 KUrl f
= getPatchFile();
553 err() << "slotToFile() could not get create patch-file";
556 out( Logger::Debug
) << "converting patch with command \"" << l
->command
<< "\" to file \"" << f
.prettyUrl() << "\"";
558 l
->setFileName( ~TeamworkFolderManager::teamworkRelative( f
) );
560 l
->setFileName( "" );
562 m_editPatch
.tabWidget
->setCurrentIndex( 0 );
563 m_editPatch
.filename
->setUrl( f
);
568 void EditPatch::slotDetermineState() {
569 m_parent
->determineState( patchFromEdit() );
571 emit
stateChanged( this );
574 void EditPatch::dialogDestroyed() {
576 EditPatchPointer p
= this;
577 emit
dialogClosed( this );
580 void EditPatch::updateByType() {
581 LocalPatchSourcePointer::Locked l
= m_editingPatch
;
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();
592 m_editPatch
.filesGroup
->hide();
594 if ( l
->patchTool( false ) == "patch" && l
->patchTool( true ) == "patch" ) {
595 m_editPatch
.determineState
->setEnabled( true );
597 m_editPatch
.determineState
->setEnabled( false );
600 m_editPatch
.determineState
->setEnabled( false );
601 m_editPatch
.filesGroup
->hide();
605 void EditPatch::showEditDialog() {
607 out( Logger::Warning
) << "there is still another edit-dialog open";
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" );
668 blockSignals( blocked
);
671 void EditPatch::hideEditDialog() {
679 void EditPatch::editPatchReadOnly() {
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() {
714 return LocalPatchSource::Unknown
;
715 switch ( m_editPatch
.state
->currentIndex() ) {
717 return LocalPatchSource::Unknown
;
719 return LocalPatchSource::Applied
;
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
) {
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();
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" );
760 throw "no factory for libkonsolepart available";
763 if ( !m_konsolePart
) {
764 p
= static_cast<KParts::Part
*>( factory
->create( ( QObject
* ) m_editDlg
) );
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
);
780 TerminalInterface
* terminal
= static_cast<TerminalInterface
*>( p
->qt_metacast( "TerminalInterface" ) );
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();
791 QString params
= ~patch
->patchParams( reverse
);
793 bool hadFile
= params
.indexOf( "$FILE" ) != -1;
794 //params.replace( "$FILE", fileName );
797 if ( patch
->patchTool( reverse
) == "patch" )
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() );
807 m_actionState
= LocalPatchSource::NotApplied
;
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 );
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
) )
834 if ( m_lastTerminalData
.contains( terminalSuccessMarker
) ) {
835 m_lastTerminalData
.replace( "echo " + terminalSuccessMarker
, "" );
836 if ( m_lastTerminalData
.contains( terminalSuccessMarker
) ) {
837 LocalPatchSourcePointer::Locked l
= patchFromEdit();
840 m_editPatch
.konsoleDock
->hide();
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
;
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
) {
868 QModelIndexList il
= m_editPatch
.filesList
->selectionModel() ->selectedIndexes();
870 throw "selection is empty";
871 if ( !m_modelList
.get() )
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
*>() )
878 const Diff2::DiffModel
* model
= v
.value
<const Diff2::DiffModel
*>();
879 if ( !model
|| !model
->differences() )
882 KUrl file
= TeamworkFolderManager::workspaceDirectory();
884 file
.addPath( model
->sourcePath() );
885 file
.addPath( model
->sourceFile() );
887 file
.addPath( model
->destinationPath() );
888 file
.addPath( model
->destinationFile() );
890 if ( !fileName
.isEmpty() && fileName
!= file
.toLocalFile() )
893 //out( Logger::Debug ) << "highlighting" << file.toLocalFile();
895 IDocument
* doc
= KDevTeamworkPlugin::staticDocumentController() ->documentForUrl( file
);
898 KDevTeamworkPlugin::staticDocumentController()->activateDocument( doc
);
899 if ( doc
->textDocument() ) {
900 KTextEditor::View
* v
= doc
->textDocument() ->activeView();
903 KTextEditor::Cursor c
= v
->cursorPosition();
904 for ( Diff2::DifferenceList::const_iterator it
= model
->differences() ->begin(); it
!= model
->differences() ->end(); ++it
) {
906 Diff2::Difference
* diff
= *it
;
908 line
= diff
->sourceLineNumber();
910 line
= diff
->destinationLineNumber();
915 if ( line
> c
.line() && ( bestLine
== -1 || line
< bestLine
) )
918 if ( line
< c
.line() && ( bestLine
== -1 || line
> bestLine
) )
922 if ( bestLine
!= -1 ) {
923 v
->setCursorPosition( KTextEditor::Cursor( bestLine
, 0 ) );
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();
942 QModelIndexList il
= m_editPatch
.filesList
->selectionModel() ->selectedIndexes();
944 throw "selection is empty";
945 if ( !m_modelList
.get() )
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
*>() )
952 const Diff2::DiffModel
* model
= v
.value
<const Diff2::DiffModel
*>();
956 KUrl file
= TeamworkFolderManager::workspaceDirectory();
958 file
.addPath( model
->sourcePath() );
959 file
.addPath( model
->sourceFile() );
961 file
.addPath( model
->destinationPath() );
962 file
.addPath( model
->destinationFile() );
965 out( Logger::Debug
) << "highlighting" << file
.toLocalFile();
967 IDocument
* doc
= KDevTeamworkPlugin::staticDocumentController() ->documentForUrl( file
);
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
) {
987 if ( !m_modelList
.get() )
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
*>();
994 throw "bad model-value";
996 KUrl file
= TeamworkFolderManager::workspaceDirectory();
998 file
.addPath( model
->sourcePath() );
999 file
.addPath( model
->sourceFile() );
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() )
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
*>();
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 )
1047 if( doc
->activeView() == 0 ) return;
1049 if ( !model
->differences() )
1051 KTextEditor::SmartInterface
* smart
= dynamic_cast<KTextEditor::SmartInterface
*>( doc
);
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
;
1063 line
= diff
->sourceLineNumber();
1064 lineCount
= diff
->sourceLineCount();
1066 line
= diff
->destinationLineNumber();
1067 lineCount
= diff
->destinationLineCount();
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
)
1102 IDocument
* DocumentHighlighter::doc() {
1106 void DocumentHighlighter::documentDestroyed() {
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
) {
1116 m_highlighters
.clear();
1118 HighlightMap::iterator it
= m_highlighters
.find( file
);
1119 if ( it
!= m_highlighters
.end() ) {
1121 m_highlighters
.erase( it
);
1127 void EditPatch::updateKompareModel() {
1129 LocalPatchSourcePointer::Locked l
= m_editingPatch
;
1131 if ( l
->state
!= LocalPatchSource::Applied
)
1133 if ( l
->type
!= "text/x-diff" )
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() )
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
) {
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();
1167 throw "no diff-models";
1168 Diff2::DiffModelList::const_iterator it
= models
->begin();
1169 while ( it
!= models
->end() ) {
1170 Diff2::DifferenceList
* diffs
= ( *it
) ->differences();
1173 cnt
= diffs
->count();
1177 file
.addPath( ( *it
) ->sourcePath() );
1178 file
.addPath( ( *it
) ->sourceFile() );
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
);
1190 v
.setValue
<const Diff2::DiffModel
*>( *it
);
1191 m_filesModel
->setData( i
, v
, Qt::UserRole
);
1195 fileSelectionChanged();
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