Writing ODF Files with Qt

Материал из Wiki.crossplatform.ru

Перейти к: навигация, поиск
Image:qt-logo_new.png Image:qq-title-article.png
Qt Quarterly | Выпуск 27 | Документация


by Thomas Zander

Предстоящий релиз Qt 4.5 отмечается появлением класса QTextDocumentWriter, который позволяет создавать файлы OpenDocument Format (ODF) из любого текстового документа Qt. Это открывает путь для автоматического создания и распространения документа в стандартном совместимом формате, который позволяет пользователю открыть его в различных текстовых редакторах.

Содержание

Основным поводом к использованию автоматической генерации документов является создание отчетов. Возьмем, к примеру, магазин, в котором периодически происходит переучет товара и возникает необходимость узнать, действительно ли то, что лежит на полках, и то, что содержится в базе данных, есть одно и то же. Предположим, существует программа, которая обрабатывает базу данных и создает документ, содержащий план работы на день. Этот документ экспортируется в ODF и отсылается человеку, производящему проверку.

center

В данной статье будет написан простой генератор отчетов, создающий телефонный счет. Мы создадим один класс, PhoneBillWriter, соответствующий телефонному счету для одного клиента, а вы можете модифицировать его, чтобы использовать как отправную точку для своих генераторов отчета.


[править] Вначале

Класс PhoneBillWriter будет иметь следующее определение:

    class PhoneBillWriter
    {
    public:
      PhoneBillWriter(const QString &client);
      ~PhoneBillWriter();
      struct PhoneCall {
        QDateTime date;
        int duration; // в секундах
        int cost; // в евро-центах
      };
      void addPhoneCall(const PhoneCall &call);
      void addGraph(QList<int> values,
                    const QString &amp;subtext);
      void write(const QString &amp;fileName);
 
    private:
      QTextDocument * const m_document;
      QTextCursor m_cursor;
    };

Это даст нам способ создания одного экземпляра PhoneBillWriter для каждого счета, и наполнения его данными при помощи вызовов метода addPhoneCall(). Закончив вносить информацию, мы вызываем метод write() для того, чтобы закрыть текущий счет. Это классический пример использования паттерна "Строитель" ("Builder").

Внутри себя класс пользуется QTextDocument, что видно в списке его закрытых членов. Класс QTextDocument применяется во многих местах в Qt и ее виджетах. QTextEdit является одним из таких виджетов, и для доступа к документу в нем вызывается метод QTextEdit::document(). QTextDocument - это текстовый документ, который может содержать структурированный текст, элементы разметки (полужирный шрифт, курсив) и многое другое. Для ознакомления см. демонстрационную программу Qt Text Edit.

Класс QTextCursor создан для того, чтобы предоставить способ управления содержимым текстового документа. Это что-то вроде мигающего курсора, который вы видите на экране в текстовых процессорах, и вводите текст, ориентируясь на него. Класс QTextCursor служит достижению такой цели внутри программы.


[править] Создание счета

Мы создадим экземпляр QTextDocument в конструкторе нашего генератора отчетов, затем добавим заголовок и симпатичное форматирование, которые станут общими для каждого телефонного счета. Вот простая реализация:

    PhoneBillWriter::PhoneBillWriter(const QString &amp;client)
        : m_document(new QTextDocument()),
        m_cursor(m_document)
    {
      m_cursor.insertText(QObject::tr(
               "Phone bill for %1\n").arg(client));
 
      QTextTableFormat tableFormat;
      tableFormat.setCellPadding(5);
      tableFormat.setHeaderRowCount(1);
      tableFormat.setBorderStyle(
                  QTextFrameFormat::BorderStyle_Solid);
      tableFormat.setWidth(QTextLength(
                  QTextLength::PercentageLength, 100));
      m_cursor.insertTable(1, 3, tableFormat);
      m_cursor.insertText(QObject::tr("Date"));
      m_cursor.movePosition(QTextCursor::NextCell);
      m_cursor.insertText(QObject::tr("Duration (sec)"));
      m_cursor.movePosition(QTextCursor::NextCell);
      m_cursor.insertText(QObject::tr("Cost"));
    }
 
    PhoneBillWriter::~PhoneBillWriter()
    {
      delete m_document;
    }

Для начала мы ввели в заголовок информацию о персоне, после чего создали таблицу с заголовком, который заполнили метками с названиями столбцов. Заранее задавать количество строк нашей таблицы мы не станем.

Обратите внимание на применение m_cursor - мы вызываем методы наподобие insertTable() и insertText(), чтобы непосредственно изменить наш документ.

Класс QTextCursor также умеет выделять и удалять текст. Очень мощным инструментом можно назвать его метод movePosition(), которому вы передаете один из множества элементов перечисления MoveOperation. Это заставляет класс вести себя как настоящий текстовый курсор, поскольку все операции ориентации в документе в нем присутствуют. В нашем примере мы используем операцию, новую для Qt 4.5: NextCell перемещает курсор в начало следующей ячейки таблицы. В результате текст, который мы затем передаем при вызове метода insertText(), появится в данной ячейке таблицы.

После создания заголовка документа мы готовы передать документу реальную информацию счета. Отдельные телефонные звонки добавим в таблицу методом addPhoneCall(), передав аргументом конкретные поля, которые мы и хотим увидеть в таблице.

    void PhoneBillWriter::addPhoneCall(
                  const PhoneBillWriter::PhoneCall &amp;call)
    {
      QTextTable *table = m_cursor.currentTable();
      table->appendRows(1);
      m_cursor.movePosition(QTextCursor::PreviousRow);
      m_cursor.movePosition(QTextCursor::NextCell);
      m_cursor.insertText(call.date.toString());
      m_cursor.movePosition(QTextCursor::NextCell);
      m_cursor.insertText(QString::number(call.duration));
      m_cursor.movePosition(QTextCursor::NextCell);
 
      QChar euro(0x20ac);
      m_cursor.insertText(QString("%1 %2").arg(euro)
              .arg(call.cost / (double) 100, 0, 'f', 2));
    }

