Logo Search packages:      
Sourcecode: qtads version File versions

qtadsgamewindow.cc

/* Copyright (C) 2003 Nikos Chantziaras.
 *
 * This file is part of the QTads program.  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, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; see the file COPYING.  If not, write to
 * the Free Software Foundation, 59 Temple Place - Suite 330, Boston,
 * MA 02111-1307, USA.
 */

#include "config.h"

#include "qtadsgamewindow.h"

#include <qstring.h>
#include <qapplication.h>
#include <qeventloop.h>
#include <qpopupmenu.h>
#include <qcursor.h>

#include "qtadstypes.h"
#include "qtadsio.h"
#include "qtadssettings.h"


const QString MORE_TEXT(QObject::tr("*** More *** (Press SPACE to continue)"));


QTadsGameWindow::QTadsGameWindow( QWidget* parent, const char* name )
: QTextEdit(parent, name), fInputMode(None), fRestoreInput(false), fRestorePar(0),
  fRestoreInd(0), fChar(0), fNoScroll(false), fLastBottomPos(QTextEdit::contentsHeight()),
  fScrollBufferSize(65536), fInHistory(false), fHistPos(0), fScrollAc(0), fNonstopMode(false)
{
      // Disable the QTextEdit's undo/redo facility, since we don't
      // need it.  We save some memory and increase speed.
      QTextEdit::setUndoRedoEnabled(false);
      // Key compression allows faster editing on slow systems.
      QTextEdit::setKeyCompression(true);
      // Never show a horizontal scroll bar, since we always wrap the
      // text.
      QTextEdit::setHScrollBarMode(QScrollView::AlwaysOff);
      // Show the vertical scroll bar, even if it's not needed.  The
      // user can disable it at runtime.
      QTextEdit::setVScrollBarMode(QScrollView::AlwaysOn);
      // *Always* wrap the text, no matter how (but wrapping at word
      // boundaries will be preferred).
      QTextEdit::setWordWrap(QTextEdit::WidgetWidth);
      QTextEdit::setWrapPolicy(QTextEdit::AtWordOrDocumentBoundary);
      // Begin in editor-mode.
      QTextEdit::setReadOnly(false);
      // We only use plain text mode and format the text using
      // methods, not HTML-tags.
      QTextEdit::setTextFormat(Qt::PlainText);
      // This style displays best.
      QTextEdit::setFrameStyle(QFrame::Box | QFrame::Plain);
      // Every time the user clicks somewhere in the window, call our
      // click handler.
      connect(this, SIGNAL(clicked(int,int)), SLOT(clickHandler(int,int)));
      // Every time the vertical scroll bar moves, call our vertical
      // scroll bar handler.
      connect(QTextEdit::verticalScrollBar(), SIGNAL(valueChanged(int)),
            SLOT(vScrollBarHandler(int)));
      // Every time text is displayed, hide the mouse cursor.  This
      // frees the user from the burden of having to move the mouse
      // cursor "out of the way."
      connect(this, SIGNAL(textChanged()), SLOT(hideMouseCursor()));
}


