text: // The implementation of the Qt specific subclass of ScintillaBase.
  //
  // Copyright (c) 2015 Riverbank Computing Limited <info@riverbankcomputing.com>
  //
  // This file is part of QScintilla.
  //
  // This file may be used under the terms of the GNU General Public License
  // version 3.0 as published by the Free Software Foundation and appearing in
  // the file LICENSE included in the packaging of this file.  Please review the
  // following information to ensure the GNU General Public License version 3.0
  // requirements will be met: http://www.gnu.org/copyleft/gpl.html.
  //
  // If you do not wish to use this file under the terms of the GPL version 3.0
  // then you may purchase a commercial license.  For more information contact
  // info@riverbankcomputing.com.
  //
  // This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
  // WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
  
  
  #include <string.h>
  
  #include <qapplication.h>
  #include <qbytearray.h>
  #include <qdrag.h>
  #include <qevent.h>
  #include <qmimedata.h>
  #include <qpainter.h>
  #include <qscrollbar.h>
  #include <qstring.h>
  
  #include "Qsci/qsciscintillabase.h"
  #include "ScintillaQt.h"
  #include "SciClasses.h"
  
  
  // We want to use the Scintilla notification names as Qt signal names.
  #undef  SCEN_CHANGE
  #undef  SCN_AUTOCCANCELLED
  #undef  SCN_AUTOCCHARDELETED
  #undef  SCN_AUTOCSELECTION
  #undef  SCN_CALLTIPCLICK
  #undef  SCN_CHARADDED
  #undef  SCN_DOUBLECLICK
  #undef  SCN_DWELLEND
  #undef  SCN_DWELLSTART
  #undef  SCN_FOCUSIN
  #undef  SCN_FOCUSOUT
  #undef  SCN_HOTSPOTCLICK
  #undef  SCN_HOTSPOTDOUBLECLICK
  #undef  SCN_HOTSPOTRELEASECLICK
  #undef  SCN_INDICATORCLICK
  #undef  SCN_INDICATORRELEASE
  #undef  SCN_MACRORECORD
  #undef  SCN_MARGINCLICK
  #undef  SCN_MODIFIED
  #undef  SCN_MODIFYATTEMPTRO
  #undef  SCN_NEEDSHOWN
  #undef  SCN_PAINTED
  #undef  SCN_SAVEPOINTLEFT
  #undef  SCN_SAVEPOINTREACHED
  #undef  SCN_STYLENEEDED
  #undef  SCN_UPDATEUI
  #undef  SCN_USERLISTSELECTION
  #undef  SCN_ZOOM
  
  enum
  {
      SCEN_CHANGE = 768,
      SCN_AUTOCCANCELLED = 2025,
      SCN_AUTOCCHARDELETED = 2026,
      SCN_AUTOCSELECTION = 2022,
      SCN_CALLTIPCLICK = 2021,
      SCN_CHARADDED = 2001,
      SCN_DOUBLECLICK = 2006,
      SCN_DWELLEND = 2017,
      SCN_DWELLSTART = 2016,
      SCN_FOCUSIN = 2028,
      SCN_FOCUSOUT = 2029,
      SCN_HOTSPOTCLICK = 2019,
      SCN_HOTSPOTDOUBLECLICK = 2020,
      SCN_HOTSPOTRELEASECLICK = 2027,
      SCN_INDICATORCLICK = 2023,
      SCN_INDICATORRELEASE = 2024,
      SCN_MACRORECORD = 2009,
      SCN_MARGINCLICK = 2010,
      SCN_MODIFIED = 2008,
      SCN_MODIFYATTEMPTRO = 2004,
      SCN_NEEDSHOWN = 2011,
      SCN_PAINTED = 2013,
      SCN_SAVEPOINTLEFT = 2003,
      SCN_SAVEPOINTREACHED = 2002,
      SCN_STYLENEEDED = 2000,
      SCN_UPDATEUI = 2007,
      SCN_USERLISTSELECTION = 2014,
      SCN_ZOOM = 2018
  };
  
  
  // The ctor.
  QsciScintillaQt::QsciScintillaQt(QsciScintillaBase *qsb_)
      : vMax(0), hMax(0), vPage(0), hPage(0), capturedMouse(false), qsb(qsb_)
  {
      wMain = qsb->viewport();
  
      // This is ignored.
      imeInteraction = imeInline;
  
      for (int i = 0; i <= static_cast<int>(tickPlatform); ++i)
          timers[i] = 0;
  
      Initialise();
  }
  
  
  // The dtor.
  QsciScintillaQt::~QsciScintillaQt()
  {
      Finalise();
  }
  
  
  // Initialise the instance.
  void QsciScintillaQt::Initialise()
  {
  }
  
  
  // Tidy up the instance.
  void QsciScintillaQt::Finalise()
  {
      for (int i = 0; i <= static_cast<int>(tickPlatform); ++i)
          FineTickerCancel(static_cast<TickReason>(i));
  
      ScintillaBase::Finalise();
  }
  
  
  // Start a drag.
  void QsciScintillaQt::StartDrag()
  {
      inDragDrop = ddDragging;
  
      QDrag *qdrag = new QDrag(qsb);
      qdrag->setMimeData(mimeSelection(drag));
  
  #if QT_VERSION >= 0x040300
      Qt::DropAction action = qdrag->exec(Qt::MoveAction | Qt::CopyAction, Qt::MoveAction);
  #else
      Qt::DropAction action = qdrag->start(Qt::MoveAction);
  #endif
  
      // Remove the dragged text if it was a move to another widget or
      // application.
      if (action == Qt::MoveAction && qdrag->target() != qsb->viewport())
          ClearSelection();
  
      SetDragPosition(QSCI_SCI_NAMESPACE(SelectionPosition)());
      inDragDrop = ddNone;
  }
  
  
  // Re-implement to trap certain messages.
  sptr_t QsciScintillaQt::WndProc(unsigned int iMessage, uptr_t wParam,
          sptr_t lParam)
  {
      switch (iMessage)
      {
      case SCI_GETDIRECTFUNCTION:
          return reinterpret_cast<sptr_t>(DirectFunction);
  
      case SCI_GETDIRECTPOINTER:
          return reinterpret_cast<sptr_t>(this);
      }
  
      return ScintillaBase::WndProc(iMessage, wParam, lParam);
  }
  
  
  // Windows nonsense.
  sptr_t QsciScintillaQt::DefWndProc(unsigned int, uptr_t, sptr_t)
  {
      return 0;
  }
  
  
  // Grab or release the mouse (and keyboard).
  void QsciScintillaQt::SetMouseCapture(bool on)
  {
      if (mouseDownCaptures)
          if (on)
              qsb->viewport()->grabMouse();
          else
              qsb->viewport()->releaseMouse();
  
      capturedMouse = on;
  }
  
  
  // Return true if the mouse/keyboard are currently grabbed.
  bool QsciScintillaQt::HaveMouseCapture()
  {
      return capturedMouse;
  }
  
  
  // Set the position of the vertical scrollbar.
  void QsciScintillaQt::SetVerticalScrollPos()
  {
      QScrollBar *sb = qsb->verticalScrollBar();
      bool was_blocked = sb->blockSignals(true);
  
      sb->setValue(topLine);
  
      sb->blockSignals(was_blocked);
  }
  
  
  // Set the position of the horizontal scrollbar.
  void QsciScintillaQt::SetHorizontalScrollPos()
  {
      QScrollBar *sb = qsb->horizontalScrollBar();
      bool was_blocked = sb->blockSignals(true);
  
      sb->setValue(xOffset);
  
      sb->blockSignals(was_blocked);
  }
  
  
  // Set the extent of the vertical and horizontal scrollbars and return true if
  // the view needs re-drawing.
  bool QsciScintillaQt::ModifyScrollBars(int nMax,int nPage)
  {
      bool modified = false;
      QScrollBar *sb;
  
      int vNewPage = nPage;
      int vNewMax = nMax - vNewPage + 1;
  
      if (vMax != vNewMax || vPage != vNewPage)
      {
          vMax = vNewMax;
          vPage = vNewPage;
          modified = true;
  
          sb = qsb->verticalScrollBar();
          sb->setMaximum(vMax);
          sb->setPageStep(vPage);
      }
  
      int hNewPage = GetTextRectangle().Width();
      int hNewMax = (scrollWidth > hNewPage) ? scrollWidth - hNewPage : 0;
      int charWidth = vs.styles[STYLE_DEFAULT].aveCharWidth;
  
      sb = qsb->horizontalScrollBar();
  
      if (hMax != hNewMax || hPage != hNewPage || sb->singleStep() != charWidth)
      {
          hMax = hNewMax;
          hPage = hNewPage;
          modified = true;
  
          sb->setMaximum(hMax);
          sb->setPageStep(hPage);
          sb->setSingleStep(charWidth);
      }
  
      return modified;
  }
  
  
  // Called after SCI_SETWRAPMODE and SCI_SETHSCROLLBAR.
  void QsciScintillaQt::ReconfigureScrollBars()
  {
      // Hide or show the scrollbars if needed.
      bool hsb = (horizontalScrollBarVisible && !Wrapping());
  
      qsb->setHorizontalScrollBarPolicy(hsb ? Qt::ScrollBarAsNeeded : Qt::ScrollBarAlwaysOff);
      qsb->setVerticalScrollBarPolicy(verticalScrollBarVisible ? Qt::ScrollBarAsNeeded : Qt::ScrollBarAlwaysOff);
  }
  
  
  // Notify interested parties of any change in the document.
  void QsciScintillaQt::NotifyChange()
  {
      emit qsb->SCEN_CHANGE();
  }
  
  
  // Notify interested parties of various events.  This is the main mapping
  // between Scintilla notifications and Qt signals.
  void QsciScintillaQt::NotifyParent(QSCI_SCI_NAMESPACE(SCNotification) scn)
  {
      switch (scn.nmhdr.code)
      {
      case SCN_CALLTIPCLICK:
          emit qsb->SCN_CALLTIPCLICK(scn.position);
          break;
  
      case SCN_AUTOCCANCELLED:
          emit qsb->SCN_AUTOCCANCELLED();
          break;
  
      case SCN_AUTOCCHARDELETED:
          emit qsb->SCN_AUTOCCHARDELETED();
          break;
  
      case SCN_AUTOCSELECTION:
          emit qsb->SCN_AUTOCSELECTION(scn.text, scn.lParam);
          break;
  
      case SCN_CHARADDED:
          emit qsb->SCN_CHARADDED(scn.ch);
          break;
  
      case SCN_DOUBLECLICK:
          emit qsb->SCN_DOUBLECLICK(scn.position, scn.line, scn.modifiers);
          break;
  
      case SCN_DWELLEND:
          emit qsb->SCN_DWELLEND(scn.position, scn.x, scn.y);
          break;
  
      case SCN_DWELLSTART:
          emit qsb->SCN_DWELLSTART(scn.position, scn.x, scn.y);
          break;
  
      case SCN_FOCUSIN:
          emit qsb->SCN_FOCUSIN();
          break;
  
      case SCN_FOCUSOUT:
          emit qsb->SCN_FOCUSOUT();
          break;
  
      case SCN_HOTSPOTCLICK:
          emit qsb->SCN_HOTSPOTCLICK(scn.position, scn.modifiers);
          break;
  
      case SCN_HOTSPOTDOUBLECLICK:
          emit qsb->SCN_HOTSPOTDOUBLECLICK(scn.position, scn.modifiers);
          break;
  
      case SCN_HOTSPOTRELEASECLICK:
          emit qsb->SCN_HOTSPOTRELEASECLICK(scn.position, scn.modifiers);
          break;
  
      case SCN_INDICATORCLICK:
          emit qsb->SCN_INDICATORCLICK(scn.position, scn.modifiers);
          break;
  
      case SCN_INDICATORRELEASE:
          emit qsb->SCN_INDICATORRELEASE(scn.position, scn.modifiers);
          break;
  
      case SCN_MACRORECORD:
          emit qsb->SCN_MACRORECORD(scn.message, scn.wParam,
                  reinterpret_cast<void *>(scn.lParam));
          break;
  
      case SCN_MARGINCLICK:
          emit qsb->SCN_MARGINCLICK(scn.position, scn.modifiers, scn.margin);
          break;
  
      case SCN_MODIFIED:
          {
              char *text;
  
              // Give some protection to the Python bindings.
              if (scn.text && (scn.modificationType & (SC_MOD_INSERTTEXT|SC_MOD_DELETETEXT) != 0))
              {
                  text = new char[scn.length + 1];
                  memcpy(text, scn.text, scn.length);
                  text[scn.length] = '\0';
              }
              else
              {
                  text = 0;
              }
  
              emit qsb->SCN_MODIFIED(scn.position, scn.modificationType, text,
                      scn.length, scn.linesAdded, scn.line, scn.foldLevelNow,
                      scn.foldLevelPrev, scn.token, scn.annotationLinesAdded);
  
              if (text)
                  delete[] text;
  
              break;
          }
  
      case SCN_MODIFYATTEMPTRO:
          emit qsb->SCN_MODIFYATTEMPTRO();
          break;
  
      case SCN_NEEDSHOWN:
          emit qsb->SCN_NEEDSHOWN(scn.position, scn.length);
          break;
  
      case SCN_PAINTED:
          emit qsb->SCN_PAINTED();
          break;
  
      case SCN_SAVEPOINTLEFT:
          emit qsb->SCN_SAVEPOINTLEFT();
          break;
  
      case SCN_SAVEPOINTREACHED:
          emit qsb->SCN_SAVEPOINTREACHED();
          break;
  
      case SCN_STYLENEEDED:
          emit qsb->SCN_STYLENEEDED(scn.position);
          break;
  
      case SCN_UPDATEUI:
          emit qsb->SCN_UPDATEUI(scn.updated);
          break;
  
      case SCN_USERLISTSELECTION:
          emit qsb->SCN_USERLISTSELECTION(scn.text, scn.wParam);
          break;
  
      case SCN_ZOOM:
          emit qsb->SCN_ZOOM();
          break;
  
      default:
          qWarning("Unknown notification: %u", scn.nmhdr.code);
      }
  }
  
  
  // Convert a selection to mime data.
  QMimeData *QsciScintillaQt::mimeSelection(
          const QSCI_SCI_NAMESPACE(SelectionText) &text) const
  {
      return qsb->toMimeData(QByteArray(text.Data()), text.rectangular);
  }
  
  
  // Copy the selected text to the clipboard.
  void QsciScintillaQt::CopyToClipboard(
          const QSCI_SCI_NAMESPACE(SelectionText) &selectedText)
  {
      QApplication::clipboard()->setMimeData(mimeSelection(selectedText));
  }
  
  
  // Implement copy.
  void QsciScintillaQt::Copy()
  {
      if (!sel.Empty())
      {
          QSCI_SCI_NAMESPACE(SelectionText) text;
  
          CopySelectionRange(&text);
          CopyToClipboard(text);
      }
  }
  
  
  // Implement pasting text.
  void QsciScintillaQt::Paste()
  {
      pasteFromClipboard(QClipboard::Clipboard);
  }
  
  
  // Paste text from either the clipboard or selection.
  void QsciScintillaQt::pasteFromClipboard(QClipboard::Mode mode)
  {
      int len;
      const char *s;
      bool rectangular;
  
      const QMimeData *source = QApplication::clipboard()->mimeData(mode);
  
      if (!source || !qsb->canInsertFromMimeData(source))
          return;
  
      QByteArray text = qsb->fromMimeData(source, rectangular);
      len = text.length();
      s = text.data();
  
      std::string dest = QSCI_SCI_NAMESPACE(Document)::TransformLineEnds(s, len,
              pdoc->eolMode);
  
      QSCI_SCI_NAMESPACE(SelectionText) selText;
      selText.Copy(dest, (IsUnicodeMode() ? SC_CP_UTF8 : 0),
              vs.styles[STYLE_DEFAULT].characterSet, rectangular, false);
  
      QSCI_SCI_NAMESPACE(UndoGroup) ug(pdoc);
  
      ClearSelection();
      InsertPasteShape(selText.Data(), selText.Length(),
              selText.rectangular ? pasteRectangular : pasteStream);
      EnsureCaretVisible();
  }
  
  
  // Create a call tip window.
  void QsciScintillaQt::CreateCallTipWindow(QSCI_SCI_NAMESPACE(PRectangle) rc)
  {
      if (!ct.wCallTip.Created())
          ct.wCallTip = ct.wDraw = new QsciSciCallTip(qsb, this);
  
      QsciSciCallTip *w = reinterpret_cast<QsciSciCallTip *>(ct.wCallTip.GetID());
  
      w->resize(rc.right - rc.left, rc.bottom - rc.top);
      ct.wCallTip.Show();
  }
  
  
  // Add an item to the right button menu.
  void QsciScintillaQt::AddToPopUp(const char *label, int cmd, bool enabled)
  {
      QsciSciPopup *pm = static_cast<QsciSciPopup *>(popup.GetID());
  
      if (*label)
          pm->addItem(qApp->translate("ContextMenu", label), cmd, enabled, this);
      else
          pm->addSeparator();
  }
  
  
  // Claim the selection.
  void QsciScintillaQt::ClaimSelection()
  {
      bool isSel = !sel.Empty();
  
      if (isSel)
      {
          QClipboard *cb = QApplication::clipboard();
  
          // If we support X11 style selection then make it available now.
          if (cb->supportsSelection())
          {
              QSCI_SCI_NAMESPACE(SelectionText) text;
  
              CopySelectionRange(&text);
  
              if (text.Data())
                  cb->setMimeData(mimeSelection(text), QClipboard::Selection);
          }
  
          primarySelection = true;
      }
      else
          primarySelection = false;
  
      emit qsb->QSCN_SELCHANGED(isSel);
  }
  
  
  // Unclaim the selection.
  void QsciScintillaQt::UnclaimSelection()
  {
      if (primarySelection)
      {
          primarySelection = false;
          qsb->viewport()->update();
      }
  }
  
  
  // Implemented to provide compatibility with the Windows version.
  sptr_t QsciScintillaQt::DirectFunction(QsciScintillaQt *sciThis, unsigned int iMessage,
          uptr_t wParam, sptr_t lParam)
  {
      return sciThis->WndProc(iMessage,wParam,lParam);
  }
  
  
  // Draw the contents of the widget.
  void QsciScintillaQt::paintEvent(QPaintEvent *e)
  {
      QSCI_SCI_NAMESPACE(Surface) *sw = QSCI_SCI_NAMESPACE(Surface)::Allocate(SC_TECHNOLOGY_DEFAULT);
  
      if (!sw)
          return;
  
      paintState = painting;
  
      const QRect &qr = e->rect();
  
      rcPaint.left = qr.left();
      rcPaint.top = qr.top();
      rcPaint.right = qr.right() + 1;
      rcPaint.bottom = qr.bottom() + 1;
  
      QSCI_SCI_NAMESPACE(PRectangle) rcText = GetTextRectangle();
      paintingAllText = rcPaint.Contains(rcText);
  
      QPainter painter(qsb->viewport());
  
      sw->Init(&painter);
      sw->SetUnicodeMode(CodePage() == SC_CP_UTF8);
      Paint(sw, rcPaint);
  
      delete sw;
  
      // If the painting area was insufficient to cover the new style or brace
      // highlight positions then repaint the whole thing.
      if (paintState == paintAbandoned)
          qsb->viewport()->update();
  
      paintState = notPainting;
  }
  
  
  // Re-implemented to drive the tickers.
  void QsciScintillaQt::timerEvent(QTimerEvent *e)
  {
      for (int i = 0; i <= static_cast<int>(tickPlatform); ++i)
          if (timers[i] == e->timerId())
              TickFor(static_cast<TickReason>(i));
  }
  
  
  // Re-implemented to say we support fine tickers.
  bool QsciScintillaQt::FineTickerAvailable()
  {
      return true;
  }
  
  
  // Re-implemented to stop a ticker.
  void QsciScintillaQt::FineTickerCancel(TickReason reason)
  {
      int &ticker = timers[static_cast<int>(reason)];
  
      if (ticker != 0)
      {
          killTimer(ticker);
          ticker = 0;
      }
  }
  
  
  // Re-implemented to check if a particular ticker is running.
  bool QsciScintillaQt::FineTickerRunning(TickReason reason)
  {
      return (timers[static_cast<int>(reason)] != 0);
  }
  
  
  // Re-implemented to start a ticker.
  void QsciScintillaQt::FineTickerStart(TickReason reason, int ms, int)
  {
      int &ticker = timers[static_cast<int>(reason)];
  
      if (ticker != 0)
          killTimer(ticker);
  
      ticker = startTimer(ms);
  }
  
  
  // Re-implemented to support idle processing.
  bool QsciScintillaQt::SetIdle(bool on)
  {
      if (on)
      {
          if (!idler.state)
          {
              QTimer *timer = reinterpret_cast<QTimer *>(idler.idlerID);
  
              if (!timer)
              {
                  idler.idlerID = timer = new QTimer(this);
                  connect(timer, SIGNAL(timeout()), this, SLOT(onIdle()));
              }
  
              timer->start(0);
              idler.state = true;
          }
      }
      else if (idler.state)
      {
          reinterpret_cast<QTimer *>(idler.idlerID)->stop();
          idler.state = false;
      }
  
      return true;
  }
  
  
  // Invoked to trigger any idle processing.
  void QsciScintillaQt::onIdle()
  {
      if (!Idle())
          SetIdle(false);
  }
  