// // C++ Implementation: kraction // // Description: // // // Author: Shie Erlich and Rafi Yanai <>, (C) 2004 // // Copyright: See COPYING file that comes with this distribution // // #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kraction.h" #include "expander.h" #include "useraction.h" #include "../krusader.h" #include "../krusaderview.h" #include "../defaults.h" //for the availabilitycheck: #include #include ////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////// KrActionProcDlg ///////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////// #include KrActionProcDlg::KrActionProcDlg( TQString caption, bool enableStderr, TQWidget *parent ) : KDialogBase( parent, 0, false, caption, KDialogBase::User1 | KDialogBase::Ok | KDialogBase::Cancel, KDialogBase::Cancel ), _stdout(0), _stderr(0), _currentTextEdit(0) { setButtonOK( i18n( "Close" ) ); enableButtonOK( false ); // disable the close button, until the process finishes setButtonCancel( KGuiItem(i18n("Kill"), i18n( "Kill the running process" )) ); setButtonText(KDialogBase::User1, i18n("Save as") ); TQVBox *page = makeVBoxMainWidget(); // do we need to separate stderr and stdout? if ( enableStderr ) { TQSplitter *splitt = new TQSplitter( TQt::Vertical, page ); // create stdout TQVBox *stdoutBox = new TQVBox( splitt, "stdout VBox" ); stdoutBox->setSpacing( 6 ); new TQLabel( i18n( "Standard Output (stdout)" ), stdoutBox ); _stdout = new TQTextEdit( stdoutBox ); _stdout->setReadOnly( true ); _stdout->setMinimumWidth( fontMetrics().maxWidth() * 40 ); // create stderr TQVBox *stderrBox = new TQVBox( splitt, "stderr VBox" ); stderrBox->setSpacing( 6 ); new TQLabel( i18n( "Standard Error (stderr)" ), stderrBox ); _stderr = new TQTextEdit( stderrBox ); _stderr->setReadOnly( true ); _stderr->setMinimumWidth( fontMetrics().maxWidth() * 40 ); } else { // create stdout new TQLabel( i18n( "Output" ), page ); _stdout = new TQTextEdit( page ); _stdout->setReadOnly( true ); _stdout->setMinimumWidth( fontMetrics().maxWidth() * 40 ); } _currentTextEdit = _stdout; connect( _stdout, TQT_SIGNAL( clicked(int, int) ), TQT_SLOT( currentTextEditChanged() ) ); if (_stderr) connect( _stderr, TQT_SIGNAL( clicked(int, int) ), TQT_SLOT( currentTextEditChanged() ) ); krConfig->setGroup( "UserActions" ); normalFont = krConfig->readFontEntry( "Normal Font", _UserActions_NormalFont ); fixedFont = krConfig->readFontEntry( "Fixed Font", _UserActions_FixedFont ); bool startupState = krConfig->readBoolEntry( "Use Fixed Font", _UserActions_UseFixedFont ); toggleFixedFont( startupState ); // HACK This fetches the layout of the buttonbox from KDialogBase, although it is not accessable with KDialogBase's API // None the less it's quite save to use since this implementation hasn't changed since KDE-3.3 (I haven't looked at earlier // versions since we don't support them) and now all work is done in KDE-4. TQWidget* buttonBox = static_cast( actionButton(KDialogBase::Ok)->parent() ); TQBoxLayout* buttonBoxLayout = static_cast( buttonBox->layout() ); TQCheckBox* useFixedFont = new TQCheckBox( i18n("Use font with fixed width"), buttonBox ); buttonBoxLayout->insertWidget( 0, useFixedFont ); useFixedFont->setChecked( startupState ); connect( useFixedFont, TQT_SIGNAL( toggled(bool) ), TQT_SLOT( toggleFixedFont(bool) ) ); } void KrActionProcDlg::addStderr( TDEProcess *, char *buffer, int buflen ) { if (_stderr) _stderr->append( TQString::fromLatin1( buffer, buflen ) ); else { _stdout->setItalic(true); _stdout->append( TQString::fromLatin1( buffer, buflen ) ); _stdout->setItalic(false); } } void KrActionProcDlg::addStdout( TDEProcess *, char *buffer, int buflen ) { _stdout->append( TQString::fromLatin1( buffer, buflen ) ); } void KrActionProcDlg::toggleFixedFont( bool state ) { if ( state ) { _stdout->setFont( fixedFont ); if ( _stderr ) _stderr->setFont( fixedFont ); } else { _stdout->setFont( normalFont ); if ( _stderr ) _stderr->setFont( normalFont ); } } void KrActionProcDlg::slotUser1() { TQString filename = KFileDialog::getSaveFileName(TQString(), i18n("*.txt|Text files\n*|all files"), this); if ( filename.isEmpty() ) return; TQFile file( filename ); int answer = KMessageBox::Yes; if ( file.exists() ) answer = KMessageBox::warningYesNoCancel( this, //parent i18n("This file already exists.\nDo you want to overwrite it or append the output?"), //text i18n("Overwrite or append?"), //caption i18n("Overwrite"), //label for Yes-Button i18n("Append") //label for No-Button ); if ( answer == KMessageBox::Cancel ) return; bool open; if ( answer == KMessageBox::No ) // this means to append open = file.open( IO_WriteOnly | IO_Append ); else open = file.open( IO_WriteOnly ); if ( ! open ) { KMessageBox::error( this, i18n("Can't open %1 for writing!\nNothing exported.").arg(filename), i18n("Export failed!") ); return; } TQTextStream stream( &file ); stream << _currentTextEdit->text(); file.close(); } void KrActionProcDlg::currentTextEditChanged() { if ( _stderr && _stderr->hasFocus() ) _currentTextEdit = _stderr; else _currentTextEdit = _stdout; } ////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////// KrActionProc //////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////// KrActionProc::KrActionProc( KrActionBase* action ) : TQObject(), _action( action ), _proc( new TDEProcess(this) ), _output( 0 ) { _proc->setUseShell( true ); connect( _proc, TQT_SIGNAL( processExited( TDEProcess* ) ), this, TQT_SLOT( processExited( TDEProcess* ) ) ) ; } KrActionProc::~KrActionProc() { delete _proc; } void KrActionProc::start( TQString cmdLine ) { TQStringList list = cmdLine; start( list ); } void KrActionProc::start( TQStringList cmdLineList ) { _proc->clearArguments(); TQString cmd; if ( ! _action->startpath().isEmpty() ) _proc->setWorkingDirectory( _action->startpath() ); if ( _action->execType() == KrAction::Terminal && cmdLineList.count() > 1) KMessageBox::sorry( 0, i18n("Support for more than one command doesn't work in a terminal. Only the first is executed in the terminal.") ); if ( _action->execType() == KrAction::RunInTE && ( MAIN_VIEW->konsole_part == NULL || MAIN_VIEW->konsole_part->widget() == NULL ) ) { KMessageBox::sorry( 0, i18n("Embedded terminal emulator does not work, using output collection instead.") ); } if( _action->execType() == KrAction::Normal || _action->execType() == KrAction::Terminal || ( _action->execType() == KrAction::RunInTE && MAIN_VIEW->konsole_part && MAIN_VIEW->konsole_part->widget() ) ) { // not collect output //TODO option to run them in paralell (not available for: collect output) for ( TQStringList::Iterator it = cmdLineList.begin(); it != cmdLineList.end(); ++it) { if ( ! cmd.isEmpty() ) cmd += " ; "; //TODO make this separator configurable (users may want && or || for spec. actions) cmd += *it; } //run in TE if ( _action->execType() == KrAction::RunInTE ) { //send the commandline contents to the terminal emulator TQKeyEvent keyEvent( TQEvent::KeyPress, 0, -1, 0, cmd+"\n"); TQApplication::sendEvent( MAIN_VIEW->konsole_part->widget(), &keyEvent ); } else { // will start a new process // run in terminal if ( _action->execType() == KrAction::Terminal ) { krConfig->setGroup( "UserActions" ); TQString term = krConfig->readEntry( "Terminal", _UserActions_Terminal ); if ( _action->user().isEmpty() ) ( *_proc ) << term << cmd; else // ( *_proc ) << "tdesu" << "-u" << *_properties->user() << "-c" << TDEProcess::quote("konsole --noclose -e " + TDEProcess::quote(cmd) ); ( *_proc ) << "tdesu" << "-u" << _action->user() << "-c" << TDEProcess::quote( term + " " + cmd ); } else { // no terminal, no output collection, start&forget if ( _action->user().isEmpty() ) ( *_proc ) << cmd; else ( *_proc ) << "tdesu" << "-u" << _action->user() << "-c" << TDEProcess::quote(cmd); } _proc->start( TDEProcess::NotifyOnExit, ( TDEProcess::Communication ) ( TDEProcess::Stdout | TDEProcess::Stderr ) ); } } else { // collect output bool separateStderr = false; if ( _action->execType() == KrAction::CollectOutputSeparateStderr ) separateStderr = true; _output = new KrActionProcDlg( _action->text(), separateStderr ); // connect the output to the dialog connect( _proc, TQT_SIGNAL( receivedStderr( TDEProcess*, char*, int ) ), _output, TQT_SLOT( addStderr( TDEProcess*, char *, int ) ) ); connect( _proc, TQT_SIGNAL( receivedStdout( TDEProcess*, char*, int ) ), _output, TQT_SLOT( addStdout( TDEProcess*, char *, int ) ) ); connect( _output, TQT_SIGNAL( cancelClicked() ), this, TQT_SLOT( kill() ) ); _output->show(); for ( TQStringList::Iterator it = cmdLineList.begin(); it != cmdLineList.end(); ++it) { if ( ! cmd.isEmpty() ) cmd += " ; "; //TODO make this separator configurable (users may want && or ||) //TODO: read header fom config or action-properties and place it on top of each command if ( cmdLineList.count() > 1 ) cmd += "echo --------------------------------------- ; "; cmd += *it; } if ( _action->user().isEmpty() ) ( *_proc ) << cmd; else // "-t" is nessesary that tdesu displays the terminal-output of the command ( *_proc ) << "tdesu" << "-t" << "-u" << _action->user() << "-c" << TDEProcess::quote(cmd); _proc->start( TDEProcess::NotifyOnExit, ( TDEProcess::Communication ) ( TDEProcess::Stdout | TDEProcess::Stderr ) ); } } void KrActionProc::processExited( TDEProcess * ) { // enable the 'close' button on the dialog (if active), disable 'kill' button if ( _output ) { _output->enableButtonOK( true ); _output->enableButtonCancel( false); } delete this; // banzai!! } ////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////// KrAction /////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////// KrAction::KrAction( TDEActionCollection *parent, const char* name ) : TDEAction( parent, name ) { connect(this, TQT_SIGNAL(activated()), this, TQT_SLOT(exec()) ); } KrAction::~KrAction() { unplugAll(); krUserAction->removeKrAction( this ); // Importent! Else Krusader will crash when writing the actions to file } bool KrAction::isAvailable( const KURL& currentURL ) { bool available = true; //show per default (FIXME: make the default an attribute of ) //check protocol if ( ! _showonlyProtocol.empty() ) { available = false; for ( TQStringList::Iterator it = _showonlyProtocol.begin(); it != _showonlyProtocol.end(); ++it ) { //kdDebug() << "KrAction::isAvailable currendProtocol: " << currentURL.protocol() << " =?= " << *it << endl; if ( currentURL.protocol() == *it ) { // FIXME remove trailing slashes at the xml-parsing (faster because done only once) available = true; break; } } } //check protocol: done //check the Path-list: if ( ! _showonlyPath.empty() ) { available = false; for ( TQStringList::Iterator it = _showonlyPath.begin(); it != _showonlyPath.end(); ++it ) { if ( (*it).right(1) == "*" ){ if ( currentURL.path().find( (*it).left( (*it).length() - 1 ) ) == 0 ) { available = true; break; } } else if ( currentURL.directory() == *it ) { // FIXME remove trailing slashes at the xml-parsing (faster because done only once) available = true; break; } } } //check the Path-list: done //check mime-type if ( ! _showonlyMime.empty() ) { available = false; KMimeType::Ptr mime = KMimeType::findByURL( currentURL ); for ( TQStringList::Iterator it = _showonlyMime.begin(); it != _showonlyMime.end(); ++it ) { if ( (*it).contains("/") ) { if ( mime->is( *it ) ) { // don't use ==; use 'is()' instead, which is aware of inheritence (ie: text/x-makefile is also text/plain) available = true; break; } } else { if ( mime->name().find( *it ) == 0 ) { // 0 is the beginning, -1 is not found available = true; break; } } } //for } //check the mime-type: done //check filename if ( ! _showonlyFile.empty() ) { available = false; for ( TQStringList::Iterator it = _showonlyFile.begin(); it != _showonlyFile.end(); ++it ) { TQRegExp regex = TQRegExp( *it, false, true ); // case-sensitive = false; wildcards = true if ( regex.exactMatch( currentURL.fileName() ) ) { available = true; break; } } } //check the filename: done return available; } bool KrAction::xmlRead( const TQDomElement& element ) { /* This has to be done elsewhere!! if ( element.tagName() != "action" ) return false; Also the name has to be checked before the action is created! setName( element.attribute( "name" ).latin1() ); */ for ( TQDomNode node = element.firstChild(); !node.isNull(); node = node.nextSibling() ) { TQDomElement e = node.toElement(); if ( e.isNull() ) continue; // this should skip nodes which are not elements ( i.e. comments, , or text nodes) if ( e.tagName() == "title" ) setText( e.text() ); else if ( e.tagName() == "tooltip" ) setToolTip( e.text() ); else if ( e.tagName() == "icon" ) setIcon( e.text() ); else if ( e.tagName() == "category" ) setCategory( e.text() ); else if ( e.tagName() == "description" ) setWhatsThis( e.text() ); else if (e.tagName() == "command") readCommand( e ); else if ( e.tagName() == "startpath" ) setStartpath( e.text() ); else if (e.tagName() == "availability") readAvailability( e ); else if ( e.tagName() == "defaultshortcut" ) setShortcut( TDEShortcut( e.text() ) ); else // unknown but not empty if ( ! e.tagName().isEmpty() ) krOut << "KrAction::xmlRead() - unrecognized tag found: <" << e.tagName() << ">" << endl; } // for ( TQDomNode node = action->firstChild(); !node.isNull(); node = node.nextSibling() ) return true; } //KrAction::xmlRead TQDomElement KrAction::xmlDump( TQDomDocument& doc ) const { TQDomElement actionElement = doc.createElement("action"); actionElement.setAttribute( "name", name() ); #define TEXT_ELEMENT( TAGNAME, TEXT ) \ { \ TQDomElement e = doc.createElement( TAGNAME ); \ e.appendChild( doc.createTextNode( TEXT ) ); \ actionElement.appendChild( e ); \ } TEXT_ELEMENT( "title", text() ) if ( ! toolTip().isEmpty() ) TEXT_ELEMENT( "tooltip", toolTip() ) if ( ! icon().isEmpty() ) TEXT_ELEMENT( "icon", icon() ) if ( ! category().isEmpty() ) TEXT_ELEMENT( "category", category() ) if ( ! whatsThis().isEmpty() ) TEXT_ELEMENT( "description", whatsThis() ) actionElement.appendChild( dumpCommand( doc ) ); if ( ! startpath().isEmpty() ) TEXT_ELEMENT( "startpath", startpath() ) TQDomElement availabilityElement = dumpAvailability( doc ); if ( availabilityElement.hasChildNodes() ) actionElement.appendChild( availabilityElement ); if ( ! shortcut().isNull() ) TEXT_ELEMENT( "defaultshortcut", shortcut().toStringInternal() ) //.toString() would return a localised string which can't be read again return actionElement; } //KrAction::xmlDump void KrAction::readCommand( const TQDomElement& element ) { TQString attr; attr = element.attribute( "executionmode", "normal" ); // default: "normal" if ( attr == "normal") setExecType( Normal ); else if ( attr == "terminal" ) setExecType( Terminal ); else if ( attr == "collect_output") setExecType( CollectOutput ); else if ( attr == "collect_output_separate_stderr") setExecType( CollectOutputSeparateStderr ); else krOut << "KrAction::readCommand() - unrecognized attribute value found: , or text nodes) TQStringList* showlist = 0; if ( e.tagName() == "protocol" ) showlist = &_showonlyProtocol; else if ( e.tagName() == "path" ) showlist = &_showonlyPath; else if ( e.tagName() == "mimetype" ) showlist = & _showonlyMime; else if ( e.tagName() == "filename" ) showlist = & _showonlyFile; else { krOut << "KrAction::readAvailability() - unrecognized element found: <" << e.tagName() << ">"<< endl; showlist = 0; } if ( showlist ) { for ( TQDomNode subnode = e.firstChild(); ! subnode.isNull(); subnode = subnode.nextSibling() ) { TQDomElement subelement = subnode.toElement(); if ( subelement.tagName() == "show" ) showlist->append( subelement.text() ); } // for } // if ( showlist ) } // for } //KrAction::readAvailability TQDomElement KrAction::dumpAvailability( TQDomDocument& doc ) const { TQDomElement availabilityElement = doc.createElement("command"); # define LIST_ELEMENT( TAGNAME, LIST ) \ { \ TQDomElement e = doc.createElement( TAGNAME ); \ for ( TQStringList::const_iterator it = LIST.constBegin(); it != LIST.constEnd(); ++it ) { \ TQDomElement show = doc.createElement( "show" ); \ show.appendChild( doc.createTextNode( *it ) ); \ e.appendChild( show ); \ } \ availabilityElement.appendChild( e ); \ } if ( ! _showonlyProtocol.isEmpty() ) LIST_ELEMENT( "protocol", _showonlyProtocol ) if ( ! _showonlyPath.isEmpty() ) LIST_ELEMENT( "path", _showonlyPath ) if ( ! _showonlyMime.isEmpty() ) LIST_ELEMENT( "mimetype", _showonlyMime ) if ( ! _showonlyFile.isEmpty() ) LIST_ELEMENT( "filename", _showonlyFile ) return availabilityElement; } //KrAction::dumpAvailability #include "kraction.moc"