void
QTadsGameWindow::inputKeyPressEvent( QKeyEvent* e )
{
      Q_ASSERT(this->fInputMode == NormalInput);

      Qt::ButtonState state(e->state());
      //bool shift = state & Qt::ShiftButton;
      bool ctrl = state & Qt::ControlButton;
      //bool alt = state & Qt::AltButton;

      // Did we recognize the key event?
      bool accept = false;

      switch (e->key()) {
        case Qt::Key_P:
            if (not ctrl) {
                  break;
            }
            // Fall through.  (Ctrl+P is the same as Key_Up.)
        case Qt::Key_Up: {
            if (not this->fInHistory) {
                  // This is the first time in this editing
                  // session that the user used the history.
                  // Since he must be able to continue his
                  // original input (with the down-key), we must
                  // save the current input.  We simply push it
                  // in the history buffer.
                  int par = this->paragraphs() - 1;
                  int len = this->paragraphLength(par);
                  QString tmp(this->text(par).right(len - this->fInputBeginInd + 1));
                  tmp.truncate(tmp.length() - 1);
                  this->fInputHistory.push_back(tmp);
                  this->fInHistory = true;
                  this->fHistPos = this->fInputHistory.size() - 1;
            } else if (this->fHistPos == this->fInputHistory.size() - 1) {
                  // The user already scrolled through the
                  // history and came back to his current input.
                  // Now he is again scrolling back.  Since he
                  // might have modified his input, we store it
                  // so that he can continue editing it later.
                  int par = this->paragraphs() - 1;
                  int len = this->paragraphLength(par);
                  QString tmp(this->text(par).right(len - this->fInputBeginInd + 1));
                  tmp.truncate(tmp.length() - 1);
                  this->fInputHistory[this->fHistPos] = tmp;
            }

            // If we haven't reached the beginning of the history,
            // delete the current input-line from the screen and
            // display the previous one.
            if (this->fHistPos != 0) {
                  // If the user selected any text, de-select it.
                  this->removeSelection();
                  this->setUpdatesEnabled(false);
                  this->setCursorPosition(this->fInputBeginPar, this->fInputBeginInd);
                  QTextEdit::doKeyboardAction(ActionKill);
                  --this->fHistPos;

                  // Aply the user's "input font" preferences.
                  this->setBold(QTadsIO::settings().currentTheme().boldInput());
                  this->setItalic(QTadsIO::settings().currentTheme().italicInput());
                  this->setUnderline(QTadsIO::settings().currentTheme().underlinedInput());

                  QTextEdit::insert(this->fInputHistory[this->fHistPos]);
                  this->setUpdatesEnabled(true);
                  this->repaintChanged();
            }

            accept = true;
            break;
        }

        case Qt::Key_N:
            if (not ctrl) {
                  break;
            }
            // Fall through.  (Ctrl+N is the same as Key_Down.)
        case Qt::Key_Down:
            // If we haven't reached the most current history
            // entry, delete the current input-line from the screen
            // and display the next one.
            if (this->fInHistory and this->fHistPos < this->fInputHistory.size() - 1) {
                  // If the user selected any text, de-select it.
                  this->removeSelection();
                  this->setUpdatesEnabled(false);
                  this->setCursorPosition(this->fInputBeginPar, this->fInputBeginInd);
                  QTextEdit::doKeyboardAction(ActionKill);
                  ++this->fHistPos;

                  // Aply the user's "input font" preferences.
                  this->setBold(QTadsIO::settings().currentTheme().boldInput());
                  this->setItalic(QTadsIO::settings().currentTheme().italicInput());
                  this->setUnderline(QTadsIO::settings().currentTheme().underlinedInput());

                  QTextEdit::insert(this->fInputHistory[this->fHistPos]);
                  this->setUpdatesEnabled(true);
                  this->repaintChanged();
            }
            accept = true;
            break;

        case Qt::Key_PageUp:
            if (ctrl) {
                  // Ctrl+PageUp; scroll up by a line.
                  this->verticalScrollBar()->subtractLine();
            } else {
                  // PageUp; scroll up by a page.
                  this->verticalScrollBar()->subtractPage();
            }
            accept = true;
            break;

        case Qt::Key_PageDown:
            if (ctrl) {
                  // Ctrl+PageDown; scroll down by a line.
                  this->verticalScrollBar()->addLine();
            } else {
                  // Ctrl+PageDown; scroll down by a page.
                  this->verticalScrollBar()->addPage();
            }
            accept = true;
            break;

        case Qt::Key_Enter:
        case Qt::Key_Return:
            // Get the input string, do a newline, and terminate
            // input-mode.  The input string consists of all
            // characters between the place where input began and
            // the end-minus-one position of the paragraph.
            this->removeSelection();
            this->moveCursor(QTextEdit::MoveEnd, false);
            this->doKeyboardAction(QTextEdit::ActionReturn);

            { // We use a block since we declare some temp vars.
                  // "-2" because we did a newline.
                  int par = this->paragraphs() - 2;
                  int len = this->paragraphLength(par);

                  this->fInput = this->text(par).right(len - this->fInputBeginInd + 1);
                  this->fInput.truncate(this->fInput.length() - 1);
            }

            if (this->fInHistory and this->fInputHistory.back().isEmpty()) {
                  // The current input has been stored in the
                  // history buffer, but it's empty; we delete it
                  // from the history.
                  this->fInputHistory.pop_back();
            }

            if (this->fInputHistory.empty() or this->fInput != this->fInputHistory.back())
            {
                  // The current input has not been inserted in
                  // the history yet; insert it in the buffer's
                  // back.
                  this->fInputHistory.push_back(this->fInput);
            }

            // Reset the current format (in case we used bold,
            // italic and/or underlined input).
            this->setBold(this->font().bold());
            this->setItalic(this->font().italic());
            this->setUnderline(this->font().underline());

            this->fInputMode = None;
            this->fInHistory = false;
            accept = true;
            break;
      }

      if (not accept) {
            // We didn't handle the event.  Let the inherited
            // handler take care of it.

            this->restoreCursorPos();
            this->setUpdatesEnabled(false);

            // TODO: Copy the input-flags to member variables for
            // faster lookup.
            this->setBold(QTadsIO::settings().currentTheme().boldInput());
            this->setItalic(QTadsIO::settings().currentTheme().italicInput());
            this->setUnderline(QTadsIO::settings().currentTheme().underlinedInput());

            QTextEdit::keyPressEvent(e);
            this->setUpdatesEnabled(true);
            this->repaintChanged();
      }

      // Save the current cursor position.
      this->getCursorPosition(&this->fRestorePar, &this->fRestoreInd);
}


