File indexing completed on 2024-04-14 03:56:09
0001 /* 0002 Nested list helper 0003 SPDX-FileCopyrightText: 2008 Stephen Kelly <steveire@gmail.com> 0004 0005 SPDX-License-Identifier: LGPL-2.1-or-later 0006 */ 0007 0008 #include "nestedlisthelper_p.h" 0009 0010 #include <QKeyEvent> 0011 #include <QTextBlock> 0012 #include <QTextCursor> 0013 #include <QTextList> 0014 0015 #include "ktextedit.h" 0016 0017 NestedListHelper::NestedListHelper(QTextEdit *te) 0018 : textEdit(te) 0019 { 0020 } 0021 0022 NestedListHelper::~NestedListHelper() 0023 { 0024 } 0025 0026 bool NestedListHelper::handleKeyPressEvent(QKeyEvent *event) 0027 { 0028 QTextCursor cursor = textEdit->textCursor(); 0029 if (!cursor.currentList()) { 0030 return false; 0031 } 0032 0033 if (event->key() == Qt::Key_Backspace && !cursor.hasSelection() && cursor.atBlockStart() && canDedent()) { 0034 changeIndent(-1); 0035 return true; 0036 } 0037 0038 if (event->key() == Qt::Key_Return && !cursor.hasSelection() && cursor.block().text().isEmpty() && canDedent()) { 0039 changeIndent(-1); 0040 return true; 0041 } 0042 0043 if (event->key() == Qt::Key_Tab && (cursor.atBlockStart() || cursor.hasSelection()) && canIndent()) { 0044 changeIndent(+1); 0045 return true; 0046 } 0047 0048 return false; 0049 } 0050 0051 bool NestedListHelper::canIndent() const 0052 { 0053 const QTextCursor cursor = topOfSelection(); 0054 const QTextBlock block = cursor.block(); 0055 if (!block.isValid()) { 0056 return false; 0057 } 0058 if (!block.textList()) { 0059 return true; 0060 } 0061 const QTextBlock prevBlock = block.previous(); 0062 if (!prevBlock.textList()) { 0063 return false; 0064 } 0065 return block.textList()->format().indent() <= prevBlock.textList()->format().indent(); 0066 } 0067 0068 bool NestedListHelper::canDedent() const 0069 { 0070 const QTextCursor cursor = bottomOfSelection(); 0071 const QTextBlock block = cursor.block(); 0072 if (!block.isValid()) { 0073 return false; 0074 } 0075 if (!block.textList() || block.textList()->format().indent() <= 0) { 0076 return false; 0077 } 0078 const QTextBlock nextBlock = block.next(); 0079 if (!nextBlock.textList()) { 0080 return true; 0081 } 0082 return block.textList()->format().indent() >= nextBlock.textList()->format().indent(); 0083 } 0084 0085 bool NestedListHelper::handleAfterDropEvent(QDropEvent *dropEvent) 0086 { 0087 Q_UNUSED(dropEvent); 0088 QTextCursor cursor = topOfSelection(); 0089 0090 QTextBlock droppedBlock = cursor.block(); 0091 int firstDroppedItemIndent = droppedBlock.textList()->format().indent(); 0092 0093 int minimumIndent = droppedBlock.previous().textList()->format().indent(); 0094 0095 if (firstDroppedItemIndent < minimumIndent) { 0096 cursor = QTextCursor(droppedBlock); 0097 QTextListFormat fmt = droppedBlock.textList()->format(); 0098 fmt.setIndent(minimumIndent); 0099 QTextList *list = cursor.createList(fmt); 0100 0101 int endOfDrop = bottomOfSelection().position(); 0102 while (droppedBlock.next().position() < endOfDrop) { 0103 droppedBlock = droppedBlock.next(); 0104 if (droppedBlock.textList()->format().indent() != firstDroppedItemIndent) { 0105 // new list? 0106 } 0107 list->add(droppedBlock); 0108 } 0109 // list.add( droppedBlock ); 0110 } 0111 0112 return true; 0113 } 0114 0115 void NestedListHelper::processList(QTextList *list) 0116 { 0117 QTextBlock block = list->item(0); 0118 int thisListIndent = list->format().indent(); 0119 0120 QTextCursor cursor = QTextCursor(block); 0121 list = cursor.createList(list->format()); 0122 bool processingSubList = false; 0123 while (block.next().textList() != nullptr) { 0124 block = block.next(); 0125 0126 QTextList *nextList = block.textList(); 0127 int nextItemIndent = nextList->format().indent(); 0128 if (nextItemIndent < thisListIndent) { 0129 return; 0130 } else if (nextItemIndent > thisListIndent) { 0131 if (processingSubList) { 0132 continue; 0133 } 0134 processingSubList = true; 0135 processList(nextList); 0136 } else { 0137 processingSubList = false; 0138 list->add(block); 0139 } 0140 } 0141 // delete nextList; 0142 // nextList = 0; 0143 } 0144 0145 void NestedListHelper::reformatList(QTextBlock block) 0146 { 0147 if (block.textList()) { 0148 int minimumIndent = block.textList()->format().indent(); 0149 0150 // Start at the top of the list 0151 while (block.previous().textList() != nullptr) { 0152 if (block.previous().textList()->format().indent() < minimumIndent) { 0153 break; 0154 } 0155 block = block.previous(); 0156 } 0157 0158 processList(block.textList()); 0159 } 0160 } 0161 0162 void NestedListHelper::reformatList() 0163 { 0164 QTextCursor cursor = textEdit->textCursor(); 0165 reformatList(cursor.block()); 0166 } 0167 0168 QTextCursor NestedListHelper::topOfSelection() const 0169 { 0170 QTextCursor cursor = textEdit->textCursor(); 0171 0172 if (cursor.hasSelection()) { 0173 cursor.setPosition(qMin(cursor.position(), cursor.anchor())); 0174 } 0175 return cursor; 0176 } 0177 0178 QTextCursor NestedListHelper::bottomOfSelection() const 0179 { 0180 QTextCursor cursor = textEdit->textCursor(); 0181 0182 if (cursor.hasSelection()) { 0183 cursor.setPosition(qMax(cursor.position(), cursor.anchor())); 0184 } 0185 return cursor; 0186 } 0187 0188 void NestedListHelper::changeIndent(int delta) 0189 { 0190 QTextCursor cursor = textEdit->textCursor(); 0191 cursor.beginEditBlock(); 0192 0193 const int top = qMin(cursor.position(), cursor.anchor()); 0194 const int bottom = qMax(cursor.position(), cursor.anchor()); 0195 0196 // A reformatList should be called on the block inside selection 0197 // with the lowest indentation level 0198 int minIndentPosition; 0199 int minIndent = -1; 0200 0201 // Changing indentation of all blocks between top and bottom 0202 cursor.setPosition(top); 0203 do { 0204 QTextList *list = cursor.currentList(); 0205 // Setting up listFormat 0206 QTextListFormat listFmt; 0207 if (!list) { 0208 if (delta > 0) { 0209 // No list, we're increasing indentation -> create a new one 0210 listFmt.setStyle(QTextListFormat::ListDisc); 0211 listFmt.setIndent(delta); 0212 } 0213 // else do nothing 0214 } else { 0215 const int newIndent = list->format().indent() + delta; 0216 if (newIndent > 0) { 0217 listFmt = list->format(); 0218 listFmt.setIndent(newIndent); 0219 } else { 0220 listFmt.setIndent(0); 0221 } 0222 } 0223 0224 if (listFmt.indent() > 0) { 0225 // This block belongs to a list: here we create a new one 0226 // for each block, and then let reformatList() sort it out 0227 cursor.createList(listFmt); 0228 if (minIndent == -1 || minIndent > listFmt.indent()) { 0229 minIndent = listFmt.indent(); 0230 minIndentPosition = cursor.block().position(); 0231 } 0232 } else { 0233 // If the block belonged to a list, remove it from there 0234 if (list) { 0235 list->remove(cursor.block()); 0236 } 0237 // The removal does not change the indentation, we need to do it explicitly 0238 QTextBlockFormat blkFmt; 0239 blkFmt.setIndent(0); 0240 cursor.mergeBlockFormat(blkFmt); 0241 } 0242 if (!cursor.block().next().isValid()) { 0243 break; 0244 } 0245 cursor.movePosition(QTextCursor::NextBlock); 0246 } while (cursor.position() < bottom); 0247 // Reformatting the whole list 0248 if (minIndent != -1) { 0249 cursor.setPosition(minIndentPosition); 0250 reformatList(cursor.block()); 0251 } 0252 cursor.setPosition(top); 0253 reformatList(cursor.block()); 0254 cursor.endEditBlock(); 0255 } 0256 0257 void NestedListHelper::handleOnBulletType(int styleIndex) 0258 { 0259 QTextCursor cursor = textEdit->textCursor(); 0260 if (styleIndex != 0) { 0261 QTextListFormat::Style style = static_cast<QTextListFormat::Style>(styleIndex); 0262 QTextList *currentList = cursor.currentList(); 0263 QTextListFormat listFmt; 0264 0265 cursor.beginEditBlock(); 0266 0267 if (currentList) { 0268 listFmt = currentList->format(); 0269 listFmt.setStyle(style); 0270 currentList->setFormat(listFmt); 0271 } else { 0272 listFmt.setStyle(style); 0273 cursor.createList(listFmt); 0274 } 0275 0276 cursor.endEditBlock(); 0277 } else { 0278 QTextBlockFormat bfmt; 0279 bfmt.setObjectIndex(-1); 0280 cursor.setBlockFormat(bfmt); 0281 } 0282 0283 reformatList(); 0284 }