From Qt4 to Qt5

I have a project where I need to build a macOS installation package. An additional requirement is to add a configuration utility built with Qt to the package.

Qt used in this project is quite old, 4.5.x, but the Qt site contains an archive of the old installation packages. Unfortunately, the Qt package for 4.5.3 fails to install on Sierra. The configure script in qt-mac-opensource-src-4.5.3.tar.gz fails with an error. The newest 4.8 release is supported only on mac OS X 10.7 "Lion".

/Users/sjl/qt/qt-mac-opensource-src-4.5.3/include/QtCore/../../src/corelib/global/qglobal.h:318:6: error:
      "This version of Mac OS X is unsupported"
#    error "This version of Mac OS X is unsupported"
     ^
1 error generated.
make: *** [project.o] Error 1

It is time to try a newer version, but this may bring with a set of other problems when building the application, as it is in Qt4 era. The transition document did not look too scary.

Qt page has good documentation on how handle the package.app packaging with Qt.

The package I'm working on is built with GNU make, and does not use qmake, which made the transition a bit harder, due to generated UI files containing references to, e.g., QtWidget/QAction, which appears in the headers generated from the <descr>.ui source files.

I had some problems compiling, encountering errors like the following:

In file included from /Library/Frameworks/QtCore.framework/Headers/QVariant:1:
In file included from /Library/Frameworks/QtCore.framework/Headers/qvariant.h:49:
/Library/Frameworks/QtCore.framework/Headers/qmap.h:205:22: error: reference to
      'bidirectional_iterator_tag' is ambiguous
        typedef std::bidirectional_iterator_tag iterator_category;
                     ^
/Library/Frameworks/QtCore.framework/Headers/qiterator.h:50:12: note: candidate
      found by name lookup is 'std::bidirectional_iterator_tag'
    struct bidirectional_iterator_tag;
           ^

These spurious errors were the because the Qt4 installation left some vestiges behind even the installation failed. After I moved /Library/Frameworks/Qt* away, these compilation problems disappeared.

I still had compilation problems with the header files, but studying a generated compilation line from the qmake run I noticed differences; the use of -F$HOME/Qt5.8.0/5.8/clang_64/lib flag to specify framework headers and -std=gnu++11 to specify a newer C++ standard. Adding these allowed compiling a single object file :)

After that the real work started.

Safeguards during the migration

I tagged stuff that I was not so sure about with /* TODO Qt5: explanation */, to remember what stuff I was not overly sure about. Naturally most of the use cases need to be tested anyway.

I started writing this post to detail what I changed, so that I could quickly look up how I handled a migration previously in the process. These notes proved very valuable during the process.

Changes required

Qt is now UTF8 by default

Use of QString::fromUtf8 should be plainly deleted, as newer Qt function by default on UTF8, which is nice.

Similarly, an error in specifying codecs:

no member
  named 'setCodecForCStrings' in 'QTextCodec'

The use can just be removed.

Many places used const char * strings; for the most part, changing these to QString did the trick.

no member named 'toAscii' in 'QString'

I changed these to toLocal8Bit() when the underlying code was about file names or user names, and to toUtf8().data() otherwise. Static ASCII strings work with these changes, at least, but the APIs in the C-library should really be Unicode aware (by, for example, using UTF-8).

UPDATE 2017-03-07

Using str.toUtf8() creates a temporary QByteArray; its lifetime is therefore tied to the lifetime of the variable it is assigned to. Using it as someFunc(str.toUtf8().data()) seems therefore a little bit undefined, as the returned array is only used for the duration of the call to .data(). At the very least, if there were multiple calls, like someFunc(foo.toUtf8().data(), bar.toUtf8().data()), the array pointed to by the first argument would be pointing to freed memory and therefore be gibberish. I would therefore not use the foo.toUtf8().data() at all, but instead rewrite the example as

QByteArray fooArr = foo.toUtf8();
QByteArray barArr = bar.toUtf8();
someFunc(fooArr.data(), barArr.data())

Better yet, rewrite the interfaces to use QStrings to as low a level as possible to avoid the string encodings altogether.

QStrings and QStringLists

Instead of

QStringList lst = QStringList::split(';', txt);
QStringList lst2 = QStringList::split("/", txt, true)

we should have

QStringList lst = txt.split(';');
QStringList lst2 = txt.split(';', QString::KeepEmptyParts);

Instead of QString upper and lower, we should use toUpper and toLower.

Instead of

int idx = txt.find(".foo", 0, false);

we should have