void
QTadsGameWindow::waitCharKeyPressEvent( QKeyEvent* e )
{
      Q_ASSERT(this->fInputMode == WaitCharInput or this->fInputMode == PagePause);

      if (not e->text().isEmpty()) {
            // A key that has a Unicode value has been pressed;
            // that's what we were waiting for.
            this->fInputMode = None;
      } else {
            // The key doesn't have a Unicode value; ignore it
            // (must have been SHIFT, CTRL, a function key, etc).
            e->ignore();
      }
}


void
QTadsGameWindow::charKeyPressEvent( QKeyEvent* e )
{
      Q_ASSERT(this->fInputMode == RawCharInput);

      if (e->ascii() > 0 and e->ascii() <= 255) {
            // Assume the caller has set fChar to 0.
            Q_ASSERT(this->fChar == 0);

            // The character is inside the (8-bit) ASCII-range.
            this->fChar = new QKeyEvent(*e);
            this->fInputMode = None;
            return;
      }

      // When the character wasn't 8-bit ASCII, we should only
      // recognize the extended keys needed by the portable layer
      // (see os_getc_raw() in osifc.h for details).  We ignore any
      // others.
      switch (e->key()) {
        case Qt::Key_Up: case Qt::Key_Down: case Qt::Key_Left: case Qt::Key_Right:
        case Qt::Key_End: case Qt::Key_Home: case Qt::Key_Delete: case Qt::Key_PageUp:
        case Qt::Key_PageDown: case Qt::Key_F1: case Qt::Key_F2: case Qt::Key_F3:
        case Qt::Key_F4: case Qt::Key_F5: case Qt::Key_F6: case Qt::Key_F7: case Qt::Key_F8:
        case Qt::Key_F9: case Qt::Key_F10: case Qt::Key_Tab:
            Q_ASSERT(this->fChar == 0);

            this->fChar = new QKeyEvent(*e);
            this->fInputMode = None;
            return;
      }

      e->ignore();
}


void
QTadsGameWindow::readOnlyMoveCursor( CursorAction action )
{
      Q_ASSERT(this->isReadOnly());

      // TODO: Implement it.
      QTextEdit::moveCursor(action, false);
}


void
QTadsGameWindow::pagePause()
{
      Q_ASSERT(this->fInputMode != PagePause);

      InputMode prevInputMode = this->fInputMode;
      this->fInputMode = PagePause;
      this->setKeyCompression(false);
      while (this->fInputMode == PagePause and QTadsIO::gameRunning) {
            qApp->eventLoop()->processEvents(QEventLoop::WaitForMore
                                     | QEventLoop::AllEvents);
      }
      this->fInputMode = prevInputMode;
      this->setKeyCompression(true);
}


void
QTadsGameWindow::restoreCursorPos()
{
      if (this->fInputMode != None and this->fRestoreInput) {
            if (this->hasSelectedText()) {
                  this->removeSelection();
            }
            this->setCursorPosition(this->fRestorePar, this->fRestoreInd);
            this->setReadOnly(false);
            this->fRestoreInput = false;
      }
}


