Friday, June 4, 2021

 After I gave up on the madness that is C++, I looked for an alternative. I wanted garbage collection, strong typing and object orientation. I quickly found D. Coming from a C background, it took be a while to adjust, but so far D has not disappointed me. I've used it for a GTK UI and a web backend. It compiles fast and runs fast.

Today I found a feature that I would never have guessed existed, but it solved the exact problem I had. Here's an example:

import std.stdio;
import std.string;

struct Life
    alias question this;
    string question = "What is the meaning";
    int answer = 42;

    void showAnswer ()
        writeln ("The answer is ", answer);

void main()
    Life l;

    string a = l ~ " of life?";
    writeln (a);

    Life foo = l;
    foo.showAnswer ();

I wanted a custom type so I could define my own member functions, but I also have to use a library that uses string concatenation on it's parameters. By aliasing "this" to the member "question", variables of type Life can also behave as if they are of type string.

This feels almost like magic, but it's just an example of how flexible D is and how clever it's compiler is.

Thursday, February 28, 2019

Giving up on Qt and C++

I gave it a valiant try, but I have decided to give up on Qt and C++. It clearly works for some people, but not for me.

On the Qt side, I had several issues. Qt is very complicated, which sometimes allows it to perform magic once you've gotten everything set up just so. On the other hand, things like a undo stack are mostly left to the developer. The worst part is that the text editor widgets have their own built-in undo stack that is very difficult to integrate with an application wide undo.

Another issue I had with Qt was trying to sort out the Qt vs QML documentation and tutorials. Not only was it distracting, but I got the feeling that QML is their future direction, at the expense of Qt/C++. I have no interest in writing any more javascript code, so QML is out for me. It also seems very mobile focused, while I'm interested in desktop apps.

C++, where to begin? I know there are many rants about the problems with C++, so I'll only touch on the highlights that drove me crazy. Number one is header files. Somehow they managed to take a C feature that is not without problems and make it 10 times worse. Having to almost duplicate the class declarations in the definitions was incredibly annoying. I guess if you use the IDE it can help you with that, but I'm a vim/make guy so the IDE was a no go.
The other big issue I had was that it was incredibly confusing when objects were created, and therefore when I had to free them. I'm sure the C++ people think they are really clever for inventing the way they can pass around objects without copying, but if I have to do memory management I need to know what's going on underneath.

So farewell Qt and C++. I know you are a great combination for many, but not for me.

Sunday, May 27, 2018

QLineEdit let's me down again

Qt has a pretty nice QUndoStack which you can use in your application, but unfortunately it does not integrate at all with the QLineEdit widget.

QLineEdit implements it's own undo stack using a completely different design that has specific knowledge about how QLineEdit works. The shitty thing about this is that the Ctrl-Z keyboard undo and the edit->undo menu do not refer to the same thing, so each will give different results. This is a terrible user experience and there should be a better way to handle it.

The only suggestion I've gotten is to implement my own undo menu code that checks to see if a QLineEdit has focus, and if it does, call it's undo rather than the application undo. Undoubtedly QLineEdit is not the only widget with this flaw, so that undo code will get pretty ugly. Any then I'd also have to catch the Ctrl-Z event in each QLineEdit and somehow make it call the application undo when appropriate.

Oh, and now that I think about it, you can never know the proper order because the commands are on different queues and you have no way to know which command was put on one of the queues last. What a mess. Maybe there's a way to disable the undo handling in QLineEdit, but I haven't found that yet if it's there.

Wow, this has been a complaint since 2011: QTBUG-16774

Monday, May 21, 2018

QLineEdit width cannot be set in terms of characters

Hopefully this will be a short post, but I can't believe how much time I've wasted on this, including digging into the QLineEdit source.

I used to use a GUI toolkit called Motif. It was written nearly 30 years ago. Of course it had a simple, single-line text editor widget. Like all widgets, you could set it's width and height, and it could be resized by managers (layouts).
There was a property you could set on the text widget to tell it how many characters it should display. This took into account the current font and always did a reasonable job of setting the width of the widget.

