Мы — долго запрягаем, быстро ездим, и сильно тормозим.
www.lissyara.su —> статьи —> FreeBSD —> Programming —> PerlQT4 (Использование С++ классов в Perl скриптах)

HOWTO по работе с С++ классами из Perl'a

Автор: ProFTP.


Описаний, как импортировать функции из С, достаточно много, а вот как использовать С++ классы, я увидел в виде кратких описаний только в XS Cookbook [1, 2] и небольшой статье «Gluing C++ And Perl Together» [4]. Пример использования С++ класса в Perl'е из XS Cookbook в сокращенном варианте перекочевал в perlxstut. Кроме того, на CPAN можно найти модули, импортирующие С++ классы и имеющие файлы импорта, которые можно использовать в качестве примера, такие, как Boost-Graph, Lucene, Search-Xapian, Однако они не покрывают многих вариантов подключения классов.

Чтобы не умножать сущности без надобности, т.е. не писать новых классов, которые потом нигде не пригодятся, воспользуемся уже готовой библиотекой QtCore из Qt4. Для удобства будут приведены части заголовочных файлов этой библиотеки, но все примеры будут работоспособны при подключении реальной библиотеки. Кроме того, использование файла perlobject.map [3] позволяет не писать заново описание объектов.

Преимущества:

1) Быстрая скорость разработки, так как perl язык с динамической типизацией (dynamically typed), C++ и в какой-то мере Java — вы можете очень просто порождать ошибки, связанные со статическим типом переменной, в которую вы помещаете динамическое значение совместимого, но не идентичного типа.
2) Быстрая инициализация классов, загрузка кода на этапе компиляции (require), так же наследование (parent) и методов класса (SelfLoader и AutoLoader etc).
3) Кроссплатформенность программ, вероятность запуска на любой машине.
4) Самое большое количество модулей на cpan.org (16292 modules).
5) Автоматическая сборка мусора.

Начальные данные для любого модуля

Начальные данные для любого модуля можно найти в уже упомянутой статье [4], однако они столь ценны и необходимы для раскрытия темы, что заслуживают отдельного рассмотрения. Скелет любого модуля можно написать вручную, но легче и быстрее это сделать командой h2xs -An имя_модуля. В результате будет создан каталог для модуля с необходимыми файлами, содержимое которых детально описано в "Программировании на Perl" [5]. Дав команду h2xs -An QtCore, получим скелет модуля. В полученный каталог QtCore необходимо скопировать perobject.map (названия всех файлов приводятся отностительно каталога QtCore). Созданный файл Makefile.PL надо привести к следующему виду:

use 5.008;
use ExtUtils::MakeMaker;

$CC = 'g++';

WriteMakefile(
    NAME                => 'QtCore',
    VERSION_FROM        => 'lib/QtCore.pm',
    PREREQ_PM           => {}, # e.g., Module::Name => 1.1
    ($] >= 5.005 ?
        (ABSTRACT_FROM  => 'lib/QtCore.pm',
        AUTHOR          => 'A. U. Thor <author@localdomain>') : ()),
    LIBS                => [''],
    DEFINE              => '',
    CC                  => $CC,
    LD                  => '$(CC)',
    INC                 => '',
    # OBJECT            => '$(O_FILES)',
    XSOPT               => '-C++',
    TYPEMAPS            => ['perlobject.map'],
);

Выделенные жирным строки надо добавить именно для того, чтобы Perl заработал с С++.
Кроме того, важно исправить файл QtCore.xs, который будет содержать импортируемые в Perl функции:

#ifdef __cplusplus
extern "C" {
#endif
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#ifdef __cplusplus
}
#endif

Для наглядного примера создадим в этом файле класс, который будет хранить, допустим, версию программы. Для этого добавим класс после подключенных заголовочных файлов перед строкой "MODULE = QtCore PACKAGE = QtCore":

class QtCore {
public:
    QtCore(){ vers = 0.001; };
    ~QtCore(){};

    double ver(){ return vers; };
    void setVer(double v){ vers = v; };

private:
    double vers;
};

Работа с обычными функциями, конструктором и деструктором уже предусмотрена в Perl XS, поэтому после объявления модуля и пакета можно использовать краткие объявления функций (также возможны комментарии в perl-стиле, которые мы и будет использовать):

MODULE = QtCore        PACKAGE = QtCore

=comment
явное указание использовать прототипы функций позволяет избежать некоторых
ошибок при передаче параметров в функции, но в тоже время не дает упростить
использование этих функций. Например, если функция получает 2 параметра,
а ваши данные для нее хранятся в массиве @aa,
то ее необходимо вызывать как my_func($aa[0], $aa[1]).
Тогда как при указании "PROTOTYPES: DISABLE" можно эту функцию вызвать как my_func(@aa).
=cut

PROTOTYPES: ENABLE

=comment
XS распознает только 1 конструктор -- "new". Если их
будет больше, то каждый будет нуждаться в подробном описании.
=cut

QtCore *
QtCore::new()

=comment
методы класса
=cut

double
QtCore::ver()

void
QtCore::setVer(v)
    double v

=comment
В подавляющем большинстве случаев такого вызова деструктора хватает.
Однако если вы хотите явно освободить память, уничтожить зависимые объекты и т.п.,
то пример вызова деструктора найдете в XS Cookbook [2, см. ArrayOfStruct].
=cut

void
QtCore::DESTROY()

Кроме того, для вызова класса следует указать Perl'у, чем является класс QtCore, т.е. как работать с этим типом данных, для чего создадим файл typemap со следующим содержимым:

TYPEMAP
QtCore *		O_OBJECT

Описания встроенных типов данных можно посмотреть в typemap.xs [6], а описание O_OBJECT находится в файле perlobject.map. Если не добавлять этот файл, то придется все дополнительные типы данных полностью описывать самим в файле typemap (пример полного описания приводится ниже). После этого остается внести изменения в файл lib/QtCore.pm, который и будет подключаться в конечных скриптах. Поскольку QtCore.pm будет объектом и ничего экспортироваться из него не будет, то следует из этого файла убрать всё относящееся к модулю Exporter. Для импорта внешних функций можно использовать как XSLoader, так и более старый DynaLoader (здесь используется второй, т.к. к нему чаще обращаются).

package QtCore;

use 5.008;
use strict;
use warnings;

require DynaLoader;

our @ISA = qw(DynaLoader);
our $VERSION = '0.01';

bootstrap QtCore $VERSION;

1;

Чтобы собрать полученный модуль, выполним команды perl Makefile.PL && make.

Всё сделанное необходимо протестировать. В модуле уже есть каталог t/ для тестовых скриптов, которые, однако, расчитаны только на то, чтоб по команде make test вывести "имя_скрипта.....ok". Этого явно недостаточно, чтобы подробно просмотреть работоспособность написанного модуля. Поэтому создадим каталог test/ со скриптом qtcore.pl и следующим содержимым:

#!/usr/bin/perl -w

use blib;
use QtCore;

my $q = new QtCore;
$q->setVer(4.001);
print $q->ver(), "\n";

В результате запуска скрипта должна появиться указанная нами версия 4.001.


Импортирование нескольких классов

Редко можно найти библиотеку, состоящую только из одного класса. Когда классов не много, их можно описать в одном xs-файле. Или последовать примеру модуля Search-Xapian, в котором один большой файл разбит на несколько, импортируемых в один командой

INCLUDE: подключаемый_файл.xs

Однако главным недостатком такого подхода является необходимость подключения в одном месте всех заголовочных файлов, содержимое которых будет находиться в одной области видимости. Третий вариант, особенно удобный для такой большой библиотеки, как QtCore, заключается в том, чтобы каждый xs-файл сделать относительно независимым и в каждом подключать только тот заголовочный файл, который описывает нам нужный класс. Это обычно делается двумя способами. Первый способ состоит в том, чтобы в главном xs-файле прописать импорт boot-функций всех файлов и вызвать их в boot-функции основного xs-файла, вызываемого функцией bootstrap. Примеры реализации данного способа можно увидеть в библиотеках perl-Glib/Gtk2, Perl-RPM (в каждой немного по-своему). Другой способ заключается в том, чтобы все вызовы сделать из главного модуля, но уже на Perl'e. Данный вариант реализован в Win32::Gui. На мой взгляд, он более удобен и обладает большей переносимостью.
Опишем последний вариант подробнее. Прежде всего следует удалить оставшиеся файлы предыдущей сборки, а именно: каталог blib и файлы Makefile, pm_to_blib, QtCore.bs, *.c, *.o.
Далее укажем обрабатывать несколько xs-файлов, для чего в Makefile.PL раскомментируем строку

OBJECT        => '$(O_FILES)'

Вследствие этого будут отрабатываться все xs-файлы, найденные в каталоге модуля (во вложенных каталогах поиск не ведётся). Подключим библиотеку QtCore.so, для чего в строку

LIBS          => [''],

пропишем её:

LIBS          => ['-L/usr/lib -lQtCore '],

Для примера импортирования нескольких классов выберем небольшой класс QSize (здесь часть файла с функциями, которые будут использованы ниже):

class Q_CORE_EXPORT QSize
{
public:
    QSize();
    QSize(int w, int h);

    bool isEmpty() const;

    int width() const;
    int height() const;
    void setWidth(int w);
    void setHeight(int h);
    void transpose();

    void scale(int w, int h, Qt::AspectRatioMode mode);
    void scale(const QSize &s, Qt::AspectRatioMode mode);

    QSize &operator+=(const QSize &);

    friend inline const QSize operator+(const QSize &, const QSize &);

private:
    int wd;
    int ht;
};

Создадим файл QSize.xs (оставив на потом некоторые функции):

#ifdef __cplusplus
extern "C" {
#endif
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#ifdef __cplusplus
}
#endif

#include <QtCore/qsize.h>

MODULE = QtCore::QSize         PACKAGE = QtCore::QSize

=comment
QSize входит в состав QtCore
=cut

PROTOTYPES: ENABLE

QSize *
QSize::new()

bool
QSize::isEmpty()

int
QSize::width()

int
QSize::height()

void
QSize::setWidth(w)
    int w

void
QSize::setHeight(h)
    int h

void
QSize::DESTROY()

Далее создаем для класса QSize свой pm-файл lib/QtCore/QSize.pm

package QtCore::QSize;

use 5.008;
use strict;
use warnings;

use QtCore; # необходимо для вызова bootstrap, находящегося в файле QtCore
QtCore::bootstrap_subpackage 'QSize';

1;

В дальнейшем файлы QtCore.xs и lib/QtCore.pm будут нужны только для вызова bootstrap модуля QtCore.pm. Заметим, что класс в QtCore.xs можно удалить, но тогда придется добавить хотя бы одну внешнюю функцию, иначе в файле QtCore.c, который создается на основе QtCore.xs, не будет всех нужных объявлений. Вообще все boot-функции и объявления в них можно прописать и вручную, но вряд ли это целесообразно, если компилятор XS делает всё сам. Теперь следует добавить в lib/QtCore.pm функцию, которая будет выполнять роль bootstrap для остальных модулей:

sub bootstrap_subpackage {
    my($package) = @_;
    $package = 'QtCore::'.$package;
    my $symbol = $package;
    $symbol =~ s/\W/_/g;
    no strict 'refs';
    DynaLoader::dl_install_xsub(
        "${package}::bootstrap",
        DynaLoader::dl_find_symbol_anywhere("boot_$symbol")
    );
    &{ "${package}::bootstrap" };
}

Остался последний шаг в нашем примере импортирования нескольких классов. Класс следует описать в файле typemap, добавив в конце:

QSize *        O_OBJECT

Вот теперь уже можно запустить perl Makefile.PL && make и потестировать, что получилось.

Создадим файл test/qsize.pl для теста:

#!/usr/bin/perl -w

use blib;
use QtCore::QSize;

use Carp 'croak';

my $q = new QtCore::QSize;             # создать класс
print  "q is empty\n" if $q->isEmpty();
$q->setWidth(2);                       # присвоить параметр
print $q->width(), "\n";               # проверить
$q->setHeight(3);
print "q isn't empty\n" unless $q->isEmpty();

Использование нескольких конструкторов

Класс QSize содержит два конструктора, а компилятор XS знает только про new. Поэтому второй конструктор реализуем сами. Чтобы увидеть, что для этого надо, достаточно посмотреть в файл QSize.c, автоматически сгенерированный компилятором XS из файла QSize.xs:

char *	CLASS = (char *)SvPV_nolen(ST(0));
QSize *	RETVAL;

RETVAL = new QSize();
ST(0) = sv_newmortal();
sv_setref_pv( ST(0), CLASS, (void*)RETVAL );

Иными словами, благодаря "QSize::", расположенному перед конструктором "new", в функцию передается строковый параметр CLASS с наименованием класса, после чего создается класс и используется bless для полученной ссылки. Для примера импорта конструктора в QSize.xs создадим конструктор "new1" с явным указанием компилятору на код и возвращаемый параметр:

QSize *
new1(CLASS)
    char * CLASS
    CODE:
        RETVAL = new QSize();
    OUTPUT:
        RETVAL

Теперь запустим make. Получный в QSize.c код для "new1" будет идентичен автоматически созданному коду для конструктора "new". Однако появятся две пометки о том, что код взят из QSize.xs. Аналогично создадим второй конструктор, но уже с параметрами инициализации:

QSize *
new2(CLASS, w, h);
    int w
    int h
    char * CLASS
    CODE:
        RETVAL = new QSize(w, h);
    OUTPUT:
        RETVAL

Заметим, что в Perl'е удобнее было бы использовать индификатор "new" для вызова любого конструктора, не запоминая их номера. Для реализации этого удалим из QSize.xs вызов QSize::new(), после чего добавим в lib/QtCore/QSize.pm функцию с таким же названием. Данная функция в зависимости от содержимого сама будет выбирать, что ей вызвать. При неверном количестве параметров функция выведет сообщение об ошибке:

sub new {
    return new1($_[0]) if ( scalar(@_) == 1 );
    return new2($_[0], $_[1], $_[2]) if ( scalar(@_) == 3 );
    croak("ожидалось 0 или 2 параметра\n");
}

Далее даем команду make и проверяем, как всё работает, для чего добавим в test/qsize.pl строку

my $w = QtCore::QSize->new(5,6);

Сложение классов ( operator+ )

Если в исходном классе, написанном на С++, содержатся операторы "арифметических" и "логических" действий c классами, то данные функции желательно тоже импортировать в Perl.
Сначала рассмотрим, что добавить в QSize.xs для

QSize &operator+=(const QSize &);

Оператор возвращает тот же класс, к которому осуществляется прибавление, поэтому возвратить QSize можно и в функции на Perl'e. Поскольку Perl внутри работает только с указателями, то перед передачей функции прибавляемого класса otherSize его (указатель) следует разыменовать:

void
QSize::operator_plus_eq(otherSize)
    QSize * otherSize
    CODE:
        THIS->operator+= (*otherSize);

Или, например, другой оператор:

friend inline const QSize operator+(const QSize &, const QSize &);

Несмотря на то что фунция operator+ не является внутренней для класса, это не мешает получить указатель на первый класс предыдущим способом. В то же время operator+ возвращает новый объект QSize, который будет жить только в пределах С-функции. Нам же необходимо вернуть указатель на новый объект QSize. Поэтому создадим новый экземпляр класса QSize и ему приравняем возвращенный класс. Класс QSize простой, поэтому конструктор копий создается компилятором автоматически.

QSize *
QSize::operator_plus(qsize2)
    QSize * qsize2
    PREINIT:
    char * CLASS = "QtCore::QSize";
    CODE:
        RETVAL = new QSize();
        *RETVAL = (operator+ ( *THIS, *qsize2 ));
    OUTPUT:
        RETVAL

