Thursday, July 31, 2014

PalMap - Get Palestine Back on Google Maps

If you are a user of Google Maps, and you care about Palestine, and you get disgusted by the word "Israel" appearing on the map, here is a solution.



PalMap is a javascript library to get Palestine back on Google Maps. It does not require much. Just copy palmap.js file and img/palmap folder, add two lines in your html file, and Palestine is back on Google Maps!


PalMap is still in an early stage, aiming to reshape this part of the map and get all the names back to their original ones.

For more info about the library, please go to PalMap repository on GitHub: https://github.com/MahmoudAdly/palmap

Sunday, May 18, 2014

Installing Multiple Redis Versions on Ubuntu

Sometimes, for testing purpose as example, a developer needs to install and run different versions of Redis Server for different projects. And since I do not like wasting time on haching config files and pid files, this is the easiest way I use to run the desired Redis Server version.

It is as simple as downloading, compiling, and running the compiled server:
$ wget http://download.redis.io/releases/redis-2.8.9.tar.gz
$ tar xzf redis-2.8.9.tar.gz
$ cd redis-2.8.9
$ make
$ ./redis-server


If the version is older, you may not find it on redis.io. So try a URL like:
http://redis.googlecode.com/files/redis-1.2.6.tar.gz


That's it. Enjoy!

Monday, September 2, 2013

[Qt] Custom QListView delegate with word wrap

Using QListView with a custom item layout has been a very popular demand by many developers. The problem with using  a custom delegate was having some basic features like word wrap depending on the allowed list size.

Here is a screenshot from a project prototype I've been working on. The text will wrap around to fit in multiple lines depending on text length and list view width.


My custom delegate is about using two important methods: QFontMetrics::boundingRect() and QPainter::drawText() .

The first method will return the minimum Rect needed to draw the given text using the given origin and font. The second method draws the given text within the given Rect. Note that the flag Qt::TextWordWrap is given to both methods when called.

The code is simple:
  • Get system default font.
  • Process the font to suite your needs. In my case: I have a normal font and another bold one for the header.
  • Create a QFontMetrics instance for each font.
  • Use the QFontMetrics instances to calculate the Rect needed to draw that text given the needed flags and option.rect.width() that indicates the allowed width inside the list view.
  • Calculate the total area needed to return it in the sizeHint() method of your list view delegate. For example: I return the total heights of the two calculated rects -header and subheader- and the item width from option.rect.width(). And I prefer to add some extra padding.
  • Almost the same calculations apply in the paint() method of the list view delegate. Then the calculations are used in drawText().
Here is my ListVewDelegate class. It is a basic class with a basic drawing for header and subheader text. I left a space for drawing an icon. I also made a simple highlighting that needs more improvement and styling.

======
[listviewdelegate.h]
#ifndef LISTVIEWDELEGATE_H
#define LISTVIEWDELEGATE_H

#include <QStyledItemDelegate>
#include <QLabel>
#include <QPainter>
#include <QApplication>

class ListViewDelegate : public QStyledItemDelegate
{
public:
    enum datarole { HeaderRole = Qt::UserRole + 100, SubheaderRole};

    ListViewDelegate();
    ~ListViewDelegate();

    void paint(QPainter *painter,
               const QStyleOptionViewItem &option,
               const QModelIndex &index) const;

    QSize sizeHint(const QStyleOptionViewItem &option,
                   const QModelIndex &index ) const;

    static QSize iconSize;
    static int padding;
};

#endif // LISTVIEWDELEGATE_H

======
[listviewdelegate.cpp]
#include "listviewdelegate.h"

QSize ListViewDelegate::iconSize = QSize(60, 60);
int ListViewDelegate::padding = 5;

ListViewDelegate::ListViewDelegate()
{
}

ListViewDelegate::~ListViewDelegate()
{
}

