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 |