int idx = txt.indexOf(".foo", 0, Qt::CaseInsensitive);

QString::stripWhiteSpace should be replaced with QString::trimmed.

QString->replace with third argument as a bool should use Qt::CaseSensitive or Qt::CaseInsensitive instead.

Getting rid of Qt4 means ditching Q3* components

The project still used some components from the Qt3 stock. I guess that's part of the reason for the continued use of on older version of Qt4.

Some existing discussion on the removal of Qt3Support gave hope that this is still doable in the time allotted. Additional help is available in the official Qt3 to Qt4 porting guide.

Q3ListBox -> QListWidget

Instead of Q3ListBox::text(i), we should use QListWidget::item(i)->text().

Instead of Q3ListBox::removeItem(i), we should probably use delete QListWidget::item(i).

QListWidget has insertItems() instead of insertStringList().

Instead of Q3ListBox::insertItem() with a single argument, QListWidget::addItem() should be used.

Deleting items matching a string in a QListWidget:

foreach(QListWidgetItem* listItem, widget->findItems(str, Qt::MatchFixedString))
  {
    delete listItem;
  }

Q3ListView -> QTreeWidget

QTreeWidget allows multiple selection. In cases where the Q3ListView was used to give back a single item, and this is still valid, use currentItem in the QTreeWidget. Properly handling multiple selection may be useful later. In many places, the code disabled sorting for a Q3ListView; this is not necessary with QTreeWidget as it should not automatically sort.

Number of columns must be set with setColumnCount(), this might be necessary to add later. Instead of tree->addColumn, the column headers should be set with tree->setHeaderLabel or tree->setHeaderLabels (the former just construct a list of single header).

Instead of Q3ListView::selectedItem, we should use QTreeWidget::currentItem.

Instead of Q3ListView::setSelected, we should use QTreeWidget::setCurrentItem.

Instead of Q3ListView::firstChild, we should use QTreeWidget::topLevelItem(0).

Instead of Q3ListView::childCount, we should use QTreeWidget::topLevelItemCount.

Instead of Q3ListView::setSortColumn(col), we should use QTreeWidget::sortItems(col, Qt::AscendingOrder). I'm not sure whether the semantics are exactly to same, and we might need a handler to sort the the widget when new items are added. In case of Q3ListView::setSortColumn(-1), we can just delete the call.

QTreeWidget has itemSelectionChanged() instead of selectionChanged(item *). There are very likely similar issues in other signals, luckily the program complains about misplaced slots and signals on startup.

Iterating over all items in QTreeWidget (QTreeWidgetItemIterator doc):

QTreeWidgetItemIterator it(widget);
while (*it)
  {
    QTreeWidgetItem *item = *it;
    // do something with item
    it++;
  }

Iterating over the first level of items:

for (int ii = 0; ii < widget->topLevelItemCount(); ii++)
  {
    QTreeWidgetItem *currItem = widget->topLevelItem(ii);
    // do something.
  }
Q3ListViewItem -> QTreeWidgetItem

Instead of Q3ListViewItem constructor with multiple arguments to stand for the labels in the columns, QTreeWidgetItem takes a QStringList. Unfortunately, when a preceding item is specified, the conveniency list can't be passed, and the columns need to set manually with item->setText(col, text).

QList<QString> list;
list <<  col1 << col2 << col3 << col4 << col5 << col6
    << col7 << col8;

QTreeWidgetItem *item = new QTreeWidgetItem(view, prev);
int idx = 0;
foreach(QString str, list) {
  item->setText(idx++, str);
}

Previously the code had a pattern where the Q3ListViewItem objects were initialized with this pattern:

if (lv->lastItem())
  new QTreeWidgetItem(lv, lv_view->lastItem(), str);
else
  new QTreeWidgetItem(lv, str);

With QTreeWidget objects, this should not be necessary, as the item gets appended to the list of top-level items.

QTreeWidgetItem *item = new QTreeWidgetItem(tw);
item->setText(0, str);

Instead of Q3ListViewITem::setRenameEnabled() we should probably set the flag Qt::ItemIsEditable. Catching the change in the data of the item is still TODO. Probably we need to use itemChanged signal instead of Q3ListViewITem::itemRenamed, and hope we do not encounter problems getting those signals when we are modifying the tree ourselves.

Q3ListViewITem::sortChildItems(col, true) should be QTreeWidgetItem::sortChildren(col, Qt::AscendingOrder).

Q3ListViewITem::setPixmap should be QTreeWidgetItem::setIcon.

