/*************************************************************************** * Copyright (C) 1999-2001 by Bernd Gehrmann and the KDevelop Team * * bernd@kdevelop.org * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "makewidget.h" #include "kdevcore.h" #include "kdevmainwindow.h" #include "kdevproject.h" #include "kdevpartcontroller.h" #include "processlinemaker.h" #include "makeviewpart.h" #include "makeitem.h" #include "tdetexteditor/document.h" #include "tdetexteditor/cursorinterface.h" #include "tdetexteditor/editinterface.h" #include "urlutil.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static const char *const error_xpm[] = { "11 11 5 1", ". c None", "# c #313062", "a c #6261cd", "b c #c50000", "c c #ff8583", "...........", "...####....", ".a#bbbb#a..", ".#ccbbbb#..", "#bccbbbbb#.", "#bbbbbbbb#.", "#bbbbbbcb#.", "#bbbbbccb#.", ".#bbbccb#..", ".a#bbbb#a..", "...####...." }; static const char *const warning_xpm[] = { "11 11 5 1", ". c None", "# c #313062", "a c #6261cd", "b c #c5c600", "c c #ffff41", "...........", "...####....", ".a#bbbb#a..", ".#ccbbbb#..", "#bccbbbbb#.", "#bbbbbbbb#.", "#bbbbbbcb#.", "#bbbbbccb#.", ".#bbbccb#..", ".a#bbbb#a..", "...####...." }; static const char *const message_xpm[] = { "11 11 5 1", ". c None", "b c #3100c5", "# c #313062", "c c #3189ff", "a c #6265cd", "...........", "...####....", ".a#bbbb#a..", ".#ccbbbb#..", "#bccbbbbb#.", "#bbbbbbbb#.", "#bbbbbbcb#.", "#bbbbbccb#.", ".#bbbccb#..", ".a#bbbb#a..", "...####...." }; class SelectionPreserver { public: SelectionPreserver( TQTextEdit& textEdit, bool stayAtEnd ) : m_textEdit( textEdit ) , m_atEnd( false ) { int para, index; m_textEdit.getCursorPosition( ¶, &index ); m_atEnd = stayAtEnd && para == m_textEdit.paragraphs() - 1 && index == m_textEdit.paragraphLength( para ); m_textEdit.getSelection(¶From, &indexFrom, ¶To, &indexTo, 0); } ~SelectionPreserver() { m_textEdit.setSelection(paraFrom, indexFrom, paraTo, indexTo, 0); if ( m_atEnd ) { m_textEdit.moveCursor(TQTextEdit::MoveEnd, false); m_textEdit.moveCursor(TQTextEdit::MoveLineStart, false);//if linewrap is off we must avoid the jumping of the vertical scrollbar } } TQTextEdit& m_textEdit; bool m_atEnd; int paraFrom, indexFrom, paraTo, indexTo; }; MakeWidget::MakeWidget(MakeViewPart *part) : TQTextEdit(0, "make widget") , m_directoryStatusFilter( m_errorFilter ) , m_errorFilter( m_continuationFilter ) , m_continuationFilter( m_actionFilter ) , m_actionFilter( m_otherFilter ) , m_pendingItem(0) , m_paragraphs(0) , m_lastErrorSelected(-1) , m_part(part) , m_vertScrolling(false) , m_horizScrolling(false) , m_bCompiling(false) { updateSettingsFromConfig(); setTextFormat( TQt::RichText ); if ( m_bLineWrapping ) setWordWrap(WidgetWidth); else setWordWrap(NoWrap); setWrapPolicy(Anywhere); setReadOnly(true); setMimeSourceFactory(new TQMimeSourceFactory); mimeSourceFactory()->setImage("error", TQImage((const char**)error_xpm)); mimeSourceFactory()->setImage("warning", TQImage((const char**)warning_xpm)); mimeSourceFactory()->setImage("message", TQImage((const char**)message_xpm)); dirstack.setAutoDelete(true); childproc = new TDEProcess(TQT_TQOBJECT(this)); procLineMaker = new ProcessLineMaker( childproc ); connect( procLineMaker, TQT_SIGNAL(receivedStdoutLine(const TQCString&)), this, TQT_SLOT(insertStdoutLine(const TQCString&) )); connect( procLineMaker, TQT_SIGNAL(receivedStderrLine(const TQCString&)), this, TQT_SLOT(insertStderrLine(const TQCString&) )); connect( procLineMaker, TQT_SIGNAL(receivedPartialStdoutLine(const TQCString&)), this, TQT_SLOT(storePartialStdoutLine(const TQCString&) )); connect( procLineMaker, TQT_SIGNAL(receivedPartialStderrLine(const TQCString&)), this, TQT_SLOT(storePartialStderrLine(const TQCString&) )); connect( childproc, TQT_SIGNAL(processExited(TDEProcess*)), this, TQT_SLOT(slotProcessExited(TDEProcess*) )) ; connect( &m_directoryStatusFilter, TQT_SIGNAL(item(EnteringDirectoryItem*)), this, TQT_SLOT(slotEnteredDirectory(EnteringDirectoryItem*)) ); connect( &m_directoryStatusFilter, TQT_SIGNAL(item(ExitingDirectoryItem*)), this, TQT_SLOT(slotExitedDirectory(ExitingDirectoryItem*)) ); connect( &m_errorFilter, TQT_SIGNAL(item(MakeItem*)), this, TQT_SLOT(insertItem(MakeItem*)) ); connect( &m_actionFilter, TQT_SIGNAL(item(MakeItem*)), this, TQT_SLOT(insertItem(MakeItem*)) ); connect( &m_otherFilter, TQT_SIGNAL(item(MakeItem*)), this, TQT_SLOT(insertItem(MakeItem*)) ); connect( verticalScrollBar(), TQT_SIGNAL(sliderPressed()), this, TQT_SLOT(verticScrollingOn()) ); connect( verticalScrollBar(), TQT_SIGNAL(sliderReleased()), this, TQT_SLOT(verticScrollingOff()) ); connect( horizontalScrollBar(), TQT_SIGNAL(sliderPressed()), this, TQT_SLOT(horizScrollingOn()) ); connect( horizontalScrollBar(), TQT_SIGNAL(sliderReleased()), this, TQT_SLOT(horizScrollingOff()) ); // this slot doesn't exist anymore // connect( m_part->partController(), TQT_SIGNAL(loadedFile(const KURL&)), // this, TQT_SLOT(slotDocumentOpened(const KURL&)) ); } MakeWidget::~MakeWidget() { delete mimeSourceFactory(); delete childproc; delete procLineMaker; } void MakeWidget::queueJob(const TQString &dir, const TQString &command) { commandList.append(command); dirList.append(dir); if (!isRunning()) { // Store the current output view so that it // can be restored after a successful compilation // m_part->mainWindow()->storeOutputViewTab(); startNextJob(); } } void MakeWidget::startNextJob() { TQStringList::Iterator it = commandList.begin(); if ( it == commandList.end() ) return; currentCommand = *it; commandList.remove(it); int i = currentCommand.findRev(" gmake"); if ( i == -1 ) i = currentCommand.findRev(" make"); if ( i == -1 ) m_bCompiling = false; else { TQString s = currentCommand.right(currentCommand.length() - i); if ( s.contains("configure ") || s.contains(" Makefile.cvs") || s.contains(" clean") || s.contains(" distclean") || s.contains(" package-messages") || s.contains(" install") ) { m_bCompiling = false; } else { m_bCompiling = true; } } it = dirList.begin(); TQString dir = *it; m_lastBuildDir = dir; dirList.remove(it); clear(); // clear the widget for ( TQValueVector::iterator it = m_items.begin(); it != m_items.end(); ++it ) delete *it; m_items.clear(); m_paragraphToItem.clear(); m_paragraphs = 0; m_lastErrorSelected = -1; insertItem( new CommandItem( currentCommand ) ); childproc->clearArguments(); *childproc << currentCommand; childproc->setUseShell(true); childproc->start(TDEProcess::OwnGroup, TDEProcess::AllOutput); dirstack.clear(); dirstack.push(new TQString(dir)); m_part->mainWindow()->raiseView(this); m_part->core()->running(m_part, true); } void MakeWidget::killJob() { if (!childproc->kill(SIGINT)) childproc->kill(); } bool MakeWidget::isRunning() { return childproc->isRunning(); } void MakeWidget::copy() { int parafrom=0, indexfrom=0, parato=0, indexto=0; getSelection(¶from, &indexfrom, ¶to, &indexto); if( parafrom < 0 || indexfrom < 0 || parato < 0 || indexto < 0 || ((parafrom == parato) && (indexfrom == indexto)) ) { return; } TQString selection; for(int i = parafrom; i<=parato; i++) selection += text(i) + "\n"; if(m_compilerOutputLevel == eShort || m_compilerOutputLevel == eVeryShort ) { TQRegExp regexp("<.*>"); regexp.setMinimal(true); selection.remove(regexp); } else { //FIXME: Selections should be precise in the eShort and eVeryShort modes, too. selection.remove(0, indexfrom); int removeend = text(parato).length() - indexto; selection.remove((selection.length()-1) - removeend, removeend); } selection.replace("<","<"); selection.replace(">",">"); selection.replace(""","\""); selection.replace("&","&"); kapp->clipboard()->setText(selection, TQClipboard::Clipboard); } void MakeWidget::nextError() { int parag; if (m_lastErrorSelected != -1) parag = m_lastErrorSelected; else parag = 0; //if there are no errors after m_lastErrorSelected try again from the beginning if (!scanErrorForward(parag)) if (m_lastErrorSelected != -1) { m_lastErrorSelected = -1; if (!scanErrorForward(0)) KNotifyClient::beep(); } else KNotifyClient::beep(); } void MakeWidget::prevError() { int parag; if (m_lastErrorSelected != -1) parag = m_lastErrorSelected; else parag = 0; //if there are no errors before m_lastErrorSelected try again from the end if (!scanErrorBackward(parag)) if (m_lastErrorSelected != -1) { m_lastErrorSelected = -1; parag = (int)m_items.count(); if (!scanErrorBackward(parag)) KNotifyClient::beep(); } else KNotifyClient::beep(); } void MakeWidget::contentsMouseReleaseEvent( TQMouseEvent* e ) { TQTextEdit::contentsMouseReleaseEvent(e); if ( e->button() != Qt::LeftButton ) return; searchItem(paragraphAt(e->pos())); } void MakeWidget::keyPressEvent(TQKeyEvent *e) { if (e->key() == Key_Return || e->key() == Key_Enter) { int parag, index; getCursorPosition(¶g, &index); searchItem(parag); } else TQTextEdit::keyPressEvent(e); } // returns the current directory for parag TQString MakeWidget::directory(int parag) const { TQValueVector::const_iterator it = tqFind( m_items.begin(), m_items.end(), m_paragraphToItem[parag] ); if ( it == m_items.end() ) return TQString(); // run backwards over directories and figure out where we are while ( it != m_items.begin() ) { --it; EnteringDirectoryItem* edi = dynamic_cast( *it ); if ( edi ) return edi->directory + "/"; } return TQString(); } // hackish function that will return true and put string "file" in "fName" if the file // exists static bool checkFileExists( const TQString& file, TQString& fName ) { if ( TQFile::exists( file ) ) { fName = file; return true; } return false; } void MakeWidget::specialCheck( const TQString& file, TQString& fName ) const { TQString firstLine = text(0); TQRegExp rx("cd \\'(.*)\\'.*"); if (rx.search(firstLine) != -1) { KURL url(rx.cap(1)+"/", file); if (url.isValid()) { kdDebug(9004) << "MakeWidget::specialCheck thinks that url is: " << url.url() << " origin: " << file << endl; fName = url.url(); return; } } // Ok the "worst case", lets see if we can find a file in the project that has the same name // obviously this will pick always the wrong file when you've got the same filename multiple times. TQStringList files = m_part->project()->allFiles(); for( TQStringList::iterator it = files.begin() ; it != files.end(); ++it) { if( (*it).contains( file ) ) { fName = URLUtil::canonicalPath( m_part->project()->projectDirectory() + "/" + *it ); } } } TQString MakeWidget::guessFileName( const TQString& fName, int parag ) const { // pathological case if ( ! m_part->project() ) return fName; TQString name; TQString dir = directory( parag ); if ( fName.startsWith( "/" ) ) { // absolute path given name = fName; } else if ( !dir.isEmpty() ) { name = dir + fName; } else { // now it gets tricky - no directory navigation messages, // no absolute path - let's guess. name = fName; if ( !checkFileExists( m_lastBuildDir + "/" + fName, name) && !checkFileExists( m_part->project()->projectDirectory() + "/" + fName, name ) && !checkFileExists( m_part->project()->projectDirectory() + "/" + m_part->project()->activeDirectory() + "/" + fName, name ) && !checkFileExists( m_part->project()->buildDirectory() + "/" + fName, name ) ) specialCheck(fName, name); } kdDebug(9004) << "Opening file: " << name << endl; // GNU make resolves symlinks. if "name" is a real path to a file the // project know by symlink path, we need to return the symlink path // TQStringList projectFiles = m_part->project()->allFiles(); TQStringList projectFiles = m_part->project()->symlinkProjectFiles(); TQStringList::iterator it = projectFiles.begin(); while ( it != projectFiles.end() ) { TQString file = m_part->project()->projectDirectory() + "/" + *it; if ( name == URLUtil::canonicalPath( file ) ) { kdDebug(9004) << "Found file in project - " << file << " == " << name << endl; return file; } ++it; } // this should only happen if the file is not in the project return name; } void MakeWidget::searchItem(int parag) { ErrorItem* item = dynamic_cast( m_paragraphToItem[parag] ); if ( item ) { // open the file kdDebug(9004) << "Opening file: " << guessFileName(item->fileName, parag) << endl; m_part->partController()->editDocument(KURL( guessFileName(item->fileName, parag) ), item->lineNum); m_part->mainWindow()->statusBar()->message( item->m_error, 10000 ); m_lastErrorSelected = parag; } } void MakeWidget::insertStdoutLine( const TQCString& line ) { TQString sline; bool forceCLocale = TDEConfigGroup( kapp->config(), "MakeOutputWidget" ).readBoolEntry( "ForceCLocale", true ); if( forceCLocale ) sline = TQString::fromAscii( stdoutbuf+line ); else sline = TQString::fromLocal8Bit( stdoutbuf+line ); if ( !appendToLastLine( sline ) ) m_directoryStatusFilter.processLine( sline ); stdoutbuf.truncate(0); } void MakeWidget::insertStderrLine( const TQCString& line ) { TQString sline; bool forceCLocale = TDEConfigGroup( kapp->config(), "MakeOutputWidget" ).readBoolEntry( "ForceCLocale", true ); if( forceCLocale ) { sline = TQString( stderrbuf+line ); } else sline = TQString::fromLocal8Bit( stderrbuf+line ); if ( !appendToLastLine( sline ) ) m_errorFilter.processLine( sline ); stderrbuf.truncate(0); } void MakeWidget::slotProcessExited(TDEProcess *) { procLineMaker->flush(); if( !stderrbuf.isEmpty() ) insertStderrLine(""); if( !stdoutbuf.isEmpty() ) insertStdoutLine(""); if (childproc->normalExit()) { if (childproc->exitStatus()) { KNotifyClient::event( topLevelWidget()->winId(), "ProcessError", i18n("The process has finished with errors")); emit m_part->commandFailed(currentCommand); } else { KNotifyClient::event( topLevelWidget()->winId(), "ProcessSuccess", i18n("The process has finished successfully")); emit m_part->commandFinished(currentCommand); } } MakeItem* item = new ExitStatusItem( childproc->normalExit(), childproc->exitStatus() ); insertItem( item ); displayPendingItem(); m_part->mainWindow()->statusBar()->message( TQString("%1: %2").arg(currentCommand).arg(item->m_text), 3000); m_part->core()->running(m_part, false); // Defensive programming: We emit this with a single shot timer so that we go once again // through the event loop. After that, we can be sure that the process is really finished // and its TDEProcess object can be reused. if (childproc->normalExit() && !childproc->exitStatus()) { TQTimer::singleShot(0, this, TQT_SLOT(startNextJob())); // if (commandList.isEmpty()) // The last command on the list was successful so restore the // output view to what it had before the compilation process started // m_part->mainWindow()->restoreOutputViewTab(); } else { commandList.clear(); dirList.clear(); } } void MakeWidget::slotEnteredDirectory( EnteringDirectoryItem* item ) { // kdDebug(9004) << "Entering dir: " << item->directory << endl; TQString* dir = new TQString( item->directory ); dirstack.push( dir ); insertItem( item ); } void MakeWidget::slotExitedDirectory( ExitingDirectoryItem* item ) { TQString eDir = item->directory; // kdDebug(9004) << "Leaving dir: " << eDir << endl; TQString *dir = dirstack.pop(); if (!dir) { kdWarning(9004) << "Left more directories than entered: " << eDir; } else if (dir->compare(eDir) != 0) { kdWarning(9004) << "Expected directory: \"" << *dir << "\" but got \"" << eDir << "\"" << endl; } insertItem( item ); if (dirstack.top()) insertItem( new EnteringDirectoryItem( *dirstack.top(), "" ) ); delete dir; } void MakeWidget::displayPendingItem() { if (!m_pendingItem) return; // this handles the case of ImmDisplay|Append // We call displayPendingItem once in insertItem // and the appends are handled directly in // appendToLastLine if (!m_items.empty() && m_items.last() == m_pendingItem) return; m_items.push_back(m_pendingItem); if ( m_bCompiling && !m_pendingItem->visible( m_compilerOutputLevel ) ) return; SelectionPreserver preserveSelection( *this, !m_vertScrolling && !m_horizScrolling ); m_paragraphToItem.insert( m_paragraphs++, m_pendingItem ); append( m_pendingItem->formattedText( m_compilerOutputLevel, brightBg() ) ); } bool MakeWidget::appendToLastLine( const TQString& text ) { if ( !m_pendingItem ) return false; if ( !m_pendingItem->append( text ) ) { displayPendingItem(); m_pendingItem = 0; return false; } int mode = m_pendingItem -> displayMode(); if ((mode & MakeItem::Append) && (mode & MakeItem::ImmDisplay)) { removeParagraph(paragraphs() - 1); SelectionPreserver preserveSelection( *this, !m_vertScrolling && !m_horizScrolling ); append( m_pendingItem->formattedText( m_compilerOutputLevel, brightBg() ) ); } return true; } void MakeWidget::insertItem( MakeItem* new_item ) { displayPendingItem(); m_pendingItem = new_item; if (!new_item) return; int mode = new_item -> displayMode(); if (mode & MakeItem::ImmDisplay) { displayPendingItem(); if (!(mode & MakeItem::Append)) m_pendingItem = 0; } } bool MakeWidget::brightBg() { int h,s,v; TQColor(paletteBackgroundColor()).hsv( &h, &s, &v ); return (v > 127); } TQPopupMenu* MakeWidget::createPopupMenu( const TQPoint& pos ) { TQPopupMenu* pMenu = TQTextEdit::createPopupMenu(pos); pMenu->setCheckable(true); pMenu->insertSeparator(); int id = pMenu->insertItem(i18n("Line Wrapping"), this, TQT_SLOT(toggleLineWrapping()) ); pMenu->setItemChecked(id, m_bLineWrapping); pMenu->setWhatsThis(id, i18n("Line wrapping

Enables or disables wrapping of command lines displayed.")); pMenu->insertSeparator(); id = pMenu->insertItem(i18n("Very Short Compiler Output"), this, TQT_SLOT(slotVeryShortCompilerOutput()) ); pMenu->setWhatsThis(id, i18n("Very short compiler output

Displays only warnings, errors and the file names which are compiled.")); pMenu->setItemChecked(id, m_compilerOutputLevel == eVeryShort); id = pMenu->insertItem(i18n("Short Compiler Output"), this, TQT_SLOT(slotShortCompilerOutput()) ); pMenu->setWhatsThis(id, i18n("Short compiler output

Suppresses all the compiler flags and formats to something readable.")); pMenu->setItemChecked(id, m_compilerOutputLevel == eShort); id = pMenu->insertItem(i18n("Full Compiler Output"), this, TQT_SLOT(slotFullCompilerOutput()) ); pMenu->setWhatsThis(id, i18n("Full compiler output

Displays unmodified compiler output.")); pMenu->setItemChecked(id, m_compilerOutputLevel == eFull); pMenu->insertSeparator(); id = pMenu->insertItem(i18n("Show Directory Navigation Messages"), this, TQT_SLOT(toggleShowDirNavigMessages())); pMenu->setWhatsThis(id, i18n("Show directory navigation messages

Shows cd commands that are executed while building.")); pMenu->setItemChecked(id, DirectoryItem::getShowDirectoryMessages()); return pMenu; } void MakeWidget::toggleLineWrapping() { m_bLineWrapping = !m_bLineWrapping; TDEConfig *pConfig = kapp->config(); pConfig->setGroup("MakeOutputView"); pConfig->writeEntry("LineWrapping", m_bLineWrapping); pConfig->sync(); if (m_bLineWrapping) { setWordWrap(WidgetWidth); } else { setWordWrap(NoWrap); } } void MakeWidget::refill() { clear(); m_paragraphToItem.clear(); m_paragraphs = 0; for( uint i = 0; i < m_items.size(); i++ ) { if ( m_bCompiling && !m_items[i]->visible( m_compilerOutputLevel ) ) continue; m_paragraphToItem.insert( m_paragraphs++, m_items[i] ); append( m_items[i]->formattedText( m_compilerOutputLevel, brightBg() ) ); } } void MakeWidget::slotVeryShortCompilerOutput() { setTextFormat( TQt::RichText ); setCompilerOutputLevel(eVeryShort); } void MakeWidget::slotShortCompilerOutput() { setTextFormat( TQt::RichText ); setCompilerOutputLevel(eShort); } void MakeWidget::slotFullCompilerOutput() { setTextFormat( TQt::RichText ); setCompilerOutputLevel(eFull); } void MakeWidget::setCompilerOutputLevel(EOutputLevel level) { m_compilerOutputLevel = level; TDEConfig *pConfig = kapp->config(); pConfig->setGroup("MakeOutputView"); pConfig->writeEntry("CompilerOutputLevel", (int) level); pConfig->sync(); refill(); } void MakeWidget::toggleShowDirNavigMessages() { DirectoryItem::setShowDirectoryMessages( !DirectoryItem::getShowDirectoryMessages() ); TDEConfig *pConfig = kapp->config(); pConfig->setGroup("MakeOutputView"); pConfig->writeEntry("ShowDirNavigMsg", DirectoryItem::getShowDirectoryMessages()); pConfig->sync(); refill(); } void MakeWidget::updateSettingsFromConfig() { TDEConfig *pConfig = kapp->config(); pConfig->setGroup("General Options"); TQFont outputFont = pConfig->readFontEntry("OutputViewFont"); setFont(outputFont); pConfig->setGroup("MakeOutputView"); m_bLineWrapping = pConfig->readBoolEntry("LineWrapping", true); m_compilerOutputLevel = (EOutputLevel) pConfig->readNumEntry("CompilerOutputLevel", (int) eShort); DirectoryItem::setShowDirectoryMessages( pConfig->readBoolEntry("ShowDirNavigMsg", false) ); } bool MakeWidget::scanErrorForward( int parag ) { for ( int it = parag + 1; it < (int)m_items.count(); ++it ) { ErrorItem* item = dynamic_cast( m_paragraphToItem[it] ); if ( !item ) continue; if( item->m_isWarning ) continue; parag = it; document()->removeSelection(0); setSelection(parag, 0, parag+1, 0, 0); setCursorPosition(parag, 0); ensureCursorVisible(); searchItem( it ); return true; } return false; } bool MakeWidget::scanErrorBackward( int parag ) { for ( int it = parag - 1; it >= 0; --it) { ErrorItem* item = dynamic_cast( m_paragraphToItem[it] ); if ( !item ) continue; if( item->m_isWarning ) continue; parag = it; document()->removeSelection(0); setSelection(parag, 0, parag+1, 0, 0); setCursorPosition(parag, 0); ensureCursorVisible(); searchItem( it ); return true; } return false; } void MakeWidget::storePartialStderrLine(const TQCString & line) { stderrbuf += line; } void MakeWidget::storePartialStdoutLine(const TQCString & line) { stdoutbuf += line; } #include "makewidget.moc"