QSize ListViewDelegate::sizeHint(const QStyleOptionViewItem &  option ,
                                        const QModelIndex & index) const
{
    if(!index.isValid())
        return QSize();

    QString headerText = index.data(HeaderRole).toString();
    QString subheaderText = index.data(SubheaderRole).toString();

    QFont headerFont = QApplication::font();
    headerFont.setBold(true);
    QFont subheaderFont = QApplication::font();
    QFontMetrics headerFm(headerFont);
    QFontMetrics subheaderFm(subheaderFont);

    /* No need for x,y here. we only need to calculate the height given the width.
     * Note that the given height is 0. That is because boundingRect() will return
     * the suitable height if the given geometry does not fit. And this is exactly
     * what we want.
     */
    QRect headerRect = headerFm.boundingRect(0, 0,
                                             option.rect.width() - iconSize.width(), 0,
                                             Qt::AlignLeft|Qt::AlignTop|Qt::TextWordWrap,
                                             headerText);
    QRect subheaderRect = subheaderFm.boundingRect(0, 0,
                                                   option.rect.width() - iconSize.width(), 0,
                                                   Qt::AlignLeft|Qt::AlignTop|Qt::TextWordWrap,
                                                   subheaderText);

    QSize size(option.rect.width(), headerRect.height() + subheaderRect.height() +  3*padding);

    /* Keep the minimum height needed in mind. */
    if(size.height()<iconSize.height())
        size.setHeight(iconSize.height());

    return size;
}

void ListViewDelegate::paint(QPainter *painter,
                                    const QStyleOptionViewItem &option,
                                    const QModelIndex &index) const
{
    if(!index.isValid())
        return;

    painter->save();

    if (option.state & QStyle::State_Selected)
        painter->fillRect(option.rect, option.palette.highlight());

    QString headerText = index.data(HeaderRole).toString();
    QString subheaderText = index.data(SubheaderRole).toString();

    QFont headerFont = QApplication::font();
    headerFont.setBold(true);
    QFont subheaderFont = QApplication::font();
    QFontMetrics headerFm(headerFont);
    QFontMetrics subheaderFm(subheaderFont);

    /*
     * The x,y coords are not (0,0) but values given by 'option'. So, calculate the
     * rects again given the x,y,w.
     * Note that the given height is 0. That is because boundingRect() will return
     * the suitable height if the given geometry does not fit. And this is exactly
     * what we want.
     */
    QRect headerRect =
            headerFm.boundingRect(option.rect.left() + iconSize.width(), option.rect.top() + padding,
                                  option.rect.width() - iconSize.width(), 0,
                                  Qt::AlignLeft|Qt::AlignTop|Qt::TextWordWrap,
                                  headerText);
    QRect subheaderRect =
            subheaderFm.boundingRect(headerRect.left(), headerRect.bottom()+padding,
                                     option.rect.width() - iconSize.width(), 0,
                                     Qt::AlignLeft|Qt::AlignTop|Qt::TextWordWrap,
                                     subheaderText);

    painter->setPen(Qt::black);

    painter->setFont(headerFont);
    painter->drawText(headerRect, Qt::AlignLeft|Qt::AlignTop|Qt::TextWordWrap, headerText);

    painter->setFont(subheaderFont);
    painter->drawText(subheaderRect, Qt::AlignLeft|Qt::AlignTop|Qt::TextWordWrap, subheaderText);

    painter->restore();
}

===========

Using it is pretty simple:

[mainwindow.cpp]
QStandardItemModel *model;  
model = new QStandardItemModel();  

ListViewDelegate *listdelegate;  
listdelegate = new ListViewDelegate(); 

ui->listView->setItemDelegate(listdelegate);  
ui->listView->setModel(model); 

QStandardItem *item = new QStandardItem();  
item->setData("Some Header",ListViewDelegate::HeaderRole);  
item->setData("Some long description text.",ListViewDelegate::SubheaderRole);  
model->appendRow(item);