void
QTadsGameWindow::keyPressEvent( QKeyEvent* e )
{
      // Ignore keyboard modifier keys; or else things like Ctrl+C
      // (Copy Selection) wouldn't work.
      switch (e->key()) {
        case Qt::Key_Shift:
        case Qt::Key_Control:
        case Qt::Key_Meta:
        case Qt::Key_Alt:
            e->ignore();
            return;
      }

      if (this->fInputMode == NormalInput) {
            // We are in normal input-mode. Call the input-mode
            // event handler.
            this->inputKeyPressEvent(e);
            if (e->isAccepted()) {
                  // The handler recognized the event; there's
                  // nothing more to do.
                  return;
            }
      } else if (this->fInputMode == WaitCharInput or this->fInputMode == PagePause) {
            this->waitCharKeyPressEvent(e);
            if (e->isAccepted()) {
                  return;
            }
      } else if (this->fInputMode == RawCharInput) {
            this->charKeyPressEvent(e);
            if (e->isAccepted()) {
                  return;
            }
      }

      // We didn't handle the event.
      e->ignore();
}


QPopupMenu*
QTadsGameWindow::createPopupMenu( const QPoint& /* pos */ )
{
      QPopupMenu* menu = new QPopupMenu(this, "game window popup menu");
      menu->insertItem(tr("Show/hide &menu"), this, SIGNAL(showHideMenu()));
      menu->insertItem(tr("Show/hide &scrollbar"), this, SLOT(showHideScrollBar()));
      return menu;
}


void
QTadsGameWindow::contentsMouseMoveEvent( QMouseEvent* e )
{
      this->showMouseCursor();

      if (e->state() & Qt::MouseButtonMask) {
            int para;
            int index = charAt(e->pos(), &para);
            clickHandler( para, index );
      }

      QTextEdit::contentsMouseMoveEvent(e);
}


void
QTadsGameWindow::resizeEvent( QResizeEvent* e )
{
      QTextEdit::resizeEvent(e);
      // TODO: Implement it.
}


void
QTadsGameWindow::focusOutEvent( QFocusEvent* )
{
      this->showMouseCursor();
}


QString
QTadsGameWindow::getInput()
{
      Q_ASSERT(this->fInputMode == None);

      this->fInput = "";
      this->fInputMode = NormalInput;
      this->moveCursor(QTextEdit::MoveEnd, false);
      this->getCursorPosition(&this->fInputBeginPar, &this->fInputBeginInd);
      this->fRestorePar = this->fInputBeginPar;
      this->fRestoreInd = this->fInputBeginInd;
      this->ensureCursorVisible();
      QTadsIO::enableCommandActions(true);

      // Get input until input-mode is terminated.
      while (this->fInputMode == NormalInput and QTadsIO::gameRunning) {
            qApp->eventLoop()->processEvents(QEventLoop::WaitForMore
                                     | QEventLoop::AllEvents);
      }
      if (not QTadsIO::gameRunning) {
            this->fInputMode = None;
            // The game should quit; return nothing.
            return QString::null;
      }

      QTadsIO::enableCommandActions(false);
      this->fInput.remove('\n');
      this->fLastBottomPos = this->contentsHeight();
      return this->fInput;
}


void
QTadsGameWindow::waitChar()
{
      Q_ASSERT(this->fInputMode == None);

      this->fInputMode = WaitCharInput;
      // Temporarily disable key compression, since we should wait
      // for a *single* character.
      this->setKeyCompression(false);
      this->moveCursor(QTextEdit::MoveEnd, false);
      this->ensureCursorVisible();
      this->getCursorPosition(&this->fInputBeginPar, &this->fInputBeginInd);
      this->fRestorePar = this->fInputBeginPar;
      this->fRestoreInd = this->fInputBeginInd;
      while (this->fInputMode == WaitCharInput and QTadsIO::gameRunning) {
            qApp->eventLoop()->processEvents(QEventLoop::WaitForMore
                                     | QEventLoop::AllEvents);
      }
      this->fInputMode = None;
      this->setKeyCompression(true);
      this->fLastBottomPos = this->contentsHeight();
}


