GADA Project Gestor Avanzado de Dietas y Alimentación

31May/140

Versión 1.2

Como ya comenté la semana pasada, hoy he subido la versión 1.2 de Gada. Esta incorpora novedades sobre todo en el aspecto gráfico y en la traducción del programa a 3 idiomas (castellano, inglés y catalán). A pesar del cambio en el aspecto, se trata de una versión menor, ya que no ha habido cambios de funcionamiento ni nuevas características.

Para la versión 1.3, tengo programado introducir las estadísticas (con gráficos incluidos) y mejorar los informes. También se depurará el programa en caso de que hubiera errores.

28Nov/130

Tip of the day: Select multiple rows in QTableView (programatically)

Some times I have need to select several rows in QTableView when one of this rows have special characteristics. In my particular table I represent items as lines and some lines can be part of a group. I cannot represent this information as a QTreeView due to external issues.

Since I have several lines represented in the same level that the group line I want to stand out all them when I select the group line. QTableView has not any method to select a list of lines so we have to workaround to fulfil this.

There are some ways to do it and I will explain two of them that works perfectly:
Using a loop with QItemSelection and the number of the row:
In this case we use a loop to select each row that fulfil the condition and merge the QItemSelection of the QTableView with the other that we have. Finally, we select all selectedItems in the selectionModel of the table.

QModelIndexList itemSelection = selectionModel()->selectedRows();
int currentRow = itemSelection.first().row();
QItemSelection selectedItems = selectionModel()->selection();
 
while (condition to stop the loop)
{
    selectRow(currentRow++);
    selectedItems.merge(selectionModel()->selection(), QItemSelectionModel::Select);
}
 
selectionModel()->clearSelection();
selectionModel()->select(selectedItems, QItemSelectionModel::Select);

Using a list of QModelIndex:
The other way spends less lines and uses QModelIndex to select the range of rows we want to select.

QItemSelection selectedItems;
int currentRow = selectionModel()->selectedRows().first().row();  //QModelIndexList is an ordered list
QModelIndex topLeft = pointerToMyModel->index(currentRow, 0, QModelIndex());
 
while (condition to stop the loop)
    currentRow++;
 
QModelIndex bottomRight = pointerToMyModel->index(currentRow-1, pointerToMyModel->columnCount()-1);
selectedItems.select(topLeft, bottomRight);
selectionModel()->clearSelection();
selectionModel()->select(selectedItems, QItemSelectionModel::Select);

14Oct/130

Tip of the day: Promoting classes in Qt

The importance in the parameters order at constructors class.

One powerful thing that Qt gives us when we design graphic user interfaces is the possibility to promote any widget of the GUI to our own one. This action not only allows us to use our widgets in the QtDesigner, but we also can connect the signals and slots of it. There are a lof of reasons to create widgets that can be used to promote base QWidget in the GUI, for example: table views that have customized functionality or where we want to use Delegates to modify the graphic appearance.

How to create it and use it is described here. What I want to explain in this post is the importance of the order in the constructor parameters. When we use QtCreator (or not) to create our class, it creates a default constructor as follows:

explicit MyClass(QWidget *parent = 0);

If we want to use this widget both in QtDesigner and outside it, it is important to add the necessary parameters in the constructor at the end, since the call in the ui_MyClass.h file will be done with the parent widget as parameter. To illustrate it:

Wrong:

explicit MyClass(int _someValue = 0, QWidget *parent = 0);

This code will fail at compilation if we promote any widget inside the QtDesigner to MyClass. The reason is that the call inside the generated ui_MyClass.h will be:

myClass = new MyClass(parentWidget);


As we can see, wrong order in the parameters causes not compilation of the application.

Important fix: To do it right (in Qt standard):

explicit MyClass(int _someValie, QWidget *parent = 0);
explicit MyClass(QWidget *parent = 0);

The code that fulfils the Qt Coding standard as Tobias Koening said in first comment, the right is to add a default constructor only with the QWidget parameter and if we need to a default second value we need to initialize it inside this constructor.

To do it right (but in non Qt standard):

explicit MyClass(QWidget *parent = 0, int _someValie = 0);

