[prev in list] [next in list] [prev in thread] [next in thread]
List: kde-pim
Subject: Re: [Kde-pim] Towards better nested list handling in KMail
From: Stephen Kelly <steveire () gmail ! com>
Date: 2008-03-26 2:26:14
Message-ID: fscc88$7tm$1 () ger ! gmane ! org
[Download RAW message or body]
Hi,
Thomas McGuire wrote:
> Hi Stephen,
>
> On Sunday 16 March 2008, Stephen Kelly wrote:
>> This is another feature I've been working on for kjots.
>>
>> KMEditor supports lists currently, but not nested lists. I've been
>> experiementing with adding a DoTheRightThing nested list feature in kjots
>> and kmail.
>
> I won't have time to look at this (and the link editor) until after the
> Akonadi meeting this weekend. But then, I'll have a torough look.
>
I've made some progress on this and created a helper class to format the
list using an event filter. I still have a lot of optimization to do and
more events and cases to cover, but I think I can do everything I want now
with it.
Just thought I'd let you know, so need to spend time on a thorough look into
the last patch. My work-in-progress is attached. It applies against kjots,
but I can apply something similat to KMEditor when it's done.
I had a deeper look into the qt rich text classes too, but I haven't yet
tracked down how the link formatting/unformatting could work.
> It's great that you work on this and that we will be able to share code
> between KMail and KJots.
>
Sure. I'd actually like to see a KRichTextEdit and KPart in kdelibs that all
could use. It might just mean moving most of KMEditor into it. As far as I
can see most of it is not specific to PIM/email and could be shared by many
applications. KMEditor could then just be a subclass of it?
Best regards,
Steve.
["listhelper.patch" (text/x-diff)]
Index: KJotsMain.cpp
===================================================================
--- KJotsMain.cpp (revision 786203)
+++ KJotsMain.cpp (working copy)
@@ -294,6 +294,28 @@
action->setText(i18n("Font Size"));
action->setIcon(KIcon("preferences-desktop-font"));
+ KSelectAction* selaction = \
actionCollection()->add<KSelectAction>("bullet_type"); + \
selaction->setText(i18n("Bullet Type")); + QStringList lst;
+ lst << i18n("None")
+ << i18n("Disc")
+ << i18n("Circle")
+ << i18n("Square")
+ << i18n("123")
+ << i18n("abc")
+ << i18n("ABC");
+ selaction->setItems(lst);
+ selaction->setCurrentItem(0);
+ selaction->setIcon(KIcon("format-list-unordered"));
+
+ action = actionCollection()->addAction("indent_more");
+ action->setText(i18n("Increase Indent"));
+ action->setIcon(KIcon("format-indent-more"));
+
+ action = actionCollection()->addAction("indent_less");
+ action->setText(i18n("Decrease Indent"));
+ action->setIcon(KIcon("format-indent-less"));
+
action = actionCollection()->addAction("auto_bullet");
action->setText(i18n("Auto Bullets"));
action->setIcon(KIcon("format-list-unordered"));
Index: kjotsedit.cpp
===================================================================
--- kjotsedit.cpp (revision 785973)
+++ kjotsedit.cpp (working copy)
@@ -27,6 +27,7 @@
#include <QMimeData>
#include <QTextCursor>
+#include <QTextList>
#include <QStackedWidget>
#include <QUrl>
#include <QMenu>
@@ -35,11 +36,15 @@
#include <kaction.h>
#include <kfontaction.h>
#include <kfontsizeaction.h>
+#include <kselectaction.h>
#include <kactioncollection.h>
+#include <kfontdialog.h>
#include <kcolordialog.h>
+#include <kdebug.h>
#include "kjotsentry.h"
#include "bookshelf.h"
+#include "nestedlisthelper.h"
KJotsEdit::KJotsEdit ( QWidget *parent ) : KTextEdit(parent)
{
@@ -82,6 +87,9 @@
connect(actionCollection->action("linkify"), SIGNAL(triggered()), \
SLOT(onLinkify()));
connect(actionCollection->action("insert_checkmark"), SIGNAL(triggered()), \
SLOT(addCheckmark()));
+ connect((KSelectAction*)actionCollection->action("bullet_type"), \
SIGNAL(triggered(int)), SLOT(onBulletType(int))); + \
connect(actionCollection->action("indent_more"), SIGNAL(triggered()), \
SLOT(onIndentMore())); + connect(actionCollection->action("indent_less"), \
SIGNAL(triggered()), SLOT(onIndentLess()));
connect(actionCollection->action("auto_bullet"), SIGNAL(triggered()), \
SLOT(onAutoBullet()));
connect(actionCollection->action("horizontal_rule"), SIGNAL(triggered()), \
SLOT(onRule()));
@@ -92,6 +100,8 @@
connect(this, SIGNAL(currentCharFormatChanged(const QTextCharFormat &)),
this, SLOT(updateCurrentCharFormat(const QTextCharFormat &)));
+ installEventFilter(this);
+ nestedListHelper = new NestedListHelper(this);
}
void KJotsEdit::disableEditing ( void )
@@ -211,6 +221,61 @@
mergeFormatOnWordOrSelection(fmt);
}
+bool KJotsEdit::eventFilter(QObject *obj, QEvent *event)
+{
+ bool filter = false;
+ if ( (event->type() == QEvent::KeyPress)
+ || (event->type() == QEvent::Drop) ){
+
+ if ( (event->type() == QEvent::KeyPress) && textCursor().currentList() )
+ {
+ QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
+
+ bool handled = false;
+
+ // False if the key press event was not handled or not completely
+ // handled by the Helper class.
+ handled = nestedListHelper->handleBeforeKeyPressEvent(keyEvent);
+
+ if (!handled){
+ keyPressEvent(keyEvent);
+ }
+ filter = true;
+ }
+
+ if ( (event->type() == QEvent::KeyPress) && textCursor().currentList() )
+ {
+ QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
+ nestedListHelper->handleAfterKeyPressEvent(keyEvent);
+ }
+
+
+// if ( (event->type() == QEvent::Drop) )
+// {
+// QDropEvent *_dropEvent = static_cast<QDropEvent*>(event);
+// dropEvent( _dropEvent );
+// nestedListHelper->handleAfterDropEvent(_dropEvent);
+// }
+ }
+ return filter;
+}
+
+void KJotsEdit::onBulletType(int styleIndex)
+{
+ nestedListHelper->handleOnBulletType(-styleIndex);
+ setFocus();
+}
+
+void KJotsEdit::onIndentMore()
+{
+ nestedListHelper->handleOnIndentMore();
+}
+
+void KJotsEdit::onIndentLess()
+{
+ nestedListHelper->handleOnIndentLess();
+}
+
void KJotsEdit::updateCurrentCharFormat(const QTextCharFormat &format)
{
fontChanged(format.font());
Index: kjotsedit.h
===================================================================
--- kjotsedit.h (revision 785973)
+++ kjotsedit.h (working copy)
@@ -35,6 +35,8 @@
class Bookshelf;
class KJotsPage;
+class NestedListHelper;
+
class KJotsEdit : public KTextEdit
{
Q_OBJECT
@@ -48,6 +50,7 @@
protected:
virtual void contextMenuEvent( QContextMenuEvent* );
+ bool eventFilter(QObject *obj, QEvent *event);
protected slots:
void onBookshelfSelection ( void );
@@ -61,6 +64,9 @@
void onFontBackgroundColor ( void );
void onFontFamilySelection( const QString& f);
void onFontSizeSelection( int s);
+ void onBulletType(int styleIndex);
+ void onIndentMore();
+ void onIndentLess();
void updateCurrentCharFormat(const QTextCharFormat &format);
void fontChanged(const QFont &f);
void mergeFormatOnWordOrSelection(const QTextCharFormat &format);
@@ -75,6 +81,7 @@
QPointer<Bookshelf> bookshelf;
QPointer<KJotsPage> currentPage;
KActionCollection *actionCollection;
+ NestedListHelper *nestedListHelper;
};
#endif // __KJOTSEDIT_H
Index: kjotsui.rc
===================================================================
--- kjotsui.rc (revision 781504)
+++ kjotsui.rc (working copy)
@@ -1,5 +1,5 @@
<!DOCTYPE kpartgui>
-<kpartgui version="4" name="kjots" >
+<kpartgui version="5" name="kjots" >
<MenuBar>
<Menu name="file">
@@ -74,5 +74,8 @@
<Separator/>
<Action name="font_color"/>
<Action name="font_background_color"/>
+ <Action name="bullet_type"/>
+ <Action name="indent_more"/>
+ <Action name="indent_less"/>
</ToolBar>
</kpartgui>
Index: nestedlisthelper.cpp
===================================================================
--- nestedlisthelper.cpp (revision 0)
+++ nestedlisthelper.cpp (revision 0)
@@ -0,0 +1,280 @@
+/**
+ * Nested list helper
+ *
+ * Copyright 2008 Stephen Kelly <steveire@gmailcom>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+#include "nestedlisthelper.h"
+
+
+#include <QKeyEvent>
+#include <QDropEvent>
+#include <QTextCursor>
+#include <QTextList>
+#include <QTextDocumentFragment>
+
+#include <KTextEdit>
+#include <KDebug>
+
+
+NestedListHelper::NestedListHelper(QTextEdit *te)
+{
+ textEdit = te;
+
+}
+
+NestedListHelper::~NestedListHelper()
+{
+}
+
+bool NestedListHelper::handleBeforeKeyPressEvent(QKeyEvent *event)
+{
+
+ // Only attempt to handle Backspace
+ if (event->key() != Qt::Key_Backspace)
+ return false;
+
+ QTextCursor cursor = textEdit->textCursor();
+ bool handled = false;
+
+ if( !cursor.hasSelection()
+ && cursor.block().textList()
+ && event->key() == Qt::Key_Backspace
+ && cursor.atBlockStart() )
+ {
+ handleOnIndentLess();
+ handled = true;
+
+ }
+
+ if ( cursor.hasSelection()
+ && cursor.block().textList()
+ && event->key() == Qt::Key_Backspace
+ && cursor.selection().toPlainText().contains("\n") )
+ {
+ handleOnIndentLess();
+ handled = true;
+
+ }
+ return handled;
+}
+
+bool NestedListHelper::handleAfterKeyPressEvent(QKeyEvent *event)
+{
+ // Only attempt to handle Backspace and Return
+ if ((event->key() != Qt::Key_Backspace)
+ &&(event->key() != Qt::Key_Return))
+ return false;
+
+ QTextCursor cursor = textEdit->textCursor();
+ bool handled = false;
+
+ if( !cursor.hasSelection() && cursor.block().textList() ) {
+ // Check if we're on the last list item.
+ // itemNumber is zero indexed
+ if (cursor.currentList()->count() == cursor.currentList()->itemNumber( \
cursor.block() ) + 1 ) + {
+ if ( ( event->key() == Qt::Key_Return) || ( event->key() == \
Qt::Key_Backspace) ) + {
+ reformatList();
+ handled = true;
+ }
+
+ }
+ }
+ return handled;
+}
+
+
+// bool NestedListHelper::handleAfterDropEvent(QDropEvent *dropEvent)
+// {
+// // Do something smart here.
+//
+// kDebug() << "handle drop event";
+// reformatList();
+// return true;
+// }
+
+
+void NestedListHelper::reformatList()
+{
+ QTextCursor cursor = textEdit->textCursor();
+ if ( !cursor.currentList() ){
+ int initialPosition = cursor.position();
+ int initialAnchor = cursor.anchor();
+ int higherPositionIndex;
+ int lowerPositionIndex;
+
+ if (initialPosition != initialAnchor){
+ if (initialPosition > initialAnchor){
+ lowerPositionIndex = initialAnchor;
+ higherPositionIndex = initialPosition;
+ }else{
+ lowerPositionIndex = initialPosition;
+ higherPositionIndex = initialAnchor;
+ }
+ }
+ else {
+ lowerPositionIndex = higherPositionIndex = initialPosition;
+ }
+
+ cursor.setPosition(lowerPositionIndex);
+ cursor.movePosition(QTextCursor::PreviousBlock);
+ if (cursor.currentList()){
+ textEdit->setTextCursor(cursor);
+ reformatList();
+ }
+
+ cursor.setPosition(higherPositionIndex);
+ cursor.movePosition(QTextCursor::NextBlock);
+ if (cursor.currentList()){
+ textEdit->setTextCursor(cursor);
+ reformatList();
+ }
+ cursor.setPosition(initialAnchor, QTextCursor::MoveAnchor);
+ cursor.setPosition(initialPosition, QTextCursor::KeepAnchor);
+ textEdit->setTextCursor(cursor);
+ return;
+ }
+
+ QTextBlock block = cursor.block();
+
+ // Start at the top of the list
+ while ( block.previous().textList() != 0 ){
+ block = block.previous();
+ }
+
+ QTextBlockFormat blockFmt = block.blockFormat();
+ blockFmt.setTopMargin(12);
+ blockFmt.setBottomMargin(0);
+ cursor.setPosition(block.position());
+ cursor.setBlockFormat(blockFmt);
+ block = cursor.block();
+
+ cursor.createList(cursor.currentList()->format());
+ QTextList* list = block.textList();
+ list->add(block);
+
+ QMap<int, QTextList *> nestedLists;
+ nestedLists.insert(list->format().indent(), list);
+
+ int indentLevel;
+ while ( block.next().textList() != 0 ) {
+ block = block.next();
+ list = block.textList();
+ indentLevel = list->format().indent();
+
+ foreach (int key, nestedLists.keys() ){
+ if (indentLevel < key){
+ nestedLists.remove(key);
+ }
+ }
+
+
+ if ( !nestedLists.contains(indentLevel) ) {
+ nestedLists.insert(indentLevel, list);
+ } else {
+
+ list = nestedLists.take(indentLevel);
+ }
+
+ blockFmt = block.blockFormat();
+ blockFmt.setTopMargin(0);
+ blockFmt.setBottomMargin(0);
+ cursor.setPosition(block.position());
+ cursor.setBlockFormat(blockFmt);
+
+ list->add(block);
+ nestedLists.insert(indentLevel, list);
+
+ }
+ blockFmt = block.blockFormat();
+ blockFmt.setBottomMargin(12);
+ cursor.setPosition(block.position());
+ cursor.setBlockFormat(blockFmt);
+}
+
+
+void NestedListHelper::handleOnIndentMore()
+{
+ QTextCursor cursor = textEdit->textCursor();
+
+ QTextListFormat listFmt;
+ if (!cursor.currentList()) {
+ QTextListFormat::Style style = QTextListFormat::ListDisc;
+ handleOnBulletType(style);
+ } else {
+ listFmt = cursor.currentList()->format();
+ listFmt.setIndent(listFmt.indent() + 1);
+
+ cursor.createList(listFmt);
+ reformatList();
+ }
+}
+
+void NestedListHelper::handleOnIndentLess()
+{
+ QTextCursor cursor = textEdit->textCursor();
+ QTextList* currentList = cursor.currentList();
+ if ( !currentList )
+ return;
+ QTextListFormat listFmt;
+ listFmt = currentList->format();
+ if ( listFmt.indent() > 1 )
+ {
+ listFmt.setIndent(listFmt.indent() - 1);
+ cursor.createList(listFmt);
+ }else{
+ QTextBlockFormat bfmt;
+ bfmt.setObjectIndex(-1);
+ cursor.setBlockFormat(bfmt);
+ }
+ reformatList();
+}
+
+
+void NestedListHelper::handleOnBulletType(int styleIndex)
+{
+ QTextCursor cursor = textEdit->textCursor();
+ if (styleIndex != 0) {
+ QTextListFormat::Style style = (QTextListFormat::Style)styleIndex;
+ QTextList *currentList = cursor.currentList();
+ QTextListFormat listFmt;
+
+ cursor.beginEditBlock();
+
+ if (currentList){
+ listFmt = currentList->format();
+ listFmt.setStyle(style);
+ currentList->setFormat(listFmt);
+ }
+ else {
+ listFmt.setStyle(style);
+ cursor.createList(listFmt);
+ }
+
+ cursor.endEditBlock();
+ } else {
+ QTextBlockFormat bfmt;
+ bfmt.setObjectIndex(-1);
+ cursor.setBlockFormat(bfmt);
+ }
+ reformatList();
+}
+
+
Index: nestedlisthelper.h
===================================================================
--- nestedlisthelper.h (revision 0)
+++ nestedlisthelper.h (revision 0)
@@ -0,0 +1,113 @@
+/**
+ * Nested list helper
+ *
+ * Copyright 2008 Stephen Kelly <steveire@gmailcom>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+#ifndef NESTEDLISTHELPER_H
+#define NESTEDLISTHELPER_H
+
+class QTextEdit;
+
+class QKeyEvent;
+class QDropEvent;
+
+/**
+ *
+ * @short Helper class for automatic handling of nested lists in a text edit
+ *
+ *
+ * @author Stephen Kelly
+ * @since 4.1
+ */
+class NestedListHelper
+{
+public:
+
+ /**
+ * Create a helper
+ *
+ * @param te The text edit object to handle lists in.
+ */
+ NestedListHelper(QTextEdit *te);
+
+ /**
+ * Destructor
+ */
+ ~NestedListHelper();
+
+ /**
+ *
+ * Handles a key press before it is processed by the text edit widget.
+ *
+ * Currently this causes a backspace at the beginning of a line or with a
+ * multi-line selection to decrease the nesting level of the list.
+ *
+ * @param event The event to be handled
+ * @return Whether the event was handled by this method.
+ */
+ bool handleBeforeKeyPressEvent(QKeyEvent *event);
+
+ /**
+ *
+ * Handles a key press after it is processed by the text edit widget.
+ *
+ * Currently this causes a Return at the end of the last list item, or
+ * a Backspace after the last list item to recalculate the spacing
+ * between the list items.
+ *
+ * @param event The event to be handled
+ * @return Whether the event was handled by this method.
+ */
+ bool handleAfterKeyPressEvent(QKeyEvent *event);
+
+
+// bool handleAfterDropEvent(QDropEvent *event);
+
+
+ /**
+ * Increases the indent (nesting level) on the current list item or selection.
+ */
+ void handleOnIndentMore();
+
+
+
+ /**
+ * Decreases the indent (nesting level) on the current list item or selection.
+ */
+ void handleOnIndentLess();
+
+
+
+ /**
+ * Changes the style of the current list or creates a new list with
+ * the specified style.
+ *
+ * @param styleIndex The QTextListStyle of the list.
+ */
+ void handleOnBulletType(int styleIndex);
+
+
+private:
+ void reformatList();
+
+ QTextEdit *textEdit;
+
+};
+
+#endif
Index: CMakeLists.txt
===================================================================
--- CMakeLists.txt (revision 781504)
+++ CMakeLists.txt (working copy)
@@ -14,6 +14,7 @@
kjotsedit.cpp
kjotsbookmarks.cpp
bookshelf.cpp
+ nestedlisthelper.cpp
kjotsbrowser.cpp )
kde4_add_ui_files(kjots_SRCS confpagemisc.ui )
_______________________________________________
KDE PIM mailing list kde-pim@kde.org
https://mail.kde.org/mailman/listinfo/kde-pim
KDE PIM home page at http://pim.kde.org/
[prev in list] [next in list] [prev in thread] [next in thread]
Configure |
About |
News |
Add a list |
Sponsored by KoreLogic