В файле lib/QtCore/QSize.pm следует сделать для данных функций оболочку, используя overload (подробности использования overload смотрите в perldoc или "Программировании на Perl" [5, стр 397]):

use overload
    '+'  => \&_plus,
    '+=' => \&_plus_eq,
    '""' => sub { $_[0] };

sub _plus_eq {
    unless ( ref($_[1]) ) {
        croak("need QSize += QSize\n");
        return;
    }
    operator_plus_eq($_[0], $_[1]);
    return $_[0]; # возвращается указатель на тот же экземпляр класса
}

sub _plus {
    if ( ref($_[0]) and ref($_[1]) ) {
        return operator_plus($_[0], $_[1]);
    }
    croak("Need QSize1 = QSize2 + QSize3\n");
}

В заключение осталось проверить работоспособность операторов. Добавим в test/qsize.pl такие строки:

$w += $q;
print "w (h, w) == ", $w->height(), " ", $w->width(), "\n";
my $e = $w + $q;
print "e (h, w) == ", $e->height(), " ", $e->width(), "\n";

И, запустив, увидим как это работает.


Особенности использования enum

Работа с enum предусмотрена в Perl XS, однако с C++ появляется одна неприятность. Во время обработки xs-файла компилятором XS обращения в другие классы за определёнными в них enum, как, например, Qt::AspectRatioMode, в с-файле Qt::AspectRatioMode превращается в Qt__AspectRatioMode. И выдается ошибка компилятора о том, что не найден тип Qt__AspectRatioMode. К сожалению, нет никакой возможности избежать этого преобразования, ибо таким способом создаются все функции с целью не допустить дублирования названий функций с другими классами. Чтобы компилятор правильно увидел используемый нами enum, переопределим его в исходный облик. В C-части xs-файла после подключения qsize.h добавим:

#define Qt__AspectRatioMode Qt::AspectRatioMode

Теперь можно функцию с этим типом данных:

void
QSize::scale(w, h, mode)
    int w
    int h
    Qt::AspectRatioMode mode
    CODE:
        THIS->scale(w, h, mode);

Обратите внимание, что важно не забыть добавить в typemap новый тип данных:

Qt::AspectRatioMode        T_ENUM

Для того чтобы не запоминать числовые значения всех enum-параметров, добавим модуль lib/Qt.pm со всеми значениями AspectRatioMode:

package Qt;

# enum AspectRatioMode
use constant IgnoreAspectRatio => 0;
use constant KeepAspectRatio => 1;
use constant KeepAspectRatioByExpanding => 2;

1;

После добавления или удаления любого файла, следует полностью очитить библиотеку, удалив каталог blib, файлы *.c, *.o & Co. После данных манипуляций и выполнения команд perl Makefile.PL && make можно тестировать программу. Для этого после use blib в файле qsize.pl следует добавить

use Qt;

а также добавить новую функцию в конце этого файла:

$e->scale(20, 20, Qt::IgnoreAspectRatio);
print "scale e (h, w) == ", $e->height(), " ", $e->width(), "\n";

Взаимодействие с STL или её аналогами

В Perl'e STL практически не нужна, поскольку большинство возможностей STL уже поддерживаются массивами и хешами Perl'a. Поэтому рассмотрим только передачу данных из шаблона list в массив Perl'a и обратно. Библиотека Qt4 инкапсулирует в себя STL, добавляя некоторые возможности. Мы подробно рассмотрим работу с шаблоном QList, ибо методы некоторых классов возвращают списки классов, используя именно его. Для получения массива обратимся к классу QByteArray, в нём есть такой конструктор:

QList <QByteArray> split(char sep) const;

В файле QByteArray.xs перед использованием шаблонов STL необходимо убрать определения do_open и do_close, иначе они начнут конфликтовать с аналогичными из Perl'a.

#undef do_open
#undef do_close

#include <QtCore/qlist.h>
#include <QtCore/qbytearray.h>

AV *
QByteArray::_split(c)
    char c
    CODE:
        RETVAL = newAV();
        QList<QByteArray> lba = THIS->split(c);
        for ( int i = 0 ; i < lba.size() ; ++i ) {
            QByteArray * ba = new QByteArray();
            *ba = lba.at(i);
            SV * rv = newSV(0);
            sv_setref_pv( rv, "QtCore::QByteArray", (void *)ba );
            av_push(RETVAL, rv);
        };
    OUTPUT:
        RETVAL
    CLEANUP:
        SvREFCNT_dec( RETVAL );

Иными словами, в описании _split создается анонимный массив, указатель на который будет передан в программу. Затем вызывается функция split класса на C++, которая возвращает список классов QByteArray. Этот список обходится в цикле, в котором по одному указателю на класс заносится в массив RETVAL. Поскольку массив принимает только тип данных SV*, то на каждой итерации цикла создается новая переменная. Затем в нее копируется ссылка на класс из списка, приведенная к типу данных Perl функцией sv_setref_pv. Подробно работа с массивами в Perl XS описана в perlguts, а примеры использования массива со строками можно посмотреть в "XS Cookbook" [2].
Следующий шаг состоит в добавлении в typemap нового класса

QByteArray *        O_OBJECT

и создании для этого класса модуля lib/QtCore/QByteArray.pm. Функция _split возвращает указатель на массив, однако в программе удобнее пользоваться обычным массивом. С этой целью напишем простейшую оболочку для этой функции. Кроме того, в Perl'e есть своя функция split, поэтому ее надо в пакете переопределить, используя use subs.

package QtCore::QByteArray;

use 5.008;
use strict;
use warnings;
use Carp qw/carp croak/;

use QtCore; # bootstraps QtCore.xs
QtCore::bootstrap_subpackage 'QByteArray';

use subs qw(split);

sub split {
    croak("split: нет разделителя\n") unless $_[1];
    return @{ _split($_[0], $_[1]) };
}

1;

Пересобираем пакет и тестируем (файл test/qbytearray.pl):

#!/usr/bin/perl -w

use blib;
use QtCore::QByteArray;

my $q = new QtCore::QByteArray("aaa bbb ccc");
print "q = ", $q->data(), "\n";
my @w = $q->split(' ');
for (my $i = 0 ; $w[$i] ; $i++ ) {
    print "$i = ", $w[$i]->data(), "\n";
}

Аналогичным способом массив превращается в шаблон QList. Для примера приведём конструктор класса QStringList, получающий для инициализации массив классов QString. В файле QtCore/qstrinlist.h конструктор объявлен как

 inline QStringList(const QStringList &l) : QList <QString> (l) { } 

В xs-файле для него необходимо создать класс QList <QString> и заполнить его классами QString, полученными из массива. av является указателем на копию этого массива. Копия используется, поскольку функция av_pop(), которой мы здесь воспользуемся, удаляет считанные элементы из массива.

QStringList *
new3(CLASS, av)
    char * CLASS
    AV * av
    CODE:
        QList<QString> qls;
        while ( av_len(av) > -1 ) {
            SV * rv = av_pop(av);
            QString * str = (QString *)SvIV((SV*)SvRV( rv ));
            qls << *str;
        }
        RETVAL = new QStringList(qls);
    OUTPUT:
       RETVAL

Описание типа данных, отсутствующего в typemap.xs и perlobject.map

В том случае, если нужно добавить новый тип данных, необходимо описать, как компилятору с ним работать, т.е. читать из него данные и записывать. Данная тема описана в документации Perl, но для полноты картины приведём пример, иллюстрирующий работу с отсутствующим в С типом string. Для этого типа в typemap следует добавить:

string        STRING

Ниже в разделах INPUT и OUTPUT необходимо описать, как перевести string из
внутреннего типа данных Perl'а (переменная $arg) в C++ (переменная $var) и обратно.

INPUT

STRING
	{
		STRLEN len;
		const char * tmp = SvPV($arg, len);
		$var.assign(tmp, len);
	}

OUTPUT

STRING
	sv_setpvn((SV*)$arg, (char *) ($var.data()), ($var.size()));

Приведение классов к привычному для Perl'a виду

В вышеприведенных способах создания объектов используется указатель на скаляр, в то время как наиболее привычным указателем для создания классов является указатель на хеш, в котором можно будет хранить переменные класса, если мы захотим что-то дописать на Perl'e:

$obj = {};
bless($obj);

Для того чтобы привести класс к привычному для Perl'a виду, создадим хеш в XS-файле, а скаляр, который является указателем на С++ класс, сохраним в этом же хеше. Бывают случаи, что класс создаётся не в XS файле, а функция получает указатель на него, и/или конструктор копии этого класса находится в секции private. Тогда при уничтожении Perl-объекта С++ класс удаляться не должен. Чтобы класс не удалился, нужно сделать пометку в хеше. Помимо этого, необходимо осуществить и обратную операцию – получить указатель на класс из хеша для работы с вызываемыми функциями.
Поскольку описанные выше операции придётся делать для большинства функций, то удобнее вынести этот код наружу – во внешний подключаемый файл, который назовём common.xs. Обратите внимание, что не cpp, а xs. Данное расширение используется, чтобы XS-компилятор сам подхватил common.xs файл.

// преобразование С++ класса в Perl-объект
SV *
class2pobj(IV iv, const char *class_name, int no_ptr)
{
    HV *hv = newHV();                    // создание хеша
    SV *retval = newSV(0);               // переменная, в которую помещается указатель на класс
    sv_setiv(retval, iv);                // почти retval = (SV*)iv;
    hv_store(hv, "_ptr", 4, retval, 0);  // копирование переменной в $хеш{_ptr}
    hv_store(hv, "_del", 4, newSViv(no_ptr), 0); // пометка-разрешение на вызов деструктора
    return sv_bless(newRV_noinc((SV*)hv), gv_stashpv(class_name, 0)); // вызов bless и возврат уже готового Perl-объекта
}

// преобразование Perl-объекта в C++ класс
IV
pobj2class(SV *sv, const char *class_name, const char *func, const char *var)
{
    char pclass_name[512];
    char fn_warn[512];
    char ptr_warn[512];
    snprintf(pclass_name, 512, "Qt::%s", class_name);         // воссоздание имени класса
    snprintf(fn_warn, 512, "%s() -- %s is not blessed Qt::%s", func, var, class_name);
    snprintf(ptr_warn, 512, "%s() -- %s->{_ptr} is NULL", func, var);

    if( sv_derived_from(sv, pclass_name) && (SvTYPE(SvRV(sv)) == SVt_PVHV) ) {
 // если это искомый класс и тип данных является указателем на хеш, то
        HV *hv = (HV*)SvRV( sv );                             // получаем хеш из указателя
        SV **ssv = hv_fetch(hv, "_ptr", 4, 0);                // получаем реальный указатель
        if ( ssv != NULL )                                    // если указатель не пустой
            return SvIV(*ssv);                                // возвращаем его
        warn( ptr_warn );
        return (IV)NULL;                                      // иначе – возвращаем 'ничего'
    }
    warn( fn_warn );
    return (IV)NULL;
}

Для подключения common.xs нужен заголовочный файл common.h:

#ifndef PQT4_COMMON_H
#define PQT4_COMMON_H

SV * class2pobj(IV iv, const char *class_name, int no_ptr);
IV pobj2class(SV *, const char *class_name, const char *func, const char *var);

#endif

Теперь посмотрим, какими станут конструктор и деструктор в XS-файле:

SV *
new()
    CODE:
        QSize * out_data;
        out_data = new QSize( );
        RETVAL = class2pobj(PTR2IV(out_data), "Qt::QSize", 1);
    OUTPUT:
        RETVAL

void
QSize::DESTROY()
    CODE:
        // предварительно проверяем можно ли удалять класс
        SV **ssv = hv_fetch((HV*)SvRV(ST(0)), "_del", 4, 0); 
        if ( SvIV(*ssv) )
            delete THIS;

Поскольку вызовы объекта были изменены, то теперь необходимо указать Perl'у, как работать с новыми типами данных, т. е. поменять описание O_OBJECT в файле perlobject.map:

INPUT

