Wednesday, September 30, 2009

Setting the mouse-cursor for Qt's item-views via model

I like Qt's Model/View architecture very much. Writing models is, after a few short headaches in the beginning, quite intuitive and straight-forward. And it becomes even easier when you don't use QAbstractItemView directly, but a more specialized class like QAbstractListModel or QAbstractTableModel. The basic idea is that the model provides information to the views that is needed to present the individual items to the user. This does not only include the text of the item, but also a decoration (icon), a tool-tip, font, background-styling and more.


One thing I found missing, however, is the mouse-cursor that should be used for the item. For me, it's a common situation that you display a table and one of the columns is clickable and executes an action (such as deleting the row maybe). The user obviously will not understand that he can click cells in this column, unless you change the cursor to something like Qt::PointingHandCursor, for example.


I guess most people that faced this problem derived from the view they are using and override mouseMoveEvent() and do things there. This solution is not in the mind of Model/View, though. The mouse-cursor for an item is something that belongs into the model. So here's what we are going to do:


  • Create a new Role for the MyMouseCursorRole

  • Create new subclasses from the item-views we want to use (Qt's only item-views are QListView, QTableView and QTreeView)

  • In those sub-classes, make sure that the model's data() is called with the MyMouseCursorRole when the mouse moves to a new item (or moves to a position that is not an item)


After this, we can start writing models and handle the MyMouseCursorRole there. In the example, I derive from QStandardItemModel and only override data() and return different shapes for certain items.


In this blog-post, I'm including a small example for QListView. You can get the full source-code for the other views at the bottom of this post.

Here's the header:


#include <QListView>

// Define our new role that can be used in the model.
const int MyMouseCursorRole = Qt::UserRole + 1;

// Define a new constant for user-defined roles in this application.
const int MyUserRole = MyMouseCursorRole + 1;

// MyListView requests the MyMouseCursorRole from the model when the mouse
// moves over a new row. It does not need to use any tricks like MyTableView
// or MyTreeView, because it does not contain headers.

class MyListView: public QListView
{
Q_OBJECT

public:
MyListView(QWidget *parent = 0);

protected:
virtual void mouseMoveEvent(QMouseEvent *event);

private:
// m_LastRow stores the row the mouse was over the last time
// mouseMoveEvent() was called. This is used to minimize calls to the
// model's data() function. m_lastRow is -1 when no valid row was hovered by
// the mouse.
int m_lastRow;
};

And here's the implementation:
MyListView::MyListView(QWidget *parent):
QListView(parent),
m_lastRow(-1)
{
// We need to enable mouse-tracking because we need to know
// about every mouse-movement.
setMouseTracking(true);
}

void MyListView::mouseMoveEvent(QMouseEvent *event)
{
QAbstractItemModel *m(model());
// Only do something when a model is set.
if (m)
{
QModelIndex index = indexAt(event->pos());
if (index.isValid())
{
// When the index is valid, compare it to the last row.
// Only do something when the the mouse has moved to a new row.
if (index.row() != m_lastRow)
{
m_lastRow = index.row();
// Request the data for the MyMouseCursorRole.
QVariant data = m->data(index, MyMouseCursorRole);

Qt::CursorShape shape = Qt::ArrowCursor;
if (!data.isNull())
shape = static_cast(data.toInt());
setCursor(shape);
}
}
else
{
if (m_lastRow != -1)
// Set he mouse-cursor to the default when it isn't already.
setCursor(Qt::ArrowCursor);
m_lastRow = -1;
}
}
QListView::mouseMoveEvent(event);
}

For QListView, that's all! It gets more complex for QTableView and QTreeView, because they contain headers and mouseMoveEvent() is not called when the mouse moves to these headers. But the cursor set with setCursor() is used for the headers, too. This means that, when the mouse moves from an item with a special cursor-shape directly to a header-control, the mouse-shape does not change back to the default. To circumvent this, we have to implement quite a hack, so I'm not covering it in this blog-post. You can see my solution in the example source-code.


To test our new functionality, we have to create a model that handles MyMouseCursorRole and returns a valid Qt::CursorShape. As noted earlier, I derive from QStandardItemModel, because QStandardItemModel already implements needed standard functionality. QStandardItemModel uses QStandardItems as it's soure of information. Because QStandardItem obviously does not have a property for the cursor's shape, I use a trick to store the cursor-information in the item's text. By convention, when the item's text starts with {hand}, Qt::PointingHandCursor is returned in MyMouseCursorRole and {hand} is snipped off of the text for Qt::DisplayRole.


Here's the header's code, it's quite simple:

class MyTestModel: public QStandardItemModel
{
Q_OBJECT

public:
QVariant data(const QModelIndex &index, int role) const;
};

And here's the implementation's code:
QVariant MyTestModel::data(const QModelIndex &index, int role) const
{
if (role == MyMouseCursorRole)
{
// We use a little hack here so we don't need to add an extra element to
// QStandardItem. When the item's text starts with {hand}, we return
// the Qt::PointingHandCursor cursor-shape.

QVariant text = QStandardItemModel::data(index, Qt::DisplayRole);
if (text.toString().startsWith("{hand}"))
return Qt::PointingHandCursor;
else
return QVariant();
}
else if (role == Qt::DisplayRole)
{
// Cut out the {hand} from actually displayed content.
QVariant result = QStandardItemModel::data(index, role);
QString text = result.toString();
if (text.startsWith("{hand}"))
return text.mid(6);
else
return result;
}
return QStandardItemModel::data(index, role);
}

As you can see, I'm using a trick to call QStandardItemModel::data() because I want to access the "raw" data without re-implementing QStandardItemModel::data()'s functionality. What's quite handy is that you can pass a different Qt::ItemDataRole to data(), so I can get the value for Qt::DisplayRole when preparing the values for MyMouseCursorRole.


Now the only component that's missing to see the result is a demo-application that uses the test-model and the new MyListView-class. That's only a few lines:


#include <QApplication>

int main(int argc, char *argv[])
{
QApplication app(argc, argv);

MyTestModel model;

// Demo data
QList row;
row.push_back(new QStandardItem("A 1"));
row.push_back(new QStandardItem("{hand}B 1"));
row.push_back(new QStandardItem("{hand}C 1"));
model.appendRow(row);

row.clear();
row.push_back(new QStandardItem("{hand}A 1.1"));
row.push_back(new QStandardItem("B 1.2"));
row.push_back(new QStandardItem("{hand}C 1.3"));
model.item(0, 0)->appendRow(row);

row.clear();
row.push_back(new QStandardItem("{hand}A 2"));
row.push_back(new QStandardItem("B 2"));
row.push_back(new QStandardItem("C 2"));
model.appendRow(row);

MyListView listView;
listView.setModel(&model);
listView.show();
app.exec();
}

That's it. I have compiled a complete project that includes derivations from QListView, QTableView and QTreeView, the test-model and the test-widget. However, this is not a complete solution and by far not suitable to, say, be included in Qt's main releases. There are a few things that need to be solved. For example, using setCursor() to set the cursor for the whole widget is completely broken with these changes.


You can download the example source-code here.

Tuesday, September 29, 2009

Exceptions: Do's and Don'ts

Exceptions in C++ are a very complex topic. In addition to the standard-facilities of throwing and catching them, there are things like exception-specifiers and throwing arbitrary objects. Here are some guidelines on what you should do with exceptions and what you definitely should not do.

Do's



  • Use exceptions. But only for exceptional situations. Don't use exceptions for error-conditions that are likely to occur, like misconfigurations by the end-user, etc. However, when the configuration is already supplied by the software and not influenced by the user, it's indeed an exceptional situation where you should throw a C++-exception.

  • Derive all your exception-classes from a standard exception-class such as std::runtime_error or at least std::exception.

  • Create your own base-classes for exceptions in your application or library so you can easily distinguish your exceptions from others'. This is especially important for libraries.

  • Don't create exceptions on the heap. Throw them by value and catch them by value or const-reference.

  • Make your exception's error-information accessible as atomic as possible. This helps the user of your exception to create meaningful error messages for different kinds of end-users. For example, when there's a database-error, make the database-function that was executed, the line and part of the SQL-statement and the error-message accessible separately.

  • Also provide a convenience-function in your exception to create a human-readable error message.

  • The more different exception-classes you provide, the easier it is to handle certain types of exceptions. On the plus side, more exception-classes does not mean more work when you don't care what kind of exception is thrown when following the guideline to have a standard base-class for your application's exceptions.


Don'ts



  • Don't throw anything that does not derive from std::exception.

  • Try not to depend on catch(...) because you can't identify what kind of error has occured. std::exception should be the "widest" scope you catch.

  • Don't use exception-specifiers. The C++ committee has done a half-assed job when inventing those and didn't correct their mistakes in C++0x. There are several reasons why exception-specifiers are broken by design:

    • If any of the called functions throws an exception that's not listed in your exception-specifier, std::terminate will be called. This may happen when modifying a function that's used by any function that uses exception-specifiers, without verifying the whole code after the change.

    • When you use exception-specifiers in functions that call third-party code, it's possible that they break with an update of that library. This update could even silently happen by exchanging a dynamic library that's installed system-wide, without you doing anything.

    • Execption-specifiers always cost speed. Because they are not checked at compile-time, the compiler needs to add code to check for validity of the thrown exceptions, which slows down execution time even when not caring for exceptions at this point.



  • Don't throw exceptions from extern "C" functions. This causes undefined behaviour, and in this case it means a crash on most popular platforms.

  • Don't throw exceptions in destructors. Since destructors usually are called implicitly when the object runs out of scope, they're easy to miss. Additionally, it's not a good idea to prevent your application from freeing up resources.

  • Create your exception-classes exception-safe. For example, using std::string can potentially lead to a std::bad_alloc exception being thrown.