Q3ListViewITem::setOpen should be QTreeWidgetItem::setExpanded.

Q3ListViewITem::listView should be QTreeWidgetItem::treeWidget.

Q3ListViewITem::startRename should be QTreeWidget::editItem.

Iterating over the children of a QTreeWidgetItem:

for (int ii = 0; ii < item->childCount(); ii++)
  {
    QTreeWidgetItem *currItem = item->child(ii);
    // do something.
  }

Deleting all children of QTreeWidgetItem:

qDeleteAll(item->takeChildren());

Instead of Q3ListViewITem::insertItem, we should use QTreeWidgetItem::addChild.

Instead of Q3ListViewITem::takeItem, we should use QTreeWidgetItem::removeChild.

Q3ButtonGroup and Q3GroupBox to QGroupBox

The only bigger issue here is that QGroupBox does not have selectedId(), instead, the radio buttons need to be either identified and queried separately, or enumerated through. In practice this is not a big issue, and in many places reduces use of magic, when the radio button was queried by its name instead of its location among other such radio buttons.

Q3ComboBox to QComboBox

QComboBox insertItem() requires index of the inserted item as an argument. Using container->count as argument should be ok, although naturally the correct index works as well :)

QComboBox::currentItem should be replaced with QComboBox::currentIndex.

QComboBox::text(i) should be replaced with QComboBox::itemText(i).

QTabWidget

QTabWidget::page should be replaced with QTabWidget::widget.

QTabWidget::currentPageIndex should be replaced with QTabWidget::currentIndex.

QTabWidget::setCurrentPage should be replaced with QTabWidget::setCurrentIndex.

(QTabWidget *)container::removePage(tab) should be replaced with container::removeTab(container->indexOf(tab)).

Layouts

no member named 'insert' in 'QGroupBox'

Instead of insert(), we should use addWidget() to a *Layout object, and set that to the QGroupBox.

QVBoxLayout *vbox = new QVBoxLayout;
vbox->addWidget(radio);
groupBox->setLayout(vbox);
Q3HBoxLayout *layout1 = new Q3HBoxLayout(0, 0, 6, "layout1")

The extra arguments can pretty safely be ignored. Spacing and margins can be set with setters separately.

Other changes and renamings

Q3Frame to QFrame.

The documentation indicates that perhaps what will be missed is frame around the butttons, and at this point this is not critical.

The Q3Frame took object name as a parameter, I guess this can be safely deleted when moving it to QFrame. It can be set with setObjectName() later, if necessary.

Q3TextEdit to QTextEdit.

Q3Table to QTableWidget

Error about missing member:

no member named
      'GuiClient' in 'QApplication'
Type type = QApplication::GuiClient

Fix is pretty non-controversial, the type has been nuked as the QApplication was split to QCoreApplication and QApplication, so just delete use from the app.

    no member named 'setPixmap' in 'QPushButton'
cip_left->setPixmap(QPixmap(":/left_arrow.png"));

From QIcon documentation, I think the following should be equivalent

button->setIcon(QIcon("open.xpm"));

container->label(i) should be replaced with container->tabText(i).

QWidget->setCaption should be replaced with QWidget->setWindowTitle.

QHeaderView->setMovable should be replaced with QHeaderView->setSectionsMovable.

QHeaderView->setResizeMode should be replaced with QHeaderView->setSectionResizeMode.

QSplitter::setResizeMode should be replaced with QSplitter::setStretchFactor. Described in the QSplitter compatibility document, also the QSplitter enums have disappeared, so the semantics of the call are different.

splitter->setResizeMode(firstChild, QSplitter::KeepSize);
splitter->setResizeMode(secondChild, QSplitter::Stretch);

should be rewritten as

splitter->setStretchFactor(splitter->indexOf(firstChild), 0);
splitter->setStretchFactor(splitter->indexOf(secondChild), 1);

Instead of QDir::convertSeparators we should use QDir::toNativeSeparators.

With

QMap<QTreeWidgetItem*, MyDataObject>::Iterator it;

we should use it.value() instead of it.data(), as described in the Qt4 doc of QMap iterators.

new QPushButton(this, "help"); should be rewritten as new QPushButton("help", this);, the text is now the first argument.

Use of setCentralWidget() in the app was weird, and caused the program to have a really funky layout. I wonder why it was not a problem with Qt4.

Current status

After working at it for a few days, the code compiles and the UI mostly looks ok. There are some problems with signals not assigned correctly to propagate changes, but overall things are progressing nicely.

links

social