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 }