1 // Copyright (C) 2007-2023 CEA/DEN, EDF R&D, OPEN CASCADE
3 // Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN,
4 // CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS
6 // This library is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU Lesser General Public
8 // License as published by the Free Software Foundation; either
9 // version 2.1 of the License, or (at your option) any later version.
11 // This library is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 // Lesser General Public License for more details.
16 // You should have received a copy of the GNU Lesser General Public
17 // License along with this library; if not, write to the Free Software
18 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 // See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
22 // File : PyConsole_Editor.cxx
23 // Author : Vadim SANDLER, Open CASCADE S.A.S. (vadim.sandler@opencascade.com)
26 \class PyConsole_Editor
27 \brief Python command line interpreter front-end GUI widget.
29 This class provides simple GUI interface to the Python interpreter, including basic
30 navigation operations, executing commands (both interactively and programmatically),
31 copy-paste operations, history of the commands and so on.
33 Here below is the shortcut keyboard boundings used for navigation and other operations:
34 - <Enter> : execute current command
35 - <Ctrl><Break> : clear current command
36 - <Escape> : clear current command
37 - <Up> : previous command in the history
38 - <Shift><Up> : move cursor one row up with selection
39 - <Ctrl><Up> : move cursor one row up without selection
40 - <Ctrl><Shift><Up> : move cursor one row up with selection
41 - <Down> : next command in the history
42 - <Shift><Down> : move cursor one row down with selection
43 - <Ctrl><Down> : move cursor one row down without selection
44 - <Ctrl><Shift><Down> : move cursor one row down with selection
45 - <Left> : move one symbol left without selection
46 - <Shift><Left> : move one symbol left with selection
47 - <Ctrl><Left> : move one word left without selection
48 - <Ctrl><Shift><Left> : move one word left with selection
49 - <Right> : move one symbol right without selection
50 - <Shift><Right> : move one symbol right with selection
51 - <Ctrl><Right> : move one word right without selection
52 - <Ctrl><Shift><Right> : move one word right with selection
53 - <PgUp> : first command in the history
54 - <Shift><PgUp> : move one page up with selection
55 - <Ctrl><PgUp> : move one page up without selection
56 - <Ctrl><Shift><PgUp> : scroll one page up
57 - <PgDn> : last command in the history
58 - <Shift><PgDn> : move one page down with selection
59 - <Ctrl><PgDn> : move one page down without selection
60 - <Ctrl><Shift><PgDn> : scroll one page down
61 - <Home> : move to the beginning of the line without selection
62 - <Shift><Home> : move to the beginning of the line with selection
63 - <Ctrl><Home> : move to the very first symbol without selection
64 - <Ctrl><Shift><Home> : move to the very first symbol with selection
65 - <End> : move to the end of the line without selection
66 - <Shift><End> : move to the end of the line with selection
67 - <Ctrl><End> : move to the very last symbol without selection
68 - <Ctrl><Shift><End> : move to the very last symbol with selection
69 - <Backspace> : delete symbol before the cursor
70 / remove selected text and put it to the clipboard (cut)
71 - <Shift><Backspace> : delete previous word
72 / remove selected text and put it to the clipboard (cut)
73 - <Ctrl><Backspace> : delete text from the cursor to the beginning of the line
74 / remove selected text and put it to the clipboard (cut)
75 - <Delete> : delete symbol after the cursor
76 / remove selected text and put it to the clipboard (cut)
77 - <Shift><Delete> : delete next word
78 / remove selected text and put it to the clipboard (cut)
79 - <Ctrl><Delete> : delete text from the cursor to the end of the line
80 / remove selected text and put it to the clipboard (cut)
81 - <Ctrl><Insert> : copy
82 - <Shift><Insert> : paste
87 - <Tab> : performs auto-completion
88 - <Ctrl><Tab> : undoes auto-completion
91 #include "PyConsole_Interp.h"
92 #include "PyConsole_Editor.h"
93 #include "PyConsole_Event.h"
94 #include "PyInterp_Dispatcher.h"
95 #include "PyConsole_Request.h"
97 #include <QApplication>
103 #include <QMouseEvent>
104 #include <QScrollBar>
105 #include <QTextBlock>
106 #include <QTextCursor>
107 #include <QTextStream>
109 #include <QFileDialog>
110 #include <QMessageBox>
113 //VSR: uncomment below macro to support unicode text properly in SALOME
114 // current commented out due to regressions
115 //#define PAL22528_UNICODE
119 QString fromUtf8( const char* txt )
121 #ifdef PAL22528_UNICODE
122 return QString::fromUtf8( txt );
124 return QString( txt );
129 static QString READY_PROMPT = ">>> ";
130 static QString DOTS_PROMPT = "... ";
132 void PyConsole_CallbackStdout( void* data, char* c )
134 if(!((PyConsole_Editor*)data)->isSuppressOutput()) {
135 PyConsole_Editor* e = (PyConsole_Editor*)data;
136 e->putLog( fromUtf8(c) );
137 QApplication::postEvent( e, new PyConsole_PrintEvent( fromUtf8(c), false ) );
141 void PyConsole_CallbackStderr( void* data, char* c )
143 if(!((PyConsole_Editor*)data)->isSuppressOutput()) {
144 PyConsole_Editor* e = (PyConsole_Editor*)data;
145 e->putLog( fromUtf8(c) );
146 QApplication::postEvent( e, new PyConsole_PrintEvent( fromUtf8(c), true ) );
151 \brief Default constructor.
153 Creates python editor window.
154 \param theParent parent widget
156 PyConsole_Editor::PyConsole_Editor( QWidget* parent )
157 : QTextEdit( parent )
159 PyConsole_Interp *interp(new PyConsole_Interp);
160 interp->initialize();
168 Creates python editor window.
169 \param parent parent widget
170 \param interp python interpreter
172 PyConsole_Editor::PyConsole_Editor( QWidget* parent,
173 PyConsole_Interp* interp )
174 : QTextEdit( parent )
176 myInterp.takeRef(interp);
181 void PyConsole_Editor::init()
187 myIsSuppressOutput = false;
188 myMultiLinePaste = false;
189 myAutoCompletion = false;
191 myComplCursorPos = -1;
193 setFont( QFont( "Courier", 11 ) ); // default font
194 setUndoRedoEnabled( false );
196 myPrompt = READY_PROMPT;
197 setLineWrapMode( QTextEdit::WidgetWidth );
198 setWordWrapMode( QTextOption::WrapAnywhere );
199 setAcceptRichText( false );
201 // set callbacks to interpreter
202 myInterp->setvoutcb( PyConsole_CallbackStdout, this );
203 myInterp->setverrcb( PyConsole_CallbackStderr, this );
205 if ( isShowBanner() )
207 // clear command buffer
208 myCommandBuffer.truncate(0);
209 // unset read-only state
210 setReadOnly( false );
211 // unset history browsing mode
216 viewport()->unsetCursor();
222 PyConsole_Editor::~PyConsole_Editor()
227 \brief Get Python interpreter
229 PyConsole_Interp *PyConsole_Editor::getInterp() const
231 return myInterp.iAmATrollConstCast();
235 \brief Get synchronous mode flag value.
238 \return \c true if python console works in synchronous mode
240 bool PyConsole_Editor::isSync() const
246 \brief Set synchronous mode flag value.
248 In synhronous mode the Python commands are executed in the GUI thread
249 and the GUI is blocked until the command is finished. In the asynchronous
250 mode each Python command is executed in the separate thread that does not
251 block the main GUI loop.
253 \param on synhronous mode flag
255 void PyConsole_Editor::setIsSync( const bool on )
261 \brief Get suppress output flag value.
263 \sa setIsSuppressOutput()
264 \return \c true if python console output is suppressed.
266 bool PyConsole_Editor::isSuppressOutput() const
268 return myIsSuppressOutput;
272 \brief Set suppress output flag value.
274 In case if suppress output flag is \c true, the python
275 console output suppressed.
277 \param on suppress output flag
279 void PyConsole_Editor::setIsSuppressOutput( const bool on )
281 myIsSuppressOutput = on;
285 \brief Get 'show banner' flag value.
287 \sa setIsShowBanner()
288 \return \c true if python console shows banner
290 bool PyConsole_Editor::isShowBanner() const
296 \brief Set 'show banner' flag value.
298 The banner is shown in the top of the python console window.
301 \param on 'show banner' flag
303 void PyConsole_Editor::setIsShowBanner( const bool on )
305 if ( myShowBanner != on ) {
312 \brief Switch on/off commands auto-completion feature
315 void PyConsole_Editor::setAutoCompletion( bool on )
317 myAutoCompletion = on;
318 document()->setUndoRedoEnabled( myAutoCompletion );
322 \brief Returns \c true if auto-completion feature is switched on
323 or \c false otherwise
324 \sa setAutoCompletion()
326 bool PyConsole_Editor::autoCompletion() const
328 return myAutoCompletion;
332 \brief Check if trace logging is switched on.
334 \sa startLog(), stopLog()
335 \return \c true if trace logging is switched on
337 bool PyConsole_Editor::isLogging() const
339 return !myLogFile.isEmpty();
343 \brief Get size hint for the Python console window
344 \return size hint value
346 QSize PyConsole_Editor::sizeHint() const
348 QFontMetrics fm( font() );
349 int nbLines = ( isShowBanner() ? banner().split("\n").count() : 0 ) + 1;
350 QSize s(100, fm.lineSpacing()*nbLines);
355 \brief Put the string \a str to the python editor.
356 \param str string to be put in the command line of the editor
357 \param newBlock if \c true, then the string is printed on a new line
358 \param isError if \c true, the text is printed in dark red
360 void PyConsole_Editor::addText( const QString& str,
364 QTextCursor aCursor = textCursor();
367 moveCursor( QTextCursor::End );
369 aCursor.insertBlock();
371 cf.setForeground( QBrush( Qt::red ) );
372 aCursor.insertText( str, cf );
373 moveCursor( QTextCursor::End );
374 ensureCursorVisible();
378 \brief Convenient method for executing a Python command,
379 as if the user typed it manually.
380 \param command python command to be executed
382 !!! WARNING: doesn't work properly with multi-line commands. !!!
384 void PyConsole_Editor::exec( const QString& command )
386 if ( isReadOnly() ) {
387 // some interactive command is being executed in this editor...
388 // schedule the command to the queue
389 myQueue.push_back( command );
394 moveCursor( QTextCursor::End );
395 moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor );
396 textCursor().removeSelectedText();
398 // set "ready" prompt
399 myPrompt = READY_PROMPT;
401 // clear command buffer
402 myCommandBuffer.truncate( 0 );
404 // unset history browsing mode
407 // print command line by line
408 QString cmd = command;
409 if ( !cmd.endsWith( "\n" ) ) cmd += "\n";
410 QStringList lines = command.split( "\n" );
411 for ( int i = 0; i < lines.size(); i++ ) {
412 if ( !lines[i].trimmed().isEmpty() )
413 myHistory.push_back( lines[i] );
414 addText( ( i == 0 ? READY_PROMPT : DOTS_PROMPT ) + lines[i], i != 0 );
415 putLog( QString( "%1%2\n" ).arg( i == 0 ? READY_PROMPT : DOTS_PROMPT ).arg( lines[i] ) );
421 // set read-only mode
425 setCursor( Qt::BusyCursor );
427 // post a request to execute Python command;
428 // editor will be informed via a custom event that execution has been completed
429 PyInterp_Dispatcher::Get()->Exec( createCmdRequest( cmd ) );
433 \brief Create request to the python dispatcher for the command execution.
434 \param command python command to be executed
436 PyInterp_Request* PyConsole_Editor::createCmdRequest( const QString& command )
438 return new PyConsole_ExecCommand( myInterp, command, this, isSync() );
442 \brief Create the Python request that will be posted to the interpreter to
444 \param input line entered by the user at the time <TAB> was pressed
445 \return completion command
446 \sa CompletionCommand
448 PyInterp_Request* PyConsole_Editor::createTabRequest( const QString& input )
451 static QStringList separators;
452 if ( separators.isEmpty() ) {
453 separators << " " << "(" << "[" << "+" << "-" << "*" << "/" << ";" << "^" << "=";
456 // parse input to extract on what part the dir() has to be executed
458 QString input2( input );
460 // split up to the last syntaxical separator
462 foreach ( QString separator, separators ) {
463 int j = input2.lastIndexOf( separator );
468 input2 = input.mid( lastSp + 1 );
470 // detect a qualified name (with a point)
471 int lastPt = input2.lastIndexOf( "." );
473 if ( lastPt != -1 ) {
474 // split the 2 surrounding parts of the qualified name
475 myComplBeforePoint = input2.left( lastPt );
476 myComplAfterPoint = input2.mid( lastPt+1 );
479 // no point found - do a global matching
480 // (the following will call dir() with an empty string)
481 myComplAfterPoint = input2;
482 myComplBeforePoint = "";
485 return new PyConsole_CompletionCommand( myInterp, myComplBeforePoint,
486 myComplAfterPoint, this, isSync() );
490 \brief Execute command in the python interpreter
491 and wait until it is finished.
493 \param command python command to be executed
495 void PyConsole_Editor::execAndWait( const QString& command )
501 // create new event loop
502 bool sync = isSync();
504 myEventLoop = new QEventLoop( this );
513 // delete event loop after command is processed
520 \brief Process <Enter> key press event.
522 Execute the command entered by the user.
524 void PyConsole_Editor::handleReturn()
526 // Position cursor at the end
527 QTextCursor aCursor = textCursor();
528 aCursor.movePosition( QTextCursor::End );
529 setTextCursor( aCursor );
532 QTextBlock par = document()->end().previous();
533 if ( !par.isValid() ) return;
536 QString cmd = par.text().remove( 0, promptSize() );
538 // extend the command buffer with the current command
539 myCommandBuffer.append( cmd );
541 // add command to the history
542 if ( !cmd.trimmed().isEmpty() )
543 myHistory.push_back( cmd );
544 putLog( QString( "%1%2\n" ).arg( myPrompt ).arg( cmd ) );
549 // set read-only mode
553 setCursor( Qt::BusyCursor );
555 // post a request to execute Python command;
556 // editor will be informed via a custom event that execution has been completed
557 PyInterp_Dispatcher::Get()->Exec( createCmdRequest( myCommandBuffer ) );
561 \brief Process <Tab> key press event.
563 Perform auto-completion of the currently entered command, if this feature is enabled
565 void PyConsole_Editor::handleTab()
567 if ( !autoCompletion() )
568 return; // auto-completion feature is disabled
571 return; // already in tab mode
573 QTextCursor aCursor = textCursor();
575 // move cursor to the end of input
576 aCursor.movePosition( QTextCursor::End );
577 setTextCursor( aCursor );
579 // save cursor position if needed
580 if ( myComplCursorPos == -1 )
581 myComplCursorPos = textCursor().position();
584 QTextBlock par = document()->end().previous();
585 if ( !par.isValid() ) return; // empty line
587 // switch to completion mode
590 // get currently entered command
591 QString cmd = par.text().mid( promptSize() );
593 // post completion request
594 // editor will be informed that completion has been done via a custom event
595 PyInterp_Dispatcher::Get()->Exec( createTabRequest( cmd ) );
599 \brief Process <Ctrl><Tab> key press event.
601 Undoe last auto-completion
603 void PyConsole_Editor::handleBackTab()
605 if ( !autoCompletion() )
606 return; // auto-completion feature is disabled
608 QTextCursor aCursor = textCursor();
610 if ( myComplCursorPos == -1 )
611 return; // invalid cursor position
613 // ensure cursor is at the end of command line
614 aCursor.setPosition( myComplCursorPos );
615 aCursor.movePosition( QTextCursor::EndOfLine );
616 //setCursor( aCursor );
618 // delete last completed text
619 int i = aCursor.position() - myComplCursorPos;
620 aCursor.movePosition( QTextCursor::Left, QTextCursor::KeepAnchor, i );
621 aCursor.removeSelectedText();
622 myComplCursorPos = -1;
626 \brief Process drop event.
629 \param event drop event
631 void PyConsole_Editor::dropEvent( QDropEvent* event )
633 // get the initial drop position
634 #if QT_VERSION >= 0x060000
635 QPoint pos = event->position().toPoint();
637 QPoint pos = event->pos();
639 QTextCursor aCursor = cursorForPosition( pos );
641 // if the position is not in the last line move it to the end of the command line
642 if ( aCursor.position() < document()->end().previous().position() + promptSize() ) {
643 moveCursor( QTextCursor::End );
644 pos = cursorRect().center();
647 // create new drop event and use it instead of the original
649 event->possibleActions(),
651 #if QT_VERSION >= 0x060000
655 event->mouseButtons(),
656 event->keyboardModifiers(),
659 QTextEdit::dropEvent( &de );
661 // accept the original event
662 event->acceptProposedAction();
666 \brief Process mouse press event
668 Clear the completion when any mouse button is pressed.
670 \param e mouse press event
672 void PyConsole_Editor::mousePressEvent( QMouseEvent* event )
674 if ( autoCompletion() ) {
676 myComplCursorPos = -1;
678 QTextEdit::mousePressEvent( event );
682 \brief Process mouse button release event.
684 Left mouse button: copy selection to the clipboard.
685 Middle mouse button: paste clipboard's contents.
687 \param event mouse event
689 void PyConsole_Editor::mouseReleaseEvent( QMouseEvent* event )
691 if ( event->button() == Qt::LeftButton ) {
692 QTextEdit::mouseReleaseEvent( event );
694 else if ( event->button() == Qt::MiddleButton ) {
695 QTextCursor aCursor = cursorForPosition( event->pos() );
696 // if the position is not in the last line move it to the end of the command line
697 if ( aCursor.position() < document()->end().previous().position() + promptSize() ) {
698 moveCursor( QTextCursor::End );
701 setTextCursor( aCursor );
703 const QMimeData* md = QApplication::clipboard()->mimeData( QApplication::clipboard()->supportsSelection() ?
704 QClipboard::Selection : QClipboard::Clipboard );
706 insertFromMimeData( md );
709 QTextEdit::mouseReleaseEvent( event );
714 \brief Check if the string is command.
716 Return \c true if the string \a str is likely to be the command
717 (i.e. it is started from the '>>>' or '...').
718 \param str string to be checked
720 bool PyConsole_Editor::isCommand( const QString& str ) const
722 return str.startsWith( READY_PROMPT ) || str.startsWith( DOTS_PROMPT );
726 \brief Handle keyboard event.
728 Implement navigation, history browsing, copy/paste and other common
731 \param event keyboard event
733 void PyConsole_Editor::keyPressEvent( QKeyEvent* event )
735 // get cursor position
736 QTextCursor aCursor = textCursor();
737 int curLine = aCursor.blockNumber();
738 int curCol = aCursor.columnNumber();
740 // get last edited line
741 int endLine = document()->blockCount()-1;
743 // get pressed key code
744 int aKey = event->key();
746 // check if <Ctrl> is pressed
747 bool ctrlPressed = event->modifiers() & Qt::ControlModifier;
748 // check if <Shift> is pressed
749 bool shftPressed = event->modifiers() & Qt::ShiftModifier;
751 if ( autoCompletion() ) {
752 // auto-completion support
753 if ( aKey == Qt::Key_Tab && !shftPressed ) {
755 if ( !ctrlPressed ) {
765 // If <Ctrl> is not pressed (or if something else is pressed with <Ctrl>),
766 // or if <Ctrl> is not pressed alone, we have to clear completion
767 if ( !ctrlPressed || ( ctrlPressed && aKey != Qt::Key_Control ) ) {
769 myComplCursorPos = -1;
772 // Discard <Ctrl> pressed alone:
773 if ( aKey == Qt::Key_Control )
777 if ( aKey == Qt::Key_Escape || ( ctrlPressed && aKey == -1 ) ) {
778 // process <Ctrl>+<Break> key-binding and <Escape> key: clear current command
779 myCommandBuffer.truncate( 0 );
780 myPrompt = READY_PROMPT;
781 addText( myPrompt, true );
782 horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
785 else if ( ctrlPressed && aKey == Qt::Key_C ) {
786 // process <Ctrl>+<C> key-binding : copy
790 else if ( ctrlPressed && aKey == Qt::Key_X ) {
791 // process <Ctrl>+<X> key-binding : cut
795 else if ( ctrlPressed && aKey == Qt::Key_V ) {
796 // process <Ctrl>+<V> key-binding : paste
801 // check for printed key
802 // #### aKey = ( aKey < Qt::Key_Space || aKey > Qt::Key_ydiaeresis ) ? aKey : 0;
804 aKey = !(QChar(aKey).isPrint()) ? aKey : 0;
808 // any printed key: just print it
810 if ( curLine < endLine || curCol < promptSize() ) {
811 moveCursor( QTextCursor::End );
813 QTextEdit::keyPressEvent( event );
818 // <Enter> key: process the current command
824 // <Up> arrow key: process as follows:
825 // - without <Ctrl>, <Shift> modifiers: previous command in history
826 // - with <Ctrl> modifier key pressed: move cursor one row up without selection
827 // - with <Shift> modifier key pressed: move cursor one row up with selection
828 // - with <Ctrl>+<Shift> modifier keys pressed: scroll one row up
830 if ( ctrlPressed && shftPressed ) {
831 int value = verticalScrollBar()->value();
832 int spacing = fontMetrics().lineSpacing();
833 verticalScrollBar()->setValue( value > spacing ? value-spacing : 0 );
835 else if ( shftPressed || ctrlPressed ) {
837 moveCursor( QTextCursor::Up,
838 shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor );
841 if ( myCmdInHistory < 0 && myHistory.count() > 0 ) {
842 // set history browsing mode
843 myCmdInHistory = myHistory.count();
844 // remember current command
845 QTextBlock par = document()->end().previous();
846 myCurrentCommand = par.text().remove( 0, promptSize() );
848 if ( myCmdInHistory > 0 ) {
850 // get previous command in the history
851 QString previousCommand = myHistory.at( myCmdInHistory );
852 // print previous command
853 moveCursor( QTextCursor::End );
854 moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor );
855 textCursor().removeSelectedText();
856 addText( myPrompt + previousCommand );
857 // move cursor to the end
858 moveCursor( QTextCursor::End );
864 // <Down> arrow key: process as follows:
865 // - without <Ctrl>, <Shift> modifiers: next command in history
866 // - with <Ctrl> modifier key pressed: move cursor one row down without selection
867 // - with <Shift> modifier key pressed: move cursor one row down with selection
868 // - with <Ctrl>+<Shift> modifier keys pressed: scroll one row down
870 if ( ctrlPressed && shftPressed ) {
871 int value = verticalScrollBar()->value();
872 int maxval = verticalScrollBar()->maximum();
873 int spacing = fontMetrics().lineSpacing();
874 verticalScrollBar()->setValue( value+spacing < maxval ? value+spacing : maxval );
876 else if ( shftPressed || ctrlPressed) {
877 if ( curLine < endLine )
878 moveCursor( QTextCursor::Down,
879 shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor );
882 if ( myCmdInHistory >= 0 ) {
883 // get next command in the history
886 if ( myCmdInHistory < myHistory.count() ) {
887 // next command in history
888 nextCommand = myHistory.at( myCmdInHistory );
891 // end of history is reached
892 // last printed command
893 nextCommand = myCurrentCommand;
894 // unset history browsing mode
897 // print next or current command
898 moveCursor( QTextCursor::End );
899 moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor );
900 textCursor().removeSelectedText();
901 addText( myPrompt + nextCommand );
902 // move cursor to the end
903 moveCursor( QTextCursor::End );
909 // <Left> arrow key: process as follows:
910 // - without <Ctrl>, <Shift> modifiers: move one symbol left (taking into account prompt)
911 // - with <Ctrl> modifier key pressed: move one word left (taking into account prompt)
912 // - with <Shift> modifier key pressed: move one symbol left with selection
913 // - with <Ctrl>+<Shift> modifier keys pressed: move one word left with selection
915 QString txt = textCursor().block().text();
916 if ( !shftPressed && isCommand( txt ) && curCol <= promptSize() ) {
917 moveCursor( QTextCursor::Up );
918 moveCursor( QTextCursor::EndOfBlock );
921 QTextEdit::keyPressEvent( event );
926 // <Right> arrow key: process as follows:
927 // - without <Ctrl>, <Shift> modifiers: move one symbol right (taking into account prompt)
928 // - with <Ctrl> modifier key pressed: move one word right (taking into account prompt)
929 // - with <Shift> modifier key pressed: move one symbol right with selection
930 // - with <Ctrl>+<Shift> modifier keys pressed: move one word right with selection
932 QString txt = textCursor().block().text();
933 if ( !shftPressed ) {
934 if ( curCol < txt.length() ) {
935 if ( isCommand( txt ) && curCol < promptSize() ) {
936 aCursor.setPosition( aCursor.block().position() + promptSize() );
937 setTextCursor( aCursor );
942 if ( curLine < endLine && isCommand( textCursor().block().next().text() ) ) {
943 aCursor.setPosition( aCursor.position() + promptSize()+1 );
944 setTextCursor( aCursor );
945 horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
950 QTextEdit::keyPressEvent( event );
954 // <PageUp> key: process as follows:
955 // - without <Ctrl>, <Shift> modifiers: first command in history
956 // - with <Ctrl> modifier key pressed: move cursor one page up without selection
957 // - with <Shift> modifier key pressed: move cursor one page up with selection
958 // - with <Ctrl>+<Shift> modifier keys pressed: scroll one page up
960 if ( ctrlPressed && shftPressed ) {
961 verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepSub);
963 else if ( shftPressed || ctrlPressed ) {
965 qreal lastY = cursorRect( aCursor ).top();
967 // move using movePosition to keep the cursor's x
969 qreal y = cursorRect( aCursor ).top();
970 distance += qAbs( y - lastY );
972 moved = aCursor.movePosition( QTextCursor::Up,
973 shftPressed ? QTextCursor::KeepAnchor :
974 QTextCursor::MoveAnchor );
975 } while ( moved && distance < viewport()->height() );
977 aCursor.movePosition( QTextCursor::Down,
978 shftPressed ? QTextCursor::KeepAnchor :
979 QTextCursor::MoveAnchor );
980 verticalScrollBar()->triggerAction( QAbstractSlider::SliderPageStepSub );
982 setTextCursor( aCursor );
985 if ( myCmdInHistory < 0 && myHistory.count() > 0 ) {
986 // set history browsing mode
987 myCmdInHistory = myHistory.count();
988 // remember current command
989 QTextBlock par = document()->end().previous();
990 myCurrentCommand = par.text().remove( 0, promptSize() );
992 if ( myCmdInHistory > 0 ) {
994 // get very first command in the history
995 QString firstCommand = myHistory.at( myCmdInHistory );
996 // print first command
997 moveCursor( QTextCursor::End );
998 moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor );
999 textCursor().removeSelectedText();
1000 addText( myPrompt + firstCommand );
1001 // move cursor to the end
1002 moveCursor( QTextCursor::End );
1007 case Qt::Key_PageDown:
1008 // <PageDown> key: process as follows:
1009 // - without <Ctrl>, <Shift> modifiers: last command in history
1010 // - with <Ctrl> modifier key pressed: move cursor one page down without selection
1011 // - with <Shift> modifier key pressed: move cursor one page down with selection
1012 // - with <Ctrl>+<Shift> modifier keys pressed: scroll one page down
1014 if ( ctrlPressed && shftPressed ) {
1015 verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepAdd);
1017 else if ( shftPressed || ctrlPressed ) {
1019 qreal lastY = cursorRect( aCursor ).top();
1021 // move using movePosition to keep the cursor's x
1023 qreal y = cursorRect( aCursor ).top();
1024 distance += qAbs( y - lastY );
1026 moved = aCursor.movePosition( QTextCursor::Down,
1027 shftPressed ? QTextCursor::KeepAnchor :
1028 QTextCursor::MoveAnchor );
1029 } while ( moved && distance < viewport()->height() );
1031 aCursor.movePosition( QTextCursor::Up,
1032 shftPressed ? QTextCursor::KeepAnchor :
1033 QTextCursor::MoveAnchor );
1034 verticalScrollBar()->triggerAction( QAbstractSlider::SliderPageStepSub );
1036 setTextCursor( aCursor );
1039 if ( myCmdInHistory >= 0 ) {
1040 // unset history browsing mode
1041 myCmdInHistory = -1;
1042 // print current command
1043 moveCursor( QTextCursor::End );
1044 moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor );
1045 textCursor().removeSelectedText();
1046 addText( myPrompt + myCurrentCommand );
1047 // move cursor to the end
1048 moveCursor( QTextCursor::End );
1054 // <Home> key: process as follows:
1055 // - without <Ctrl>, <Shift> modifiers: move cursor to the beginning of the current line without selection
1056 // - with <Ctrl> modifier key pressed: move cursor to the very first symbol without selection
1057 // - with <Shift> modifier key pressed: move cursor to the beginning of the current line with selection
1058 // - with <Ctrl>+<Shift> modifier keys pressed: move cursor to the very first symbol with selection
1060 if ( ctrlPressed ) {
1061 moveCursor( QTextCursor::Start,
1062 shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor );
1065 QString txt = textCursor().block().text();
1066 if ( isCommand( txt ) ) {
1067 if ( shftPressed ) {
1068 if ( curCol > promptSize() ) {
1069 aCursor.movePosition( QTextCursor::StartOfLine, QTextCursor::KeepAnchor );
1070 aCursor.movePosition( QTextCursor::Right, QTextCursor::KeepAnchor, promptSize() );
1074 aCursor.movePosition( QTextCursor::StartOfLine );
1075 aCursor.movePosition( QTextCursor::Right, QTextCursor::MoveAnchor, promptSize() );
1077 setTextCursor( aCursor );
1080 moveCursor( QTextCursor::StartOfBlock,
1081 shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor );
1083 horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
1088 // <End> key: process as follows:
1089 // - without <Ctrl>, <Shift> modifiers: move cursor to the end of the current line without selection
1090 // - with <Ctrl> modifier key pressed: move cursor to the very last symbol without selection
1091 // - with <Shift> modifier key pressed: move cursor to the end of the current line with selection
1092 // - with <Ctrl>+<Shift> modifier keys pressed: move cursor to the very last symbol with selection
1094 moveCursor( ctrlPressed ? QTextCursor::End : QTextCursor::EndOfBlock,
1095 shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor );
1098 case Qt::Key_Backspace :
1099 // <Backspace> key: process as follows
1100 // - without any modifiers : delete symbol before the cursor / selection (taking into account prompt)
1101 // - with <Shift> modifier key pressed: delete previous word
1102 // - with <Ctrl> modifier key pressed: delete text from the cursor to the line beginning
1103 // works only for last (command) line
1105 if ( aCursor.hasSelection() ) {
1108 else if ( aCursor.position() > document()->end().previous().position() + promptSize() ) {
1109 if ( shftPressed ) {
1110 moveCursor( QTextCursor::PreviousWord, QTextCursor::KeepAnchor );
1111 textCursor().removeSelectedText();
1113 else if ( ctrlPressed ) {
1114 aCursor.setPosition( document()->end().previous().position() + promptSize(),
1115 QTextCursor::KeepAnchor );
1116 setTextCursor( aCursor );
1117 textCursor().removeSelectedText();
1120 QTextEdit::keyPressEvent( event );
1124 aCursor.setPosition( document()->end().previous().position() + promptSize() );
1125 setTextCursor( aCursor );
1126 horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
1130 case Qt::Key_Delete :
1131 // <Delete> key: process as follows
1132 // - without any modifiers : delete symbol after the cursor / selection (taking into account prompt)
1133 // - with <Shift> modifier key pressed: delete next word
1134 // - with <Ctrl> modifier key pressed: delete text from the cursor to the end of line
1135 // works only for last (command) line
1137 if ( aCursor.hasSelection() ) {
1140 else if ( aCursor.position() > document()->end().previous().position() + promptSize()-1 ) {
1141 if ( shftPressed ) {
1142 moveCursor( QTextCursor::NextWord, QTextCursor::KeepAnchor );
1143 textCursor().removeSelectedText();
1145 else if ( ctrlPressed ) {
1146 moveCursor( QTextCursor::EndOfBlock, QTextCursor::KeepAnchor );
1147 textCursor().removeSelectedText();
1150 QTextEdit::keyPressEvent( event );
1154 aCursor.setPosition( document()->end().previous().position() + promptSize() );
1155 setTextCursor( aCursor );
1156 horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
1160 case Qt::Key_Insert :
1161 // <Insert> key: process as follows
1162 // - with <Ctrl> modifier key pressed: copy()
1163 // - with <Shift> modifier key pressed: paste() to the command line
1165 if ( ctrlPressed ) {
1168 else if ( shftPressed ) {
1172 QTextEdit::keyPressEvent( event );
1181 \brief Handle notification event coming from Python dispatcher.
1182 \param event notification event
1184 void PyConsole_Editor::customEvent( QEvent* event )
1186 switch( (int) event->type() )
1188 case PyConsole_PrintEvent::EVENT_ID:
1190 PyConsole_PrintEvent* pe = (PyConsole_PrintEvent*)event;
1191 addText( pe->text(), false, pe->isError() );
1194 case PyConsole_CompletionEvent::EVENT_ID:
1196 PyConsole_CompletionEvent* ce = (PyConsole_CompletionEvent*)event;
1197 bool status = ce->status();
1198 QStringList matches = ce->matches();
1199 QString doc = ce->doc();
1202 // completion was successful
1203 QTextCursor aCursor = textCursor();
1205 if ( matches.isEmpty() ) {
1206 // completion successful but there are no matches.
1208 myComplCursorPos = -1;
1212 if ( matches.size() == 1 ) {
1213 // there's only one match - complete directly and update doc string window
1214 aCursor.insertText( matches[0].mid( myComplAfterPoint.size() ) );
1216 if ( doc.isEmpty() )
1217 emit updateDoc( formatDocHTML( QString( "(%1)\n" ).arg( tr( "NO_DOC_AVAILABLE" ) ) ) );
1219 emit updateDoc( formatDocHTML( doc ) );
1222 // there are several matches
1224 // detect if there is a common base to all available completion
1225 // in this case append this base to the text
1226 QString base = extractCommon( matches );
1227 aCursor.insertText( base.mid( myComplAfterPoint.size() ) );
1229 // if this happens to match exactly the first completion
1231 if ( base == matches[0] )
1232 emit updateDoc( formatDocHTML( doc ) );
1234 // print all matching completion in a "undo-able" block
1235 int cursorPos = aCursor.position();
1236 aCursor.insertBlock();
1237 aCursor.beginEditBlock();
1239 // insert all matches
1241 cf.setForeground( QBrush( Qt::darkGreen ) );
1242 aCursor.setCharFormat( cf );
1243 aCursor.insertText( formatCompletion( matches ) );
1244 aCursor.endEditBlock();
1246 // position cursor where it was before inserting the completion list
1247 aCursor.setPosition( cursorPos );
1248 setTextCursor( aCursor );
1252 // completion failed
1254 myComplCursorPos = -1;
1258 case PyInterp_Event::ES_OK:
1259 case PyInterp_Event::ES_ERROR:
1261 // clear command buffer
1262 myCommandBuffer.truncate( 0 );
1263 // add caret return line if necessary
1264 QTextBlock par = document()->end().previous();
1265 QString txt = par.text();
1266 txt.truncate( txt.length() - 1 );
1267 // IPAL19397 : addText moved to handleReturn() method
1268 //if ( !txt.isEmpty() )
1269 // addText( "", true );
1270 // set "ready" prompt
1271 myPrompt = READY_PROMPT;
1272 addText( myPrompt );
1273 // unset busy cursor
1275 // stop event loop (if running)
1277 myEventLoop->exit();
1278 // if we are in multi_paste_mode, process the next item
1279 multiLineProcessNextLine();
1282 case PyInterp_Event::ES_INCOMPLETE:
1284 // extend command buffer (multi-line command)
1285 myCommandBuffer.append( "\n" );
1286 // add caret return line if necessary
1287 QTextBlock par = document()->end().previous();
1288 QString txt = par.text();
1289 txt.truncate( txt.length() - 1 );
1290 // IPAL19397 : addText moved to handleReturn() method
1291 //if ( !txt.isEmpty() )
1292 // addText( "", true );
1294 myPrompt = DOTS_PROMPT;
1295 addText( myPrompt/*, true*/ ); // IPAL19397
1296 // unset busy cursor
1298 // stop event loop (if running)
1300 myEventLoop->exit();
1301 // if we are in multi_paste_mode, process the next item
1302 multiLineProcessNextLine();
1306 QTextEdit::customEvent( event );
1309 // unset read-only state
1310 setReadOnly( false );
1311 // unset history browsing mode
1312 myCmdInHistory = -1;
1314 if ( (int)event->type() == (int)PyInterp_Event::ES_OK && myQueue.count() > 0 )
1316 // process the next scheduled command from the queue (if there is any)
1317 QString nextcmd = myQueue[0];
1318 myQueue.pop_front();
1324 \brief "Copy" operation.
1326 Reimplemented from Qt.
1327 Warning! In Qt this method is not virtual.
1329 void PyConsole_Editor::cut()
1331 QTextCursor aCursor = textCursor();
1332 if ( aCursor.hasSelection() ) {
1333 QApplication::clipboard()->setText( aCursor.selectedText() );
1334 int startSelection = aCursor.selectionStart();
1335 if ( startSelection < document()->end().previous().position() + promptSize() )
1336 startSelection = document()->end().previous().position() + promptSize();
1337 int endSelection = aCursor.selectionEnd();
1338 if ( endSelection < document()->end().previous().position() + promptSize() )
1339 endSelection = document()->end().previous().position() + promptSize();
1340 aCursor.setPosition( startSelection );
1341 aCursor.setPosition( endSelection, QTextCursor::KeepAnchor );
1342 horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
1343 setTextCursor( aCursor );
1344 textCursor().removeSelectedText();
1349 \brief "Paste" operation.
1351 Reimplemented from Qt.
1352 Warning! In Qt this method is not virtual.
1354 void PyConsole_Editor::paste()
1356 QTextCursor aCursor = textCursor();
1357 if ( aCursor.hasSelection() ) {
1358 int startSelection = aCursor.selectionStart();
1359 if ( startSelection < document()->end().previous().position() + promptSize() )
1360 startSelection = document()->end().previous().position() + promptSize();
1361 int endSelection = aCursor.selectionEnd();
1362 if ( endSelection < document()->end().previous().position() + promptSize() )
1363 endSelection = document()->end().previous().position() + promptSize();
1364 aCursor.setPosition( startSelection );
1365 aCursor.setPosition( endSelection, QTextCursor::KeepAnchor );
1366 horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
1367 setTextCursor( aCursor );
1368 textCursor().removeSelectedText();
1370 if ( textCursor().position() < document()->end().previous().position() + promptSize() )
1371 moveCursor( QTextCursor::End );
1376 \brief "Clear" operation.
1378 Reimplemented from Qt.
1379 Warning! In Qt this method is not virtual.
1381 void PyConsole_Editor::clear()
1384 if ( isShowBanner() )
1385 addText( banner() );
1386 myPrompt = READY_PROMPT;
1387 addText( myPrompt );
1391 \brief Dumps recorded Python commands to the file
1392 \param fileName path to the dump file
1393 \return \c true if dump operation succeeded or \c false otherwise
1395 bool PyConsole_Editor::dump( const QString& fileName )
1398 if ( !fileName.isEmpty() ) {
1399 QFile file( fileName );
1400 if ( file.open( QFile::WriteOnly ) ) {
1401 QTextStream out( &file );
1402 for ( int i = 0; i < myHistory.count(); i++ ) {
1403 #if QT_VERSION >= QT_VERSION_CHECK(5,14,0)
1404 out << myHistory[i] << Qt::endl;
1406 out << myHistory[i] << endl;
1417 \brief Dump menu action slot
1419 void PyConsole_Editor::dump()
1423 QString fileName = getDumpFileName();
1425 if ( fileName.isEmpty() )
1428 if ( dump( fileName ) )
1431 QMessageBox::warning( this,
1433 tr( "ERR_FILE_NOT_WRITEABLE" ) );
1438 \brief Get file name for Dump commands operation.
1440 This function can be redefined in successor classes to show application
1441 specific dialog box.
1443 \return path to the dump file
1445 QString PyConsole_Editor::getDumpFileName()
1447 return QFileDialog::getSaveFileName( this,
1448 tr( "GET_DUMP_COMMANDS_FILENAME" ),
1450 QString( "%1 (*.py)" ).arg( tr( "PYTHON_SCRIPTS" ) ) );
1454 \brief Get file name for Log Python trace operation.
1456 This function can be redefined in successor classes to show application
1457 specific dialog box.
1459 \return path to the log file
1461 QString PyConsole_Editor::getLogFileName()
1463 return QFileDialog::getSaveFileName( this,
1464 tr( "GET_PYTHON_TRACE_FILENAME" ),
1466 QString( "%1 (*.log *.txt)" ).arg( tr( "LOG_FILES" ) ) );
1470 \brief Start python trace logging
1471 \param fileName the path to the log file
1472 \return \c true if operation succeeded or \c false otherwise
1473 (for example, if file is not writeable)
1476 bool PyConsole_Editor::startLog( const QString& fileName )
1478 // stop possibly already running logging
1483 if ( !fileName.isEmpty() ) {
1484 QFile file( fileName );
1485 if ( file.open( QFile::WriteOnly ) ) {
1487 myLogFile = fileName;
1495 \brief Start log action slot
1497 void PyConsole_Editor::startLog()
1501 QString fileName = getLogFileName();
1503 if ( fileName.isEmpty() )
1506 if ( startLog( fileName ) )
1509 QMessageBox::warning( this,
1511 tr( "File is not writable" ) );
1516 \brief Stop log action slot
1518 Stops Python trace logging.
1520 void PyConsole_Editor::stopLog()
1522 myLogFile = QString();
1526 \brief Put data to the log file
1528 void PyConsole_Editor::putLog( const QString& s )
1530 if ( !myLogFile.isEmpty() ) {
1531 QFile file( myLogFile );
1532 if ( !file.open( QFile::Append ) )
1535 QTextStream out( &file );
1543 \brief Handle properly multi-line pasting. Qt documentation recommends overriding this function.
1544 If the pasted text doesn't contain a line return, no special treatment is done.
1547 void PyConsole_Editor::insertFromMimeData(const QMimeData* source)
1549 if ( myMultiLinePaste )
1552 if ( source->hasText() ) {
1553 QString s = source->text();
1554 if ( s.contains( "\n" ) )
1555 multilinePaste( s );
1557 QTextEdit::insertFromMimeData( source );
1560 QTextEdit::insertFromMimeData( source );
1565 Start multi-line paste operation
1568 void PyConsole_Editor::multilinePaste( const QString& s )
1570 // Turn on multi line pasting mode
1571 myMultiLinePaste = true;
1573 // Split string data to lines
1575 s2.replace( "\r", "" ); // Windows string format converted to Unix style
1576 #if QT_VERSION >= QT_VERSION_CHECK(5,15,0)
1577 QStringList lst = s2.split( QChar('\n'), Qt::KeepEmptyParts );
1579 QStringList lst = s2.split( QChar('\n'), QString::KeepEmptyParts );
1582 // Perform the proper paste operation for the first line to handle the case where
1583 // something was already there
1585 source.setText( lst[0] );
1586 QTextEdit::insertFromMimeData( &source );
1588 // Prepare what will have to be executed after the first line
1589 myMultiLineContent.clear();
1590 for ( int i = 1; i < lst.size(); ++i )
1591 myMultiLineContent.enqueue( lst[i] );
1593 // Trigger the execution of the first (mixed) line
1596 // See customEvent() and multiLineProcessNext() for the rest of the handling.
1600 \brief Process the next line in the queue of multi-line paste operation; called
1601 from customEvent() function
1604 void PyConsole_Editor::multiLineProcessNextLine()
1606 // not in multi-line paste mode
1607 if ( !myMultiLinePaste || myMultiLineContent.isEmpty() )
1610 QString line = myMultiLineContent.dequeue();
1611 if ( myMultiLineContent.empty() )
1613 // this isa last line in the queue, just paste it
1614 addText( line, false, false );
1615 myMultiLinePaste = false;
1619 // paste the line and simulate a <RETURN> key stroke
1620 addText( line, false, false );
1626 \brief Clear results of completion
1628 void PyConsole_Editor::clearCompletion()
1630 // delete completion text if present
1632 // remove completion display
1634 // remove trailing line return:
1635 QTextCursor tc( textCursor() );
1636 tc.setPosition( document()->characterCount()-1 );
1637 setTextCursor( tc );
1638 textCursor().deletePreviousChar();
1639 // TODO: before wait for any <Tab> event to be completed
1645 \brief Format completion results - this is where we should create 3 columns etc ...
1646 \param matches list of possible completions
1647 \return result string
1649 QString PyConsole_Editor::formatCompletion( const QStringList& matches ) const
1651 static const int MAX_COMPLETIONS = 70;
1654 int sz = matches.size();
1656 if ( sz > MAX_COMPLETIONS )
1657 result.append( QString( "[%1]" ).arg( tr( "TOO_MANY_MATCHES" ) ) );
1659 for ( int i = 0; i < qMin( sz, MAX_COMPLETIONS); ++i )
1660 result.append( matches[i] );
1662 return result.join( "\n" );
1666 \brief Format the doc string in HTML format with the first line in bold blue
1667 \param doc initial doc string
1670 QString PyConsole_Editor::formatDocHTML( const QString& doc ) const
1672 static const char* templ = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" " \
1673 "\"http://www.w3.org/TR/REC-html40/strict.dtd\">\n" \
1674 "<html><head><meta name=\"qrichtext\" content=\"1\" /> " \
1675 "<style type=\"text/css\">\np, li { white-space: pre-wrap; }\n</style> " \
1676 "</head><body style=\" font-family:'Sans Serif'; font-size:10pt; font-weight:400; font-style:normal;\">\n" \
1677 "<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"> " \
1678 "<span style=\" font-weight:600; color:#0000ff;\">%1</span></p> " \
1679 "<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%2</p> " \
1682 QString fst, rest("");
1684 // Extract first line of doc
1685 int idx = doc.indexOf( "\n" );
1687 fst = doc.left( idx );
1688 rest = doc.mid( idx+1 );
1694 fst = fst.replace( "\n", " " );
1695 rest = rest.replace(" \n", " " );
1696 return QString( templ ).arg( fst ).arg( rest );
1700 \fn void PyConsole_Editor::updateDoc( const QString& doc);
1701 \brief Signal emitted by the editor widget when the doc string should be updated.
1702 \param doc a HTML block with the formatted doc string.
1703 \todo currently this signal is left uncaught.
1707 \brief Extract the common leading part of all strings in matches.
1711 QString PyConsole_Editor::extractCommon( const QStringList& matches ) const
1713 QString result = "";
1715 if ( matches.size() > 1 ) {
1718 while ( ok && l+1 < matches[0].size() ) {
1719 QString match = matches[0].left( l+1 );
1720 for ( int j = 1; j < matches.size() && ok; j++ )
1721 ok = matches[j].startsWith( match );
1725 result = matches[0].left( l );
1732 \brief Useful method to get banner from Python interpreter
1735 QString PyConsole_Editor::banner() const
1737 return myInterp->getBanner().c_str();