File indexing completed on 2024-05-19 05:21:42

0001 /**
0002  * Nested list helper
0003  *
0004  * SPDX-FileCopyrightText: 2008 Stephen Kelly <steveire@gmail.com>
0005  *
0006  * SPDX-License-Identifier: LGPL-2.1-or-later
0007  */
0008 
0009 #include "nestedlisthelper_p.h"
0010 
0011 #include <QKeyEvent>
0012 #include <QTextBlock>
0013 #include <QTextCursor>
0014 #include <QTextEdit>
0015 #include <QTextList>
0016 using namespace KPIMTextEdit;
0017 NestedListHelper::NestedListHelper(QTextEdit *te)
0018     : textEdit(te)
0019 {
0020     listBottomMargin = 12;
0021     listTopMargin = 12;
0022     listNoMargin = 0;
0023 }
0024 
0025 bool NestedListHelper::handleBeforeKeyPressEvent(QKeyEvent *event)
0026 {
0027     QTextCursor cursor = textEdit->textCursor();
0028 
0029     // Only attempt to handle Backspace while on a list
0030     if ((event->key() != Qt::Key_Backspace) || (!cursor.currentList())) {
0031         return false;
0032     }
0033 
0034     bool handled = false;
0035 
0036     if (!cursor.hasSelection() && cursor.currentList() && event->key() == Qt::Key_Backspace && cursor.atBlockStart()) {
0037         handleOnIndentLess();
0038         handled = true;
0039     }
0040 
0041     return handled;
0042 }
0043 
0044 bool NestedListHelper::canIndent() const
0045 {
0046     if ((textEdit->textCursor().block().isValid())
0047         //            && (  textEdit->textCursor().block().previous().isValid() )
0048     ) {
0049         const QTextBlock block = textEdit->textCursor().block();
0050         const QTextBlock prevBlock = textEdit->textCursor().block().previous();
0051         if (block.textList()) {
0052             if (prevBlock.textList()) {
0053                 return block.textList()->format().indent() <= prevBlock.textList()->format().indent();
0054             }
0055         } else {
0056             return true;
0057         }
0058     }
0059     return false;
0060 }
0061 
0062 bool NestedListHelper::canDedent() const
0063 {
0064     QTextBlock thisBlock = textEdit->textCursor().block();
0065     QTextBlock nextBlock = thisBlock.next();
0066     if (thisBlock.isValid()) {
0067         int nextBlockIndent = 0;
0068         if (nextBlock.isValid() && nextBlock.textList()) {
0069             nextBlockIndent = nextBlock.textList()->format().indent();
0070         }
0071         if (thisBlock.textList()) {
0072             const int thisBlockIndent = thisBlock.textList()->format().indent();
0073             if (thisBlockIndent >= nextBlockIndent) {
0074                 return thisBlockIndent > 0;
0075             }
0076         }
0077     }
0078     return false;
0079 }
0080 
0081 bool NestedListHelper::handleAfterKeyPressEvent(QKeyEvent *event)
0082 {
0083     // Only attempt to handle Backspace and Return
0084     if ((event->key() != Qt::Key_Backspace) && (event->key() != Qt::Key_Return)) {
0085         return false;
0086     }
0087 
0088     QTextCursor cursor = textEdit->textCursor();
0089     bool handled = false;
0090 
0091     if (!cursor.hasSelection() && cursor.currentList()) {
0092         // Check if we're on the last list item.
0093         // itemNumber is zero indexed
0094         QTextBlock currentBlock = cursor.block();
0095         if (cursor.currentList()->count() == cursor.currentList()->itemNumber(currentBlock) + 1) {
0096             // Last block in this list, but may have just gained another list below.
0097             if (currentBlock.next().textList()) {
0098                 reformatList();
0099             }
0100 
0101             // No need to reformatList in this case. reformatList is slow.
0102             if ((event->key() == Qt::Key_Return) || (event->key() == Qt::Key_Backspace)) {
0103                 handled = true;
0104             }
0105         } else {
0106             reformatList();
0107         }
0108     }
0109     return handled;
0110 }
0111 
0112 void NestedListHelper::processList(QTextList *list)
0113 {
0114     QTextBlock block = list->item(0);
0115     const int thisListIndent = list->format().indent();
0116 
0117     QTextCursor cursor = QTextCursor(block);
0118     list = cursor.createList(list->format());
0119     bool processingSubList = false;
0120     while (block.next().textList() != nullptr) {
0121         block = block.next();
0122 
0123         QTextList *nextList = block.textList();
0124         const int nextItemIndent = nextList->format().indent();
0125         if (nextItemIndent < thisListIndent) {
0126             return;
0127         } else if (nextItemIndent > thisListIndent) {
0128             if (processingSubList) {
0129                 continue;
0130             }
0131             processingSubList = true;
0132             processList(nextList);
0133         } else {
0134             processingSubList = false;
0135             list->add(block);
0136         }
0137     }
0138     //     delete nextList;
0139     //     nextList = 0;
0140 }
0141 
0142 void NestedListHelper::reformatList(QTextBlock block)
0143 {
0144     if (block.textList()) {
0145         const int minimumIndent = block.textList()->format().indent();
0146 
0147         // Start at the top of the list
0148         while (block.previous().textList() != nullptr) {
0149             if (block.previous().textList()->format().indent() < minimumIndent) {
0150                 break;
0151             }
0152             block = block.previous();
0153         }
0154 
0155         processList(block.textList());
0156     }
0157 }
0158 
0159 void NestedListHelper::reformatList()
0160 {
0161     const QTextCursor cursor = textEdit->textCursor();
0162     reformatList(cursor.block());
0163 }
0164 
0165 QTextCursor NestedListHelper::topOfSelection()
0166 {
0167     QTextCursor cursor = textEdit->textCursor();
0168 
0169     if (cursor.hasSelection()) {
0170         cursor.setPosition(qMin(cursor.position(), cursor.anchor()));
0171     }
0172     return cursor;
0173 }
0174 
0175 QTextCursor NestedListHelper::bottomOfSelection()
0176 {
0177     QTextCursor cursor = textEdit->textCursor();
0178 
0179     if (cursor.hasSelection()) {
0180         cursor.setPosition(qMax(cursor.position(), cursor.anchor()));
0181     }
0182     return cursor;
0183 }
0184 
0185 void NestedListHelper::handleOnIndentMore()
0186 {
0187     QTextCursor cursor = textEdit->textCursor();
0188 
0189     QTextListFormat listFmt;
0190     if (!cursor.currentList()) {
0191         QTextListFormat::Style style;
0192         cursor = topOfSelection();
0193         cursor.movePosition(QTextCursor::PreviousBlock);
0194         if (cursor.currentList()) {
0195             style = cursor.currentList()->format().style();
0196         } else {
0197             cursor = bottomOfSelection();
0198             cursor.movePosition(QTextCursor::NextBlock);
0199 
0200             if (cursor.currentList()) {
0201                 style = cursor.currentList()->format().style();
0202             } else {
0203                 style = QTextListFormat::ListDisc;
0204             }
0205         }
0206         handleOnBulletType(style);
0207     } else {
0208         listFmt = cursor.currentList()->format();
0209         listFmt.setIndent(listFmt.indent() + 1);
0210 
0211         cursor.createList(listFmt);
0212         reformatList();
0213     }
0214 }
0215 
0216 void NestedListHelper::handleOnIndentLess()
0217 {
0218     QTextCursor cursor = textEdit->textCursor();
0219     QTextList *currentList = cursor.currentList();
0220     if (!currentList) {
0221         return;
0222     }
0223     QTextListFormat listFmt = currentList->format();
0224     if (listFmt.indent() > 1) {
0225         listFmt.setIndent(listFmt.indent() - 1);
0226         cursor.createList(listFmt);
0227         reformatList(cursor.block());
0228     } else {
0229         QTextBlockFormat bfmt;
0230         bfmt.setObjectIndex(-1);
0231         cursor.setBlockFormat(bfmt);
0232         reformatList(cursor.block().next());
0233     }
0234 }
0235 
0236 void NestedListHelper::handleOnBulletType(int styleIndex)
0237 {
0238     QTextCursor cursor = textEdit->textCursor();
0239     if (styleIndex != 0) {
0240         auto style = static_cast<QTextListFormat::Style>(styleIndex);
0241         QTextList *currentList = cursor.currentList();
0242         QTextListFormat listFmt;
0243 
0244         cursor.beginEditBlock();
0245 
0246         if (currentList) {
0247             listFmt = currentList->format();
0248             listFmt.setStyle(style);
0249             currentList->setFormat(listFmt);
0250         } else {
0251             listFmt.setStyle(style);
0252             cursor.createList(listFmt);
0253         }
0254 
0255         cursor.endEditBlock();
0256     } else {
0257         QTextBlockFormat bfmt;
0258         bfmt.setObjectIndex(-1);
0259         cursor.setBlockFormat(bfmt);
0260     }
0261 
0262     reformatList();
0263 }