O_OBJECT
	$var = INT2PTR($type, pobj2class($arg, \"${(my $ttt = $type) =~ 
 s/^(?:Perl)?([0-9A-Za-z_]+) \*/$1/;\$ttt}\", \"${Package}::$func_name\", \"$var\"));
	if ( $var == NULL )
		XSRETURN_UNDEF;

OUTPUT

O_OBJECT
	$arg = class2pobj(PTR2IV($var), \"Qt::${(my $ttt = $type) =~ s/^([0-9A-Za-z_]+) \*/$1/;\$ttt}\", 1);

Обратите внимание, что в коде уже учтено использование библиотеки Qt. Для использования кода с другой библиотекой его необходимо исправить.


Post Scriptum

В начале этой статьи мы писали проверку на количество параметров для перегруженных функций в Perl-коде. Однако параметры могут различаться и по типу переменных, чего на уровне Perl-кода не проверишь, а вот на уровне Perl XS можно проверить, в каком контексте последний раз использовалась переменная. Воспользуемся этим и заодно перенесём на уровень Perl XS обработку перегруженных функций. В результате получаем конструктор такого вида:

// перегруженные функции имеют переменное количество параметров, поэтому
PROTOTYPES: DISABLE

SV *
new(...)
  // вызов без параметров
  CASE: items == 0
    CODE:
        QSize * out_data;
        out_data = new QSize(  );
        RETVAL = class2pobj(PTR2IV(out_data), "Qt::QSize", 1);
    OUTPUT:
        RETVAL
  // вызов с двумя параметрами, и оба должны быть целыми числами
  CASE: items == 2 && SvIOK(ST(0))  && SvIOK(ST(1))
    PREINIT:
        int w = (int)SvIV(ST(0));
        int h = (int)SvIV(ST(1));
    CODE:
        QSize * out_data;
        out_data = new QSize( w, h );
        RETVAL = class2pobj(PTR2IV(out_data), "Qt::QSize", 1);
    OUTPUT:
        RETVAL
  // если ни одно из условий не подошло, то возвращаем ошибку
  CASE:
    CODE:
        warn( "Qt::QSize::QSize() -- Error in parameters" );
        XSRETURN_UNDEF;

// такие комментарии в реальном коде следует убрать, т. к. они могут быть неверно интерпретированы

Подобным образом можно сделать разбор и других перегруженных функций, а не только конструкторов.
Таким образом, в данной статье были рассмотрены все основные варианты использования C++ и Perl XS. За пределами рассмотрения остались только прямое использование шаблонных классов (но, как было указано выше, использовать их нецелесообразно, т. к. STL покрывается возможностями самого Perl'a) и использование lvalue-функций из классов С++ в Perl'e (когда разрабатывался Perl XS для 5-ой версии, lvalue изначально не был выписан и в самом Perl5, а более поздних описаний расширений Perl XS на данный момент, по моим сведениям, не существует).

Несколько примеров:

main.cpp

/****************************************************************************
 **
 ** Copyright (C) 2005-2007 Trolltech ASA. All rights reserved.
 **
 ** This file is part of the example classes of the Qt Toolkit.
 **
 ** This file may be used under the terms of the GNU General Public
 ** License version 2.0 as published by the Free Software Foundation
 ** and appearing in the file LICENSE.GPL included in the packaging of
 ** this file.  Please review the following information to ensure GNU
 ** General Public Licensing requirements will be met:
 ** http://www.trolltech.com/products/qt/opensource.html
 **
 ** If you are unsure which license is appropriate for your use, please
 ** review the following information:
 ** http://www.trolltech.com/products/qt/licensing.html or contact the
 ** sales department at sales@trolltech.com.
 **
 ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
 ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 **
 ****************************************************************************/

#include <QApplication>
#include <QPushButton>

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

    QPushButton hello("Hello world!");
    hello.resize(100, 30);

    hello.show();
    return app.exec();
}

main.pl

#!/usr/bin/perl -w

# use blib;
use Qt::QApplication;
use Qt::QString;
use Qt::QPushButton;

unshift @ARGV, 'tutorial_1';

my $app = QApplication(\@ARGV);

my $hello = QPushButton(QString("Hello world!"));
$hello->resize(100,30);
$hello->show();

$app->exec();

1;

main.cpp
/****************************************************************************
**
** Copyright (C) 2005-2007 Trolltech ASA. All rights reserved.
**
** This file is part of the example classes of the Qt Toolkit.
**
** This file may be used under the terms of the GNU General Public
** License version 2.0 as published by the Free Software Foundation
** and appearing in the file LICENSE.GPL included in the packaging of
** this file.  Please review the following information to ensure GNU
** General Public Licensing requirements will be met:
** http://trolltech.com/products/qt/licenses/licensing/opensource/
**
** If you are unsure which license is appropriate for your use, please
** review the following information:
** http://trolltech.com/products/qt/licenses/licensing/licensingoverview
** or contact the sales department at sales@trolltech.com.
**
** In addition, as a special exception, Trolltech gives you certain
** additional rights. These rights are described in the Trolltech GPL
** Exception version 1.0, which can be found at
** http://www.trolltech.com/products/qt/gplexception/ and in the file
** GPL_EXCEPTION.txt in this package.
**
** In addition, as a special exception, Trolltech, as the sole copyright
** holder for Qt Designer, grants users of the Qt/Eclipse Integration
** plug-in the right for the Qt/Eclipse Integration to link to
** functionality provided by Qt Designer and its related libraries.
**
** Trolltech reserves all rights not expressly granted herein.
** 
** Trolltech ASA (c) 2007
**
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
****************************************************************************/

#include <QApplication>

#include "screenshot.h"

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    Screenshot screenshot;
    screenshot.show();
    return app.exec();
}

screenshot.h

/****************************************************************************
**
** Copyright (C) 2005-2007 Trolltech ASA. All rights reserved.
**
** This file is part of the example classes of the Qt Toolkit.
**
** This file may be used under the terms of the GNU General Public
** License version 2.0 as published by the Free Software Foundation
** and appearing in the file LICENSE.GPL included in the packaging of
** this file.  Please review the following information to ensure GNU
** General Public Licensing requirements will be met:
** http://trolltech.com/products/qt/licenses/licensing/opensource/
**
** If you are unsure which license is appropriate for your use, please
** review the following information:
** http://trolltech.com/products/qt/licenses/licensing/licensingoverview
** or contact the sales department at sales@trolltech.com.
**
** In addition, as a special exception, Trolltech gives you certain
** additional rights. These rights are described in the Trolltech GPL
** Exception version 1.0, which can be found at
** http://www.trolltech.com/products/qt/gplexception/ and in the file
** GPL_EXCEPTION.txt in this package.
**
** In addition, as a special exception, Trolltech, as the sole copyright
** holder for Qt Designer, grants users of the Qt/Eclipse Integration
** plug-in the right for the Qt/Eclipse Integration to link to
** functionality provided by Qt Designer and its related libraries.
**
** Trolltech reserves all rights not expressly granted herein.
** 
** Trolltech ASA (c) 2007
**
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
****************************************************************************/

#ifndef SCREENSHOT_H
#define SCREENSHOT_H

#include <QPixmap>
#include <QWidget>

class QCheckBox;
class QGridLayout;
class QGroupBox;
class QHBoxLayout;
class QLabel;
class QPushButton;
class QSpinBox;
class QVBoxLayout;

class Screenshot : public QWidget
{
    Q_OBJECT

public:
    Screenshot();

protected:
    void resizeEvent(QResizeEvent *event);

private slots:
    void newScreenshot();
    void saveScreenshot();
    void shootScreen();
    void updateCheckBox();

private:
    void createOptionsGroupBox();
    void createButtonsLayout();
    QPushButton *createButton(const QString &text, QWidget *receiver,
                              const char *member);
    void updateScreenshotLabel();

    QPixmap originalPixmap;

    QLabel *screenshotLabel;
    QGroupBox *optionsGroupBox;
    QSpinBox *delaySpinBox;
    QLabel *delaySpinBoxLabel;
    QCheckBox *hideThisWindowCheckBox;
    QPushButton *newScreenshotButton;
    QPushButton *saveScreenshotButton;
    QPushButton *quitScreenshotButton;

    QVBoxLayout *mainLayout;
    QGridLayout *optionsGroupBoxLayout;
    QHBoxLayout *buttonsLayout;
};

#endif

screenshot.cpp

/****************************************************************************
**
** Copyright (C) 2005-2007 Trolltech ASA. All rights reserved.
**
** This file is part of the example classes of the Qt Toolkit.
**
** This file may be used under the terms of the GNU General Public
** License version 2.0 as published by the Free Software Foundation
** and appearing in the file LICENSE.GPL included in the packaging of
** this file.  Please review the following information to ensure GNU
** General Public Licensing requirements will be met:
** http://trolltech.com/products/qt/licenses/licensing/opensource/
**
** If you are unsure which license is appropriate for your use, please
** review the following information:
** http://trolltech.com/products/qt/licenses/licensing/licensingoverview
** or contact the sales department at sales@trolltech.com.
**
** In addition, as a special exception, Trolltech gives you certain
** additional rights. These rights are described in the Trolltech GPL
** Exception version 1.0, which can be found at
** http://www.trolltech.com/products/qt/gplexception/ and in the file
** GPL_EXCEPTION.txt in this package.
**
** In addition, as a special exception, Trolltech, as the sole copyright
** holder for Qt Designer, grants users of the Qt/Eclipse Integration
** plug-in the right for the Qt/Eclipse Integration to link to
** functionality provided by Qt Designer and its related libraries.
**
** Trolltech reserves all rights not expressly granted herein.
** 
** Trolltech ASA (c) 2007
**
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
****************************************************************************/

#include <QtGui>

#include "screenshot.h"

Screenshot::Screenshot()
{
    screenshotLabel = new QLabel;
    screenshotLabel->setSizePolicy(QSizePolicy::Expanding,
                                   QSizePolicy::Expanding);
    screenshotLabel->setAlignment(Qt::AlignCenter);
    screenshotLabel->setMinimumSize(240, 160);

    createOptionsGroupBox();
    createButtonsLayout();

    mainLayout = new QVBoxLayout;
    mainLayout->addWidget(screenshotLabel);
    mainLayout->addWidget(optionsGroupBox);
    mainLayout->addLayout(buttonsLayout);
    setLayout(mainLayout);

    shootScreen();
    delaySpinBox->setValue(5);

    setWindowTitle(tr("Screenshot"));
    resize(300, 200);
}

void Screenshot::resizeEvent(QResizeEvent * /* event */)
{
    QSize scaledSize = originalPixmap.size();
    scaledSize.scale(screenshotLabel->size(), Qt::KeepAspectRatio);
    if (!screenshotLabel->pixmap()
            || scaledSize != screenshotLabel->pixmap()->size())
        updateScreenshotLabel();
}

void Screenshot::newScreenshot()
{
    if (hideThisWindowCheckBox->isChecked())
        hide();
    newScreenshotButton->setDisabled(true);

    QTimer::singleShot(delaySpinBox->value() * 1000, this, SLOT(shootScreen()));
}

void Screenshot::saveScreenshot()
{
    QString format = "png";
    QString initialPath = QDir::currentPath() + tr("/untitled.") + format;

    QString fileName = QFileDialog::getSaveFileName(this, tr("Save As"),
                               initialPath,
                               tr("%1 Files (*.%2);;All Files (*)")
                               .arg(format.toUpper())
                               .arg(format));
    if (!fileName.isEmpty())
        originalPixmap.save(fileName, format.toAscii());
}

void Screenshot::shootScreen()
{
    if (delaySpinBox->value() != 0)
        qApp->beep();
    originalPixmap = QPixmap::grabWindow(QApplication::desktop()->winId());
    updateScreenshotLabel();

    newScreenshotButton->setDisabled(false);
    if (hideThisWindowCheckBox->isChecked())
        show();
}

void Screenshot::updateCheckBox()
{
    if (delaySpinBox->value() == 0)
        hideThisWindowCheckBox->setDisabled(true);
    else
        hideThisWindowCheckBox->setDisabled(false);
}

void Screenshot::createOptionsGroupBox()
{
    optionsGroupBox = new QGroupBox(tr("Options"));

    delaySpinBox = new QSpinBox;
    delaySpinBox->setSuffix(tr(" s"));
    delaySpinBox->setMaximum(60);
    connect(delaySpinBox, SIGNAL(valueChanged(int)), this, SLOT(updateCheckBox()));

    delaySpinBoxLabel = new QLabel(tr("Screenshot Delay:"));

    hideThisWindowCheckBox = new QCheckBox(tr("Hide This Window"));

    optionsGroupBoxLayout = new QGridLayout;
    optionsGroupBoxLayout->addWidget(delaySpinBoxLabel, 0, 0);
    optionsGroupBoxLayout->addWidget(delaySpinBox, 0, 1);
    optionsGroupBoxLayout->addWidget(hideThisWindowCheckBox, 1, 0, 1, 2);
    optionsGroupBox->setLayout(optionsGroupBoxLayout);
}

void Screenshot::createButtonsLayout()
{
    newScreenshotButton = createButton(tr("New Screenshot"),
                                       this, SLOT(newScreenshot()));

    saveScreenshotButton = createButton(tr("Save Screenshot"),
                                        this, SLOT(saveScreenshot()));

    quitScreenshotButton = createButton(tr("Quit"), this, SLOT(close()));

    buttonsLayout = new QHBoxLayout;
    buttonsLayout->addStretch();
    buttonsLayout->addWidget(newScreenshotButton);
    buttonsLayout->addWidget(saveScreenshotButton);
    buttonsLayout->addWidget(quitScreenshotButton);
}

QPushButton *Screenshot::createButton(const QString &text, QWidget *receiver,
                                      const char *member)
{
    QPushButton *button = new QPushButton(text);
    button->connect(button, SIGNAL(clicked()), receiver, member);
    return button;
}

void Screenshot::updateScreenshotLabel()
{
    screenshotLabel->setPixmap(originalPixmap.scaled(screenshotLabel->size(),
                                                     Qt::KeepAspectRatio,
                                                     Qt::SmoothTransformation));
}

main.pl

#!/usr/bin/perl -w

use Qt;
use Qt::QApplication;

use Screenshot;

unshift @ARGV, 'screenshot';

my $app = QApplication(\@ARGV);
my $screenshot = Screenshot();
$screenshot->show();
$app->exec();

screenshot.cpp

package Screenshot;

use Qt;
use Qt::QSize;
use Qt::QString;
use Qt::QByteArray;
use Qt::QTimer;
use Qt::QDir;
use Qt::QPixmap;
use Qt::QWidget;
use Qt::QDesktopWidget;
use Qt::QCheckBox;
use Qt::QGridLayout;
use Qt::QGroupBox;
use Qt::QBoxLayout;
use Qt::QLabel;
use Qt::QSizePolicy;
use Qt::QPushButton;
use Qt::QSpinBox;
use Qt::QApplication;
use Qt::QFileDialog;

our @ISA=qw(Qt::QWidget);

our @EXPORT=qw(Screenshot);

sub Screenshot {
    my $class = 'Screenshot';
    my @signals = ();
    my @slots = ('newScreenshot()', 'saveScreenshot()', 'shootScreen()', 'updateCheckBox()');
    my $this = QWidget(\@signals, \@slots);
    bless $this, $class;
    
    $this->{screenshotLabel} = QLabel();
    $this->{screenshotLabel}->setSizePolicy(Qt::QSizePolicy::Expanding, Qt::QSizePolicy::Expanding);
    $this->{screenshotLabel}->setAlignment(Qt::AlignCenter);
    $this->{screenshotLabel}->setMinimumSize(240, 160);

    $this->createOptionsGroupBox();
    $this->createButtonsLayout();

    $this->{mainLayout} = QVBoxLayout();
    $this->{mainLayout}->addWidget($this->{screenshotLabel});
    $this->{mainLayout}->addWidget($this->{optionsGroupBox});
    $this->{mainLayout}->addLayout($this->{buttonsLayout});
    $this->setLayout($this->{mainLayout});

    $this->shootScreen();
    $this->{delaySpinBox}->setValue(5);

    $this->setWindowTitle(TR("Screenshot"));
    $this->resize(300, 200);
    
    return $this;
}

sub resizeEvent { #(QResizeEvent * /* event */)
    my $this = shift;
    my $scaledSize = $this->{originalPixmap}->size();
    $scaledSize->scale($this->{screenshotLabel}->size(), Qt::KeepAspectRatio);
    unless ( $this->{screenshotLabel}->pixmap() and $scaledSize == $this->{screenshotLabel}->pixmap()->size() ) {
        $this->updateScreenshotLabel();
    }
}

sub newScreenshot {
    my $this = shift;
    $this->hide() if $this->{hideThisWindowCheckBox}->isChecked();
        
    $this->{newScreenshotButton}->setDisabled(1); # true

    Qt::QTimer::singleShot($this->{delaySpinBox}->value() * 1000, $this, SLOT('shootScreen()'));
}

sub saveScreenshot {
    my $this = shift;
    my $format = QString("png");
    my $initialPath = Qt::QDir::currentPath();
    $initialPath += TR("/untitled.");
    $initialPath += $format;

    my $fileName = Qt::QFileDialog::getSaveFileName($this, TR("Save As"), $initialPath, 
	    TR("%1 Files (*.%2);;All Files (*)")->arg($format->toUpper())->arg($format));
    unless ( $fileName->isEmpty() ) {
        $this->{originalPixmap}->save($fileName, $format->toAscii()->data());
    }
}

sub shootScreen {
    my $this = shift;
    $qApp->beep() if $this->{delaySpinBox}->value() != 0;
    $this->{originalPixmap} = Qt::QPixmap::grabWindow( Qt::QApplication::desktop()->winId() );
    $this->updateScreenshotLabel();

    $this->{newScreenshotButton}->setDisabled(0); # false
    if ( $this->{hideThisWindowCheckBox}->isChecked() ) {
        $this->show();
    }
}

sub updateCheckBox {
    my $this = shift;
    if ( $this->{delaySpinBox}->value() == 0) {
        $this->{hideThisWindowCheckBox}->setDisabled(1); # true
    }
    else {
        $this->{hideThisWindowCheckBox}->setDisabled(0); # false
    }
}

sub createOptionsGroupBox {
    my $this = shift;
    $this->{optionsGroupBox} = QGroupBox(TR("Options"));

    $this->{delaySpinBox} = QSpinBox();
    $this->{delaySpinBox}->setSuffix(TR(" s"));
    $this->{delaySpinBox}->setMaximum(60);
    $this->connect($this->{delaySpinBox}, SIGNAL('valueChanged(int)'), $this, SLOT('updateCheckBox()'));

    $this->{delaySpinBoxLabel} = QLabel(TR("Screenshot Delay:"));

    $this->{hideThisWindowCheckBox} = QCheckBox(TR("Hide This Window"));

    $this->{optionsGroupBoxLayout} = QGridLayout();
    $this->{optionsGroupBoxLayout}->addWidget($this->{delaySpinBoxLabel}, 0, 0);
    $this->{optionsGroupBoxLayout}->addWidget($this->{delaySpinBox}, 0, 1);
    $this->{optionsGroupBoxLayout}->addWidget($this->{hideThisWindowCheckBox}, 1, 0, 1, 2);
    $this->{optionsGroupBox}->setLayout($this->{optionsGroupBoxLayout});
}

sub createButtonsLayout {
    my $this = shift;
    $this->{newScreenshotButton} = $this->createButton(TR("New Screenshot"), $this, SLOT('newScreenshot()'));

    $this->{saveScreenshotButton} = $this->createButton(TR("Save Screenshot"), $this, SLOT('saveScreenshot()'));

    $this->{quitScreenshotButton} = $this->createButton(TR("Quit"), $this, SLOT('close()'));

    $this->{buttonsLayout} = QHBoxLayout();
    $this->{buttonsLayout}->addStretch();
    $this->{buttonsLayout}->addWidget($this->{newScreenshotButton});
    $this->{buttonsLayout}->addWidget($this->{saveScreenshotButton});
    $this->{buttonsLayout}->addWidget($this->{quitScreenshotButton});
}

sub createButton{ # const QString &text, QWidget *receiver, const char *member)
    my $this = shift;
    my $text = shift;
    my $receiver = shift;
    my $member = shift;
    my $button = QPushButton($text);
    $button->connect($button, SIGNAL('clicked()'), $receiver, $member);
    return $button;
}

sub updateScreenshotLabel {
    my $this = shift;
    $this->{screenshotLabel}->setPixmap($this->{originalPixmap}->scaled($this->{screenshotLabel}->size(),
	    Qt::KeepAspectRatio, Qt::SmoothTransformation) );
}


1;

main.cpp

/****************************************************************************
**
** Copyright (C) 2004-2007 Trolltech ASA. All rights reserved.
**
** This file is part of the example classes of the Qt Toolkit.
**
** This file may be used under the terms of the GNU General Public
** License version 2.0 as published by the Free Software Foundation
** and appearing in the file LICENSE.GPL included in the packaging of
** this file.  Please review the following information to ensure GNU
** General Public Licensing requirements will be met:
** http://trolltech.com/products/qt/licenses/licensing/opensource/
**
** If you are unsure which license is appropriate for your use, please
** review the following information:
** http://trolltech.com/products/qt/licenses/licensing/licensingoverview
** or contact the sales department at sales@trolltech.com.
**
** In addition, as a special exception, Trolltech gives you certain
** additional rights. These rights are described in the Trolltech GPL
** Exception version 1.0, which can be found at
** http://www.trolltech.com/products/qt/gplexception/ and in the file
** GPL_EXCEPTION.txt in this package.
**
** In addition, as a special exception, Trolltech, as the sole copyright
** holder for Qt Designer, grants users of the Qt/Eclipse Integration
** plug-in the right for the Qt/Eclipse Integration to link to
** functionality provided by Qt Designer and its related libraries.
**
** Trolltech reserves all rights not expressly granted herein.
**
** Trolltech ASA (c) 2007
**
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
****************************************************************************/

#include <QApplication>

#include "mainwindow.h"

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    MainWindow window;
    window.show();
    return app.exec();
}