Perhaps it is basic, but I have need some coding time to take it into account when I create my own widgets (mainly because of I don't use QtDesigner)

16Sep/130

Log library with Qt (QLogger)

Today I present the QLogger library.

To store logs of an application is a good practice in order to discover quickly where the problem is when it crashes or it has a wrong behavior. I have created a new one because the use of QDebug is restricted to debug compilations. In addition, once I have started to do this task, I add some characteristics I think they are important and useful. This are:

  • Automatically cut of the log file when it exceeds 1 MByte of size: I have decided to store all logs we want by renaming the old one with the date and time of the log that makes the file cut.
  • Thread-safe: It is important that a log library could be used in multithreading applications.
  • Multiple module configuration: We can configure the log library to store logs in several files structured in modules. In addition, we can choose to print the messages of several modules in the same file. It allows us to bring together the logs of related functionalities of our application.
  • Log levels: In the library have been defined several log levels. This levels are conditionals to store all logs in a file, so if we would like to have different modules in the same file, they will have the same level. Perhaps in next releases I will solve it, but for me is not clear that applications in production should have different log levels.

The library is divided in two classes: QLoggerManager and QLoggerWriter. The first one manages the destination files where the logs of each module will be printed. The second one is the class that stores the message and it prints the log with the current date and time, the module and the level used for this file.

To start this instance we only have to type the following code:

QLoggerManager *manager = QLoggerManager::getInstance();
manager->addDestination("file.log", QStringList("ModuleName"), QLogger::LogLevel);
 
//To print a log message
QLog_Trace("ModuleName", "Message: " + msg);

As we can see in the addDestination() call, we are using a namespace called QLogger. All classes, methods and variables of the the library are delcared inside, so it is necessary to use it. To write a log message we can use the different QLog_ existing methods. For more information about how it runs, we can look up in the source code: it is doxygen documented.

The source code can be compiled as library or added to the project.

The code can be found here: QLogger at GitHub!

6Ago/130

Tip of the day: Redirect QDebug to a file

The tip of today does not represents a general output for the application state in real time, since QDebug only works in debug compilatos.

General usage

Some times we prefer to analyse the our application debug messages in a separated file. It is useful when we want to work with Qt-Creator in full code window and we have two monitors (one for coding and another for application execution and debug file). To redirect the output we have to type in Qt 5.1:

void customMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
   Q_UNUSED(context);
 
   QString dt = QDateTime::currentDateTime().toString("dd/MM/yyyy hh:mm:ss");
   QString txt = QString("[%1] ").arg(dt);
 
   switch (type)
   {
      case QtDebugMsg:
         txt += QString("{Debug} \t\t %1").arg(msg);
         break;
      case QtWarningMsg:
         txt += QString("{Warning} \t %1").arg(msg);
         break;
      case QtCriticalMsg:
         txt += QString("{Critical} \t %1").arg(msg);
         break;
      case QtFatalMsg:
         txt += QString("{Fatal} \t\t %1").arg(msg);
         abort();
         break;
   }
 
   QFile outFile("LogFile.log");
   outFile.open(QIODevice::WriteOnly | QIODevice::Append);
 
   QTextStream textStream(&outFile);
   textStream << txt << endl;
}

Then, in main function we have to add the following line:

qInstallMessageHandler(customMessageHandler);

There are two small changes to run this code in Qt 4:

  1. Change the parameters in name function:
    void customMessageHandler(QtMsgType type, const char *msg)
  2. Change the name of the function called:
  3. qInstallMsgHandler(customMessageHandler);

Redirecting QDebug with custom class

If we want to redirect the output of our custom class to QDebug, it is necessary to declare the operator << of QDebug type as a friend method:

friend QDebug operator << (QDebug d, const Protocol &p);
1Ago/130

Customize window title styles and buttons

Brief introduction

A lot of times I have seen in Qt Forums questions about how to customize the predefined window title buttons or the styles of it. First of all is necessary to explain that this is not a trivial issue since these controls and styles are defined by the API of the OS.

In the case of Windows, the title bar is created by Windows Manager so it is impossible to modify it. If we want a personalized titlebar or modify the styles of the border of the window or whatever, it is necessary to remove it and then, create our custom QWidget with our own titlebar. Before start to write our own custom QWindow is necessary to know that is not an easy task. We have to take into account that our QWidget must fulfil the following requirements:

  • We need to catch some mouse events: press, release, move... In order to know where to move or how to resize de window.
  • We need to implement the top-right buttons behavior: close, minimize, maximize, restore...
  • We need to manage the include of the central widget that will has the main functionality.
  • We could want to add a top-left menu and the title of the window. It is necessary to implement too.

