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 }