mainwindow.h

/****************************************************************************
**
** Copyright (C) 2004-2007 Trolltech ASA. All rights reserved.
**
** This file is part of the example classes of the Qt Toolkit.
**
** This file may be used under the terms of the GNU General Public
** License version 2.0 as published by the Free Software Foundation
** and appearing in the file LICENSE.GPL included in the packaging of
** this file.  Please review the following information to ensure GNU
** General Public Licensing requirements will be met:
** http://trolltech.com/products/qt/licenses/licensing/opensource/
**
** If you are unsure which license is appropriate for your use, please
** review the following information:
** http://trolltech.com/products/qt/licenses/licensing/licensingoverview
** or contact the sales department at sales@trolltech.com.
**
** In addition, as a special exception, Trolltech gives you certain
** additional rights. These rights are described in the Trolltech GPL
** Exception version 1.0, which can be found at
** http://www.trolltech.com/products/qt/gplexception/ and in the file
** GPL_EXCEPTION.txt in this package.
**
** In addition, as a special exception, Trolltech, as the sole copyright
** holder for Qt Designer, grants users of the Qt/Eclipse Integration
** plug-in the right for the Qt/Eclipse Integration to link to
** functionality provided by Qt Designer and its related libraries.
**
** Trolltech reserves all rights not expressly granted herein.
**
** Trolltech ASA (c) 2007
**
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
****************************************************************************/

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

class QAction;
class QActionGroup;
class QLabel;
class QMenu;

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow();

protected:
    void contextMenuEvent(QContextMenuEvent *event);

private slots:
    void newFile();
    void open();
    void save();
    void print();
    void undo();
    void redo();
    void cut();
    void copy();
    void paste();
    void bold();
    void italic();
    void leftAlign();
    void rightAlign();
    void justify();
    void center();
    void setLineSpacing();
    void setParagraphSpacing();
    void about();
    void aboutQt();

private:
    void createActions();
    void createMenus();

    QMenu *fileMenu;
    QMenu *editMenu;
    QMenu *formatMenu;
    QMenu *helpMenu;
    QActionGroup *alignmentGroup;
    QAction *newAct;
    QAction *openAct;
    QAction *saveAct;
    QAction *printAct;
    QAction *exitAct;
    QAction *undoAct;
    QAction *redoAct;
    QAction *cutAct;
    QAction *copyAct;
    QAction *pasteAct;
    QAction *boldAct;
    QAction *italicAct;
    QAction *leftAlignAct;
    QAction *rightAlignAct;
    QAction *justifyAct;
    QAction *centerAct;
    QAction *setLineSpacingAct;
    QAction *setParagraphSpacingAct;
    QAction *aboutAct;
    QAction *aboutQtAct;
    QLabel *infoLabel;
};

#endif

mainwindow.cpp

/****************************************************************************
**
** Copyright (C) 2004-2007 Trolltech ASA. All rights reserved.
**
** This file is part of the example classes of the Qt Toolkit.
**
** This file may be used under the terms of the GNU General Public
** License version 2.0 as published by the Free Software Foundation
** and appearing in the file LICENSE.GPL included in the packaging of
** this file.  Please review the following information to ensure GNU
** General Public Licensing requirements will be met:
** http://trolltech.com/products/qt/licenses/licensing/opensource/
**
** If you are unsure which license is appropriate for your use, please
** review the following information:
** http://trolltech.com/products/qt/licenses/licensing/licensingoverview
** or contact the sales department at sales@trolltech.com.
**
** In addition, as a special exception, Trolltech gives you certain
** additional rights. These rights are described in the Trolltech GPL
** Exception version 1.0, which can be found at
** http://www.trolltech.com/products/qt/gplexception/ and in the file
** GPL_EXCEPTION.txt in this package.
**
** In addition, as a special exception, Trolltech, as the sole copyright
** holder for Qt Designer, grants users of the Qt/Eclipse Integration
** plug-in the right for the Qt/Eclipse Integration to link to
** functionality provided by Qt Designer and its related libraries.
**
** Trolltech reserves all rights not expressly granted herein.
**
** Trolltech ASA (c) 2007
**
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
****************************************************************************/

#include <QtGui>

#include "mainwindow.h"

MainWindow::MainWindow()
{
    QWidget *widget = new QWidget;
    setCentralWidget(widget);

    QWidget *topFiller = new QWidget;
    topFiller->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);

    infoLabel = new QLabel(tr("<i>Choose a menu option, or right-click to "
                              "invoke a context menu</i>"));
    infoLabel->setFrameStyle(QFrame::StyledPanel | QFrame::Sunken);
    infoLabel->setAlignment(Qt::AlignCenter);

    QWidget *bottomFiller = new QWidget;
    bottomFiller->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);

    QVBoxLayout *layout = new QVBoxLayout;
    layout->setMargin(5);
    layout->addWidget(topFiller);
    layout->addWidget(infoLabel);
    layout->addWidget(bottomFiller);
    widget->setLayout(layout);

    createActions();
    createMenus();

    QString message = tr("A context menu is available by right-clicking");
    statusBar()->showMessage(message);

    setWindowTitle(tr("Menus"));
    setMinimumSize(160, 160);
    resize(480, 320);
}

void MainWindow::contextMenuEvent(QContextMenuEvent *event)
{
    QMenu menu(this);
    menu.addAction(cutAct);
    menu.addAction(copyAct);
    menu.addAction(pasteAct);
    menu.exec(event->globalPos());
}

void MainWindow::newFile()
{
    infoLabel->setText(tr("Invoked <b>File|New</b>"));
}

void MainWindow::open()
{
    infoLabel->setText(tr("Invoked <b>File|Open</b>"));
}

void MainWindow::save()
{
    infoLabel->setText(tr("Invoked <b>File|Save</b>"));
}

void MainWindow::print()
{
    infoLabel->setText(tr("Invoked <b>File|Print</b>"));
}

void MainWindow::undo()
{
    infoLabel->setText(tr("Invoked <b>Edit|Undo</b>"));
}

void MainWindow::redo()
{
    infoLabel->setText(tr("Invoked <b>Edit|Redo</b>"));
}

void MainWindow::cut()
{
    infoLabel->setText(tr("Invoked <b>Edit|Cut</b>"));
}

void MainWindow::copy()
{
    infoLabel->setText(tr("Invoked <b>Edit|Copy</b>"));
}

void MainWindow::paste()
{
    infoLabel->setText(tr("Invoked <b>Edit|Paste</b>"));
}

void MainWindow::bold()
{
    infoLabel->setText(tr("Invoked <b>Edit|Format|Bold</b>"));
}

void MainWindow::italic()
{
    infoLabel->setText(tr("Invoked <b>Edit|Format|Italic</b>"));
}

void MainWindow::leftAlign()
{
    infoLabel->setText(tr("Invoked <b>Edit|Format|Left Align</b>"));
}

void MainWindow::rightAlign()
{
    infoLabel->setText(tr("Invoked <b>Edit|Format|Right Align</b>"));
}

void MainWindow::justify()
{
    infoLabel->setText(tr("Invoked <b>Edit|Format|Justify</b>"));
}

void MainWindow::center()
{
    infoLabel->setText(tr("Invoked <b>Edit|Format|Center</b>"));
}

void MainWindow::setLineSpacing()
{
    infoLabel->setText(tr("Invoked <b>Edit|Format|Set Line Spacing</b>"));
}

void MainWindow::setParagraphSpacing()
{
    infoLabel->setText(tr("Invoked <b>Edit|Format|Set Paragraph Spacing</b>"));
}

void MainWindow::about()
{
    infoLabel->setText(tr("Invoked <b>Help|About</b>"));
    QMessageBox::about(this, tr("About Menu"),
            tr("The <b>Menu</b> example shows how to create "
               "menu-bar menus and context menus."));
}

void MainWindow::aboutQt()
{
    infoLabel->setText(tr("Invoked <b>Help|About Qt</b>"));
}

void MainWindow::createActions()
{
    newAct = new QAction(tr("&New"), this);
    newAct->setShortcut(tr("Ctrl+N"));
    newAct->setStatusTip(tr("Create a new file"));
    connect(newAct, SIGNAL(triggered()), this, SLOT(newFile()));

    openAct = new QAction(tr("&Open..."), this);
    openAct->setShortcut(tr("Ctrl+O"));
    openAct->setStatusTip(tr("Open an existing file"));
    connect(openAct, SIGNAL(triggered()), this, SLOT(open()));

    saveAct = new QAction(tr("&Save"), this);
    saveAct->setShortcut(tr("Ctrl+S"));
    saveAct->setStatusTip(tr("Save the document to disk"));
    connect(saveAct, SIGNAL(triggered()), this, SLOT(save()));

    printAct = new QAction(tr("&Print..."), this);
    printAct->setShortcut(tr("Ctrl+P"));
    printAct->setStatusTip(tr("Print the document"));
    connect(printAct, SIGNAL(triggered()), this, SLOT(print()));

    exitAct = new QAction(tr("E&xit"), this);
    exitAct->setShortcut(tr("Ctrl+Q"));
    exitAct->setStatusTip(tr("Exit the application"));
    connect(exitAct, SIGNAL(triggered()), this, SLOT(close()));

    undoAct = new QAction(tr("&Undo"), this);
    undoAct->setShortcut(tr("Ctrl+Z"));
    undoAct->setStatusTip(tr("Undo the last operation"));
    connect(undoAct, SIGNAL(triggered()), this, SLOT(undo()));

    redoAct = new QAction(tr("&Redo"), this);
    redoAct->setShortcut(tr("Ctrl+Y"));
    redoAct->setStatusTip(tr("Redo the last operation"));
    connect(redoAct, SIGNAL(triggered()), this, SLOT(redo()));

    cutAct = new QAction(tr("Cu&t"), this);
    cutAct->setShortcut(tr("Ctrl+X"));
    cutAct->setStatusTip(tr("Cut the current selection's contents to the "
                            "clipboard"));
    connect(cutAct, SIGNAL(triggered()), this, SLOT(cut()));

    copyAct = new QAction(tr("&Copy"), this);
    copyAct->setShortcut(tr("Ctrl+C"));
    copyAct->setStatusTip(tr("Copy the current selection's contents to the "
                             "clipboard"));
    connect(copyAct, SIGNAL(triggered()), this, SLOT(copy()));

    pasteAct = new QAction(tr("&Paste"), this);
    pasteAct->setShortcut(tr("Ctrl+V"));
    pasteAct->setStatusTip(tr("Paste the clipboard's contents into the current "
                              "selection"));
    connect(pasteAct, SIGNAL(triggered()), this, SLOT(paste()));

    boldAct = new QAction(tr("&Bold"), this);
    boldAct->setCheckable(true);
    boldAct->setShortcut(tr("Ctrl+B"));
    boldAct->setStatusTip(tr("Make the text bold"));
    connect(boldAct, SIGNAL(triggered()), this, SLOT(bold()));

    QFont boldFont = boldAct->font();
    boldFont.setBold(true);
    boldAct->setFont(boldFont);

    italicAct = new QAction(tr("&Italic"), this);
    italicAct->setCheckable(true);
    italicAct->setShortcut(tr("Ctrl+I"));
    italicAct->setStatusTip(tr("Make the text italic"));
    connect(italicAct, SIGNAL(triggered()), this, SLOT(italic()));

    QFont italicFont = italicAct->font();
    italicFont.setItalic(true);
    italicAct->setFont(italicFont);

    setLineSpacingAct = new QAction(tr("Set &Line Spacing..."), this);
    setLineSpacingAct->setStatusTip(tr("Change the gap between the lines of a "
                                       "paragraph"));
    connect(setLineSpacingAct, SIGNAL(triggered()), this, SLOT(setLineSpacing()));

    setParagraphSpacingAct = new QAction(tr("Set &Paragraph Spacing..."), this);
    setLineSpacingAct->setStatusTip(tr("Change the gap between paragraphs"));
    connect(setParagraphSpacingAct, SIGNAL(triggered()),
            this, SLOT(setParagraphSpacing()));

    aboutAct = new QAction(tr("&About"), this);
    aboutAct->setStatusTip(tr("Show the application's About box"));
    connect(aboutAct, SIGNAL(triggered()), this, SLOT(about()));

    aboutQtAct = new QAction(tr("About &Qt"), this);
    aboutQtAct->setStatusTip(tr("Show the Qt library's About box"));
    connect(aboutQtAct, SIGNAL(triggered()), qApp, SLOT(aboutQt()));
    connect(aboutQtAct, SIGNAL(triggered()), this, SLOT(aboutQt()));

    leftAlignAct = new QAction(tr("&Left Align"), this);
    leftAlignAct->setCheckable(true);
    leftAlignAct->setShortcut(tr("Ctrl+L"));
    leftAlignAct->setStatusTip(tr("Left align the selected text"));
    connect(leftAlignAct, SIGNAL(triggered()), this, SLOT(leftAlign()));

    rightAlignAct = new QAction(tr("&Right Align"), this);
    rightAlignAct->setCheckable(true);
    rightAlignAct->setShortcut(tr("Ctrl+R"));
    rightAlignAct->setStatusTip(tr("Right align the selected text"));
    connect(rightAlignAct, SIGNAL(triggered()), this, SLOT(rightAlign()));

    justifyAct = new QAction(tr("&Justify"), this);
    justifyAct->setCheckable(true);
    justifyAct->setShortcut(tr("Ctrl+J"));
    justifyAct->setStatusTip(tr("Justify the selected text"));
    connect(justifyAct, SIGNAL(triggered()), this, SLOT(justify()));

    centerAct = new QAction(tr("&Center"), this);
    centerAct->setCheckable(true);
    centerAct->setShortcut(tr("Ctrl+E"));
    centerAct->setStatusTip(tr("Center the selected text"));
    connect(centerAct, SIGNAL(triggered()), this, SLOT(center()));

    alignmentGroup = new QActionGroup(this);
    alignmentGroup->addAction(leftAlignAct);
    alignmentGroup->addAction(rightAlignAct);
    alignmentGroup->addAction(justifyAct);
    alignmentGroup->addAction(centerAct);
    leftAlignAct->setChecked(true);
}