The good things that provide us a custom titlebar is the possibility to add new buttons with extended functionality, customize the top-left menu with our own options and definitely, to have our application window style unified.

The best way to learn Qt goes trough program, program and program. In this case, create our custom window title and all the functionality that it implies is a good way to learn several important things of Qt window management and help us if we would like to obtain the certification. The creation of this widget will give us knowledge about:

  • Window and mouse event handling.
  • Qt system coordinates.
  • QWidget and QObject: QToolButtons, QPushButtons, QActions...
  • Signals and slots.
  • Layaout handling.
  • Manage resources and styles.

My custom QWindow

I have created my own QWindow with a customized titlebar. It has a minimalist aspect and it can be modified as much as you want. If you only want to use it without modify it, this solution that I present fulfil all you need in a customized QWindow.

The class I have created has a method to set the central widget (that sets the title of the window) and another to set the menu of the top-left button. One important question is if we want to connect the signals of the top-left menu with slots or other signals of our CentralWidget. Well, since we create our QMenu with QActions outside the QWindow and we set it the of creating the custom titlebar, we can connect the signals that emits the QActions inserted in this menu. Where we define this connects depends on our preferences.

A connect links two objects vy their memory addresses, so is not important where we define the connect but it is which objects we want to link. In conclusion, we can connect the signals of the actions of our menu with the slots of our central widget outside the QWindow and then set the menu and the central widget to it without problems.

The code is composed by one class named QWindow and you can find a link to the code at the end of the post. Following I explain briefly how to use it (although the code is commented):

(Steps 1 to 3 and 5 to 7 are not extrictly sorted)

  1. Create an instance of our CentralWidget (named i.e. centralWidget).
  2. Create a QMenu with QActions or get one created (named i.e. titlebarMenu).
  3. We create an instance of the class QWindow.
  4. Connect the signals of the QMenu to the slots of the CentralWidget (or to other places).
  5. We use QWindow::setCentralWidget(centralWidget, "Our applications"); to set the central widget
  6. We use QWindow::setTitlebarMenu(titlebarMenu, "path/to/menu/img.png"); to set the menu and an icon to it. The menu icon is optional.
  7. We use QWindow::setTitlebarMode(QWindow::TitleMode); with the option of TitleMode that we want. The title mode defines wich buttons and behavior we want to have in the window. It allows us to create dialogs, windows or other window types without any effort.
  8. IMPORTANT: If we have a close button or a close action in our centralWidget, it is necessary to connect it to the QWindow instance to close it. There are two ways to do it:
    1. Connecting the signal of the centralWidget to QWindow::close().
    2. Emit a signal CentralWidget::cancelled() that it is automatically connected inside QWindow to close() slot.
  9. Call QWindow::show(); and enjoy!

Note: If we didn't do the 8th step, when we click the close/cancel button, only the central widget will be closed.

Link to QWindow.

23Jul/130

Fusion of LXDE and Razor-Qt

From the blog "La mirada del replicante" we get news about Razor-Qt lightweight destkop and its fusion with LXDE. What starts as a colaboration between both teams as finally result in a fusion of both projects.

In the mail sent to the community via Google+, Jerome Leclanche (Razor-Qt developer) says that despite some improvements have to be done in Razor-Qt is quite complete. It also note that the LXDE team has done efforts and advances in the development.

The most important part of the email is that refers to the future. Leclanche has said that then of colaborate together, they have decided to merge the best parts of LXDE and Razor-Qt to create a unique project named LXDE-Qt. Despite some parts of the new destkop project will be coded from the start they hope to enjoy the maximum amount of code possible.

Finally, Leclanche express that the future of Razor-Qt as independent project finishes with the last version to be announced: 0.6.6; it realease will include some features of LXDE as part of the fusion.

The mail can be found here.

8Jul/130

Using ProxyModel’s in Qt

One thing I have discovered while I was doing the XML editor was to use, in a basic way, the ProxyModel's. In my case it have been used to filter the data that I want to show in the TreeView. When I use trees I don't want to show the leafs of the nodes since the data edition wants to be shown ordered by node.

