1 /* 2 Nested list helper 3 SPDX-FileCopyrightText: 2008 Stephen Kelly <steveire@gmail.com> 4 5 SPDX-License-Identifier: LGPL-2.1-or-later 6 */ 7 8 #include "nestedlisthelper_p.h" 9 10 #include <QKeyEvent> 11 #include <QTextBlock> 12 #include <QTextCursor> 13 #include <QTextList> 14 15 #include "ktextedit.h" 16 NestedListHelper(QTextEdit * te)17NestedListHelper::NestedListHelper(QTextEdit *te) 18 : textEdit(te) 19 { 20 } 21 ~NestedListHelper()22NestedListHelper::~NestedListHelper() 23 { 24 } 25 handleKeyPressEvent(QKeyEvent * event)26bool NestedListHelper::handleKeyPressEvent(QKeyEvent *event) 27 { 28 QTextCursor cursor = textEdit->textCursor(); 29 if (!cursor.currentList()) { 30 return false; 31 } 32 33 if (event->key() == Qt::Key_Backspace && !cursor.hasSelection() && cursor.atBlockStart() && canDedent()) { 34 changeIndent(-1); 35 return true; 36 } 37 38 if (event->key() == Qt::Key_Return && !cursor.hasSelection() && cursor.block().text().isEmpty() && canDedent()) { 39 changeIndent(-1); 40 return true; 41 } 42 43 if (event->key() == Qt::Key_Tab && (cursor.atBlockStart() || cursor.hasSelection()) && canIndent()) { 44 changeIndent(+1); 45 return true; 46 } 47 48 return false; 49 } 50 canIndent() const51bool NestedListHelper::canIndent() const 52 { 53 const QTextCursor cursor = topOfSelection(); 54 const QTextBlock block = cursor.block(); 55 if (!block.isValid()) { 56 return false; 57 } 58 if (!block.textList()) { 59 return true; 60 } 61 const QTextBlock prevBlock = block.previous(); 62 if (!prevBlock.textList()) { 63 return false; 64 } 65 return block.textList()->format().indent() <= prevBlock.textList()->format().indent(); 66 } 67 canDedent() const68bool NestedListHelper::canDedent() const 69 { 70 const QTextCursor cursor = bottomOfSelection(); 71 const QTextBlock block = cursor.block(); 72 if (!block.isValid()) { 73 return false; 74 } 75 if (!block.textList() || block.textList()->format().indent() <= 0) { 76 return false; 77 } 78 const QTextBlock nextBlock = block.next(); 79 if (!nextBlock.textList()) { 80 return true; 81 } 82 return block.textList()->format().indent() >= nextBlock.textList()->format().indent(); 83 } 84 handleAfterDropEvent(QDropEvent * dropEvent)85bool NestedListHelper::handleAfterDropEvent(QDropEvent *dropEvent) 86 { 87 Q_UNUSED(dropEvent); 88 QTextCursor cursor = topOfSelection(); 89 90 QTextBlock droppedBlock = cursor.block(); 91 int firstDroppedItemIndent = droppedBlock.textList()->format().indent(); 92 93 int minimumIndent = droppedBlock.previous().textList()->format().indent(); 94 95 if (firstDroppedItemIndent < minimumIndent) { 96 cursor = QTextCursor(droppedBlock); 97 QTextListFormat fmt = droppedBlock.textList()->format(); 98 fmt.setIndent(minimumIndent); 99 QTextList *list = cursor.createList(fmt); 100 101 int endOfDrop = bottomOfSelection().position(); 102 while (droppedBlock.next().position() < endOfDrop) { 103 droppedBlock = droppedBlock.next(); 104 if (droppedBlock.textList()->format().indent() != firstDroppedItemIndent) { 105 // new list? 106 } 107 list->add(droppedBlock); 108 } 109 // list.add( droppedBlock ); 110 } 111 112 return true; 113 } 114 processList(QTextList * list)115void NestedListHelper::processList(QTextList *list) 116 { 117 QTextBlock block = list->item(0); 118 int thisListIndent = list->format().indent(); 119 120 QTextCursor cursor = QTextCursor(block); 121 list = cursor.createList(list->format()); 122 bool processingSubList = false; 123 while (block.next().textList() != nullptr) { 124 block = block.next(); 125 126 QTextList *nextList = block.textList(); 127 int nextItemIndent = nextList->format().indent(); 128 if (nextItemIndent < thisListIndent) { 129 return; 130 } else if (nextItemIndent > thisListIndent) { 131 if (processingSubList) { 132 continue; 133 } 134 processingSubList = true; 135 processList(nextList); 136 } else { 137 processingSubList = false; 138 list->add(block); 139 } 140 } 141 // delete nextList; 142 // nextList = 0; 143 } 144 reformatList(QTextBlock block)145void NestedListHelper::reformatList(QTextBlock block) 146 { 147 if (block.textList()) { 148 int minimumIndent = block.textList()->format().indent(); 149 150 // Start at the top of the list 151 while (block.previous().textList() != nullptr) { 152 if (block.previous().textList()->format().indent() < minimumIndent) { 153 break; 154 } 155 block = block.previous(); 156 } 157 158 processList(block.textList()); 159 } 160 } 161 reformatList()162void NestedListHelper::reformatList() 163 { 164 QTextCursor cursor = textEdit->textCursor(); 165 reformatList(cursor.block()); 166 } 167 topOfSelection() const168QTextCursor NestedListHelper::topOfSelection() const 169 { 170 QTextCursor cursor = textEdit->textCursor(); 171 172 if (cursor.hasSelection()) { 173 cursor.setPosition(qMin(cursor.position(), cursor.anchor())); 174 } 175 return cursor; 176 } 177 bottomOfSelection() const178QTextCursor NestedListHelper::bottomOfSelection() const 179 { 180 QTextCursor cursor = textEdit->textCursor(); 181 182 if (cursor.hasSelection()) { 183 cursor.setPosition(qMax(cursor.position(), cursor.anchor())); 184 } 185 return cursor; 186 } 187 changeIndent(int delta)188void NestedListHelper::changeIndent(int delta) 189 { 190 QTextCursor cursor = textEdit->textCursor(); 191 cursor.beginEditBlock(); 192 193 const int top = qMin(cursor.position(), cursor.anchor()); 194 const int bottom = qMax(cursor.position(), cursor.anchor()); 195 196 // A reformatList should be called on the block inside selection 197 // with the lowest indentation level 198 int minIndentPosition; 199 int minIndent = -1; 200 201 // Changing indentation of all blocks between top and bottom 202 cursor.setPosition(top); 203 do { 204 QTextList *list = cursor.currentList(); 205 // Setting up listFormat 206 QTextListFormat listFmt; 207 if (!list) { 208 if (delta > 0) { 209 // No list, we're increasing indentation -> create a new one 210 listFmt.setStyle(QTextListFormat::ListDisc); 211 listFmt.setIndent(delta); 212 } 213 // else do nothing 214 } else { 215 const int newIndent = list->format().indent() + delta; 216 if (newIndent > 0) { 217 listFmt = list->format(); 218 listFmt.setIndent(newIndent); 219 } else { 220 listFmt.setIndent(0); 221 } 222 } 223 224 if (listFmt.indent() > 0) { 225 // This block belongs to a list: here we create a new one 226 // for each block, and then let reformatList() sort it out 227 cursor.createList(listFmt); 228 if (minIndent == -1 || minIndent > listFmt.indent()) { 229 minIndent = listFmt.indent(); 230 minIndentPosition = cursor.block().position(); 231 } 232 } else { 233 // If the block belonged to a list, remove it from there 234 if (list) { 235 list->remove(cursor.block()); 236 } 237 // The removal does not change the indentation, we need to do it explicitly 238 QTextBlockFormat blkFmt; 239 blkFmt.setIndent(0); 240 cursor.mergeBlockFormat(blkFmt); 241 } 242 if (!cursor.block().next().isValid()) { 243 break; 244 } 245 cursor.movePosition(QTextCursor::NextBlock); 246 } while (cursor.position() < bottom); 247 // Reformatting the whole list 248 if (minIndent != -1) { 249 cursor.setPosition(minIndentPosition); 250 reformatList(cursor.block()); 251 } 252 cursor.setPosition(top); 253 reformatList(cursor.block()); 254 cursor.endEditBlock(); 255 } 256 handleOnBulletType(int styleIndex)257void NestedListHelper::handleOnBulletType(int styleIndex) 258 { 259 QTextCursor cursor = textEdit->textCursor(); 260 if (styleIndex != 0) { 261 QTextListFormat::Style style = static_cast<QTextListFormat::Style>(styleIndex); 262 QTextList *currentList = cursor.currentList(); 263 QTextListFormat listFmt; 264 265 cursor.beginEditBlock(); 266 267 if (currentList) { 268 listFmt = currentList->format(); 269 listFmt.setStyle(style); 270 currentList->setFormat(listFmt); 271 } else { 272 listFmt.setStyle(style); 273 cursor.createList(listFmt); 274 } 275 276 cursor.endEditBlock(); 277 } else { 278 QTextBlockFormat bfmt; 279 bfmt.setObjectIndex(-1); 280 cursor.setBlockFormat(bfmt); 281 } 282 283 reformatList(); 284 } 285