void MainWindow::createMenus()
{
    fileMenu = menuBar()->addMenu(tr("&File"));
    fileMenu->addAction(newAct);
    fileMenu->addAction(openAct);
    fileMenu->addAction(saveAct);
    fileMenu->addAction(printAct);
    fileMenu->addSeparator();
    fileMenu->addAction(exitAct);

    editMenu = menuBar()->addMenu(tr("&Edit"));
    editMenu->addAction(undoAct);
    editMenu->addAction(redoAct);
    editMenu->addSeparator();
    editMenu->addAction(cutAct);
    editMenu->addAction(copyAct);
    editMenu->addAction(pasteAct);
    editMenu->addSeparator();

    helpMenu = menuBar()->addMenu(tr("&Help"));
    helpMenu->addAction(aboutAct);
    helpMenu->addAction(aboutQtAct);

    formatMenu = editMenu->addMenu(tr("&Format"));
    formatMenu->addAction(boldAct);
    formatMenu->addAction(italicAct);
    formatMenu->addSeparator()->setText(tr("Alignment"));
    formatMenu->addAction(leftAlignAct);
    formatMenu->addAction(rightAlignAct);
    formatMenu->addAction(justifyAct);
    formatMenu->addAction(centerAct);
    formatMenu->addSeparator();
    formatMenu->addAction(setLineSpacingAct);
    formatMenu->addAction(setParagraphSpacingAct);
}

main.pl

#!/usr/bin/perl -w

use Qt;
#use blib;
use Qt::QApplication;

use MainWindow;

unshift @ARGV, 'menus';

my $app = QApplication(\@ARGV);
my $window = MainWindow();
$window->show();
$app->exec();

MainWindow.pl

package MainWindow;

use Qt;
use Qt::QString;
#use blib;
use Qt::QApplication;
use Qt::QMainWindow;
use Qt::QWidget;
use Qt::QFont;
use Qt::QAction;
use Qt::QActionGroup;
use Qt::QKeySequence;
use Qt::QLabel;
use Qt::QMenu;
use Qt::QMenuBar;
use Qt::QStatusBar;
use Qt::QSizePolicy;
use Qt::QBoxLayout;

our @ISA = qw(Qt::QMainWindow);
our @EXPORT = qw(MainWindow);


sub MainWindow {
    my $class = 'MainWindow';
    my @signals = ();
    my @slots = ('newFile()','open_()','save()','print_()','undo()','redo()','cut()','copy()','paste()','bold()','italic()',
	'leftAlign()','rightAlign()','justify()','center()','setLineSpacing()','setParagraphSpacing()','about()','aboutQt()');
    my $this = QMainWindow(\@signals, \@slots);
    bless $this, $class;

    $this->{widget} = QWidget();
    $this->setCentralWidget($this->{widget});

    $this->{topFiller} = QWidget();
    $this->{topFiller}->setSizePolicy(Qt::QSizePolicy::Expanding, Qt::QSizePolicy::Expanding);

    $this->{infoLabel} = QLabel(QString('<i>Choose a menu option, or right-click to '.
                              'invoke a context menu</i>'));
    $this->{infoLabel}->setFrameStyle(Qt::QFrame::StyledPanel | Qt::QFrame::Sunken);
    $this->{infoLabel}->setAlignment(Qt::AlignCenter);

    $this->{bottomFiller} = QWidget();
    $this->{bottomFiller}->setSizePolicy(Qt::QSizePolicy::Expanding, Qt::QSizePolicy::Expanding);

    $this->{layout} = QVBoxLayout();
    $this->{layout}->setMargin(5);
    $this->{layout}->addWidget($this->{topFiller});
    $this->{layout}->addWidget($this->{infoLabel});
    $this->{layout}->addWidget($this->{bottomFiller});
    $this->{widget}->setLayout($this->{layout});

    $this->createActions();
    $this->createMenus();

    $message = QString("A context menu is available by right-clicking");
    $this->statusBar()->showMessage($message);

    $this->setWindowTitle(QString("Menus"));
    $this->setMinimumSize(160, 160);
    $this->resize(480, 320);
    
    return $this;
}

sub contextMenuEvent {
    my $this = shift;
    my $event = shift;
    $this->{menu} = QMenu($this);
    $this->{menu}->addAction($this->{cutAct});
    $this->{menu}->addAction($this->{copyAct});
    $this->{menu}->addAction($this->{pasteAct});
    $this->{menu}->exec($event->globalPos());
}

sub newFile {
    my $this = shift;
    $this->{infoLabel}->setText(QString('Invoked <b>File|New</b>'));
}

sub open_ {
    my $this = shift;
    $this->{infoLabel}->setText(QString('Invoked <b>File|Open</b>'));
}

sub save {
    my $this = shift;
    $this->{infoLabel}->setText(QString('Invoked <b>File|Save</b>'));
}

sub print_ {
    my $this = shift;
    $this->{infoLabel}->setText(QString('Invoked <b>File|Print</b>'));
}

sub undo {
    my $this = shift;
    $this->{infoLabel}->setText(QString('Invoked <b>Edit|Undo</b>'));
}

sub redo {
    my $this = shift;
    $this->{infoLabel}->setText(QString('Invoked <b>Edit|Redo</b>'));
}

sub cut {
    my $this = shift;
    $this->{infoLabel}->setText(QString('Invoked <b>Edit|Cut</b>'));
}

sub copy {
    my $this = shift;
    $this->{infoLabel}->setText(QString('Invoked <b>Edit|Copy</b>'));
}

sub paste {
    my $this = shift;
    $this->{infoLabel}->setText(QString('Invoked <b>Edit|Paste</b>'));
}

sub bold {
    my $this = shift;
    $this->{infoLabel}->setText(QString('Invoked <b>Edit|Format|Bold</b>'));
}

sub italic {
    my $this = shift;
    $this->{infoLabel}->setText(QString('Invoked <b>Edit|Format|Italic</b>'));
}

sub leftAlign {
    my $this = shift;
    $this->{infoLabel}->setText(QString('Invoked <b>Edit|Format|Left Align</b>'));
}

sub rightAlign {
    my $this = shift;
    $this->{infoLabel}->setText(QString('Invoked <b>Edit|Format|Right Align</b>'));
}

sub justify {
    my $this = shift;
    $this->{infoLabel}->setText(QString('Invoked <b>Edit|Format|Justify</b>'));
}

sub center {
    my $this = shift;
    $this->{infoLabel}->setText(QString('Invoked <b>Edit|Format|Center</b>'));
}

sub setLineSpacing {
    my $this = shift;
    $this->{infoLabel}->setText(QString('Invoked <b>Edit|Format|Set Line Spacing</b>'));
}

sub setParagraphSpacing {
    my $this = shift;
    $this->{infoLabel}->setText(QString('Invoked <b>Edit|Format|Set Paragraph Spacing</b>'));
}

sub about {
    my $this = shift;
    $this->{infoLabel}->setText(QString('Invoked <b>Help|About</b>'));
    QMessageBox::about($this, QString("About Menu"),
            QString('The <b>Menu</b> example shows how to create menu-bar menus and context menus.'));
}

sub aboutQt {
    my $this = shift;
    $this->{infoLabel}->setText(QString('Invoked <b>Help|About Qt</b>'));
}

sub createActions {
    my $this = shift;

    $this->{newAct} = QAction(QString("&New"), $this);
    $this->{newAct}->setShortcut(QKeySequence(QString("Ctrl+N")));
    $this->{newAct}->setStatusTip(QString("Create a new file"));
    $this->connect($this->{newAct}, SIGNAL('triggered()'), $this, SLOT('newFile()'));

    $this->{openAct} = QAction(QString("&Open..."), $this);
    $this->{openAct}->setShortcut(QKeySequence(QString("Ctrl+O")));
    $this->{openAct}->setStatusTip(QString("Open an existing file"));
    $this->connect($this->{openAct}, SIGNAL('triggered()'), $this, SLOT('open_()'));

    $this->{saveAct} = QAction(QString("&Save"), $this);
    $this->{saveAct}->setShortcut(QKeySequence(QString("Ctrl+S")));
    $this->{saveAct}->setStatusTip(QString("Save the document to disk"));
    $this->connect($this->{saveAct}, SIGNAL('triggered()'), $this, SLOT('save()'));

    $this->{printAct} = QAction(QString("&Print..."), $this);
    $this->{printAct}->setShortcut(QKeySequence(QString("Ctrl+P")));
    $this->{printAct}->setStatusTip(QString("Print the document"));
    $this->connect($this->{printAct}, SIGNAL('triggered()'), $this, SLOT('print_()'));

    $this->{exitAct} = QAction(QString("E&xit"), $this);
    $this->{exitAct}->setShortcut(QKeySequence(QString("Ctrl+Q")));
    $this->{exitAct}->setStatusTip(QString("Exit the application"));
    $this->connect($this->{exitAct}, SIGNAL('triggered()'), $this, SLOT('close()'));

    $this->{undoAct} = QAction(QString("&Undo"), $this);
    $this->{undoAct}->setShortcut(QKeySequence(QString("Ctrl+Z")));
    $this->{undoAct}->setStatusTip(QString("Undo the last operation"));
    $this->connect($this->{undoAct}, SIGNAL('triggered()'), $this, SLOT('undo()'));

    $this->{redoAct} = QAction(QString("&Redo"), $this);
    $this->{redoAct}->setShortcut(QKeySequence(QString("Ctrl+Y")));
    $this->{redoAct}->setStatusTip(QString("Redo the last operation"));
    $this->connect($this->{redoAct}, SIGNAL('triggered()'), $this, SLOT('redo()'));

    $this->{cutAct} = QAction(QString("Cu&t"), $this);
    $this->{cutAct}->setShortcut(QKeySequence(QString("Ctrl+X")));
    $this->{cutAct}->setStatusTip(QString("Cut the current selection's contents to the clipboard"));
    $this->connect($this->{cutAct}, SIGNAL('triggered()'), $this, SLOT('cut()'));

    $this->{copyAct} = QAction(QString("&Copy"), $this);
    $this->{copyAct}->setShortcut(QKeySequence(QString("Ctrl+C")));
    $this->{copyAct}->setStatusTip(QString("Copy the current selection's contents to the clipboard"));
    $this->connect($this->{copyAct}, SIGNAL('triggered()'), $this, SLOT('copy()'));

    $this->{pasteAct} = QAction(QString("&Paste"), $this);
    $this->{pasteAct}->setShortcut(QKeySequence(QString("Ctrl+V")));
    $this->{pasteAct}->setStatusTip(QString("Paste the clipboard's contents into the current selection"));
    $this->connect($this->{pasteAct}, SIGNAL('triggered()'), $this, SLOT('paste()'));

    $this->{boldAct} = QAction(QString("&Bold"), $this);
    $this->{boldAct}->setCheckable(1); # 1 == true
    $this->{boldAct}->setShortcut(QKeySequence(QString("Ctrl+B")));
    $this->{boldAct}->setStatusTip(QString("Make the text bold"));
    $this->connect($this->{boldAct}, SIGNAL('triggered()'), $this, SLOT('bold()'));

    $this->{boldFont} = $this->{boldAct}->font();
    $this->{boldFont}->setBold(1); # 1 == true
    $this->{boldAct}->setFont($this->{boldFont});

    $this->{italicAct} = QAction(QString("&Italic"), $this);
    $this->{italicAct}->setCheckable(1); # true
    $this->{italicAct}->setShortcut(QKeySequence(QString("Ctrl+I")));
    $this->{italicAct}->setStatusTip(QString("Make the text italic"));
    $this->connect($this->{italicAct}, SIGNAL('triggered()'), $this, SLOT('italic()'));

    $this->{italicFont} = $this->{italicAct}->font();
    $this->{italicFont}->setItalic(1); # true
    $this->{italicAct}->setFont($this->{italicFont});

    $this->{setLineSpacingAct} = QAction(QString("Set &Line Spacing..."), $this);
    $this->{setLineSpacingAct}->setStatusTip(QString("Change the gap between the lines of a paragraph"));
    $this->connect($this->{setLineSpacingAct}, SIGNAL('triggered()'), $this, SLOT('setLineSpacing()'));

    $this->{setParagraphSpacingAct} = QAction(QString("Set &Paragraph Spacing..."), $this);
    $this->{setLineSpacingAct}->setStatusTip(QString("Change the gap between paragraphs"));
    $this->connect($this->{setParagraphSpacingAct}, SIGNAL('triggered()'), $this, SLOT('setParagraphSpacing()'));

    $this->{aboutAct} = QAction(QString("&About"), $this);
    $this->{aboutAct}->setStatusTip(QString("Show the application's About box"));
    $this->connect($this->{aboutAct}, SIGNAL('triggered()'), $this, SLOT('about()'));

    $this->{aboutQtAct} = QAction(QString("About &Qt"), $this);
    $this->{aboutQtAct}->setStatusTip(QString("Show the Qt library's About box"));
    $this->connect($this->{aboutQtAct}, SIGNAL('triggered()'), $qApp, SLOT('aboutQt()'));
    $this->connect($this->{aboutQtAct}, SIGNAL('triggered()'), $this, SLOT('aboutQt()'));

    $this->{leftAlignAct} = QAction(QString("&Left Align"), $this);
    $this->{leftAlignAct}->setCheckable(1); # true
    $this->{leftAlignAct}->setShortcut(QKeySequence(QString("Ctrl+L")));
    $this->{leftAlignAct}->setStatusTip(QString("Left align the selected text"));
    $this->connect($this->{leftAlignAct}, SIGNAL('triggered()'), $this, SLOT('leftAlign()'));

    $this->{rightAlignAct} = QAction(QString("&Right Align"), $this);
    $this->{rightAlignAct}->setCheckable(1); # true
    $this->{rightAlignAct}->setShortcut(QKeySequence(QString("Ctrl+R")));
    $this->{rightAlignAct}->setStatusTip(QString("Right align the selected text"));
    $this->connect($this->{rightAlignAct}, SIGNAL('triggered()'), $this, SLOT('rightAlign()'));

    $this->{justifyAct} = QAction(QString("&Justify"), $this);
    $this->{justifyAct}->setCheckable(1); # true
    $this->{justifyAct}->setShortcut(QKeySequence(QString("Ctrl+J")));
    $this->{justifyAct}->setStatusTip(QString("Justify the selected text"));
    $this->connect($this->{justifyAct}, SIGNAL('triggered()'), $this, SLOT('justify()'));

    $this->{centerAct} = QAction(QString("&Center"), $this);
    $this->{centerAct}->setCheckable(1); # true
    $this->{centerAct}->setShortcut(QKeySequence(QString("Ctrl+E")));
    $this->{centerAct}->setStatusTip(QString("Center the selected text"));
    $this->connect($this->{centerAct}, SIGNAL('triggered()'), $this, SLOT('center()'));
    
    $this->{alignmentGroup} = QActionGroup($this);
    $this->{alignmentGroup}->addAction($this->{leftAlignAct});
    $this->{alignmentGroup}->addAction($this->{rightAlignAct});
    $this->{alignmentGroup}->addAction($this->{justifyAct});
    $this->{alignmentGroup}->addAction($this->{centerAct});
    $this->{leftAlignAct}->setChecked(1); # true 
}