A ProxyModle is a layer between the view and the real (and/or customized) model of data. It allows us to represent the data of our model in a QTableView, QTreeView, QListView or CustomView without changing the model. In this specific case I have my own customized model that inherits from QStandardItemModel and shown in a QTreeView.

In the following code I explain how to hide this nodes and how to implement different filters and sort patterns to use with the ProxyModel selected. To done it more real, I will use the custom tree structure and a inherited QSortFilterProxyModel.

For filter the leafs of a node I have overloaded the filterAcceptsRow method. This method specifies if a row defined by the current source_row and the index (of the source model [tree model]) given. It is important to take into account that the given index refers to the parent, so the first time that goes inside the method, the index will be invalid. This is the main reason to filter this exceptional case.

bool TreeFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex &index) const
{
    if (index.isValid())
    {
        TreeModel *model = dynamic_cast<TreeModel*>(sourceModel());

        if (model)
        {
            TreeItem *item = model->itemFromIndex(index);

            if (item and item->child(source_row)->hasChildren())
                return true;
        }

    }
	else if (source_row == 0)
		return true;

    return false;
}

For other filters we can use the following methods. For example for organize the childs of a node alphabethicaly. I will show it in next posts.

4Jul/130

Tip of the day: Center text in QLabel

Some times when we are styling our widgets on Qt we need to set the text of a QLabel centered. Well, the tip of today is really easy, but I always forget the necessary text to do it. With this post I know where to finde it without surfing into my source code.

To center the text of a QLabel we need to edit the styles adding:

QLabel
{
    qproperty-alignment: AlignCenter;
}
25Jun/130

Creating QTreeView from XML files

Some time, we need to set a configuration file when we create our applications. To do it it is usually to create a XML file organized in nodes and subnodes. The problem is that this configuration file is not intuitive for a normal user. To solve this problem I have created a XML configurator that reads an XML file and show the structure in a QTreeView. In addition, at right side we have a panel where set the values of the tags for each node.

Before publish the application I would like to explain how I have created the QTreeView for XML files for who need it in the future.

Structure

The treeview is structured in three classes: QTreeView, TreeModel and TreeItem. The first one belongs to Qt, and the other two are external. TreeModel inherits from QStandardItemModel and TreeItem from QStandardItem. I have created an inherited class from QStandardItem because I need more information than the last give us. I call setModel(...) in the constructor of the class that reads the XML file and setItem(...) only when I insert a new node, not the leafs. The leafs are inserted in each respective node using appendRow(...). I read the XML file recursively and I create a node (new TreeItem) if it has childs, else I apppend the leaf (created also using TreeItem) to it father:

void MainWindow::readXml(const QString &xmlContent)
{
  QDomDocument doc;
  QDomElement root;
  QDomNode dNode;
  TreeItem *item;
 
  doc.setContent(xmlContent);
  root = doc.documentElement();
 
  dNode = root.firstChild();
  item = new TreeItem(root.tagName());
 
  printNodes(dNode, item);
}
 
void MainWindow::printNodes(QDomNode dNode, TreeItem *item)
{
  do
  {
    int totalOfChilds = dNode.childNodes().size();
 
    if (dNode.nodeType() != QDomNode::CommentNode)
    {
      if (totalOfChilds == 0)
      {
        if (dNode.nodeType() == QDomNode::TextNode)
          item->setValue(dNode.nodeValue());
        else
        {
          TreeItem *subItem = new TreeItem(dNode.nodeName());
 
          for (int i = 0; i < dNode.attributes().size(); i++)
          {
            subItem->addAttribute(dNode.attributes().item(i).nodeName(),
                                  dNode.attributes().item(i).nodeValue());
          }
          item->appendRow(subItem);
        }
      }
      else
      {
        TreeItem *item2 = new TreeItem(dNode.nodeName());
 
        for (int i = 0; i < totalOfChilds; i++)
          if (dNode.childNodes().size() > 0 && i == 0)
            printNodes(dNode.childNodes().at(i), item2);
 
        item->appendRow(item2);
      }
    }
    dNode = dNode.nextSibling();
  }
  while (!dNode.isNull());
}

As you can see I filter the nodeType() to know if it is text and consecuently, set the value of the tag. If it is not text it implies that corresponds to a leaf, it corresponds to attributes of the leaf and I store it in the TreeItem class.

Next time when I publish the application I will also distribute the while code and an XML example to run it.