[graphics/drawy/gsoc2026] src: feat: add list format support
Abdelhadi Wael <[email protected]>
| Newsgroups | gmane.comp.kde.cvs |
|---|---|
| Message-ID | <[email protected]> |
Git commit 0c4d4367b02cba301e9eb2ec66d63fa600fce48c by Abdelhadi Wael.
Committed on 26/07/2026 at 23:51.
Pushed by mlaurent into branch 'gsoc2026'.
feat: add list format support
M +4 -0 src/gui/autotests/data/text/defaultvalue.ref
M +28 -0 src/gui/autotests/itemutilstest.cpp
M +3 -0 src/gui/autotests/itemutilstest.hpp
M +3 -2 src/gui/autotests/textitemtest.cpp
M +0 -2 src/gui/common/constants.hpp
M +45 -0 src/gui/item/itemutils.cpp
M +3 -0 src/gui/item/itemutils.hpp
M +90 -31 src/gui/item/text.cpp
M +1 -0 src/gui/item/text.hpp
M +6 -0 src/gui/properties/property.cpp
M +8 -5 src/gui/properties/property.hpp
M +2 -0 src/widgets/CMakeLists.txt
A +64 -0 src/widgets/properties/widgets/liststylewidget.cpp [License: LGPL(v2.0+)]
A +26 -0 src/widgets/properties/widgets/liststylewidget.hpp [License: LGPL(v2.0+)]
M +2 -0 src/widgets/properties/widgets/propertymanager.cpp
M +8 -4 src/widgets/tools/texttool.cpp
https://invent.kde.org/graphics/drawy/-/commit/0c4d4367b02cba301e9eb2ec66d63fa600fce48c
diff --git a/src/gui/autotests/data/text/defaultvalue.ref b/src/gui/autotests/data/text/defaultvalue.ref
index 707cbb15..3651270b 100644
--- a/src/gui/autotests/data/text/defaultvalue.ref
+++ b/src/gui/autotests/data/text/defaultvalue.ref
@@ -25,6 +25,10 @@
{
"type": "TextAlignment",
"value": "AlignLeft"
+ },
+ {
+ "type": "ListStyle",
+ "value": "None"
}
],
"text": "",
diff --git a/src/gui/autotests/itemutilstest.cpp b/src/gui/autotests/itemutilstest.cpp
index b1b263ec..d4cc7018 100644
--- a/src/gui/autotests/itemutilstest.cpp
+++ b/src/gui/autotests/itemutilstest.cpp
@@ -7,6 +7,7 @@
#include "item/item.hpp"
#include "item/itemutils.hpp"
#include <QTest>
+#include <QTextListFormat>
QTEST_GUILESS_MAIN(ItemUtilsTest)
using namespace Qt::Literals::StringLiterals;
ItemUtilsTest::ItemUtilsTest(QObject *parent)
@@ -158,4 +159,31 @@ void ItemUtilsTest::shouldConvertItemCornerRectangleTypeEnumToString()
QCOMPARE(ItemUtils::convertItemCornerRectangleTypeEnumToString(Item::CornerRectangleType::Rounded), u"Rounded"_s);
}
+void ItemUtilsTest::shouldConvertListStyleToString()
+{
+ QCOMPARE(ItemUtils::convertListStyleToString(QTextListFormat::ListDisc), u"ListDisc"_s);
+ QCOMPARE(ItemUtils::convertListStyleToString(QTextListFormat::ListCircle), u"ListCircle"_s);
+ QCOMPARE(ItemUtils::convertListStyleToString(QTextListFormat::ListSquare), u"ListSquare"_s);
+ QCOMPARE(ItemUtils::convertListStyleToString(QTextListFormat::ListDecimal), u"ListDecimal"_s);
+ QCOMPARE(ItemUtils::convertListStyleToString(QTextListFormat::ListLowerAlpha), u"ListLowerAlpha"_s);
+ QCOMPARE(ItemUtils::convertListStyleToString(QTextListFormat::ListUpperAlpha), u"ListUpperAlpha"_s);
+ QCOMPARE(ItemUtils::convertListStyleToString(QTextListFormat::ListLowerRoman), u"ListLowerRoman"_s);
+ QCOMPARE(ItemUtils::convertListStyleToString(QTextListFormat::ListUpperRoman), u"ListUpperRoman"_s);
+ QCOMPARE(ItemUtils::convertListStyleToString(QTextListFormat::ListStyleUndefined), u"None"_s);
+}
+
+void ItemUtilsTest::shouldConvertStringToListStyle()
+{
+ QCOMPARE(ItemUtils::convertStringToListStyle(u"ListDisc"_s), QTextListFormat::ListDisc);
+ QCOMPARE(ItemUtils::convertStringToListStyle(u"ListCircle"_s), QTextListFormat::ListCircle);
+ QCOMPARE(ItemUtils::convertStringToListStyle(u"ListSquare"_s), QTextListFormat::ListSquare);
+ QCOMPARE(ItemUtils::convertStringToListStyle(u"ListDecimal"_s), QTextListFormat::ListDecimal);
+ QCOMPARE(ItemUtils::convertStringToListStyle(u"ListLowerAlpha"_s), QTextListFormat::ListLowerAlpha);
+ QCOMPARE(ItemUtils::convertStringToListStyle(u"ListUpperAlpha"_s), QTextListFormat::ListUpperAlpha);
+ QCOMPARE(ItemUtils::convertStringToListStyle(u"ListLowerRoman"_s), QTextListFormat::ListLowerRoman);
+ QCOMPARE(ItemUtils::convertStringToListStyle(u"ListUpperRoman"_s), QTextListFormat::ListUpperRoman);
+ QCOMPARE(ItemUtils::convertStringToListStyle(u"None"_s), QTextListFormat::ListStyleUndefined);
+ QCOMPARE(ItemUtils::convertStringToListStyle(QString()), QTextListFormat::ListStyleUndefined);
+}
+
#include "moc_itemutilstest.cpp"
diff --git a/src/gui/autotests/itemutilstest.hpp b/src/gui/autotests/itemutilstest.hpp
index 446c3743..d669da80 100644
--- a/src/gui/autotests/itemutilstest.hpp
+++ b/src/gui/autotests/itemutilstest.hpp
@@ -34,4 +34,7 @@ private Q_SLOTS:
void shouldConvertItemCornerRectangleTypeStringToEnum();
void shouldConvertItemCornerRectangleTypeEnumToString();
+
+ void shouldConvertListStyleToString();
+ void shouldConvertStringToListStyle();
};
diff --git a/src/gui/autotests/textitemtest.cpp b/src/gui/autotests/textitemtest.cpp
index 2c7fff8c..e117e4d7 100644
--- a/src/gui/autotests/textitemtest.cpp
+++ b/src/gui/autotests/textitemtest.cpp
@@ -22,8 +22,9 @@ void TextItemTest::shouldHaveDefaultValues()
const TextItem i;
QCOMPARE(i.formType(), Item::FormType::Text);
const auto properties = QList<Property::Type>() << Property::Type::FontSize << Property::Type::FontStyle << Property::Type::StrokeColor
- << Property::Type::Opacity << Property::Type::FontFamily << Property::Type::TextAlignment;
- QCOMPARE(i.allPropertyTypes().count(), 6);
+ << Property::Type::Opacity << Property::Type::FontFamily << Property::Type::TextAlignment
+ << Property::Type::ListStyle;
+ QCOMPARE(i.allPropertyTypes().count(), 7);
for (const auto &prop : properties) {
QVERIFY(i.allPropertyTypes().contains(prop));
}
diff --git a/src/gui/common/constants.hpp b/src/gui/common/constants.hpp
index 6ef59478..0d852c47 100644
--- a/src/gui/common/constants.hpp
+++ b/src/gui/common/constants.hpp
@@ -43,8 +43,6 @@ inline constexpr QColor darkBackgroundColor{18, 18, 18};
inline constexpr int maxItemOpacity{255};
inline constexpr int erasedItemOpacityDecrease{30}; // % to reduce opacity by when erasing items
-inline constexpr double defaultTextBoxWidth{5}; // in pixels
-
inline constexpr int doubleClickInterval{300}; // milliseconds
inline constexpr qreal tabStopDistance{4};
diff --git a/src/gui/item/itemutils.cpp b/src/gui/item/itemutils.cpp
index 3550a757..d18e4626 100644
--- a/src/gui/item/itemutils.cpp
+++ b/src/gui/item/itemutils.cpp
@@ -14,6 +14,7 @@
#include "item/line.hpp"
#include "item/rectangle.hpp"
#include "item/text.hpp"
+#include <QTextListFormat>
using namespace Qt::Literals::StringLiterals;
Item::FormType ItemUtils::convertItemTypeStringToEnum(const QString &str)
@@ -313,3 +314,47 @@ int ItemUtils::convertStringToFontStyle(const QString &str)
}
return style;
}
+
+QString ItemUtils::convertListStyleToString(const int style)
+{
+ if (style == QTextListFormat::ListDisc) {
+ return u"ListDisc"_s;
+ } else if (style == QTextListFormat::ListCircle) {
+ return u"ListCircle"_s;
+ } else if (style == QTextListFormat::ListSquare) {
+ return u"ListSquare"_s;
+ } else if (style == QTextListFormat::ListDecimal) {
+ return u"ListDecimal"_s;
+ } else if (style == QTextListFormat::ListLowerAlpha) {
+ return u"ListLowerAlpha"_s;
+ } else if (style == QTextListFormat::ListUpperAlpha) {
+ return u"ListUpperAlpha"_s;
+ } else if (style == QTextListFormat::ListLowerRoman) {
+ return u"ListLowerRoman"_s;
+ } else if (style == QTextListFormat::ListUpperRoman) {
+ return u"ListUpperRoman"_s;
+ }
+ return u"None"_s;
+}
+
+int ItemUtils::convertStringToListStyle(const QString &str)
+{
+ if (str == u"ListDisc"_s) {
+ return QTextListFormat::ListDisc;
+ } else if (str == u"ListCircle"_s) {
+ return QTextListFormat::ListCircle;
+ } else if (str == u"ListSquare"_s) {
+ return QTextListFormat::ListSquare;
+ } else if (str == u"ListDecimal"_s) {
+ return QTextListFormat::ListDecimal;
+ } else if (str == u"ListLowerAlpha"_s) {
+ return QTextListFormat::ListLowerAlpha;
+ } else if (str == u"ListUpperAlpha"_s) {
+ return QTextListFormat::ListUpperAlpha;
+ } else if (str == u"ListLowerRoman"_s) {
+ return QTextListFormat::ListLowerRoman;
+ } else if (str == u"ListUpperRoman"_s) {
+ return QTextListFormat::ListUpperRoman;
+ }
+ return QTextListFormat::ListStyleUndefined;
+}
diff --git a/src/gui/item/itemutils.hpp b/src/gui/item/itemutils.hpp
index 8a7b6e08..b6e1dc01 100644
--- a/src/gui/item/itemutils.hpp
+++ b/src/gui/item/itemutils.hpp
@@ -46,6 +46,9 @@ enum class ZorderMove : uint8_t {
[[nodiscard]] LIBDRAWYGUI_EXPORT QString convertItemCornerRectangleTypeEnumToString(Item::CornerRectangleType type);
[[nodiscard]] LIBDRAWYGUI_EXPORT Item::CornerRectangleType convertItemCornerRectangleTypeStringToEnum(const QString &str);
+[[nodiscard]] LIBDRAWYGUI_EXPORT QString convertListStyleToString(int style);
+[[nodiscard]] LIBDRAWYGUI_EXPORT int convertStringToListStyle(const QString &str);
+
[[nodiscard]] std::shared_ptr<Item> createItemFromType(Item::FormType type);
} // namespace ItemUtils
diff --git a/src/gui/item/text.cpp b/src/gui/item/text.cpp
index 707d7c92..16d5839e 100644
--- a/src/gui/item/text.cpp
+++ b/src/gui/item/text.cpp
@@ -11,6 +11,8 @@
#include <QJsonObject>
#include <QTextBlock>
#include <QTextLayout>
+#include <QTextList>
+#include <QTextListFormat>
#include <utility>
#include "common/constants.hpp"
@@ -30,6 +32,7 @@ TextItem::TextItem()
m_properties[Property::Type::FontStyle] = Property{u"Normal"_s, Property::Type::FontStyle};
m_properties[Property::Type::FontFamily] = Property{QStringLiteral("Fuzzy Bubbles"), Property::Type::FontFamily};
m_properties[Property::Type::TextAlignment] = Property{u"AlignLeft"_s, Property::Type::TextAlignment};
+ m_properties[Property::Type::ListStyle] = Property{u"None"_s, Property::Type::ListStyle};
m_document.setDefaultFont(getFont());
@@ -50,13 +53,9 @@ TextItem::~TextItem()
void TextItem::createTextBox(const QPointF position)
{
- m_boundingBox.setTopLeft(position);
- m_boundingBox.setWidth(Common::defaultTextBoxWidth);
-
m_document.setDefaultFont(getFont());
- const QFontMetricsF metrics{getFont()};
- m_boundingBox.setHeight(metrics.height());
-
+ updateBoundingBox();
+ m_boundingBox.moveTopLeft(QPointF(position.x() - m_document.indentWidth(), position.y()));
setDirty(true);
}
@@ -143,17 +142,19 @@ void TextItem::commitTransformation()
if (m_isHorizontalResize) {
const qreal width = m_wrapWidth > 0 ? m_wrapWidth : m_boundingBox.width();
m_wrapWidth = std::max(width * scaleX, minWrapWidth());
+ } else if (m_wrapWidth > 0) {
+ m_wrapWidth = std::max(m_wrapWidth * scaleX, minWrapWidth());
}
m_boundingBox = filtered.map(m_boundingBox).boundingRect();
if (!qFuzzyCompare(1.0, scaleY)) {
- scaleTextFragments(scaleY);
const qreal curFontSize{property(Property::Type::FontSize).value<qreal>()};
+ scaleTextFragments(scaleY);
const qreal newFontSize{std::max(1.0, curFontSize * scaleY)};
Item::setProperty(Property::Type::FontSize, Property{newFontSize, Property::Type::FontSize});
}
- updateBoundingBox();
+ updateIntendWidth();
m_isHorizontalResize = false;
}
@@ -161,35 +162,37 @@ void TextItem::scaleTextFragments(const qreal scaleY)
{
const QSignalBlocker blocker{m_document};
m_cursor.beginEditBlock();
-
- m_document.setTextWidth(-1);
- const qreal oldIdealWidth = m_document.idealWidth();
-
QTextBlock block = m_document.firstBlock();
while (block.isValid()) {
- for (auto it = block.begin(); !it.atEnd(); ++it) {
- const QTextFragment fragment = it.fragment();
- QTextCharFormat fmt = fragment.charFormat();
+ if (block.begin() == block.end()) {
+ // empty block -> could be a bullet point with no text
+ QTextCharFormat fmt = block.charFormat();
const qreal size = getFontSize(fmt);
-
fmt.setFontPointSize(std::max(1.0, size * scaleY));
fmt.setProperty(QTextFormat::FontPixelSize, QVariant());
fmt.setProperty(QTextFormat::FontSizeAdjustment, QVariant());
-
- m_cursor.setPosition(fragment.position());
- m_cursor.setPosition(fragment.position() + fragment.length(), QTextCursor::KeepAnchor);
- m_cursor.mergeCharFormat(fmt);
+ m_cursor.setPosition(block.position());
+ m_cursor.mergeBlockCharFormat(fmt);
+ m_currentFormat.merge(fmt);
+ } else {
+ for (auto it = block.begin(); !it.atEnd(); ++it) {
+ const QTextFragment fragment = it.fragment();
+ QTextCharFormat fmt = fragment.charFormat();
+ const qreal size = getFontSize(fmt);
+
+ fmt.setFontPointSize(std::max(1.0, size * scaleY));
+ fmt.setProperty(QTextFormat::FontPixelSize, QVariant());
+ fmt.setProperty(QTextFormat::FontSizeAdjustment, QVariant());
+
+ m_cursor.setPosition(fragment.position());
+ m_cursor.setPosition(fragment.position() + fragment.length(), QTextCursor::KeepAnchor);
+ m_cursor.mergeBlockCharFormat(fmt);
+ m_cursor.mergeCharFormat(fmt);
+ m_currentFormat.merge(fmt);
+ }
}
block = block.next();
}
-
- m_document.setTextWidth(-1);
- const qreal newIdealWidth = m_document.idealWidth();
- const qreal scale = newIdealWidth / oldIdealWidth;
- if (m_wrapWidth > 0) {
- m_wrapWidth = std::max(m_wrapWidth * scale, minWrapWidth());
- }
-
m_cursor.endEditBlock();
}
@@ -296,6 +299,35 @@ qreal TextItem::getFontSize(const QTextCharFormat &fmt)
return std::max(size, 1.0);
}
+void TextItem::updateIntendWidth()
+{
+ QTextBlock block = m_document.firstBlock();
+ QFont font = block.charFormat().font();
+ while (block.isValid()) {
+ const qreal size = block.charFormat().fontPointSize();
+ if (size > font.pointSize()) {
+ font = block.charFormat().font();
+ }
+ block = block.next();
+ }
+ const QFontMetricsF metrics(font);
+ qreal indentWidth = 0.0;
+
+ const int style = ItemUtils::convertStringToListStyle(property(Property::Type::ListStyle).value<QString>());
+
+ if (style == QTextListFormat::ListDisc || style == QTextListFormat::ListCircle || style == QTextListFormat::ListSquare) {
+ indentWidth = metrics.horizontalAdvance(QStringLiteral("■ "));
+ } else if (style == QTextListFormat::ListDecimal) {
+ indentWidth = metrics.horizontalAdvance(QStringLiteral("99. "));
+ } else if (style == QTextListFormat::ListLowerRoman || style == QTextListFormat::ListUpperRoman) {
+ indentWidth = metrics.horizontalAdvance(QStringLiteral("VIII. "));
+ } else if (style == QTextListFormat::ListLowerAlpha || style == QTextListFormat::ListUpperAlpha) {
+ indentWidth = metrics.horizontalAdvance(QStringLiteral("W. "));
+ }
+ m_document.setIndentWidth(indentWidth);
+ updateBoundingBox();
+}
+
QFont TextItem::getFont() const
{
QFont font;
@@ -452,6 +484,13 @@ Property TextItem::property(const Property::Type propertyType) const
const int alignment = cursor.blockFormat().alignment();
return Property{ItemUtils::convertTextAlignmentToString(alignment), Property::Type::TextAlignment};
}
+ case Property::Type::ListStyle: {
+ if (m_cursor.currentList()) {
+ return Property{ItemUtils::convertListStyleToString(m_cursor.currentList()->format().style()), Property::Type::ListStyle};
+ } else {
+ return Property{u"None"_s, Property::Type::ListStyle};
+ }
+ }
default:
return Item::property(propertyType);
}
@@ -551,12 +590,26 @@ void TextItem::setProperty(const Property::Type propertyType, const Property new
case Property::Type::TextAlignment: {
QTextBlockFormat blockFmt;
blockFmt.setAlignment(static_cast<Qt::Alignment>(ItemUtils::convertStringToTextAlignment(newObj.value<QString>())));
- QTextCursor docCursor(&m_document);
- docCursor.select(QTextCursor::Document);
- docCursor.mergeBlockFormat(blockFmt);
+ QTextCursor cursor = m_cursor;
+ cursor.select(QTextCursor::Document);
+ cursor.mergeBlockFormat(blockFmt);
Item::setProperty(propertyType, newObj);
return;
}
+ case Property::Type::ListStyle: {
+ const auto style = static_cast<QTextListFormat::Style>(ItemUtils::convertStringToListStyle(newObj.value<QString>()));
+ QTextCursor cursor = m_cursor;
+ cursor.select(QTextCursor::Document);
+ QTextBlockFormat blockFmt;
+ blockFmt.setIndent(0);
+
+ cursor.mergeBlockFormat(blockFmt);
+ QTextListFormat listFmt;
+ listFmt.setStyle(style);
+ cursor.createList(listFmt);
+ cursor.mergeBlockCharFormat(cursor.charFormat());
+ break;
+ }
default:
Item::setProperty(propertyType, newObj);
return;
@@ -566,14 +619,20 @@ void TextItem::setProperty(const Property::Type propertyType, const Property new
if (m_mode == Mode::Normal) {
m_cursor.select(QTextCursor::Document);
m_cursor.mergeCharFormat(fmt);
+ m_cursor.mergeBlockCharFormat(fmt);
m_cursor.clearSelection();
} else {
m_cursor.mergeCharFormat(fmt);
+ if (m_cursor.hasSelection()) {
+ m_cursor.mergeBlockCharFormat(fmt);
+ }
}
m_cursor.endEditBlock();
m_currentFormat.merge(fmt);
Item::setProperty(propertyType, newObj);
+
+ updateIntendWidth();
}
QJsonObject TextItem::serialize(const int zorder) const
diff --git a/src/gui/item/text.hpp b/src/gui/item/text.hpp
index d8b54815..1db03a6a 100644
--- a/src/gui/item/text.hpp
+++ b/src/gui/item/text.hpp
@@ -81,6 +81,7 @@ private:
LIBDRAWYGUI_NO_EXPORT void scaleTextFragments(const qreal scaleY);
LIBDRAWYGUI_NO_EXPORT void updateBoundingBox();
+ LIBDRAWYGUI_NO_EXPORT void updateIntendWidth();
QTextDocument m_document;
SpellCheckHighlighter *const m_highlighter;
diff --git a/src/gui/properties/property.cpp b/src/gui/properties/property.cpp
index 3d3e3491..d6eca3a9 100644
--- a/src/gui/properties/property.cpp
+++ b/src/gui/properties/property.cpp
@@ -56,6 +56,8 @@ Property::Type Property::convertStringToEnum(const QString &str)
return Property::Type::FontStyle;
} else if (str == u"CornerRectangleType") {
return Property::Type::CornerRectangleType;
+ } else if (str == u"ListStyle") {
+ return Property::Type::ListStyle;
} else {
qCWarning(DRAWY_GUI_LOG) << "Property::Type is not defined for: " << str;
}
@@ -100,6 +102,8 @@ QString Property::convertEnumToString(Property::Type type)
return u"ZOrder"_s;
case Property::Type::CornerRectangleType:
return u"CornerRectangleType"_s;
+ case Property::Type::ListStyle:
+ return u"ListStyle"_s;
case Property::Type::Null:
qCWarning(DRAWY_GUI_LOG) << "Don't save null property ";
}
@@ -144,6 +148,8 @@ QString Property::information(Type type)
return i18n("Change ZOrder");
case Property::Type::CornerRectangleType:
return i18n("Change Corner Rectangle Type");
+ case Property::Type::ListStyle:
+ return i18n("Change List Style");
case Property::Type::Null:
qCWarning(DRAWY_GUI_LOG) << "Don't save null property ";
}
diff --git a/src/gui/properties/property.hpp b/src/gui/properties/property.hpp
index e8c997be..bc8e542f 100644
--- a/src/gui/properties/property.hpp
+++ b/src/gui/properties/property.hpp
@@ -35,6 +35,7 @@ public:
TextAlignment,
ZOrder,
CornerRectangleType,
+ ListStyle,
};
enum FontStyle : int8_t {
@@ -75,16 +76,18 @@ public:
return 12;
case Type::TextAlignment:
return 13;
- case Type::Alignment:
+ case Type::ListStyle:
return 14;
- case Type::ZOrder:
+ case Type::Alignment:
return 15;
- case Type::Actions:
+ case Type::ZOrder:
return 16;
- case Type::CornerRectangleType:
+ case Type::Actions:
return 17;
- case Type::Null:
+ case Type::CornerRectangleType:
return 18;
+ case Type::Null:
+ return 19;
}
return 0;
diff --git a/src/widgets/CMakeLists.txt b/src/widgets/CMakeLists.txt
index 60cee94e..a1f33876 100644
--- a/src/widgets/CMakeLists.txt
+++ b/src/widgets/CMakeLists.txt
@@ -146,6 +146,8 @@ target_sources(
properties/widgets/strokestyleactionswidget.cpp
properties/widgets/textalignmentwidget.hpp
properties/widgets/textalignmentwidget.cpp
+ properties/widgets/liststylewidget.hpp
+ properties/widgets/liststylewidget.cpp
properties/widgets/cornerrectangletypewidget.hpp
properties/widgets/cornerrectangletypewidget.cpp
tools/arrowtool.cpp
diff --git a/src/widgets/properties/widgets/liststylewidget.cpp b/src/widgets/properties/widgets/liststylewidget.cpp
new file mode 100644
index 00000000..a575b322
--- /dev/null
+++ b/src/widgets/properties/widgets/liststylewidget.cpp
@@ -0,0 +1,64 @@
+/*
+ * SPDX-FileCopyrightText: 2026 Abdelhadi Wael <[email protected]>
+ *
+ * SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#include "liststylewidget.hpp"
+#include "properties/property.hpp"
+#include <KLocalizedString>
+#include <QComboBox>
+#include <QHBoxLayout>
+
+using namespace Qt::Literals::StringLiterals;
+
+ListStyleWidget::ListStyleWidget(QWidget *parent)
+ : PropertyWidget{parent}
+ , m_comboBox{new QComboBox}
+{
+ m_widget = new QWidget(parent);
+ const auto layout = new QHBoxLayout(m_widget);
+ layout->setContentsMargins({});
+
+ m_comboBox->addItem(i18n("None"), u"None"_s);
+ m_comboBox->addItem(i18n("Bullet (Disc)"), u"ListDisc"_s);
+ m_comboBox->addItem(i18n("Bullet (Circle)"), u"ListCircle"_s);
+ m_comboBox->addItem(i18n("Bullet (Square)"), u"ListSquare"_s);
+ m_comboBox->addItem(i18n("Numbered (Decimal)"), u"ListDecimal"_s);
+ m_comboBox->addItem(i18n("Numbered (Lower Alpha)"), u"ListLowerAlpha"_s);
+ m_comboBox->addItem(i18n("Numbered (Upper Alpha)"), u"ListUpperAlpha"_s);
+ m_comboBox->addItem(i18n("Numbered (Lower Roman)"), u"ListLowerRoman"_s);
+ m_comboBox->addItem(i18n("Numbered (Upper Roman)"), u"ListUpperRoman"_s);
+ layout->addWidget(m_comboBox);
+
+ connect(m_comboBox, &QComboBox::currentIndexChanged, this, [this] {
+ Q_EMIT changed(value());
+ });
+
+ m_widget->hide();
+}
+
+void ListStyleWidget::setValue(const QVariant &val)
+{
+ const QSignalBlocker blocker(m_comboBox);
+
+ const QString style = val.toString();
+ const int index = m_comboBox->findData(style);
+ if (index != -1) {
+ m_comboBox->setCurrentIndex(index);
+ } else {
+ m_comboBox->setCurrentIndex(0);
+ }
+}
+
+QString ListStyleWidget::name() const
+{
+ return i18n("List Style");
+}
+
+Property ListStyleWidget::value() const
+{
+ return Property(m_comboBox->currentData().toString(), Property::Type::ListStyle);
+}
+
+#include "moc_liststylewidget.cpp"
diff --git a/src/widgets/properties/widgets/liststylewidget.hpp b/src/widgets/properties/widgets/liststylewidget.hpp
new file mode 100644
index 00000000..8b18a468
--- /dev/null
+++ b/src/widgets/properties/widgets/liststylewidget.hpp
@@ -0,0 +1,26 @@
+/*
+ * SPDX-FileCopyrightText: 2026 Abdelhadi Wael <[email protected]>
+ *
+ * SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#pragma once
+#include "libdrawywidgets_private_export.h"
+#include "propertywidget.hpp"
+
+class QComboBox;
+
+class LIBDRAWYWIDGETS_TESTS_EXPORT ListStyleWidget : public PropertyWidget
+{
+ Q_OBJECT
+public:
+ explicit ListStyleWidget(QWidget *parent = nullptr);
+
+ [[nodiscard]] QString name() const override;
+ [[nodiscard]] Property value() const override;
+
+ void setValue(const QVariant &val) override;
+
+private:
+ QComboBox *const m_comboBox;
+};
diff --git a/src/widgets/properties/widgets/propertymanager.cpp b/src/widgets/properties/widgets/propertymanager.cpp
index 6f1c20ce..2e25391a 100644
--- a/src/widgets/properties/widgets/propertymanager.cpp
+++ b/src/widgets/properties/widgets/propertymanager.cpp
@@ -16,6 +16,7 @@
#include "properties/widgets/alignmentwidget.hpp"
#include "properties/widgets/backgroundstylewidget.hpp"
#include "properties/widgets/cornerrectangletypewidget.hpp"
+#include "properties/widgets/liststylewidget.hpp"
#include "properties/widgets/stokestylewidget.hpp"
#include "properties/widgets/textalignmentwidget.hpp"
#include "properties/widgets/zorderwidget.hpp"
@@ -42,6 +43,7 @@ PropertyManager::PropertyManager(ActionManager *actionManager, QWidget *parent)
m_widgets[Property::Type::StartArrowType] = new StartArrowTypeWidget(parent);
m_widgets[Property::Type::EndArrowType] = new EndArrowTypeWidget(parent);
m_widgets[Property::Type::TextAlignment] = new TextAlignmentWidget(parent);
+ m_widgets[Property::Type::ListStyle] = new ListStyleWidget(parent);
m_widgets[Property::Type::CornerRectangleType] = new CornerRectangleTypeWidget(parent);
for (auto i = m_widgets.cbegin(), end = m_widgets.cend(); i != end; ++i) {
diff --git a/src/widgets/tools/texttool.cpp b/src/widgets/tools/texttool.cpp
index 3ee3e0f9..5f315bb8 100644
--- a/src/widgets/tools/texttool.cpp
+++ b/src/widgets/tools/texttool.cpp
@@ -30,7 +30,6 @@
#include <QInputMethod>
#include <QTextBlock>
#include <QTextCharFormat>
-#include <QTextLayout>
#include <QToolTip>
using namespace Qt::Literals::StringLiterals;
@@ -45,7 +44,8 @@ TextTool::TextTool(ApplicationContext *context)
Property::Type::FontSize,
Property::Type::FontStyle,
Property::Type::FontFamily,
- Property::Type::TextAlignment};
+ Property::Type::TextAlignment,
+ Property::Type::ListStyle};
}
void TextTool::mousePressed(ApplicationContext *context)
@@ -74,6 +74,7 @@ void TextTool::mousePressed(ApplicationContext *context)
m_curItem->setProperty(Property::Type::FontStyle, uiContext->propertyManager()->value(Property::Type::FontStyle));
m_curItem->setProperty(Property::Type::FontFamily, uiContext->propertyManager()->value(Property::Type::FontFamily));
m_curItem->setProperty(Property::Type::TextAlignment, uiContext->propertyManager()->value(Property::Type::TextAlignment));
+ m_curItem->setProperty(Property::Type::ListStyle, uiContext->propertyManager()->value(Property::Type::ListStyle));
m_curItem->createTextBox(transformer.viewToWorld(uiContext->appEvent()->pos()));
@@ -288,8 +289,11 @@ void TextTool::processKey(const Event *ev) const
switch (ev->key()) {
case Qt::Key_Return:
case Qt::Key_Enter:
- // QChar::LineSeparator
- cursor.insertText(u"\u2028"_s);
+ if (shift) {
+ cursor.insertText(u"\u2028"_s);
+ } else {
+ cursor.insertText(u"\n"_s);
+ }
return;
case Qt::Key_Left:
cursor.movePosition(ctrl ? QTextCursor::WordLeft : QTextCursor::Left, moveMode);