QKeyEvent
QTadsGameWindow::getChar()
{
      Q_ASSERT(this->fInputMode == None);

      this->setKeyCompression(false);
      this->fInputMode = RawCharInput;
      if (this->fChar != 0) {
            delete this->fChar;
            this->fChar = 0;
      }
      this->moveCursor(QTextEdit::MoveEnd, false);
      this->ensureCursorVisible();
      this->getCursorPosition(&this->fInputBeginPar, &this->fInputBeginInd);
      this->fRestorePar = this->fInputBeginPar;
      this->fRestoreInd = this->fInputBeginInd;
      while (this->fInputMode == RawCharInput and QTadsIO::gameRunning) {
            qApp->eventLoop()->processEvents(QEventLoop::WaitForMore
                                     | QEventLoop::AllEvents);
      }
      this->setKeyCompression(true);
      this->fLastBottomPos = this->contentsHeight();

      if (not QTadsIO::gameRunning) {
            this->fInputMode = None;
            return QKeyEvent(QEvent::KeyPress, 0, 0, 0);
      }
      return *(this->fChar);
}


void
QTadsGameWindow::scrolling( bool on )
{
      Q_ASSERT(this->fScrollAc >= 0);
      Q_ASSERT(not (on == true and this->fScrollAc == 0));

      if (on) {
            if (this->fScrollAc == 0) {
                  return;
            }
            if (--this->fScrollAc == 0) {
                  this->fNoScroll = false;
            }
      } else {
            ++this->fScrollAc;
            this->fNoScroll = true;
      }
}


void
QTadsGameWindow::insert( std::queue<QTadsFormattedString>& text, uint )
{
      Q_ASSERT(this->fInputMode == None);

      // Disable automatic scrolling, since we handle it on our own
      // (to provide "more" prompts).
      this->scrolling(false);

      this->setUpdatesEnabled(false);
      this->moveCursor(QTextEdit::MoveEnd, false);
      while (not text.empty()) {
            // Set the text attributes.
            if (text.front().f.high != this->bold()) {
                  this->setBold(text.front().f.high);
            }
            if (text.front().f.italics != this->italic()) {
                  this->setItalic(text.front().f.italics);
            }
            // Display the text.
            QTextEdit::insert(text.front().s);
            // Remove the text from the buffer.
            text.pop();
      }
      this->setUpdatesEnabled(true);
      this->repaintChanged();

      // Scroll down.  We do this on our own since we must provide
      // "more" prompts when the new text was larger than the window
      // height.  Furthermore, we want to scroll "softly", not just
      // pop-in the new text and let the user wonder where the new
      // text begins.
      while (this->verticalScrollBar()->value() < this->verticalScrollBar()->maxValue()) {
            if (this->contentsY() >= this->fLastBottomPos
                - this->verticalScrollBar()->lineStep() * 2 and not this->fNonstopMode)
            {
                  // The text was too large; display a "more"
                  // prompt (if not in nonstop mode).
                  QTadsIO::sysStatusPrint(MORE_TEXT);
                  this->pagePause();
                  QTadsIO::clearSysStatus();
                  this->fLastBottomPos = this->contentsY() + this->visibleHeight();
            }
            // If a game is currently running, scroll softly.  If
            // not, simple scroll to the bottom.
            if (QTadsIO::gameRunning) {
                  this->verticalScrollBar()->addLine();
            } else {
                  this->verticalScrollBar()
                        ->setValue(this->verticalScrollBar()->maxValue());
            }
            // Process any drawing.
            qApp->eventLoop()->processEvents(QEventLoop::AllEvents, 1);
      }

      // If the text grew too large, delete an amount of the oldest
      // available.
      if (this->text().length() > this->fScrollBufferSize + this->fScrollBufferSize / 2) {
            unsigned int i = 0;
            int par = 0;
            while (i < this->fScrollBufferSize / 2) {
                  i += this->paragraphLength(par);
                  ++par;
            }
            --par;
            int ind = this->paragraphLength(par) - 1;
            if (i > this->fScrollBufferSize / 2) {
                  ind -= i - this->fScrollBufferSize / 2;
            }
            this->setSelection(0, 0, par, ind, 1);
            this->removeSelectedText(1);
            this->sync();
      }

      this->scrolling(true);
}