sub createMenus {
    my $this = shift;

    $this->{fileMenu} = $this->menuBar()->addMenu(QString("&File"));
    $this->{fileMenu}->Qt::QWidget::addAction($this->{newAct});
    $this->{fileMenu}->Qt::QWidget::addAction($this->{openAct});
    $this->{fileMenu}->Qt::QWidget::addAction($this->{saveAct});
    $this->{fileMenu}->Qt::QWidget::addAction($this->{printAct});
    $this->{fileMenu}->addSeparator();
    $this->{fileMenu}->Qt::QWidget::addAction($this->{exitAct});

    $this->{editMenu} = $this->menuBar()->addMenu(QString("&Edit"));
    $this->{editMenu}->Qt::QWidget::addAction($this->{undoAct});
    $this->{editMenu}->Qt::QWidget::addAction($this->{redoAct});
    $this->{editMenu}->addSeparator();
    $this->{editMenu}->Qt::QWidget::addAction($this->{cutAct});
    $this->{editMenu}->Qt::QWidget::addAction($this->{copyAct});
    $this->{editMenu}->Qt::QWidget::addAction($this->{pasteAct});
    $this->{editMenu}->addSeparator();

    $this->{helpMenu} = $this->menuBar()->addMenu(QString("&Help"));
    $this->{helpMenu}->Qt::QWidget::addAction($this->{aboutAct});
    $this->{helpMenu}->Qt::QWidget::addAction($this->{aboutQtAct});

    $this->{formatMenu} = $this->{editMenu}->addMenu(QString("&Format"));
    $this->{formatMenu}->Qt::QWidget::addAction($this->{boldAct});
    $this->{formatMenu}->Qt::QWidget::addAction($this->{italicAct});
    $this->{formatMenu}->addSeparator()->setText(QString("Alignment"));
    $this->{formatMenu}->Qt::QWidget::addAction($this->{leftAlignAct});
    $this->{formatMenu}->Qt::QWidget::addAction($this->{rightAlignAct});
    $this->{formatMenu}->Qt::QWidget::addAction($this->{justifyAct});
    $this->{formatMenu}->Qt::QWidget::addAction($this->{centerAct});
    $this->{formatMenu}->addSeparator();
    $this->{formatMenu}->Qt::QWidget::addAction($this->{setLineSpacingAct});
    $this->{formatMenu}->Qt::QWidget::addAction($this->{setParagraphSpacingAct});
}

1;

main.cpp

/****************************************************************************
**
** Copyright (C) 2004-2007 Trolltech ASA. All rights reserved.
**
** This file is part of the example classes of the Qt Toolkit.
**
** This file may be used under the terms of the GNU General Public
** License version 2.0 as published by the Free Software Foundation
** and appearing in the file LICENSE.GPL included in the packaging of
** this file.  Please review the following information to ensure GNU
** General Public Licensing requirements will be met:
** http://trolltech.com/products/qt/licenses/licensing/opensource/
**
** If you are unsure which license is appropriate for your use, please
** review the following information:
** http://trolltech.com/products/qt/licenses/licensing/licensingoverview
** or contact the sales department at sales@trolltech.com.
**
** In addition, as a special exception, Trolltech gives you certain
** additional rights. These rights are described in the Trolltech GPL
** Exception version 1.0, which can be found at
** http://www.trolltech.com/products/qt/gplexception/ and in the file
** GPL_EXCEPTION.txt in this package.
**
** In addition, as a special exception, Trolltech, as the sole copyright
** holder for Qt Designer, grants users of the Qt/Eclipse Integration
** plug-in the right for the Qt/Eclipse Integration to link to
** functionality provided by Qt Designer and its related libraries.
**
** Trolltech reserves all rights not expressly granted herein.
** 
** Trolltech ASA (c) 2007
**
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
****************************************************************************/

#include <QApplication>
#include <QTranslator>
#include <QLocale>
#include <QLibraryInfo>

#include "dialog.h"

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

    QString translatorFileName = QLatin1String("qt_");
    translatorFileName += QLocale::system().name();
    QTranslator *translator = new QTranslator(&app);
    if (translator->load(translatorFileName, QLibraryInfo::location(QLibraryInfo::TranslationsPath)))
        app.installTranslator(translator);

    Dialog dialog;
    return dialog.exec();
}

dialog.cpp

/****************************************************************************
**
** Copyright (C) 2004-2007 Trolltech ASA. All rights reserved.
**
** This file is part of the example classes of the Qt Toolkit.
**
** This file may be used under the terms of the GNU General Public
** License version 2.0 as published by the Free Software Foundation
** and appearing in the file LICENSE.GPL included in the packaging of
** this file.  Please review the following information to ensure GNU
** General Public Licensing requirements will be met:
** http://trolltech.com/products/qt/licenses/licensing/opensource/
**
** If you are unsure which license is appropriate for your use, please
** review the following information:
** http://trolltech.com/products/qt/licenses/licensing/licensingoverview
** or contact the sales department at sales@trolltech.com.
**
** In addition, as a special exception, Trolltech gives you certain
** additional rights. These rights are described in the Trolltech GPL
** Exception version 1.0, which can be found at
** http://www.trolltech.com/products/qt/gplexception/ and in the file
** GPL_EXCEPTION.txt in this package.
**
** In addition, as a special exception, Trolltech, as the sole copyright
** holder for Qt Designer, grants users of the Qt/Eclipse Integration
** plug-in the right for the Qt/Eclipse Integration to link to
** functionality provided by Qt Designer and its related libraries.
**
** Trolltech reserves all rights not expressly granted herein.
** 
** Trolltech ASA (c) 2007
**
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
****************************************************************************/

#include <QtGui>

#include "dialog.h"

#define MESSAGE \
    Dialog::tr("<p>Message boxes have a caption, a text, " \
               "and any number of buttons, each with standard or custom texts." \
               "<p>Click a button to close the message box. Pressing the Esc button " \
               "will activate the detected escape button (if any).")

Dialog::Dialog(QWidget *parent)
    : QDialog(parent)
{
    errorMessageDialog = new QErrorMessage(this);

    int frameStyle = QFrame::Sunken | QFrame::Panel;

    integerLabel = new QLabel;
    integerLabel->setFrameStyle(frameStyle);
    QPushButton *integerButton =
            new QPushButton(tr("QInputDialog::get&Integer()"));

    doubleLabel = new QLabel;
    doubleLabel->setFrameStyle(frameStyle);
    QPushButton *doubleButton =
            new QPushButton(tr("QInputDialog::get&Double()"));

    itemLabel = new QLabel;
    itemLabel->setFrameStyle(frameStyle);
    QPushButton *itemButton = new QPushButton(tr("QInputDialog::getIte&m()"));

    textLabel = new QLabel;
    textLabel->setFrameStyle(frameStyle);
    QPushButton *textButton = new QPushButton(tr("QInputDialog::get&Text()"));

    colorLabel = new QLabel;
    colorLabel->setFrameStyle(frameStyle);
    QPushButton *colorButton = new QPushButton(tr("QColorDialog::get&Color()"));

    fontLabel = new QLabel;
    fontLabel->setFrameStyle(frameStyle);
    QPushButton *fontButton = new QPushButton(tr("QFontDialog::get&Font()"));

    directoryLabel = new QLabel;
    directoryLabel->setFrameStyle(frameStyle);
    QPushButton *directoryButton =
            new QPushButton(tr("QFileDialog::getE&xistingDirectory()"));

    openFileNameLabel = new QLabel;
    openFileNameLabel->setFrameStyle(frameStyle);
    QPushButton *openFileNameButton =
            new QPushButton(tr("QFileDialog::get&OpenFileName()"));

    openFileNamesLabel = new QLabel;
    openFileNamesLabel->setFrameStyle(frameStyle);
    QPushButton *openFileNamesButton =
            new QPushButton(tr("QFileDialog::&getOpenFileNames()"));

    saveFileNameLabel = new QLabel;
    saveFileNameLabel->setFrameStyle(frameStyle);
    QPushButton *saveFileNameButton =
            new QPushButton(tr("QFileDialog::get&SaveFileName()"));

    criticalLabel = new QLabel;
    criticalLabel->setFrameStyle(frameStyle);
    QPushButton *criticalButton =
            new QPushButton(tr("QMessageBox::critica&l()"));

    informationLabel = new QLabel;
    informationLabel->setFrameStyle(frameStyle);
    QPushButton *informationButton =
            new QPushButton(tr("QMessageBox::i&nformation()"));

    questionLabel = new QLabel;
    questionLabel->setFrameStyle(frameStyle);
    QPushButton *questionButton =
            new QPushButton(tr("QMessageBox::&question()"));

    warningLabel = new QLabel;
    warningLabel->setFrameStyle(frameStyle);
    QPushButton *warningButton = new QPushButton(tr("QMessageBox::&warning()"));

    errorLabel = new QLabel;
    errorLabel->setFrameStyle(frameStyle);
    QPushButton *errorButton =
            new QPushButton(tr("QErrorMessage::show&M&essage()"));

    connect(integerButton, SIGNAL(clicked()), this, SLOT(setInteger()));
    connect(doubleButton, SIGNAL(clicked()), this, SLOT(setDouble()));
    connect(itemButton, SIGNAL(clicked()), this, SLOT(setItem()));
    connect(textButton, SIGNAL(clicked()), this, SLOT(setText()));
    connect(colorButton, SIGNAL(clicked()), this, SLOT(setColor()));
    connect(fontButton, SIGNAL(clicked()), this, SLOT(setFont()));
    connect(directoryButton, SIGNAL(clicked()),
            this, SLOT(setExistingDirectory()));
    connect(openFileNameButton, SIGNAL(clicked()),
            this, SLOT(setOpenFileName()));
    connect(openFileNamesButton, SIGNAL(clicked()),
            this, SLOT(setOpenFileNames()));
    connect(saveFileNameButton, SIGNAL(clicked()),
            this, SLOT(setSaveFileName()));
    connect(criticalButton, SIGNAL(clicked()), this, SLOT(criticalMessage()));
    connect(informationButton, SIGNAL(clicked()),
            this, SLOT(informationMessage()));
    connect(questionButton, SIGNAL(clicked()), this, SLOT(questionMessage()));
    connect(warningButton, SIGNAL(clicked()), this, SLOT(warningMessage()));
    connect(errorButton, SIGNAL(clicked()), this, SLOT(errorMessage()));

    native = new QCheckBox(this);
    native->setText("Use native file dialog.");
    native->setChecked(true);
#ifndef Q_WS_WIN
#ifndef Q_OS_MAC
    native->hide();
#endif
#endif
    QGridLayout *layout = new QGridLayout;
    layout->setColumnStretch(1, 1);
    layout->setColumnMinimumWidth(1, 250);
    layout->addWidget(integerButton, 0, 0);
    layout->addWidget(integerLabel, 0, 1);
    layout->addWidget(doubleButton, 1, 0);
    layout->addWidget(doubleLabel, 1, 1);
    layout->addWidget(itemButton, 2, 0);
    layout->addWidget(itemLabel, 2, 1);
    layout->addWidget(textButton, 3, 0);
    layout->addWidget(textLabel, 3, 1);
    layout->addWidget(colorButton, 4, 0);
    layout->addWidget(colorLabel, 4, 1);
    layout->addWidget(fontButton, 5, 0);
    layout->addWidget(fontLabel, 5, 1);
    layout->addWidget(directoryButton, 6, 0);
    layout->addWidget(directoryLabel, 6, 1);
    layout->addWidget(openFileNameButton, 7, 0);
    layout->addWidget(openFileNameLabel, 7, 1);
    layout->addWidget(openFileNamesButton, 8, 0);
    layout->addWidget(openFileNamesLabel, 8, 1);
    layout->addWidget(saveFileNameButton, 9, 0);
    layout->addWidget(saveFileNameLabel, 9, 1);
    layout->addWidget(criticalButton, 10, 0);
    layout->addWidget(criticalLabel, 10, 1);
    layout->addWidget(informationButton, 11, 0);
    layout->addWidget(informationLabel, 11, 1);
    layout->addWidget(questionButton, 12, 0);
    layout->addWidget(questionLabel, 12, 1);
    layout->addWidget(warningButton, 13, 0);
    layout->addWidget(warningLabel, 13, 1);
    layout->addWidget(errorButton, 14, 0);
    layout->addWidget(errorLabel, 14, 1);
    layout->addWidget(native, 15, 0);
    setLayout(layout);

    setWindowTitle(tr("Standard Dialogs"));
}

void Dialog::setInteger()
{
    bool ok;
    int i = QInputDialog::getInteger(this, tr("QInputDialog::getInteger()"),
                                     tr("Percentage:"), 25, 0, 100, 1, &ok);
    if (ok)
        integerLabel->setText(tr("%1%").arg(i));
}

void Dialog::setDouble()
{
    bool ok;
    double d = QInputDialog::getDouble(this, tr("QInputDialog::getDouble()"),
                                       tr("Amount:"), 37.56, -10000, 10000, 2, &ok);
    if (ok)
        doubleLabel->setText(QString("$%1").arg(d));
}

void Dialog::setItem()
{
    QStringList items;
    items << tr("Spring") << tr("Summer") << tr("Fall") << tr("Winter");

    bool ok;
    QString item = QInputDialog::getItem(this, tr("QInputDialog::getItem()"),
                                         tr("Season:"), items, 0, false, &ok);
    if (ok && !item.isEmpty())
        itemLabel->setText(item);
}

void Dialog::setText()
{
    bool ok;
    QString text = QInputDialog::getText(this, tr("QInputDialog::getText()"),
                                         tr("User name:"), QLineEdit::Normal,
                                         QDir::home().dirName(), &ok);
    if (ok && !text.isEmpty())
        textLabel->setText(text);
}

void Dialog::setColor()
{
    QColor color = QColorDialog::getColor(Qt::green, this);
    if (color.isValid()) {
        colorLabel->setText(color.name());
        colorLabel->setPalette(QPalette(color));
        colorLabel->setAutoFillBackground(true);
    }
}

void Dialog::setFont()
{
    bool ok;
    QFont font = QFontDialog::getFont(&ok, QFont(fontLabel->text()), this);
    if (ok) {
        fontLabel->setText(font.key());
        fontLabel->setFont(font);
    }
}

void Dialog::setExistingDirectory()
{
    QFileDialog::Options options = QFileDialog::DontResolveSymlinks | QFileDialog::ShowDirsOnly;
    if (!native->isChecked())
        options |= QFileDialog::DontUseNativeDialog;
    QString directory = QFileDialog::getExistingDirectory(this,
                                tr("QFileDialog::getExistingDirectory()"),
                                directoryLabel->text(),
                                options);
    if (!directory.isEmpty())
        directoryLabel->setText(directory);
}

