ctkConsoleWidget.cpp

Go to the documentation of this file.
00001 /*=========================================================================
00002 
00003   Library:   CTK
00004  
00005   Copyright (c) 2010  Kitware Inc.
00006 
00007   Licensed under the Apache License, Version 2.0 (the "License");
00008   you may not use this file except in compliance with the License.
00009   You may obtain a copy of the License at
00010 
00011       http://www.commontk.org/LICENSE
00012 
00013   Unless required by applicable law or agreed to in writing, software
00014   distributed under the License is distributed on an "AS IS" BASIS,
00015   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
00016   See the License for the specific language governing permissions and
00017   limitations under the License.
00018  
00019 =========================================================================*/
00020 /*=========================================================================
00021 
00022    Program: ParaView
00023    Module:    $RCSfile$
00024 
00025    Copyright (c) 2005-2008 Sandia Corporation, Kitware Inc.
00026    All rights reserved.
00027 
00028    ParaView is a free software; you can redistribute it and/or modify it
00029    under the terms of the ParaView license version 1.2. 
00030 
00031    See License_v1.2.txt for the full ParaView license.
00032    A copy of this license can be obtained by contacting
00033    Kitware Inc.
00034    28 Corporate Drive
00035    Clifton Park, NY 12065
00036    USA
00037 
00038 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00039 ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00040 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
00041 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR
00042 CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
00043 EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
00044 PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
00045 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
00046 LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
00047 NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
00048 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
00049 
00050 =========================================================================*/
00051 
00052 // Qt includes
00053 #include <QAbstractItemView>
00054 #include <QApplication>
00055 #include <QClipboard>
00056 #include <QCompleter>
00057 #include <QKeyEvent>
00058 #include <QPointer>
00059 #include <QTextCursor>
00060 #include <QTextEdit>
00061 #include <QVBoxLayout>
00062 #include <QScrollBar>
00063 
00064 // CTK includes
00065 #include "ctkConsoleWidget.h"
00066 
00068 // ctkConsoleWidget::pqImplementation
00069 
00070 class ctkConsoleWidget::pqImplementation :
00071   public QTextEdit
00072 {
00073 public:
00074   pqImplementation(ctkConsoleWidget& p) :
00075     QTextEdit(&p),
00076     Parent(p),
00077     InteractivePosition(documentEnd())
00078   {
00079     this->setTabChangesFocus(false);
00080     this->setAcceptDrops(false);
00081     this->setAcceptRichText(false);
00082     this->setUndoRedoEnabled(false);
00083     
00084     QFont f;
00085     f.setFamily("Courier");
00086     f.setStyleHint(QFont::TypeWriter);
00087     f.setFixedPitch(true);
00088     
00089     QTextCharFormat format;
00090     format.setFont(f);
00091     format.setForeground(QColor(0, 0, 0));
00092     this->setCurrentCharFormat(format);
00093     
00094     this->CommandHistory.append("");
00095     this->CommandPosition = 0;
00096   }
00097 
00098   void keyPressEvent(QKeyEvent* e)
00099   {
00100 
00101   if (this->Completer && this->Completer->popup()->isVisible())
00102     {
00103     // The following keys are forwarded by the completer to the widget
00104     switch (e->key())
00105       {
00106       case Qt::Key_Enter:
00107       case Qt::Key_Return:
00108       case Qt::Key_Escape:
00109       case Qt::Key_Tab:
00110       case Qt::Key_Backtab:
00111         e->ignore();
00112         return; // let the completer do default behavior
00113       default:
00114         break;
00115       }
00116     }
00117 
00118     QTextCursor text_cursor = this->textCursor();
00119 
00120     // Set to true if there's a current selection
00121     const bool selection = text_cursor.anchor() != text_cursor.position();
00122     // Set to true if the cursor overlaps the history area
00123     const bool history_area =
00124       text_cursor.anchor() < this->InteractivePosition
00125       || text_cursor.position() < this->InteractivePosition;
00126 
00127     // Allow copying anywhere in the console ...
00128     if(e->key() == Qt::Key_C && e->modifiers() == Qt::ControlModifier)
00129       {
00130       if(selection)
00131         {
00132         this->copy();
00133         }
00134         
00135       e->accept();
00136       return;
00137       }
00138       
00139     // Allow cut only if the selection is limited to the interactive area ...
00140     if(e->key() == Qt::Key_X && e->modifiers() == Qt::ControlModifier)
00141       {
00142       if(selection && !history_area)
00143         {
00144         this->cut();
00145         }
00146         
00147       e->accept();
00148       return;
00149       }
00150     
00151     // Allow paste only if the selection is in the interactive area ...
00152     if(e->key() == Qt::Key_V && e->modifiers() == Qt::ControlModifier)
00153       {
00154       if(!history_area)
00155         {
00156         const QMimeData* const clipboard = QApplication::clipboard()->mimeData();
00157         const QString text = clipboard->text();
00158         if(!text.isNull())
00159           {
00160           text_cursor.insertText(text);
00161           this->updateCommandBuffer();
00162           }
00163         }
00164         
00165       e->accept();
00166       return;
00167       }
00168     
00169     // Force the cursor back to the interactive area
00170     if(history_area && e->key() != Qt::Key_Control)
00171       {
00172       text_cursor.setPosition(this->documentEnd());
00173       this->setTextCursor(text_cursor);
00174       }
00175     
00176     switch(e->key())
00177       {
00178       case Qt::Key_Up:
00179         e->accept();
00180         if (this->CommandPosition > 0)
00181           {
00182           this->replaceCommandBuffer(this->CommandHistory[--this->CommandPosition]);
00183           }
00184         break;
00185         
00186       case Qt::Key_Down:
00187         e->accept();
00188         if (this->CommandPosition < this->CommandHistory.size() - 2)
00189           {
00190           this->replaceCommandBuffer(this->CommandHistory[++this->CommandPosition]);
00191           }
00192         else
00193           {
00194           this->CommandPosition = this->CommandHistory.size()-1;
00195           this->replaceCommandBuffer("");
00196           }
00197         break;
00198 
00199       case Qt::Key_Left:
00200         if (text_cursor.position() > this->InteractivePosition)
00201           {
00202           QTextEdit::keyPressEvent(e);
00203           }
00204         else
00205           {
00206           e->accept();
00207           }
00208         break;
00209         
00210   
00211       case Qt::Key_Delete:
00212         e->accept();
00213         QTextEdit::keyPressEvent(e);
00214         this->updateCommandBuffer();
00215         break;
00216         
00217       case Qt::Key_Backspace:
00218         e->accept();
00219         if(text_cursor.position() > this->InteractivePosition)
00220           {
00221           QTextEdit::keyPressEvent(e);
00222           this->updateCommandBuffer();
00223           this->updateCompleterIfVisible();
00224           }
00225         break;
00226 
00227       case Qt::Key_Tab:
00228         e->accept();
00229         this->updateCompleter();
00230         this->selectCompletion();
00231         break;
00232         
00233 
00234       case Qt::Key_Home:
00235         e->accept();
00236         text_cursor.setPosition(this->InteractivePosition);
00237         this->setTextCursor(text_cursor);
00238         break;
00239         
00240       case Qt::Key_Return:
00241       case Qt::Key_Enter:
00242         e->accept();
00243         
00244         text_cursor.setPosition(this->documentEnd());
00245         this->setTextCursor(text_cursor);
00246         
00247         this->internalExecuteCommand();
00248         break;
00249         
00250       default:
00251         e->accept();
00252         QTextEdit::keyPressEvent(e);
00253         this->updateCommandBuffer();
00254         this->updateCompleterIfVisible();
00255         break;
00256       }
00257   }
00258   
00260   /*const*/ int documentEnd()
00261   {
00262     QTextCursor c(this->document());
00263     c.movePosition(QTextCursor::End);
00264     return c.position();
00265   }
00266 
00267   void focusOutEvent(QFocusEvent *e)
00268   {
00269     QTextEdit::focusOutEvent(e);
00270 
00271     // For some reason the QCompleter tries to set the focus policy to
00272     // NoFocus, set let's make sure we set it back to the default WheelFocus.
00273     this->setFocusPolicy(Qt::WheelFocus);
00274   }
00275 
00276   void updateCompleterIfVisible()
00277   {
00278     if (this->Completer && this->Completer->popup()->isVisible())
00279       {
00280       this->updateCompleter();
00281       }
00282   }
00283 
00286   void selectCompletion()
00287   {
00288   if (this->Completer && this->Completer->completionCount() == 1)
00289     {
00290     this->Parent.insertCompletion(this->Completer->currentCompletion());
00291     this->Completer->popup()->hide();
00292     }
00293   }
00294 
00295   void updateCompleter()
00296   {
00297     if (this->Completer)
00298       {
00299       // Get the text between the current cursor position
00300       // and the start of the line
00301       QTextCursor text_cursor = this->textCursor();
00302       text_cursor.setPosition(this->InteractivePosition, QTextCursor::KeepAnchor);
00303       QString commandText = text_cursor.selectedText();
00304 
00305       // Call the completer to update the completion model
00306       this->Completer->updateCompletionModel(commandText);
00307 
00308       // Place and show the completer if there are available completions
00309       if (this->Completer->completionCount())
00310         {
00311         // Get a QRect for the cursor at the start of the
00312         // current word and then translate it down 8 pixels.
00313         text_cursor = this->textCursor();
00314         text_cursor.movePosition(QTextCursor::StartOfWord);
00315         QRect cr = this->cursorRect(text_cursor);
00316         cr.translate(0,8);
00317         cr.setWidth(this->Completer->popup()->sizeHintForColumn(0)
00318           + this->Completer->popup()->verticalScrollBar()->sizeHint().width());
00319         this->Completer->complete(cr);
00320         }
00321       else
00322         {
00323         this->Completer->popup()->hide();
00324         }
00325       }
00326   }
00327   
00329   void updateCommandBuffer()
00330   {
00331     this->commandBuffer() = this->toPlainText().mid(this->InteractivePosition);
00332   }
00333   
00335   void replaceCommandBuffer(const QString& Text)
00336   {
00337     this->commandBuffer() = Text;
00338   
00339     QTextCursor c(this->document());
00340     c.setPosition(this->InteractivePosition);
00341     c.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
00342     c.removeSelectedText();
00343     c.insertText(Text);
00344   }
00345   
00347   QString& commandBuffer()
00348   {
00349     return this->CommandHistory.back();
00350   }
00351   
00353   void internalExecuteCommand()
00354     {
00355     // First update the history cache. It's essential to update the
00356     // this->CommandPosition before calling internalExecuteCommand() since that
00357     // can result in a clearing of the current command (BUG #8765).
00358     QString command = this->commandBuffer();
00359     if (!command.isEmpty()) // Don't store empty commands in the history
00360       {
00361       this->CommandHistory.push_back("");
00362       this->CommandPosition = this->CommandHistory.size() - 1;
00363       }
00364     QTextCursor c(this->document());
00365     c.movePosition(QTextCursor::End);
00366     c.insertText("\n");
00367 
00368     this->InteractivePosition = this->documentEnd();
00369     this->Parent.internalExecuteCommand(command);
00370     }
00371 
00372   void setCompleter(ctkConsoleWidgetCompleter* completer)
00373     {
00374     if (this->Completer)
00375       {
00376       this->Completer->setWidget(0);
00377       QObject::disconnect(this->Completer, SIGNAL(activated(const QString&)),
00378                         &this->Parent, SLOT(insertCompletion(const QString&)));
00379 
00380       }
00381     this->Completer = completer;
00382     if (this->Completer)
00383       {
00384       this->Completer->setWidget(this);
00385       QObject::connect(this->Completer, SIGNAL(activated(const QString&)),
00386                       &this->Parent, SLOT(insertCompletion(const QString&)));
00387       }
00388     }
00389   
00391   ctkConsoleWidget& Parent;
00392 
00394   QPointer<ctkConsoleWidgetCompleter> Completer;
00395 
00398   int InteractivePosition;
00400   QStringList CommandHistory;
00402   int CommandPosition;
00403 };
00404 
00406 // ctkConsoleWidget
00407 
00408 ctkConsoleWidget::ctkConsoleWidget(QWidget* Parent) :
00409   QWidget(Parent),
00410   Implementation(new pqImplementation(*this))
00411 {
00412   QVBoxLayout* const l = new QVBoxLayout(this);
00413   l->setMargin(0);
00414   l->addWidget(this->Implementation);
00415 }
00416 
00417 //-----------------------------------------------------------------------------
00418 ctkConsoleWidget::~ctkConsoleWidget()
00419 {
00420   delete this->Implementation;
00421 }
00422 
00423 //-----------------------------------------------------------------------------
00424 QTextCharFormat ctkConsoleWidget::getFormat()
00425 {
00426   return this->Implementation->currentCharFormat();
00427 }
00428 
00429 //-----------------------------------------------------------------------------
00430 void ctkConsoleWidget::setFormat(const QTextCharFormat& Format)
00431 {
00432   this->Implementation->setCurrentCharFormat(Format);
00433 }
00434 
00435 //-----------------------------------------------------------------------------
00436 void ctkConsoleWidget::setCompleter(ctkConsoleWidgetCompleter* completer)
00437 {
00438   this->Implementation->setCompleter(completer);
00439 }
00440 
00441 //-----------------------------------------------------------------------------
00442 void ctkConsoleWidget::insertCompletion(const QString& completion)
00443 {
00444   QTextCursor tc = this->Implementation->textCursor();
00445   tc.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor);
00446   if (tc.selectedText()==".")
00447     {
00448     tc.insertText(QString(".") + completion);
00449     }
00450   else
00451     {
00452     tc = this->Implementation->textCursor();
00453     tc.movePosition(QTextCursor::StartOfWord, QTextCursor::MoveAnchor);
00454     tc.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
00455     tc.insertText(completion);
00456     this->Implementation->setTextCursor(tc);
00457     }
00458   this->Implementation->updateCommandBuffer();
00459 }
00460 
00461 //-----------------------------------------------------------------------------
00462 void ctkConsoleWidget::printString(const QString& Text)
00463 {
00464   this->Implementation->textCursor().movePosition(QTextCursor::End);
00465   this->Implementation->textCursor().insertText(Text);
00466   this->Implementation->InteractivePosition = this->Implementation->documentEnd();
00467   this->Implementation->ensureCursorVisible();
00468 }
00469 
00470 //-----------------------------------------------------------------------------
00471 void ctkConsoleWidget::printCommand(const QString& cmd)
00472 {
00473   this->Implementation->textCursor().insertText(cmd);
00474   this->Implementation->updateCommandBuffer();
00475 }
00476 
00477 //-----------------------------------------------------------------------------
00478 void ctkConsoleWidget::prompt(const QString& text)
00479 {
00480   QTextCursor text_cursor = this->Implementation->textCursor();
00481 
00482   // if the cursor is currently on a clean line, do nothing, otherwise we move
00483   // the cursor to a new line before showing the prompt.
00484   text_cursor.movePosition(QTextCursor::StartOfLine);
00485   int startpos = text_cursor.position();
00486   text_cursor.movePosition(QTextCursor::EndOfLine);
00487   int endpos = text_cursor.position();
00488   if (endpos != startpos)
00489     {
00490     this->Implementation->textCursor().insertText("\n");
00491     }
00492 
00493   this->Implementation->textCursor().insertText(text);
00494   this->Implementation->InteractivePosition = this->Implementation->documentEnd();
00495   this->Implementation->ensureCursorVisible();
00496 }
00497 
00498 //-----------------------------------------------------------------------------
00499 void ctkConsoleWidget::clear()
00500 {
00501   this->Implementation->clear();
00502 
00503   // For some reason the QCompleter tries to set the focus policy to
00504   // NoFocus, set let's make sure we set it back to the default WheelFocus.
00505   this->Implementation->setFocusPolicy(Qt::WheelFocus);
00506 }
00507 
00508 //-----------------------------------------------------------------------------
00509 void ctkConsoleWidget::internalExecuteCommand(const QString& Command)
00510 {
00511   emit this->executeCommand(Command);
00512 }
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Defines

Generated on 21 May 2010 for CTK by  doxygen 1.6.1