void
QTadsGameWindow::setContentsPos( int x, int y )
{
      if (not this->scrolling()) {
            // If scrolling is disabled, do nothing.
            return;
      }
      // Scrolling is enabled; allow the operation.
      QTextEdit::setContentsPos(x, y);
}


void
QTadsGameWindow::moveCursor( CursorAction action, bool )
{
      //if (this->isReadOnly()) {
      //    this->readOnlyMoveCursor(action);
      //    return;
      //}

      // We need to enable immediate widget-updates when moving the
      // cursor, or else the user will not be able to see what he's
      // doing.  If updates are currently disabled, enable them and
      // disable them again when we're finished.
      bool wasUpdatesEnabled = this->isUpdatesEnabled();
      if (not wasUpdatesEnabled) {
            this->setUpdatesEnabled(true);
      }

      // Find out the current position of the cursor.
      int par, ind;
      this->getCursorPosition(&par, &ind);

      switch (action) {
        case MoveLineStart:
        case MoveHome:
        //case MovePgUp:
            // Move the cursor to the beginning of the input-area.
            this->setCursorPosition(this->fInputBeginPar, this->fInputBeginInd);
            break;

        case MoveBackward:
            if ((ind - 1) < this->fInputBeginInd or ind == 0) {
                  // Already in the beginning of the input-area;
                  // just flash the cursor.
                  this->setCursorPosition(par, ind);
            } else {
                  QTextEdit::moveCursor(action, false);
            }
            break;

        case MoveWordBackward: {
            // Get the text paragraph so we can analyse it.
            const QString& txt = this->text(par);

            // Skip characters other than letters and numbers.
            while (ind > 0 and ind > this->fInputBeginInd
                   and not txt[ind-1].isLetterOrNumber())
            {
                  --ind;
            }
            // Now skip the letters and numbers.
            while (ind > 0 and ind > this->fInputBeginInd
                   and txt[ind-1].isLetterOrNumber())
            {
                  --ind;
            }
            // We skipped a word; set the new cursor position.
            this->setCursorPosition(par, ind);
            break;
        }

        case MoveWordForward: {
            // Get the text paragraph so we can analyse it.
            const QString& txt = this->text(par);

            // Skip characters other than letters and numbers.
            while (static_cast<uint>(ind) < txt.length()
                   and not txt[ind].isLetterOrNumber())
            {
                  ++ind;
            }
            // Now skip the letters and numbers.
            while (static_cast<uint>(ind) < txt.length() and txt[ind].isLetterOrNumber())
            {
                  ++ind;
            }
            // We skipped a word; set the new cursor position.
            this->setCursorPosition(par, ind);
            break;
        }

        default:
            // We don't handle the action in any special way; let
            // the overriden method take care of it.
            QTextEdit::moveCursor(action, false);
      }

      if (not wasUpdatesEnabled) {
            // Widget-updates were originally disabled; disable
            // them again.
            this->setUpdatesEnabled(false);
      }
}


void
QTadsGameWindow::doKeyboardAction( KeyboardAction action )
{
      // Find out the current position of the cursor.
      int par, ind;
      this->getCursorPosition(&par, &ind);

      // Disable widget-updates; this results in a major speed-up.
      // Furthermore, the user won't see the temporary selections we
      // create in order to delete text.
      this->setUpdatesEnabled(false);

      switch (action) {
        case ActionBackspace:
            if ((ind - 1) < this->fInputBeginInd or ind == 0) {
                  // There's nothing left to delete; just flash
                  // the cursor.
                  this->setCursorPosition(par, ind);
            } else {
                  // The default behavior works fine, so use it.
                  QTextEdit::doKeyboardAction(action);
            }
            break;

        case ActionWordBackspace: {
            // This is Ctrl+Backspace; delete the word to the left
            // of the cursor.

            if ((ind - 1) < this->fInputBeginInd or ind == 0) {
                  // There's nothing left to delete; just flash
                  // the cursor.
                  this->setCursorPosition(par, ind);
                  break;
            }

            // Store the current cursor position's index.
            int prevInd = ind;
            // Get the text paragraph so we can analyse it.
            const QString txt(this->text(par));

            // Skip characters other than letters and numbers.
            while (ind > 0 and ind > this->fInputBeginInd
                   and not txt[ind-1].isLetterOrNumber())
            {
                  --ind;
            }
            // Now skip the letters and numbers.
            while (ind > 0 and ind > this->fInputBeginInd
                   and txt[ind-1].isLetterOrNumber())
            {
                  --ind;
            }
            // Now select everything between the previous index and
            // the new one, then delete it.  The cursor will be
            // moved automatically to the right location.
            this->setSelection(par, ind, par, prevInd, 1);
            this->removeSelectedText(1);
            break;
        }

        case ActionWordDelete: {
            // This is Ctrl+Delete; delete the word to the right of
            // the cursor.  Works like the `ActionWordBackspace'
            // case, but in the other direction.  Also, there's no
            // need to check if this would delete game-text, since
            // the input-area is at the end of the contents.

            int prevInd = ind;
            const QString txt(this->text(par));

            while (static_cast<uint>(ind) < txt.length()
                   and not txt[ind].isLetterOrNumber())
            {
                  ++ind;
            }
            while (static_cast<uint>(ind) < txt.length() and txt[ind].isLetterOrNumber())
            {
                  ++ind;
            }
            this->setSelection(par, prevInd, par, ind, 1);
            this->removeSelectedText(1);
            break;
        }

        default:
            // We didn't recognize the action; use the default
            // behavior.
            QTextEdit::doKeyboardAction(action);
      }

      // Enable widget-updates again and repaint anything that has
      // changed.
      this->setUpdatesEnabled(true);
      this->repaintChanged();
}


