| 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 | |
| 17 | NestedListHelper::NestedListHelper(QTextEdit *te) |
| 18 | : textEdit(te) |
| 19 | { |
| 20 | } |
| 21 | |
| 22 | NestedListHelper::~NestedListHelper() |
| 23 | { |
| 24 | } |
| 25 | |
| 26 | bool 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(delta: -1); |
| 35 | return true; |
| 36 | } |
| 37 | |
| 38 | if (event->key() == Qt::Key_Return && !cursor.hasSelection() && cursor.block().text().isEmpty() && canDedent()) { |
| 39 | changeIndent(delta: -1); |
| 40 | return true; |
| 41 | } |
| 42 | |
| 43 | if (event->key() == Qt::Key_Tab && (cursor.atBlockStart() || cursor.hasSelection()) && canIndent()) { |
| 44 | changeIndent(delta: +1); |
| 45 | return true; |
| 46 | } |
| 47 | |
| 48 | return false; |
| 49 | } |
| 50 | |
| 51 | bool 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 | |
| 68 | bool 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 | |
| 85 | bool 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(format: 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(block: droppedBlock); |
| 108 | } |
| 109 | // list.add( droppedBlock ); |
| 110 | } |
| 111 | |
| 112 | return true; |
| 113 | } |
| 114 | |
| 115 | void NestedListHelper::processList(QTextList *list) |
| 116 | { |
| 117 | QTextBlock block = list->item(i: 0); |
| 118 | int thisListIndent = list->format().indent(); |
| 119 | |
| 120 | QTextCursor cursor = QTextCursor(block); |
| 121 | list = cursor.createList(format: 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(list: nextList); |
| 136 | } else { |
| 137 | processingSubList = false; |
| 138 | list->add(block); |
| 139 | } |
| 140 | } |
| 141 | // delete nextList; |
| 142 | // nextList = 0; |
| 143 | } |
| 144 | |
| 145 | void 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(list: block.textList()); |
| 159 | } |
| 160 | } |
| 161 | |
| 162 | void NestedListHelper::reformatList() |
| 163 | { |
| 164 | QTextCursor cursor = textEdit->textCursor(); |
| 165 | reformatList(block: cursor.block()); |
| 166 | } |
| 167 | |
| 168 | QTextCursor NestedListHelper::topOfSelection() const |
| 169 | { |
| 170 | QTextCursor cursor = textEdit->textCursor(); |
| 171 | |
| 172 | if (cursor.hasSelection()) { |
| 173 | cursor.setPosition(pos: qMin(a: cursor.position(), b: cursor.anchor())); |
| 174 | } |
| 175 | return cursor; |
| 176 | } |
| 177 | |
| 178 | QTextCursor NestedListHelper::bottomOfSelection() const |
| 179 | { |
| 180 | QTextCursor cursor = textEdit->textCursor(); |
| 181 | |
| 182 | if (cursor.hasSelection()) { |
| 183 | cursor.setPosition(pos: qMax(a: cursor.position(), b: cursor.anchor())); |
| 184 | } |
| 185 | return cursor; |
| 186 | } |
| 187 | |
| 188 | void NestedListHelper::changeIndent(int delta) |
| 189 | { |
| 190 | QTextCursor cursor = textEdit->textCursor(); |
| 191 | cursor.beginEditBlock(); |
| 192 | |
| 193 | const int top = qMin(a: cursor.position(), b: cursor.anchor()); |
| 194 | const int bottom = qMax(a: cursor.position(), b: 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(pos: 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(format: 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(modifier: blkFmt); |
| 241 | } |
| 242 | if (!cursor.block().next().isValid()) { |
| 243 | break; |
| 244 | } |
| 245 | cursor.movePosition(op: QTextCursor::NextBlock); |
| 246 | } while (cursor.position() < bottom); |
| 247 | // Reformatting the whole list |
| 248 | if (minIndent != -1) { |
| 249 | cursor.setPosition(pos: minIndentPosition); |
| 250 | reformatList(block: cursor.block()); |
| 251 | } |
| 252 | cursor.setPosition(pos: top); |
| 253 | reformatList(block: cursor.block()); |
| 254 | cursor.endEditBlock(); |
| 255 | } |
| 256 | |
| 257 | void 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(format: 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 |