Fast forward nearly 30 years. I'm learning this shiny new Qt toolkit and want to set the number of characters to display in my QLineEdit. Nope, all you get is width in pixels. To fix this, you actually have to subclass QLineEdit and re-implement the sizeHint function:

QSize QLineEdit::sizeHint() const
    Q_D(const QLineEdit);
    QFontMetrics fm(font());
    int h = qMax(fm.height(), 14) + 2*d->verticalMargin
            + d->topTextMargin + d->bottomTextMargin
            + d->topmargin + d->bottommargin;
    int w = fm.horizontalAdvance(QLatin1Char('x')) * 17 + 2*d->horizontalMargin
            + d->effectiveLeftTextMargin() + d->effectiveRightTextMargin()
            + d->leftmargin + d->rightmargin; // "some"
    QStyleOptionFrame opt;
    return (style()->sizeFromContents(QStyle::CT_LineEdit, &opt, QSize(w, h).
                                      expandedTo(QApplication::globalStrut()), this));
Notice the 17? That's how many characters you get. Silly programmer didn't you know 17 is always the right length?

Now that I know how stupid the code is, I can fix it, but a subclass is never a first class widget in the QtDesigner, so it will always be annoying, forcing me to add an extra function call for ever QLineEdit rather than just setting a property in QtDesigner.

Thursday, May 17, 2018

Did the Hightlighted row in a TableView change?

Seems like this would be one of the basic things you'd want to know about a TableView.
Granted the Qt TableView is insanely complex because it implements most of the functionality of a spreadsheet.
Maybe they should have had a simpler TableView and a TableEditor class.
Regardless, I am using a TableView as a fancy list display where you can sort on different columns and "select" the whole row.

So confusing point number one is that a TableView has a "current index" and a "selection" and they are not necessarily the same.

For example, this code changes both at once:

tableview->setCurrentIndex (index);

But this code, which appears to work, leaves a light blue "current index" indicator on the previously selected row if you sort the tableView first.

tableView->selectionModel()->select (index, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);

As you can see from the above code, there are 2 levels to the API. This is annoying and confusing to say the least. Many operations can be done directly on the TableView, while others can only be done on the TableView's selection model. As shown above sometimes you can perform the operation on both, but with different results.

So back to the topic. I want to know what a row in my table is selected. Either when I user clicks on it with the mouse, or by using the cursor keys to run up and down the list.
I can do this to get clicks or double clicks:

void ActionList::on_tableView_clicked (QModelIndex index)

void ActionList::on_tableView_doubleClicked (QModelIndex index)
Usual Qt magic applies and if I get the name right these slots will automatically be connected. Kind of nice. Unfortunately, there is no selectionChanged signal on the TableView. There is one on the TableView's selection Model, but since it is an automatically created object whose name is "", you can't use the moc naming trick. Instead, you get to use this delight:

     connect (tableView->selectionModel(),
     SIGNAL (selectionChanged (const QItemSelection &, const QItemSelection &)),
     SLOT(selectionChanged (const QItemSelection &, const QItemSelection &)));
 At least it works, but it really should not be that convoluted.

Monday, May 14, 2018

Adding a new data element to a table and selecting it

I had no idea something so simple would take me so long to figure out. I want to have a menu item that adds a new data element to a table and selects it. See, sound easy, right?
Well, I also want to be able to undo it, which adds some complexity, but that's not the part that took me longest to figure out.

For one thing, there are a lot of objects involved in this. First you need a data model. I subclassed mine from the QAbstractTableModel since I'm going to display my data in a TableView. I really don't see much difference in the QAbstractTableModel and the QAbstractItemModel. I think they would work interchangeably for my case.

The TableView I created in Qt Designer, but since I want the table to be sortable and searchable, my data model needs a QSortFilterProxyModel. Right now I'm using the default one, but I think I'll need to create my own subclass of it when I get to the filtering part. So the constructor for my TableView looks like this:

ActionList::ActionList(QWidget *parent) :
ui(new Ui::ActionList)
proxy = new QSortFilterProxyModel(parent);

Not too bad, so far. Now I need a slot for my menu item. All it does is create an undo command and push it on the stack. The undo commands are really tedious because of all the object boiler plate required. I simplified mine somewhat by making the declaration and definition one in the same. This means I have to include the .cpp file into another .cpp file to compile it, which is ugly, but saves a lot of duplication. I'll see if it works out as the program gets more complicated.

undoStack->push (new AddActionCommand (actionModel, Action::Audio));

The QAbstractTableModel has a bunch of functions to add rows, columns and insert data, but I think these are all for edit-in-place tables. In my case the table is not editable, so I created a new function to add data in the form of my custom Action object.
QModelIndex ActionModel::addAction (Action *act)
int newRow = actions.count();
beginInsertRows(QModelIndex(), newRow, newRow);
return (index(newRow,0));

It stores the data inside the model and uses signals to alert the view to update the display. Note the return of the QModelIndex. It took me a long time to figure that out, and I'm still not sure it's the best way to do it but it allows me to select that ModelIndex in the View. Where it gets tricky is that the view may have been sorted, so the index(row) in the model is not necessarily the same in the view. Figuring out that I had to have a QModelIndex and map it between the original model and the proxy took me many hours. Finally making it work wasn't too bad, but even that has multiple ways to do it. So inside the undo command I do this:
AddActionCommand(ActionModel *model, Action::Type action_type, QUndoCommand *parent = 0)
: QUndoCommand(parent)
qDebug () << "AddActionCommand called"; action = new Action (action_type); actions = model; QModelIndex qmi = actions->addAction (action);

app->ui->action_list->setCurrentIndex (qmi);
setText(QObject::tr("Add thing"));

And the real magic happens in the actionlist:

ui->actionListView->setCurrentIndex (proxy->mapFromSource (qmi));
This all may be clear as mud since I left a lot out, but the summary is:
Keep track of your QSortFilterProxyModel
find the QItemIndex for your new data in the main model
use proxy->mapFromSource to turn your main model index into a proxy model index
finally call setCurrentIndex on your TableView with the proxy model index

Simple, right?

Friday, May 11, 2018

Signals and Slots

I'm going to start here, not because it's the first thing you need to know, but because I just lost most of a day getting bitten by Qt Creator's inconsistencies in this area.

On the surface I don't really see how signals and slots are significantly different that traditional callback functions, though the Qt docs claim they are better. Any time you start adding a language extension and another pre-compiler, I question the design. In this case I probably question the design of Qt and C++ equally.

Menus are special in Qt Creator. Instead of dragging and dropping components like you do for other things, they have a special editor right in the menubar and menus to let you add items and separators. When you create a menu item, you are actually creating a QAction that shows up in a special Action Editor window. If you right click on an action and select "Go to slot...", a helpful dialog will be displayed where you can pick the slot you are interested. Triggered is the default and most likely what you want. When you hit OK, a function and it's prototype are automatically created for you. This is very handy. I thought this also created some invisible link between the menu item and the function, but it does not. The link is simply the function name which must be of the form:
on_<widget name>_<signal name> ();
That's it, just name your function correctly, and the moc pre-pre-processor will connect it like magic.

Now here's where it gets weird, there is a special signal connection mode where you draw lines from one widget to another. If you are doing something simple like connecting a slider to a text box to display the value, it's great, you don't even have to write code. But if you need to actually write code to handle the signal, it gets a little confusing.

You can connect a signal to your window which looks like a ground connection on an electronics schematic. A dialog comes up that is similar to the "Go to slot..." one for menus. It lets you pick the signal to connect and you can define a new slot for it to connect to. I expected this to create the prototype and definition for the new slot automatically, but it does not. It really seems like a total waste of time, since you can just use the magical naming convention and not have to do anything in the UI.