Для того, чтобы телефонный звонок позже появился в таблице, мы добавляем в нее строку и вводим текст в каждую из добавившихся ячеек. Для преобразования численной информации в строковую мы используем соответствующие методы класса QString, поэтому мы можем тонко настроить формат, в котором данные будут видны пользователю. В конце концов, наша задача - сделать симпатичное представление "сырых" данных - и правильно отформатированный текст является как раз тем, что нам нужно.

Если документ содержит только текст, пусть даже текст и таблицы, его очень печально воспринимать человеку. Добавив графики или прочие картинки, мы делаем документ гораздо более интересным и читабельным для пользователя. Данная статья повествует о создании документа ODF, а формат OpenDocument позволяет встраивать изображения в конечный файл, в отличие от, скажем, HTML.

На пути к тому, чтобы превратить наш документ в финальный файл ODF, нам осталось только вставить картинку в QTextDocument. Думаем, вы не очень удивитесь, узнав о существовании метода QTextCursor::insertImage(), производящего в точности данное действие.

Добавим в наш образец документа график, показывающий сравнение количества звонков, которые клиент совершал за предшествующие этому несколько месяцев. Для этого используем код:

    QList<int> callsPerMonth;
    callsPerMonth << 6 << 84 << 76 << 0 << 93 << 128 << 76
                  << 31 << 19 << 4 << 12 << 78;
    phoneBill.addGraph(callsPerMonth, "Your past usage:");

Непосредственно создание графика будет заключаться в инстанцировании объекта изображения QImage, использовании объекта класса QPainter для рисования значений на изображении, и вставке изобажения в нужное место нашего документа.

    void PhoneBillWriter::addGraph(QList<int> values, const QString &amp;subtext)
    {
      const int columnSize = 10;
      int width = values.count() * columnSize;
      int max = 0;
      foreach (int x, values)
        max = qMax(max, x);
      QImage image(width, 100, QImage::Format_Mono);
      QPainter painter(&amp;image);
      painter.fillRect(0, 0, image.width(), image.height(),
                       Qt::white); // фон
      for (int index = 0; index < values.count(); ++index) {
        // Изменить шкалу для нашей 100-пиксельной в высоту картинки:
        int height = values[index] * 100 / max;
        painter.fillRect(index * columnSize,
            image.height() - height, columnSize, height,
            Qt::black);
      }
      painter.end();
 
      QTextCursor cursor(m_document);
      cursor.movePosition(QTextCursor::End);
      cursor.insertText(subtext);
      cursor.insertBlock();
      cursor.insertImage(image);
    }

Сначала вычислим требуемую ширину графика, базирующуюся на количестве значений, передаваемых в него. Пусть график всегда имеет высоту, равную 100 пикселам, и все значения отмасштабируются так, чтобы уместиться в этот интервал - ведь большинство графиков существуют для отображения размеров одной величины относительно другой.

Применим монохромный формат картинки, что означает всего два цвета для ее создания. Хотя вы можете использовать любой другой формат для ODF, для нашего случая вполне достаточно двух цветов.

После того, как изображение готово, создадим новый объект QTextCursor, и переместим его в конец документа в то место, куда и будет вставлен график. Обратите внимание на то, что положение этого курсора будет независимо от положения m_cursor. Мы сделали новый курсор для того, чтобы пользователь мог вызывать метод addPhoneCall() уже после того, как добавлен график, без изменения взаимного положения частей, составляющих наш документ.

Последним штрихом к классу PhoneBillWriter станет непосредственно создание файла формата OpenDocument, содержащего все данные, которые мы записали в объект класса. Весь этот нелегкий процесс выполнит класс QTextDocumentWriter, который отвечает за запись в некоторые форматы, такие как простой текст, HTML и ODF. По умолчанию используется ODF, что облегчает нашу запись:

    void PhoneBillWriter::write(const QString &amp;fileName)
    {
      QTextDocumentWriter writer(fileName);
      writer.write(m_document);
    }

center

Изображение сверху демонстрирует наш документ, открытый в OpenOffice.org. Как вы видите, он достаточно прост, каким и должен быть образец, но вы можете изучить гораздо более декорированный вариант, доступный по ссылке чуть ниже в виде архива с сайта Qt Quarterly.


[править] В заключение

Образец, рассмотренный в статье, очень прост и имеет целью показать базовые способности класса QTextDocumentWriter по записи ODF-файла, а не сделать вывод как можно красивее.

Qt включает массу способов улучшить внешний вид нашего телефонного счета и полезность записывающей программы. Например, мы можем использовать SQL-модуль и получить реальную информацию из реальной базы данных, и использовать QPainter, чтобы отрисовывать более привлекательные графики и диаграммы.

Вы также можете создать приятный графический интерефейс вашему приложению, чтобы облегчить труд пользователя по созданию отчетов; или использовать поддержку ODF в Qt для совершенно иных, нежели телефонные счета, отчеты или аккаунты, целей. Все, что вы будете делать с этой предоставленной возможностью, зависит только от вас!

Исходные коды образца, представленного в данной статье, доступны здесь: from the Qt Quarterly Web site.