File indexing completed on 2024-05-12 04:21:29

0001 
0002 // TODO: This is bad design as it's easy to get out of sync with the selection.
0003 //       e.g. You could have textCursorEnabled() but no text selection or
0004 //       vice versa.  And the cursor could be outside of the selection.
0005 //
0006 //       In fact, our text commands momentarily violate these "invariants":
0007 //
0008 //       1. A text box with content must have the cursor somewhere on an
0009 //          existing text line, possibly 1 position after the last character
0010 //          on a line.
0011 //
0012 //       2. Special case: A content-less text box (i.e. no text lines) must
0013 //          have the cursor at (0,0).
0014 //
0015 //       We don't assert-check them at the moment.  We should when we fix
0016 //       the design so that the invariants are always maintained.
0017 
0018 /*
0019    Copyright (c) 2003-2007 Clarence Dang <dang@kde.org>
0020    Copyright (c) 2005 Kazuki Ohta <mover@hct.zaq.ne.jp>
0021    Copyright (c) 2010 Tasuku Suzuki <stasuku@gmail.com>
0022    All rights reserved.
0023 
0024    Redistribution and use in source and binary forms, with or without
0025    modification, are permitted provided that the following conditions
0026    are met:
0027 
0028    1. Redistributions of source code must retain the above copyright
0029       notice, this list of conditions and the following disclaimer.
0030    2. Redistributions in binary form must reproduce the above copyright
0031       notice, this list of conditions and the following disclaimer in the
0032       documentation and/or other materials provided with the distribution.
0033 
0034    THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
0035    IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
0036    OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
0037    IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
0038    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
0039    NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
0040    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
0041    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
0042    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
0043    THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
0044 */
0045 
0046 
0047 #define DEBUG_KP_VIEW_MANAGER 0
0048 
0049 
0050 #include "kpViewManager.h"
0051 #include "kpViewManagerPrivate.h"
0052 
0053 #include <QApplication>
0054 #include <QTimer>
0055 //#include <QInputContext>
0056 
0057 #include "kpLogCategories.h"
0058 
0059 #include "kpDefs.h"
0060 #include "document/kpDocument.h"
0061 #include "mainWindow/kpMainWindow.h"
0062 #include "layers/tempImage/kpTempImage.h"
0063 #include "layers/selections/text/kpTextSelection.h"
0064 #include "tools/kpTool.h"
0065 #include "views/kpView.h"
0066 
0067 
0068 // public
0069 bool kpViewManager::textCursorEnabled () const
0070 {
0071     return static_cast<bool> (d->textCursorBlinkTimer);
0072 }
0073 
0074 // public
0075 void kpViewManager::setTextCursorEnabled (bool yes)
0076 {
0077 #if DEBUG_KP_VIEW_MANAGER && 1
0078     qCDebug(kpLogViews) << "kpViewManager::setTextCursorEnabled(" << yes << ")";
0079 #endif
0080 
0081     if (yes == textCursorEnabled ()) {
0082         return;
0083     }
0084 
0085     delete d->textCursorBlinkTimer;
0086     d->textCursorBlinkTimer = nullptr;
0087 
0088     setFastUpdates ();
0089     setQueueUpdates ();
0090     {
0091         if (yes)
0092         {
0093             d->textCursorBlinkTimer = new QTimer (this);
0094             d->textCursorBlinkTimer->setSingleShot (true);
0095             connect (d->textCursorBlinkTimer, &QTimer::timeout, this, &kpViewManager::slotTextCursorBlink);
0096 
0097             d->textCursorBlinkState = true;
0098             slotTextCursorBlink ();
0099         }
0100         else
0101         {
0102             d->textCursorBlinkState = false;
0103             updateTextCursor ();
0104         }
0105     }
0106     restoreQueueUpdates ();
0107     restoreFastUpdates ();
0108 }
0109 
0110 
0111 // public
0112 bool kpViewManager::textCursorBlinkState () const
0113 {
0114     return d->textCursorBlinkState;
0115 }
0116 
0117 // public
0118 void kpViewManager::setTextCursorBlinkState (bool on)
0119 {
0120     if (on == d->textCursorBlinkState) {
0121         return;
0122     }
0123 
0124     d->textCursorBlinkState = on;
0125 
0126     updateTextCursor ();
0127 }
0128 
0129 
0130 // public
0131 int kpViewManager::textCursorRow () const
0132 {
0133     return d->textCursorRow;
0134 }
0135 
0136 // public
0137 int kpViewManager::textCursorCol () const
0138 {
0139     return d->textCursorCol;
0140 }
0141 
0142 // public
0143 void kpViewManager::setTextCursorPosition (int row, int col)
0144 {
0145     if (row == d->textCursorRow && col == d->textCursorCol) {
0146         return;
0147     }
0148 
0149     setFastUpdates ();
0150     setQueueUpdates ();
0151     {
0152         // Clear the old cursor.
0153         d->textCursorBlinkState = false;
0154         updateTextCursor ();
0155 
0156         d->textCursorRow = row;
0157         d->textCursorCol = col;
0158 
0159         // Render the new cursor.
0160         d->textCursorBlinkState = true;
0161         updateTextCursor ();
0162     }
0163     restoreQueueUpdates ();
0164     restoreFastUpdates ();
0165 }
0166 
0167 
0168 // public
0169 QRect kpViewManager::textCursorRect () const
0170 {
0171     kpTextSelection *textSel = document ()->textSelection ();
0172     if (!textSel) {
0173         return {};
0174     }
0175 
0176     QPoint topLeft = textSel->pointForTextRowCol (d->textCursorRow, d->textCursorCol);
0177     if (topLeft == KP_INVALID_POINT)
0178     {
0179         // Text cursor row/col hasn't been specified yet?
0180         if (textSel->hasContent ()) {
0181             return {};
0182         }
0183 
0184         // Empty text box should still display a cursor so that the user
0185         // knows where typed text will go.
0186         topLeft = textSel->textAreaRect ().topLeft ();
0187     }
0188 
0189     Q_ASSERT (topLeft != KP_INVALID_POINT);
0190 
0191     return  {topLeft.x (), topLeft.y (),
0192                 1, textSel->textStyle ().fontMetrics ().height ()};
0193 }
0194 
0195 
0196 // protected
0197 void kpViewManager::updateTextCursor ()
0198 {
0199 #if DEBUG_KP_VIEW_MANAGER && 0
0200     qCDebug(kpLogViews) << "kpViewManager::updateTextCursor()";
0201 #endif
0202 
0203     const QRect r = textCursorRect ();
0204     if (!r.isValid ()) {
0205         return;
0206     }
0207 
0208     setFastUpdates ();
0209     {
0210         // If !textCursorEnabled(), this will clear.
0211         updateViews (r);
0212     }
0213     restoreFastUpdates ();
0214 }
0215 
0216 // protected slot
0217 void kpViewManager::slotTextCursorBlink ()
0218 {
0219 #if DEBUG_KP_VIEW_MANAGER && 0
0220     qCDebug(kpLogViews) << "kpViewManager::slotTextCursorBlink() cursorBlinkState="
0221                << d->textCursorBlinkState;
0222 #endif
0223 
0224     if (d->textCursorBlinkTimer)
0225     {
0226         // (single shot)
0227         d->textCursorBlinkTimer->start (QApplication::cursorFlashTime () / 2);
0228     }
0229 
0230     updateTextCursor ();
0231     // TODO: Shouldn't this be done _before_ updating the text cursor
0232     //       because textCursorBlinkState() is supposed to reflect what
0233     //       updateTextCursor() just rendered, until the next timer tick?
0234     d->textCursorBlinkState = !d->textCursorBlinkState;
0235 }