void Dialog::setOpenFileName()
{
    QFileDialog::Options options;
    if (!native->isChecked())
        options |= QFileDialog::DontUseNativeDialog;
    QString selectedFilter;
    QString fileName = QFileDialog::getOpenFileName(this,
                                tr("QFileDialog::getOpenFileName()"),
                                openFileNameLabel->text(),
                                tr("All Files (*);;Text Files (*.txt)"),
                                &selectedFilter,
                                options);
    if (!fileName.isEmpty())
        openFileNameLabel->setText(fileName);
}

void Dialog::setOpenFileNames()
{
    QFileDialog::Options options;
    if (!native->isChecked())
        options |= QFileDialog::DontUseNativeDialog;
    QString selectedFilter;
    QStringList files = QFileDialog::getOpenFileNames(
                                this, tr("QFileDialog::getOpenFileNames()"),
                                openFilesPath,
                                tr("All Files (*);;Text Files (*.txt)"),
                                &selectedFilter,
                                options);
    if (files.count()) {
        openFilesPath = files[0];
        openFileNamesLabel->setText(QString("[%1]").arg(files.join(", ")));
    }
}

void Dialog::setSaveFileName()
{
    QFileDialog::Options options;
    if (!native->isChecked())
        options |= QFileDialog::DontUseNativeDialog;
    QString selectedFilter;
    QString fileName = QFileDialog::getSaveFileName(this,
                                tr("QFileDialog::getSaveFileName()"),
                                saveFileNameLabel->text(),
                                tr("All Files (*);;Text Files (*.txt)"),
                                &selectedFilter,
                                options);
    if (!fileName.isEmpty())
        saveFileNameLabel->setText(fileName);
}

void Dialog::criticalMessage()
{
    QMessageBox::StandardButton reply;
    reply = QMessageBox::critical(this, tr("QMessageBox::critical()"),
                                    MESSAGE,
                                    QMessageBox::Abort | QMessageBox::Retry | QMessageBox::Ignore);
    if (reply == QMessageBox::Abort)
        criticalLabel->setText(tr("Abort"));
    else if (reply == QMessageBox::Retry)
        criticalLabel->setText(tr("Retry"));
    else
        criticalLabel->setText(tr("Ignore"));
}

void Dialog::informationMessage()
{
    QMessageBox::StandardButton reply;
    reply = QMessageBox::information(this, tr("QMessageBox::information()"), MESSAGE);
    if (reply == QMessageBox::Ok)
        informationLabel->setText(tr("OK"));
    else
        informationLabel->setText(tr("Escape"));
}

void Dialog::questionMessage()
{
    QMessageBox::StandardButton reply;
    reply = QMessageBox::question(this, tr("QMessageBox::question()"),
                                    MESSAGE,
                                    QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
    if (reply == QMessageBox::Yes)
        questionLabel->setText(tr("Yes"));
    else if (reply == QMessageBox::No)
        questionLabel->setText(tr("No"));
    else
        questionLabel->setText(tr("Cancel"));
}

void Dialog::warningMessage()
{
    QMessageBox msgBox(QMessageBox::Warning, tr("QMessageBox::warning()"),
                       MESSAGE, 0, this);
    msgBox.addButton(tr("Save &Again"), QMessageBox::AcceptRole);
    msgBox.addButton(tr("&Continue"), QMessageBox::RejectRole);
    if (msgBox.exec() == QMessageBox::AcceptRole)
        warningLabel->setText(tr("Save Again"));
    else
        warningLabel->setText(tr("Continue"));

}

void Dialog::errorMessage()
{
    errorMessageDialog->showMessage(
            tr("This dialog shows and remembers error messages. "
               "If the checkbox is checked (as it is by default), "
               "the shown message will be shown again, "
               "but if the user unchecks the box the message "
               "will not appear again if QErrorMessage::showMessage() "
               "is called with the same message."));
    errorLabel->setText(tr("If the box is unchecked, the message "
                           "won't appear again."));
}

dialog.h

/****************************************************************************
**
** Copyright (C) 2004-2007 Trolltech ASA. All rights reserved.
**
** This file is part of the example classes of the Qt Toolkit.
**
** This file may be used under the terms of the GNU General Public
** License version 2.0 as published by the Free Software Foundation
** and appearing in the file LICENSE.GPL included in the packaging of
** this file.  Please review the following information to ensure GNU
** General Public Licensing requirements will be met:
** http://trolltech.com/products/qt/licenses/licensing/opensource/
**
** If you are unsure which license is appropriate for your use, please
** review the following information:
** http://trolltech.com/products/qt/licenses/licensing/licensingoverview
** or contact the sales department at sales@trolltech.com.
**
** In addition, as a special exception, Trolltech gives you certain
** additional rights. These rights are described in the Trolltech GPL
** Exception version 1.0, which can be found at
** http://www.trolltech.com/products/qt/gplexception/ and in the file
** GPL_EXCEPTION.txt in this package.
**
** In addition, as a special exception, Trolltech, as the sole copyright
** holder for Qt Designer, grants users of the Qt/Eclipse Integration
** plug-in the right for the Qt/Eclipse Integration to link to
** functionality provided by Qt Designer and its related libraries.
**
** Trolltech reserves all rights not expressly granted herein.
** 
** Trolltech ASA (c) 2007
**
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
****************************************************************************/

#ifndef DIALOG_H
#define DIALOG_H

#include <QDialog>

class QCheckBox;
class QLabel;
class QErrorMessage;

class Dialog : public QDialog
{
    Q_OBJECT

public:
    Dialog(QWidget *parent = 0);

private slots:
    void setInteger();
    void setDouble();
    void setItem();
    void setText();
    void setColor();
    void setFont();
    void setExistingDirectory();
    void setOpenFileName();
    void setOpenFileNames();
    void setSaveFileName();
    void criticalMessage();
    void informationMessage();
    void questionMessage();
    void warningMessage();
    void errorMessage();

private:
    QCheckBox *native;
    QLabel *integerLabel;
    QLabel *doubleLabel;
    QLabel *itemLabel;
    QLabel *textLabel;
    QLabel *colorLabel;
    QLabel *fontLabel;
    QLabel *directoryLabel;
    QLabel *openFileNameLabel;
    QLabel *openFileNamesLabel;
    QLabel *saveFileNameLabel;
    QLabel *criticalLabel;
    QLabel *informationLabel;
    QLabel *questionLabel;
    QLabel *warningLabel;
    QLabel *errorLabel;
    QErrorMessage *errorMessageDialog;

    QString openFilesPath;
};

#endif

main.pl

#!/usr/bin/perl -w

use Qt;
#use Qt::QByteArray;
use Qt::QString;
use Qt::QApplication;
use Qt::QTranslator;
use Qt::QLocale;
use Qt::QLibraryInfo;

use Dialog;

unshift @ARGV, 'standarddialogs';

my $app = QApplication(\@ARGV);
my $translatorFileName = QString("qt_");
$translatorFileName += Qt::QLocale::system()->name();
#print $translatorFileName->toLatin1()->data(), "\n";
my $translator = QTranslator($app);
if ($translator->load($translatorFileName, Qt::QLibraryInfo::location(Qt::QLibraryInfo::TranslationsPath))) {
    $app->installTranslator($translator);
}
my $dialog = Dialog();
$dialog->exec();

dialog.pm

package Dialog;

use Qt;
use Qt::QString;
use Qt::QStringList;
use Qt::QCheckBox;
use Qt::QLabel;
use Qt::QPushButton;
use Qt::QLineEdit;
use Qt::QGridLayout;
use Qt::QDir;
use Qt::QColor;
use Qt::QPalette;
use Qt::QFont;
use Qt::QMessageBox;
use Qt::QErrorMessage;
use Qt::QDialog;
use Qt::QInputDialog;
use Qt::QColorDialog;
use Qt::QFontDialog;
use Qt::QFileDialog;


our @ISA=qw(Qt::QDialog);

our @EXPORT=qw(Dialog);

my $MESSAGE = TR('<p>Message boxes have a caption, a text, '.
               'and any number of buttons, each with standard or custom texts.'.
               '<p>Click a button to close the message box. Pressing the Esc button '.
               'will activate the detected escape button (if any).');

sub Dialog {
    my $class = 'Dialog';
    my @signals = ();
    my @slots = ('setInteger()', 'setDouble()', 'setItem()', 'setText()', 'setColor()', 'setFont()', 'setExistingDirectory()',
	'setOpenFileName()', 'setOpenFileNames()', 'setSaveFileName()', 'criticalMessage()', 'informationMessage()',
	'questionMessage()', 'warningMessage()', 'errorMessage()');
    my $this = QDialog(\@signals, \@slots);
    bless $this, $class;

    $this->{errorMessageDialog} = QErrorMessage($this);

    my $frameStyle = Qt::QFrame::Sunken | Qt::QFrame::Panel;

    $this->{integerLabel} = QLabel();
    $this->{integerLabel}->setFrameStyle($frameStyle);
    $this->{integerButton} = QPushButton(TR("QInputDialog::get&Integer()"));

    $this->{doubleLabel} = QLabel();
    $this->{doubleLabel}->setFrameStyle($frameStyle);
    $this->{doubleButton} = QPushButton(TR("QInputDialog::get&Double()"));

    $this->{itemLabel} = QLabel();
    $this->{itemLabel}->setFrameStyle($frameStyle);
    $this->{itemButton} = QPushButton(TR("QInputDialog::getIte&m()"));

    $this->{textLabel} = QLabel();
    $this->{textLabel}->setFrameStyle($frameStyle);
    $this->{textButton} = QPushButton(TR("QInputDialog::get&Text()"));

    $this->{colorLabel} = QLabel();
    $this->{colorLabel}->setFrameStyle($frameStyle);
    $this->{colorButton} = QPushButton(TR("QColorDialog::get&Color()"));

    $this->{fontLabel} = QLabel();
    $this->{fontLabel}->setFrameStyle($frameStyle);
    $this->{fontButton} = QPushButton(TR("QFontDialog::get&Font()"));

    $this->{directoryLabel} = QLabel();
    $this->{directoryLabel}->setFrameStyle($frameStyle);
    $this->{directoryButton} = QPushButton(TR("QFileDialog::getE&xistingDirectory()"));

    $this->{openFileNameLabel} = QLabel();
    $this->{openFileNameLabel}->setFrameStyle($frameStyle);
    $this->{openFileNameButton} = QPushButton(TR("QFileDialog::get&OpenFileName()"));

    $this->{openFileNamesLabel} = QLabel();
    $this->{openFileNamesLabel}->setFrameStyle($frameStyle);
    $this->{openFileNamesButton} = QPushButton(TR("QFileDialog::&getOpenFileNames()"));

    $this->{saveFileNameLabel} = QLabel;
    $this->{saveFileNameLabel}->setFrameStyle($frameStyle);
    $this->{saveFileNameButton} = QPushButton(TR("QFileDialog::get&SaveFileName()"));

    $this->{criticalLabel} = QLabel();
    $this->{criticalLabel}->setFrameStyle($frameStyle);
    $this->{criticalButton} = QPushButton(TR("QMessageBox::critica&l()"));

    $this->{informationLabel} = QLabel();
    $this->{informationLabel}->setFrameStyle($frameStyle);
    $this->{informationButton} = QPushButton(TR("QMessageBox::i&nformation()"));

    $this->{questionLabel} = QLabel();
    $this->{questionLabel}->setFrameStyle($frameStyle);
    $this->{questionButton} = QPushButton(TR("QMessageBox::&question()"));

    $this->{warningLabel} = QLabel();
    $this->{warningLabel}->setFrameStyle($frameStyle);
    $this->{warningButton} = QPushButton(TR("QMessageBox::&warning()"));

    $this->{errorLabel} = QLabel();
    $this->{errorLabel}->setFrameStyle($frameStyle);
    $this->{errorButton} = QPushButton(TR("QErrorMessage::show&M&essage()"));

    $this->connect($this->{integerButton}, SIGNAL('clicked()'), $this, SLOT('setInteger()'));
    $this->connect($this->{doubleButton}, SIGNAL('clicked()'), $this, SLOT('setDouble()'));
    $this->connect($this->{itemButton}, SIGNAL('clicked()'), $this, SLOT('setItem()'));
    $this->connect($this->{textButton}, SIGNAL('clicked()'), $this, SLOT('setText()'));
    $this->connect($this->{colorButton}, SIGNAL('clicked()'), $this, SLOT('setColor()'));
    $this->connect($this->{fontButton}, SIGNAL('clicked()'), $this, SLOT('setFont()'));
    CONNECT($this->{directoryButton}, SIGNAL('clicked()'),
            $this, SLOT('setExistingDirectory()'));
    CONNECT($this->{openFileNameButton}, SIGNAL('clicked()'),
            $this, SLOT('setOpenFileName()'));
    CONNECT($this->{openFileNamesButton}, SIGNAL('clicked()'),
            $this, SLOT('setOpenFileNames()'));
    $this->connect($this->{saveFileNameButton}, SIGNAL('clicked()'),
            $this, SLOT('setSaveFileName()'));
    $this->connect($this->{criticalButton}, SIGNAL('clicked()'), $this, SLOT('criticalMessage()'));
    $this->connect($this->{informationButton}, SIGNAL('clicked()'),
            $this, SLOT('informationMessage()'));
    $this->connect($this->{questionButton}, SIGNAL('clicked()'), $this, SLOT('questionMessage()'));
    $this->connect($this->{warningButton}, SIGNAL('clicked()'), $this, SLOT('warningMessage()'));
    $this->connect($this->{errorButton}, SIGNAL('clicked()'), $this, SLOT('errorMessage()'));

    $this->{native} = QCheckBox($this);
    $this->{native}->setText(QString("Use native file dialog."));
    $this->{native}->setChecked(1); # true
#ifndef Q_WS_WIN
#ifndef Q_OS_MAC
    $this->{native}->hide();
#endif
#endif
    $this->{layout} = QGridLayout();
    $this->{layout}->setColumnStretch(1, 1);
    $this->{layout}->setColumnMinimumWidth(1, 250);
    $this->{layout}->addWidget($this->{integerButton}, 0, 0);
    $this->{layout}->addWidget($this->{integerLabel}, 0, 1);
    $this->{layout}->addWidget($this->{doubleButton}, 1, 0);
    $this->{layout}->addWidget($this->{doubleLabel}, 1, 1);
    $this->{layout}->addWidget($this->{itemButton}, 2, 0);
    $this->{layout}->addWidget($this->{itemLabel}, 2, 1);
    $this->{layout}->addWidget($this->{textButton}, 3, 0);
    $this->{layout}->addWidget($this->{textLabel}, 3, 1);
    $this->{layout}->addWidget($this->{colorButton}, 4, 0);
    $this->{layout}->addWidget($this->{colorLabel}, 4, 1);
    $this->{layout}->addWidget($this->{fontButton}, 5, 0);
    $this->{layout}->addWidget($this->{fontLabel}, 5, 1);
    $this->{layout}->addWidget($this->{directoryButton}, 6, 0);
    $this->{layout}->addWidget($this->{directoryLabel}, 6, 1);
    $this->{layout}->addWidget($this->{openFileNameButton}, 7, 0);
    $this->{layout}->addWidget($this->{openFileNameLabel}, 7, 1);
    $this->{layout}->addWidget($this->{openFileNamesButton}, 8, 0);
    $this->{layout}->addWidget($this->{openFileNamesLabel}, 8, 1);
    $this->{layout}->addWidget($this->{saveFileNameButton}, 9, 0);
    $this->{layout}->addWidget($this->{saveFileNameLabel}, 9, 1);
    $this->{layout}->addWidget($this->{criticalButton}, 10, 0);
    $this->{layout}->addWidget($this->{criticalLabel}, 10, 1);
    $this->{layout}->addWidget($this->{informationButton}, 11, 0);
    $this->{layout}->addWidget($this->{informationLabel}, 11, 1);
    $this->{layout}->addWidget($this->{questionButton}, 12, 0);
    $this->{layout}->addWidget($this->{questionLabel}, 12, 1);
    $this->{layout}->addWidget($this->{warningButton}, 13, 0);
    $this->{layout}->addWidget($this->{warningLabel}, 13, 1);
    $this->{layout}->addWidget($this->{errorButton}, 14, 0);
    $this->{layout}->addWidget($this->{errorLabel}, 14, 1);
    $this->{layout}->addWidget($this->{native}, 15, 0);
    $this->setLayout($this->{layout});

    $this->setWindowTitle(TR("Standard Dialogs"));
    
    return $this;
}

sub setInteger {
    my $this = shift;
    my $ok = 0; # need for idendification this variable as bool
    my $i = Qt::QInputDialog::getInteger($this, TR('QInputDialog::getInteger()'), TR('Percentage:'), 25, 0, 100, 1, $ok);
    $this->{integerLabel}->setText(TR("%1%")->arg($i)) if $ok;
}

sub setDouble {
    my $this = shift;
    my $ok = 0;
    my $d = Qt::QInputDialog::getDouble($this, TR("QInputDialog::getDouble()"), TR("Amount:"), 37.56, -10000.0, 10000.0, 2, $ok);
    $this->{doubleLabel}->setText(QString("$%1")->arg($d)) if $ok;
}

sub setItem {
    my $this = shift;
    my $items = QStringList();
    $items << TR("Spring") << TR("Summer") << TR("Fall") << TR("Winter");
    my $ok = 0;
    my $item = Qt::QInputDialog::getItem($this, TR("QInputDialog::getItem()"), TR("Season:"), $items, 0, 0, $ok);
    if ( $ok and not $item->isEmpty()) {
        $this->{itemLabel}->setText($item);
    }
}

sub setText {
    my $this = shift;
    my $ok = 0;
    my $text = Qt::QInputDialog::getText($this, TR("QInputDialog::getText()"), TR("User name:"), Qt::QLineEdit::Normal, Qt::QDir::home()->dirName(), $ok);
    if ($ok and not $text->isEmpty()) {
        $this->{textLabel}->setText($text);
    }
}

sub setColor {
    my $this = shift;
    $color = Qt::QColorDialog::getColor(QColor(Qt::green), $this);
    if ( $color->isValid() ) {
        $this->{colorLabel}->setText($color->name());
        $this->{colorLabel}->setPalette(QPalette($color));
        $this->{colorLabel}->setAutoFillBackground(1); # true
    }
}

sub setFont {
    my $this = shift;
    my $ok = 0;
    my $font = Qt::QFontDialog::getFont($ok, QFont($this->{fontLabel}->text()), $this);
    if ( $ok ) {
        $this->{fontLabel}->setText($font->key());
        $this->{fontLabel}->setFont($font);
    }
}

sub setExistingDirectory {
    my $this = shift;
    my $options = Qt::QFileDialog::DontResolveSymlinks | Qt::QFileDialog::ShowDirsOnly;
    unless ($this->{native}->isChecked()) {
        $options |= Qt::QFileDialog::DontUseNativeDialog;
    }
    my $directory = Qt::QFileDialog::getExistingDirectory($this, TR("QFileDialog::getExistingDirectory()"), $this->{directoryLabel}->text(), $options);
    unless ($directory->isEmpty()) {
        $this->{directoryLabel}->setText($directory);
    }
}

sub setOpenFileName {
    my $this = shift;
    my $options = 0;
    unless ($this->{native}->isChecked()) {
        $options |= Qt::QFileDialog::DontUseNativeDialog;
    }
    my $selectedFilter = QString();
    my $fileName = Qt::QFileDialog::getOpenFileName($this, TR("QFileDialog::getOpenFileName()"), $this->{openFileNameLabel}->text(),
        TR("All Files (*);;Text Files (*.txt)"), $selectedFilter, $options);
    unless ($fileName->isEmpty()) {
        $this->{openFileNameLabel}->setText($fileName);
    }
}

sub setOpenFileNames {
    my $this = shift;
    my $options = 0;
    if (!$this->{native}->isChecked()) {
        $options |= Qt::QFileDialog::DontUseNativeDialog;
    }
    my $selectedFilter = QString();
    my $files = Qt::QFileDialog::getOpenFileNames( $this, TR("QFileDialog::getOpenFileNames()"), QString("./"),
        TR("All Files (*);;Text Files (*.txt)"), $selectedFilter, $options);
    # count inherites from QList<>
#    if ( $files->count() ) { 
#        $this->{openFilesPath} = $files[0];
#        $this->{openFileNamesLabel}->setText(QString("[%1]")->arg($files->join(", ")));
#    }
    $this->{openFileNamesLabel}->setText($files->join(QString", "));
}

sub setSaveFileName {
    my $this = shift;
    my $options = 0;
    unless ($this->{native}->isChecked()) {
        $options |= Qt::QFileDialog::DontUseNativeDialog;
    }
    my $selectedFilter = QString();
    $fileName = Qt::QFileDialog::getSaveFileName($this, TR("QFileDialog::getSaveFileName()"), $this->{saveFileNameLabel}->text(),
        TR("All Files (*);;Text Files (*.txt)"), $selectedFilter, $options);
    unless ($fileName->isEmpty()) {
        $this->{saveFileNameLabel}->setText($fileName);
    }
}

sub criticalMessage {
    my $this = shift;
    my $reply = Qt::QMessageBox::critical($this, TR("QMessageBox::critical()"), $MESSAGE,
    		Qt::QMessageBox::Abort | Qt::QMessageBox::Retry | Qt::QMessageBox::Ignore);
    if ($reply == Qt::QMessageBox::Abort) {
        $this->{criticalLabel}->setText(TR("Abort"));
    }
    elsif ($reply == Qt::QMessageBox::Retry) {
        $this->{criticalLabel}->setText(TR("Retry"));
    }
    else {
        $this->{criticalLabel}->setText(TR("Ignore"));
    }
}

sub informationMessage {
    my $this = shift;
    my $reply = Qt::QMessageBox::information($this, TR("QMessageBox::information()"), $MESSAGE);
    if ($reply == Qt::QMessageBox::Ok) {
        $this->{informationLabel}->setText(TR("OK"));
    }
    else {
        $this->{informationLabel}->setText(TR("Escape"));
    }
}

sub questionMessage {
    my $this = shift;
    my $reply = Qt::QMessageBox::question($this, TR("QMessageBox::question()"), $MESSAGE,
                Qt::QMessageBox::Yes | Qt::QMessageBox::No | Qt::QMessageBox::Cancel);
    if ($reply == Qt::QMessageBox::Yes) {
        $this->{questionLabel}->setText(TR("Yes"));
    }
    elsif ($reply == Qt::QMessageBox::No) {
        $this->{questionLabel}->setText(TR("No"));
    }
    else {
        $this->{questionLabel}->setText(TR("Cancel"));
    }
}

sub warningMessage {
    my $this = shift;
    my $msgBox = QMessageBox(Qt::QMessageBox::Warning, TR("QMessageBox::warning()"), $MESSAGE, 0, $this);
    $msgBox->addButton(TR("Save &Again"), Qt::QMessageBox::AcceptRole);
    $msgBox->addButton(TR("&Continue"), Qt::QMessageBox::RejectRole);
    if ($msgBox->exec() == Qt::QMessageBox::AcceptRole) {
        $this->{warningLabel}->setText(TR("Save Again"));
    }
    else {
        $this->{warningLabel}->setText(TR("Continue"));
    }
}

sub errorMessage {
    $this = shift;
    $this->{errorMessageDialog}->showMessage(
            TR("This dialog shows and remembers error messages. ".
               "If the checkbox is checked (as it is by default), ".
               "the shown message will be shown again, ".
               "but if the user unchecks the box the message ".
               "will not appear again if QErrorMessage::showMessage() ".
               "is called with the same message."));
    $this->{errorLabel}->setText(TR("If the box is unchecked, the message won't appear again."));
}

1;

main.cpp

/****************************************************************************
 **
 ** Copyright (C) 2005-2007 Trolltech ASA. All rights reserved.
 **
 ** This file is part of the example classes of the Qt Toolkit.
 **
 ** This file may be used under the terms of the GNU General Public
 ** License version 2.0 as published by the Free Software Foundation
 ** and appearing in the file LICENSE.GPL included in the packaging of
 ** this file.  Please review the following information to ensure GNU
 ** General Public Licensing requirements will be met:
 ** http://www.trolltech.com/products/qt/opensource.html
 **
 ** If you are unsure which license is appropriate for your use, please
 ** review the following information:
 ** http://www.trolltech.com/products/qt/licensing.html or contact the
 ** sales department at sales@trolltech.com.
 **
 ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
 ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 **
 ****************************************************************************/

#include <QApplication>
#include <QFont>
#include <QGridLayout>
#include <QLCDNumber>
#include <QPushButton>
#include <QSlider>
#include <QVBoxLayout>
#include <QWidget>

class LCDRange : public QWidget
{
public:
    LCDRange(QWidget *parent = 0);
};

LCDRange::LCDRange(QWidget *parent)
    : QWidget(parent)
{
    QLCDNumber *lcd = new QLCDNumber(2);
    lcd->setSegmentStyle(QLCDNumber::Filled);

    QSlider *slider = new QSlider(Qt::Horizontal);
    slider->setRange(0, 99);
    slider->setValue(0);
    connect(slider, SIGNAL(valueChanged(int)),
            lcd, SLOT(display(int)));

    QVBoxLayout *layout = new QVBoxLayout;
    layout->addWidget(lcd);
    layout->addWidget(slider);
    setLayout(layout);
}

class MyWidget : public QWidget
{
public:
    MyWidget(QWidget *parent = 0);
};

MyWidget::MyWidget(QWidget *parent)
    : QWidget(parent)
{
    QPushButton *quit = new QPushButton(tr("Quit"));
    quit->setFont(QFont("Times", 18, QFont::Bold));
    connect(quit, SIGNAL(clicked()), qApp, SLOT(quit()));

    QGridLayout *grid = new QGridLayout;
    for (int row = 0; row < 3; ++row) {
        for (int column = 0; column < 3; ++column) {
            LCDRange *lcdRange = new LCDRange;
            grid->addWidget(lcdRange, row, column);
        }
    }

    QVBoxLayout *layout = new QVBoxLayout;
    layout->addWidget(quit);
    layout->addLayout(grid);
    setLayout(layout);
}

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    MyWidget widget;
    widget.show();
    return app.exec();
}


