void TestDocumentLayout::testLetterSynchronization() { // make numbering be 'y, z, aa, bb, cc' initForNewTest("a\nb\na\nb\nc"); KoParagraphStyle h1; m_styleManager->add(&h1); KoListStyle listStyle; KoListLevelProperties llp; llp.setStyle(KoListStyle::AlphaLowerItem); llp.setLetterSynchronization(true); llp.setStartValue(25); listStyle.setLevelProperties(llp); h1.setListStyle(&listStyle); QTextBlock block = m_doc->begin(); while (block.isValid()) { h1.applyStyle(block); block = block.next(); } m_layout->layout(); static const char *const values[] = { "y", "z", "aa", "bb", "cc" }; block = m_doc->begin(); int i = 0; while (block.isValid()) { KoTextBlockData *data = dynamic_cast<KoTextBlockData *>(block.userData()); QVERIFY(data); // qDebug() << "-> " << data->counterText() << endl; QCOMPARE(data->counterText(), QString(values[i++])); block = block.next(); } }
void TestDocumentLayout::testAutoRestartList() { initForNewTest("Humans\nGandhi\nEinstein\nInventions\nCar\nToilet\nLaboratory\n"); KoParagraphStyle h1; m_styleManager->add(&h1); KoParagraphStyle h2; m_styleManager->add(&h2); KoListStyle listStyle; KoListLevelProperties llp = listStyle.levelProperties(1); llp.setStyle(KoListStyle::DecimalItem); llp.setStartValue(1); llp.setListItemSuffix("."); listStyle.setLevelProperties(llp); h1.setListStyle(&listStyle); KoListStyle listStyle2; KoListLevelProperties llp2 = listStyle2.levelProperties(2); llp2.setStyle(KoListStyle::DecimalItem); llp2.setStartValue(1); llp2.setDisplayLevel(2); llp2.setListItemSuffix("."); listStyle2.setLevelProperties(llp2); h2.setListStyle(&listStyle2); QTextBlock block = m_doc->begin(); h1.applyStyle(block); block = block.next(); h2.applyStyle(block); block = block.next(); h2.applyStyle(block); block = block.next(); h1.applyStyle(block); // inventions block = block.next(); h2.applyStyle(block); QTextBlock car = block; block = block.next(); h2.applyStyle(block); block = block.next(); h2.applyStyle(block); m_layout->layout(); KoTextBlockData *data = dynamic_cast<KoTextBlockData *>(car.userData()); QVERIFY(data); // qDebug() << data->counterText(); QCOMPARE(data->counterText(), QString("2.1.")); }
void TestDocumentLayout::testRestartNumbering() { // create 5 items; restart the 3th. Check numbering. initForNewTest("a\nb\na\nb\nc"); KoParagraphStyle h1; m_styleManager->add(&h1); KoListStyle listStyle; KoListLevelProperties llp; llp.setStyle(KoListStyle::DecimalItem); llp.setStartValue(1); listStyle.setLevelProperties(llp); h1.setListStyle(&listStyle); QTextBlock block = m_doc->begin(); while (block.isValid()) { h1.applyStyle(block); block = block.next(); } QTextCursor cursor(m_doc); cursor.setPosition(5); QCOMPARE(cursor.block().text(), QString("a")); QTextBlockFormat format = cursor.blockFormat(); format.setProperty(KoParagraphStyle::RestartListNumbering, true); cursor.setBlockFormat(format); m_layout->layout(); static const char *const values[] = { "1", "2", "1", "2", "3" }; block = m_doc->begin(); int i = 0; while (block.isValid()) { KoTextBlockData *data = dynamic_cast<KoTextBlockData *>(block.userData()); QVERIFY(data); // qDebug() << data->counterText() << QString(values[i]); QCOMPARE(data->counterText(), QString(values[i++])); block = block.next(); } }
void TestDocumentLayout::testMultiLevel() { initForNewTest("ListItem1\n"); KoListStyle listStyle; KoListLevelProperties llp; llp.setStyle(KoListStyle::DecimalItem); llp.setLevel(3); llp.setDisplayLevel(4); // we won't show a .0 at the end so this is truncated to 3 listStyle.setLevelProperties(llp); QTextBlock block = m_doc->begin(); QVERIFY(block.isValid()); listStyle.applyStyle(block); QVERIFY(block.textList()); m_layout->layout(); KoTextBlockData *data = dynamic_cast<KoTextBlockData *>(block.userData()); QVERIFY(data); QVERIFY(data->hasCounterData()); QCOMPARE(data->counterText(), QString("1.1.1")); }
void TestDocumentLayout::testNumberedList() { initForNewTest("Base\nListItem1\nListItem2\nListItem3\nListItem4\nListItem5\nListItem6\nListItem6\nListItem7\nListItem8\nListItem9\nListItem10\nListItem11\nListItem12\n"); KoParagraphStyle style; m_styleManager->add(&style); QTextBlock block = m_doc->begin(); style.applyStyle(block); block = block.next(); KoListStyle listStyle; KoListLevelProperties llp; llp.setStyle(KoListStyle::DecimalItem); listStyle.setLevelProperties(llp); style.setListStyle(&listStyle); QTextList *previous = 0; int i; for (i = 1; i <= 9; i++) { QVERIFY(block.isValid()); // qDebug() << "->" << block.text(); style.applyStyle(block); QTextList *textList = block.textList(); QVERIFY(textList); if (previous == 0) { previous = textList; } else { QCOMPARE(textList, previous); } QCOMPARE(textList->format().intProperty(QTextListFormat::ListStyle), (int)(KoListStyle::DecimalItem)); block = block.next(); } m_layout->layout(); QTextLayout *blockLayout = m_block.layout(); QCOMPARE(blockLayout->lineAt(0).x(), 0.0); QTextBlock blok = m_doc->begin().next(); qreal indent = blok.layout()->lineAt(0).x(); QVERIFY(indent > 0.0); for (i = 1; i <= 9; ++i) { // qDebug() << "=>" << blok.text(); QTextList *textList = blok.textList(); QVERIFY(textList); QCOMPARE(blok.layout()->lineAt(0).x(), indent); // all the same indent. blok = blok.next(); } // now make number of listitems be more than 10, so we use 2 digits. for (i = 9; i <= 12; ++i) { QVERIFY(block.isValid()); style.applyStyle(block); // qDebug() << "->" << block.text(); block = block.next(); } m_layout->layout(); blockLayout = m_block.layout(); QCOMPARE(blockLayout->lineAt(0).x(), 0.0); blok = m_doc->begin().next(); qreal indent2 = blok.layout()->lineAt(0).x(); QVERIFY(indent2 > indent); // since it takes an extra digit for (i = 2; i <= 12; ++i) { // qDebug() << "=>" << blok.text(); QCOMPARE(blok.layout()->lineAt(0).x(), indent2); // all the same indent. blok = blok.next(); } // now to make sure the text is actually properly set. block = m_doc->begin().next(); i = 1; while (block.isValid() && i < 13) { KoTextBlockData *data = dynamic_cast<KoTextBlockData *>(block.userData()); QVERIFY(data); QCOMPARE(data->counterText(), QString::number(i++)); block = block.next(); } llp.setListItemSuffix("."); llp.setStartValue(4); listStyle.setLevelProperties(llp); QTextCursor cursor(m_doc); cursor.setPosition(10); // listItem1 QTextBlockFormat format = cursor.blockFormat(); format.setProperty(KoParagraphStyle::ListStartValue, 4); cursor.setBlockFormat(format); cursor.setPosition(40); // listItem4 format = cursor.blockFormat(); format.setProperty(KoParagraphStyle::ListStartValue, 12); cursor.setBlockFormat(format); // at this point we start numbering at 4. Have 4, 5, 6, 12, 13, 14, 15 etc m_layout->layout(); // now to make sur the text is actually properly set. block = m_doc->begin().next(); i = 4; while (block.isValid() && i < 22) { if (i == 7) { i = 12; } KoTextBlockData *data = dynamic_cast<KoTextBlockData *>(block.userData()); QVERIFY(data); QCOMPARE(data->counterText(), QString::number(i++)); block = block.next(); } }
void TestDocumentLayout::testNestedPrefixedLists() { /* A list with different prefix for each level should show only the prefix of that level * Specifically we should not concatenate the prefixes of the higher levels * Specifically we should not concatenate the suffixes of the higher levels * That is only the prefix and the suffix of the current level should be applied */ initForNewTest("MMMM\nSSSS\n"); KoParagraphStyle h1; m_styleManager->add(&h1); KoParagraphStyle h2; m_styleManager->add(&h2); KoListStyle listStyle; KoListLevelProperties llp1; llp1.setStartValue(1); llp1.setStyle(KoListStyle::DecimalItem); llp1.setListItemPrefix("Main"); llp1.setListItemSuffix(":"); listStyle.setLevelProperties(llp1); KoListLevelProperties llp2; llp2.setStartValue(1); llp2.setStyle(KoListStyle::DecimalItem); llp2.setLevel(2); llp2.setListItemPrefix("Sub"); llp2.setListItemSuffix("*"); llp2.setDisplayLevel(2); listStyle.setLevelProperties(llp2); h1.setListStyle(&listStyle); h2.setListLevel(2); h2.setListStyle(&listStyle); QVERIFY(listStyle.hasLevelProperties(1)); QVERIFY(listStyle.hasLevelProperties(2)); QVERIFY(!listStyle.hasLevelProperties(3)); QTextBlock block = m_doc->begin().next(); h1.applyStyle(block); block = block.next(); QVERIFY(block.isValid()); h2.applyStyle(block); m_layout->layout(); block = m_doc->begin(); QVERIFY(block.userData() == 0); block = block.next(); static const char *const texts[] = { "Main1:", "Sub1.1*"}; int i = 0; while (block.isValid()) { KoTextBlockData *data = dynamic_cast<KoTextBlockData *>(block.userData()); //qDebug() << "text: " << block.text(); //qDebug() << "expected: " << texts[i]; QVERIFY(data); //qDebug() << data->counterText(); QCOMPARE(data->counterText(), QString(texts[i++])); block = block.next(); } }
void TestDocumentLayout::testNestedLists() { initForNewTest("Root\nplants\nherbs\ncinnamon\ncurry\nroses\nhumans\nFrank\nAnkje\nOther\nSkip\nLastItem"); KoParagraphStyle h1; m_styleManager->add(&h1); KoParagraphStyle h2; m_styleManager->add(&h2); KoParagraphStyle h3; m_styleManager->add(&h3); KoParagraphStyle h4; m_styleManager->add(&h4); KoListStyle listStyle; KoListLevelProperties llp1; llp1.setStartValue(1); llp1.setStyle(KoListStyle::DecimalItem); listStyle.setLevelProperties(llp1); h1.setListStyle(&listStyle); KoListStyle listStyle2; KoListLevelProperties llp2; llp2.setStartValue(1); llp2.setStyle(KoListStyle::DecimalItem); llp2.setLevel(2); llp2.setListItemSuffix("."); llp2.setDisplayLevel(2); listStyle2.setLevelProperties(llp2); h2.setListStyle(&listStyle2); // purpusfully leave this one out, as it should default to the only known one: // h2.setListLevel(2); KoListLevelProperties llp3; llp3.setStartValue(1); llp3.setStyle(KoListStyle::DecimalItem); llp3.setLevel(3); llp3.setListItemSuffix(""); llp3.setDisplayLevel(3); KoListStyle listStyle3; listStyle3.setLevelProperties(llp3); h3.setListStyle(&listStyle3); h3.setListLevel(3); KoListStyle listStyle4; KoListLevelProperties llp4; llp4.setStartValue(1); llp4.setStyle(KoListStyle::DecimalItem); llp4.setLevel(4); llp4.setDisplayLevel(2); listStyle4.setLevelProperties(llp4); h4.setListStyle(&listStyle4); h4.setListLevel(4); QTextBlock block = m_doc->begin().next(); h1.applyStyle(block); block = block.next(); h2.applyStyle(block); block = block.next(); h3.applyStyle(block); block = block.next(); h3.applyStyle(block); block = block.next(); // roses h2.applyStyle(block); block = block.next(); h1.applyStyle(block); // humans block = block.next(); h2.applyStyle(block); block = block.next(); h2.applyStyle(block); block = block.next(); h1.applyStyle(block); block = block.next(); h3.applyStyle(block); // notice missing h2 block = block.next(); QVERIFY(block.isValid()); h4.applyStyle(block); m_layout->layout(); block = m_doc->begin(); QVERIFY(block.userData() == 0); block = block.next(); static const char *const texts[] = { "1", "1.1.", "1.1.1", "1.1.2", "1.2.", "2", "2.1.", "2.2.", "3", "3.1.1", "1.1" }; int i = 0; qreal indent = 0.0; while (block.isValid()) { KoTextBlockData *data = dynamic_cast<KoTextBlockData *>(block.userData()); //qDebug() << "text: " << block.text(); //qDebug() << "expected: " << texts[i]; QVERIFY(data); //qDebug() << data->counterText(); QCOMPARE(data->counterText(), QString(texts[i++])); if (i < 3) { //qDebug() << "indent:" << data->counterWidth(); QVERIFY(indent < data->counterWidth()); // deeper indent, larger width indent = data->counterWidth(); } block = block.next(); } }
void TestDocumentLayout::testInterruptedLists() { initForNewTest("ListItem1\nListItem2\nNormal Parag\nAnother parag\nListItem3\n"); // expect that normal paragraphs do not break a list (i.e not restart it) KoParagraphStyle style; KoListStyle listStyle; KoListLevelProperties llp = listStyle.levelProperties(1); llp.setStyle(KoListStyle::DecimalItem); llp.setStartValue(1); llp.setListItemSuffix("."); listStyle.setLevelProperties(llp); style.setListStyle(&listStyle); QTextBlock block = m_doc->begin(); style.applyStyle(block); block = block.next(); style.applyStyle(block); block = block.next(); block = block.next(); block = block.next(); style.applyStyle(block); m_layout->layout(); block = m_doc->begin(); KoTextBlockData *data = dynamic_cast<KoTextBlockData *>(block.userData()); QVERIFY(data); QVERIFY(data->counterText() == "1."); block = block.next(); data = dynamic_cast<KoTextBlockData *>(block.userData()); QVERIFY(data); QVERIFY(data->counterText() == "2."); block = block.next(); QCOMPARE(block.layout()->lineAt(0).x(), 0.0); QVERIFY(block.userData() == 0); block = block.next(); QCOMPARE(block.layout()->lineAt(0).x(), 0.0); QVERIFY(block.userData() == 0); block = block.next(); // list item 3 data = dynamic_cast<KoTextBlockData *>(block.userData()); QVERIFY(data); //qDebug() << data->counterText(); QVERIFY(data->counterText() == "3."); // I have doubts what consecutiveNumbering should do. Disable the feature for now. #if 0 // now the other way around block = m_doc->begin(); listStyle.setConsecutiveNumbering(false); listStyle.applyStyle(block); m_layout->layout(); data = dynamic_cast<KoTextBlockData *>(block.userData()); QVERIFY(data); QVERIFY(data->counterText() == "1."); block = block.next(); data = dynamic_cast<KoTextBlockData *>(block.userData()); QVERIFY(data); QVERIFY(data->counterText() == "2."); block = block.next(); QCOMPARE(block.layout()->lineAt(0).x(), 0.0); QVERIFY(block.userData() == 0); block = block.next(); QCOMPARE(block.layout()->lineAt(0).x(), 0.0); QVERIFY(block.userData() == 0); block = block.next(); // list item 3 data = dynamic_cast<KoTextBlockData *>(block.userData()); QVERIFY(data); qDebug() << data->counterText(); QVERIFY(data->counterText() == "1."); #endif }
void ListItemsHelper::recalculate() { //kDebug(32500); const QTextListFormat format = m_textList->format(); const KoListStyle::Style listStyle = static_cast<KoListStyle::Style>(m_textList->format().style()); const QString prefix = format.stringProperty(KoListStyle::ListItemPrefix); const QString suffix = format.stringProperty(KoListStyle::ListItemSuffix); const int level = format.intProperty(KoListStyle::Level); int dp = format.intProperty(KoListStyle::DisplayLevel); if (dp > level) dp = level; const int displayLevel = dp ? dp : 1; int startValue = 1; if (format.hasProperty(KoListStyle::StartValue)) startValue = format.intProperty(KoListStyle::StartValue); if (format.boolProperty(KoListStyle::ContinueNumbering)) { // Look for the index of a previous list of the same numbering style and level for (QTextBlock tb = m_textList->item(0).previous(); tb.isValid(); tb = tb.previous()) { if (!tb.textList() || tb.textList() == m_textList) continue; // no list here or it's the same list; keep looking QTextListFormat otherFormat = tb.textList()->format(); if (otherFormat.intProperty(KoListStyle::Level) != level) break; // found a different list but of a different level if (otherFormat.style() == format.style()) { if (KoTextBlockData *data = dynamic_cast<KoTextBlockData *>(tb.userData())) startValue = data->counterIndex() + 1; // Start from previous list value + 1 } break; } } int index = startValue; QList<QTextList*> sublistsToRecalculate; qreal width = format.doubleProperty(KoListStyle::MinimumWidth); for (int i = 0; i < m_textList->count(); i++) { QTextBlock tb = m_textList->item(i); //kDebug(32500) <<" *" << tb.text(); KoTextBlockData *data = dynamic_cast<KoTextBlockData*>(tb.userData()); if (!data) { data = new KoTextBlockData(); tb.setUserData(data); } QTextBlockFormat blockFormat = tb.blockFormat(); if (blockFormat.boolProperty(KoParagraphStyle::UnnumberedListItem) || blockFormat.boolProperty(KoParagraphStyle::IsListHeader)) { data->setCounterText(QString()); data->setPartialCounterText(QString()); continue; } if (blockFormat.boolProperty(KoParagraphStyle::RestartListNumbering)) index = format.intProperty(KoListStyle::StartValue); const int paragIndex = blockFormat.intProperty(KoParagraphStyle::ListStartValue); if (paragIndex > 0) index = paragIndex; //check if this is the first of this level meaning we should start from startvalue QTextBlock b = tb.previous(); for (;b.isValid(); b = b.previous()) { if (b.textList() == m_textList) break; // all fine if (b.textList() == 0) continue; QTextListFormat otherFormat = b.textList()->format(); if (otherFormat.style() != format.style()) continue; // uninteresting for us if (b.textList()->format().intProperty(KoListStyle::Level) < level) { index = startValue; break; } } QString item; if (displayLevel > 1) { int checkLevel = level; int tmpDisplayLevel = displayLevel; for (QTextBlock b = tb.previous(); tmpDisplayLevel > 1 && b.isValid(); b = b.previous()) { if (b.textList() == 0) continue; QTextListFormat lf = b.textList()->format(); if (lf.style() != format.style()) continue; // uninteresting for us const int otherLevel = lf.intProperty(KoListStyle::Level); if (checkLevel <= otherLevel) continue; /*if(needsRecalc(b->textList())) { TODO } */ KoTextBlockData *otherData = dynamic_cast<KoTextBlockData*>(b.userData()); if (! otherData) { kWarning(32500) << "Missing KoTextBlockData, Skipping textblock"; continue; } if (tmpDisplayLevel - 1 < otherLevel) { // can't just copy it fully since we are // displaying less then the full counter item += otherData->partialCounterText(); tmpDisplayLevel--; checkLevel--; for (int i = otherLevel + 1; i < level; i++) { tmpDisplayLevel--; item += ".1"; // add missing counters. } } else { // just copy previous counter as prefix QString otherPrefix = lf.stringProperty(KoListStyle::ListItemPrefix); QString otherSuffix = lf.stringProperty(KoListStyle::ListItemSuffix); QString pureCounter = otherData->counterText().mid(otherPrefix.size()); pureCounter = pureCounter.left(pureCounter.size() - otherSuffix.size()); item += pureCounter; for (int i = otherLevel + 1; i < level; i++) item += ".1"; // add missing counters. tmpDisplayLevel = 0; break; } } for (int i = 1; i < tmpDisplayLevel; i++) item = "1." + item; // add missing counters. } if ((listStyle == KoListStyle::DecimalItem || listStyle == KoListStyle::AlphaLowerItem || listStyle == KoListStyle::UpperAlphaItem || listStyle == KoListStyle::RomanLowerItem || listStyle == KoListStyle::UpperRomanItem) && !(item.isEmpty() || item.endsWith('.') || item.endsWith(' '))) { item += '.'; } bool calcWidth = true; QString partialCounterText; switch (listStyle) { case KoListStyle::DecimalItem: partialCounterText = QString::number(index); break; case KoListStyle::AlphaLowerItem: partialCounterText = intToAlpha(index, Lowercase, m_textList->format().boolProperty(KoListStyle::LetterSynchronization)); break; case KoListStyle::UpperAlphaItem: partialCounterText = intToAlpha(index, Uppercase, m_textList->format().boolProperty(KoListStyle::LetterSynchronization)); break; case KoListStyle::RomanLowerItem: partialCounterText = intToRoman(index); break; case KoListStyle::UpperRomanItem: partialCounterText = intToRoman(index).toUpper(); break; case KoListStyle::SquareItem: case KoListStyle::DiscItem: case KoListStyle::CircleItem: case KoListStyle::HeavyCheckMarkItem: case KoListStyle::BallotXItem: case KoListStyle::RightArrowItem: case KoListStyle::RightArrowHeadItem: case KoListStyle::RhombusItem: case KoListStyle::BoxItem: { calcWidth = false; item = ' '; width = m_displayFont.pointSizeF(); int percent = format.intProperty(KoListStyle::BulletSize); if (percent > 0) width = width * (percent / 100.0); break; } case KoListStyle::CustomCharItem: calcWidth = false; if (format.intProperty(KoListStyle::BulletCharacter)) item = QString(QChar(format.intProperty(KoListStyle::BulletCharacter))); width = m_fm.width(item); break; case KoListStyle::None: calcWidth = false; width = 10.0; // simple indenting break; case KoListStyle::Bengali: case KoListStyle::Gujarati: case KoListStyle::Gurumukhi: case KoListStyle::Kannada: case KoListStyle::Malayalam: case KoListStyle::Oriya: case KoListStyle::Tamil: case KoListStyle::Telugu: case KoListStyle::Tibetan: case KoListStyle::Thai: partialCounterText = intToScript(index, listStyle); break; case KoListStyle::Abjad: case KoListStyle::ArabicAlphabet: case KoListStyle::AbjadMinor: partialCounterText = intToScriptList(index, listStyle); break; case KoListStyle::ImageItem: calcWidth = false; width = qMax(format.doubleProperty(KoListStyle::Width), (qreal)1.0); break; default: // others we ignore. calcWidth = false; } data->setCounterIsImage(listStyle == KoListStyle::ImageItem); data->setPartialCounterText(partialCounterText); data->setCounterIndex(index); item += partialCounterText; if (calcWidth) width = qMax(width, m_fm.width(item)); data->setCounterText(prefix + item + suffix); index++; // have to recalculate any sublists under this element too QTextBlock nb = tb.next(); while (nb.isValid() && nb.textList() == 0) nb = nb.next(); if (nb.isValid()) { QTextListFormat lf = nb.textList()->format(); if ((lf.style() == format.style()) && nb.textList()->format().intProperty(KoListStyle::Level) > level) { // this is a sublist // have to remember to recalculate this list after the current level is done // cant do it right away since the sublist's prefix text is dependant on this level sublistsToRecalculate.append(nb.textList()); } } } for (int i = 0; i < sublistsToRecalculate.count(); i++) { ListItemsHelper lih(sublistsToRecalculate.at(i), m_displayFont); lih.recalculate(); } qreal counterSpacing = m_fm.width(' '); counterSpacing = qMax(format.doubleProperty(KoListStyle::MinimumDistance), counterSpacing); width += m_fm.width(prefix + suffix); // same for all width = qMax(format.doubleProperty(KoListStyle::MinimumWidth), width); for (int i = 0; i < m_textList->count(); i++) { QTextBlock tb = m_textList->item(i); KoTextBlockData *data = dynamic_cast<KoTextBlockData*>(tb.userData()); Q_ASSERT(data); data->setCounterWidth(width); data->setCounterSpacing(counterSpacing); //kDebug(32500) << data->counterText() <<"" << tb.text(); //kDebug(32500) <<" setCounterWidth:" << width; } //kDebug(32500); }