void
QTadsGameWindow::paste()
{
      // Only allow the operation when in NormalInput mode.
      if (this->fInputMode == NormalInput) {
            // Make sure the cursor is inside the input area.
            this->restoreCursorPos();
            // Paste the text as usual.
            QTextEdit::paste();
      }
}


void
QTadsGameWindow::enterCommand( const QString& cmd )
{
      // Disable widget-updates since it's faster.
      this->setUpdatesEnabled(false);
      // Place the cursor at the beginning of the input-area.
      this->setCursorPosition(this->fInputBeginPar, this->fInputBeginInd);
      // Delete the input-area.
      this->doKeyboardAction(ActionKill);
      // Insert the text.
      QTextEdit::insert(cmd);
      // Enable widget-updates and repaint the changed region.
      this->setUpdatesEnabled(true);
      this->repaintChanged();
      // Emulate a [Return] keypress.
      QKeyEvent* e = new QKeyEvent(QEvent::KeyPress, Qt::Key_Enter, '\n', 0);
      this->inputKeyPressEvent(e);
      delete e;
}


void
QTadsGameWindow::clickHandler( int para, int pos )
{
      if (this->fInputMode == None) {
            return;
      }

      // Get the current selection (if any).
      int paraFrom;
      int paraTo;
      int indexFrom;
      int indexTo;
      getSelection ( &paraFrom, &indexFrom, &paraTo, &indexTo );

      if (pos < this->fInputBeginInd or para < this->fInputBeginPar) {
            // The click was outside the input-area.  Switch to
            // read-only mode, do an update, and mark that the
            // cursor position needs to be restored later.
            this->setReadOnly(true);
            this->updateContents(this->paragraphRect(para));
            this->fRestoreInput = true;
      } else if (indexTo != -1 &&
               (indexFrom < this->fInputBeginInd or paraFrom < this->fInputBeginPar)) {
            // There is a selection extending outside the
            // input-area; we can't disable read-only mode, since
            // this would enable the user to delete the selected
            // text that belongs to the game.
            this->setReadOnly(true);
            this->updateContents(this->paragraphRect(para));
            this->fRestoreInput = true;
      } else {
            // The click was inside the input-area and there's no
            // selection extending outside of it.  Disable
            // read-only mode, get the new cursor position, and
            // mark that there's no need to restore that position
            // later.
            this->setReadOnly(false);
            this->getCursorPosition(&this->fRestorePar, &this->fRestoreInd);
            this->fRestoreInput = false;
      }
}


void
QTadsGameWindow::vScrollBarHandler( int value )
{
      if (this->fInputMode != PagePause) {
            return;
      }

      if (value >= this->verticalScrollBar()->maxValue()) {
            this->fInputMode = None;
            // Remove the prompt-message from the system
            // statusline.
            QTadsIO::clearSysStatus();
      }
}

Generated by  Doxygen 1.6.0   Back to index