main.pl

#!/usr/bin/perl -w

package LCDRange;

# use blib;
use Qt;
use Qt::QLCDNumber;
use Qt::QSlider;
use Qt::QBoxLayout;
use Qt::QWidget;

our @ISA = qw(Qt::QWidget);

sub LCDRange {
    my $class = 'LCDRange';
    my $this = QWidget();
    bless $this, $class;
    
    $this->{lcd} = QLCDNumber(2);
    $this->{lcd}->setSegmentStyle(Qt::QLCDNumber::Filled);

    $this->{slider} = QSlider(Qt::Horizontal);
    $this->{slider}->setRange(0, 99);
    $this->{slider}->setValue(0);
    $this->connect($this->{slider}, SIGNAL('valueChanged(int)'), $this->{lcd}, SLOT('display(int)'));
    
    $this->{layout} = QVBoxLayout();
    $this->{layout}->addWidget($this->{lcd});
    $this->{layout}->addWidget($this->{slider});
    
    $this->setLayout($this->{layout});
    
    return $this;
}

1;

package MyWidget;

use blib;
use Qt;
use Qt::QString;
use Qt::QApplication;
use Qt::QFont;
use Qt::QGridLayout;
use Qt::QPushButton;
use Qt::QBoxLayout;
use Qt::QWidget;

our @ISA = qw(Qt::QWidget);

sub MyWidget {
    my $class = 'MyWidget';
    my $this = QWidget();
    bless $this, $class;

    $this->{quit} = QPushButton(QString("Quit"));
    $this->{quit}->setFont(QFont(QString("Times"), 18, Qt::QFont::Bold));
    $this->connect($this->{quit}, SIGNAL('clicked()'), $qApp, SLOT('quit()'));
    
    $this->{grid} = QGridLayout();
    for ( my $row = 0 ; $row < 3 ; ++$row ) {
	for ( my $col = 0 ; $col < 3 ; ++$col ) {
	    $this->{lcdRange}[$row*3+$col] = LCDRange::LCDRange();
	    $this->{grid}->addWidget($this->{lcdRange}[$row*3+$col], $row, $col);
	}
    }
    
    $this->{layout} = QVBoxLayout();
    $this->{layout}->addWidget($this->{quit});
    $this->{layout}->addLayout($this->{grid});
    $this->setLayout($this->{layout});
    
    return $this;
}

1;

package main;

# use blib;
use Qt;
use Qt::QApplication;
use Qt::QWidget;

unshift @ARGV, 'tutorial_6';

my $app = QApplication(\@ARGV);
my $widget = MyWidget::MyWidget();
$widget->show();
$app->exec();

Литература:
0.Документация Perl (perlxs, perlxstut, perlguts)
1. Dean's Extension-Building Cookbook in two parts:
Part A: http://www.cpan.org/authors/Dean_Roehrich/CookBookA-19960430.tar.gz
2. Dean's Extension-Building Cookbook in two parts:
Part B: http://www.cpan.org/authors/Dean_Roehrich/CookBookB-19960430.tar.gz
3. http://www.cpan.org/authors/Dean_Roehrich/perlobject.map-19960302.gz
4. John Keiser. Gluing C++ And Perl Together. — 2001. — http://www.johnkeiser.com/perl-xs-c++.html
5. Уолл Л., Кристиансен Т., Орвант Д. Программирование на Perl. — СПб.: Символ-плюс, 2005. — 1152 с.
6. http://search.cpan.org/~nwclark/perl-5.8.8/ext/XS/Typemap/Typemap.xs

- Рассмотренные в конце статьи файлы common.xs и perlobject.map могут быть взяты на CPAN'е из http://search.cpan.org/CPAN/authors/id/V/VA/VADIML/QtCore-4.004.tar.gz и остальные примеры тут http://search.cpan.org/CPAN/authors/id/V/VA/VADIML/QtGui-4.004.tar.gz

взято тут



размещено: 2009-07-18,
последнее обновление: 2009-08-14,
автор: ProFTP




 

  Этот информационный блок появился по той простой причине, что многие считают нормальным, брать чужую информацию не уведомляя автора (что не так страшно), и не оставляя линк на оригинал и автора — что более существенно. Я не против распространения информации — только за. Только условие простое — извольте подписывать автора, и оставлять линк на оригинальную страницу в виде прямой, активной, нескриптовой, незакрытой от индексирования, и не запрещенной для следования роботов ссылки.
  Если соизволите поставить автора в известность — то вообще почёт вам и уважение.

© lissyara 2006-10-24 08:47 MSK

Время генерации страницы 0.2806 секунд
Из них PHP: 87%; SQL: 13%; Число SQL-запросов: 86 шт.
Исходный размер: 242975; Сжатая: 36884