Пишем виджет рабочего стола под Maemo5 на Qt. Часть первая

Доброго времени суток хабрачеловек. Продолжаю писать статьи о N900. На этот раз адресую ее разработчикам. И не только разработчикам для N900 а всем разработчикам вообще.



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







В этой и следующей статьях я продемонстрирую создание приложения для N900 на Qt. И это не простое приложение, а виджет рабочего стола. Исходными данными будут:


  • отсутствие знаний Hildon и GTK;
  • отсутствие специфичных знаний о мобильных платформах вообще;
  • небольшие знания разработки настольных приложений на Qt/С++ или на любом другом языке (в этом случае придется потратить немного больше времени за чтением Qt-документации);
  • немножко усидчивости и заинтересованности(хоть это и самый последний пункт, но совершенно очевидно, что он самый важный).



Таким образом я намереваюсь продемонстрировать, что создание приложений на Qt действительно не требует знаний специфики платформы в подавляющем большинстве случаев. И если приложение использует только Qt API, то оно является абсолютно переносимым между официально поддерживаемыми платформами.


Введение.



И так, чем мы будем заниматься?



Я решил убить двух зайцев: не только доказать свою точку зрения (что писать для N900 легко и перспективно), но еще и сделать нужное мне приложение. И это приложение — расписание движения электропоездов. Оно достаточно простое, чтоб на основе него сделать вводный курс и достаточно полезное, чтоб улучшить мне (и надеюсь еще кому-нибудь) жизнь.



Я часто пользуюсь электричками (не нужно стоять в пробках и есть возможность почитать или посмотреть фильмы, так как не нужно сосредотачиваться на управлении автомобилем), но те кто ездит на этом виде транспорта знают, что иногда попадаешь в ситуацию, когда между электричками достаточно большая пауза и приходится ждать на платформе(платформа в данном случае — это бетонная конструкция, чтоб удобней было заходить в высокие ж/д вагоны, а не компьютерная платформа). Наличие в кармане расписание ближайших электричек может улучшить ситуацию.







Естественно удобней будет, если приложение будет в N900 на самом видном месте — на рабочем столе. Это можно реализовать, сделав приложение в виде виджета. Плюс ко всему писать будем на Qt, так как это официальный инструмент разработки для Maemo5 (не смотря на то, что Maemo5 — это GTK/Hildon платформа).



Более того, приложение можно будет легко переделать в плазмоид или просто маленькую утилитку, которая будет висеть и показывать расписание и на настольном компьютере. Да и вообще, данное приложение не ограничивается электричками. Может кто-то пользуется автобусами, которые отправляются по расписанию или любой другой вид транспорта (может быть даже паромная переправа :-) ).


Где брать расписание?



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







Рассудив, что приложение не должно быть привязано к конкретному поставщику расписания (по крайней мере пока), так как это может отнять много времени с организационной стороны, я решил уделить этому вопросу минимум времени и сосредоточиться на разработке самого приложения. Самый простой вариант, который сразу бросается в глаза, является написание приложения — которое распарсивает html страницу с расписанием и преобразование к единому виду, которое понимает приложения.



Преимущества подхода:


  • приложение не привязано к конкретному источнику информации(это может быть даже не html страница);
  • позволяет прозрачно масштабировать приложение, без изменения исходного кода (например, можно предоставлять приложение из статического контента, веб-сервиса или прокси-сервиса).



Недостатки:


  • требуется самостоятельно следить за актуальностью контента и писать под каждый источник расписания конвертер;
  • расписание получается статическим, в виде просто таблицы типа «от станции до станции», то есть если нужно ехать от станции 1 до станции 2 нужно одно расписание, а если уже хотите посмотреть расписание от станции 1 до станции 3 (даже если Вы едете через станцию 2), то нужно еще одно отдельное расписание;
  • нет возможности выбрать расписание из самой программы.



Учитывая недостатки подхода, я сразу решил, что это все временно. И позже нужно будет переработать формат данных таким образом, чтоб была возможность динамически выбирать пункты следования.



Что же будем парсить? Мне сразу бросился в глаза сервис от Яндекса и наличие мобильной версии меня убедили в том, что это самый простой источник для написания и отладки приложения. Но к моему разочарованию, я обнаружил, что таким образом я нарушаю пользовательское соглашение от Яндекса. В нем четко написано:


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



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



Позже я нашел легальные источники информации, по крайней мере в этих источниках я не нашел информации, что использование в собственных целях запрещено. Например непосредственно на сайте РЖД есть расписание пригородных поездов в формате xls (правда без оперативных изменений в расписании). Плюс в пользовательском соглашении от Яндекса указан список источников информации. На многих из них я тоже не нашел запретов на использовании предоставленной информации. Поэтому считаю, что это легальные источники. Но они разрозненные и разношерстные. Поэтому Яндекс возьмем как отправную точку, хоть и не легальную.


Парсер Яндекса.



Так как решение с парсером для Яндекса является решением временным, и основная задача на текущий момент — это создание Qt-виджета на рабочий стол, то решил уделить этому вопросу минимум времени. Самым простым решением для меня показалось написание парсера на Python с использованием библиотеки Beautiful Soap. Прошу знатокам питона оценить код и внести свои коррективы, при необходимости. Так как знатоком питона я не являюсь, то буду очень благодарен за любую конструктивную критику.



Код можете посмотреть здесь.



Чтоб получить на выходе нужный xml, нужно «скормить» скрипту нужный URL страницы мобильного расписания от Яндекса. Например URL для расписания поездов ленинградского направления от станции «Останкино» до «Ховрино» выглядит так:



m.rasp.yandex.ru/suburban_search?direction=msk_len&station_from_suggest=&station_to_suggest=&station_to=9603505&mode=all&station_from=9603877



Запускаем скрипт:


python ./rasp.py «m.rasp.yandex.ru/suburban_search?direction=msk_len&station_from_suggest=&station_to_suggest=&station_to=9603505&mode=all&station_from=9603877» 





и получаем на выходе xml.


<rasp name="Ленинградское направление">
<train>
 <time>
  11:04
 </time>
 <url>
  not realized
 </url>
 <note>
  ежедневно
 </note>
</train>
.......................
</rasp>





Далее кладем этот файл в сеть (на Ваш хостинг или еще куда-нибудь, откуда он будет доступен по URL'у). Я положил его в публичную папку dropbox'a. Все, подготовка данных закончена. Файл нужного нам формата лежит в сети и доступен по вышеуказанному адресу. Теперь можно переходить к дизайну приложения.


Проектирование.



Внешний вид приложения я думаю должен быть простым и наглядным. В шапке статьи можно увидеть чертеж виджета, который я взял из статьи по разработке виджетов рабочего стола для Maemo.



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



Внизу три кнопки: предыдущее расписание, обновить и следующее расписание. Как минимум нужно будет 2 расписания: туда и обратно.



Если есть дизайнеры готовые нарисовать этот виджет — буду очень рад. нужны подложки и кнопки и общий вид с названиями и размерами шрифтов. Если таких не найдется, то придется самому придумывать. Вознаграждение за дизайн — внесение студии/человека в со-разработчики. Кто знает, может этот проект перерастет в серьезный продукт.



Внутренний дизайн тоже не оригинален. Я воспользовался стандартным для Qt шаблоном MVC и стандартными классами, которые его реализуют. Реализация MVC от Qt выглядит таким образом:







Она отличается от канонической реализации, но разобраться очень не сложно.


Реализация.



Вот что в итоге у меня получилось:







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



RaspParser — наследник QXmlStreamReader, который разбирает этот xml. Разбор осуществляется путем ркурсивного спуска (recursive descent technique). Это Qt реализация Pull парсера.



Вся основная часть реализации умещается в несколько строк:


void RaspParser::readRaspItem()
{
    Q_ASSERT(xml.isStartElement() && xml.name() == "train");

    RaspItem theItem;
    while (xml.readNextStartElement()) {
        if (xml.name() == "time")
            theItem.time = QTime::fromString(xml.readElementText().trimmed(),"hh:mm");
         else if (xml.name() == "url")
             theItem.url = QUrl(xml.readElementText().trimmed());
         else if (xml.name() == "note")
             theItem.note = xml.readElementText().trimmed();
         else
             xml.skipCurrentElement();
     }
    items->append(theItem);

}





Данные возвращаются модели — YandexHttpRaspModel (название не удачное, возможно подвергну рефакторингу). Это наследник QAbstractListModel, который возвращает время отправления электричек. Для обеспечения работы модели необходимо перекрыть несколько методов:


int rowCount ( const QModelIndex & parent = QModelIndex() ) const;
QVariant data ( const QModelIndex & index, int role = Qt::DisplayRole ) const;





Реализация тоже не поражает воображение:


int YandexHttpRaspModel::rowCount ( const QModelIndex & parent) const
{
    Q_UNUSED(parent);
    Q_D(const YandexHttpRaspModel);
    return d->items.count();

}
QVariant YandexHttpRaspModel::data ( const QModelIndex & index, int role) const
{
    Q_D(const YandexHttpRaspModel);

    if (!index.isValid())
         return QVariant();

     if (index.row() >= d->items.size())
         return QVariant();

     if (role == Qt::DisplayRole)
         return d->items.at(index.row()).time;
     else if (role == Qt::UserRole+1)
         return d->items.at(index.row()).note;
     else if (role == Qt::UserRole+2)
         return d->items.at(index.row()).url;
     else
         return QVariant();
}





Но необходимы небольшие разъяснения. Класс написан с применением шаблона pimpl, который я описывал ранее. В нем так-же присутствует приватный слот.



В методе data задействовал две роли: Qt::UserRole+1 и Qt::UserRole+2. Так как у нас 3 поля (время, примечание и ссылка), и модель в виде одномерного списка, то делегату нужно будет как-то узнавать информацию об примечании и ссылке («время отправления» возвращается непосредственно как данные, через роль Qt::DisplayRole) при необходимости. Вот через эти роли делегат сможет получить доступ к этим данным.



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



И последняя цепочка в звене: QListView. Это стандартный Qt виджет для отображения информации из модели, предоставляющей информации в виде списка (наследник QAbstractListModel).



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


Результат.



В результате сборки и запуска должно получиться что-то вроде такого приложения (приложение пока только десктопное):







На этом этапе я считаю первую часть завершенной. Мы имеем практически работоспособное приложение (вернее прототип), способное отображать информацию о расписании движения в виде списка.



Времени затрачено:



Поиск расписания и источников расписания — 1 час.

Написание парсера на питоне — 15 минут.

Написание и отладка приложения — 1 час.

Итого затрат на первом этапе: 2 часа 15 минут чистого времени.

Примите к сведению, что написание данной статьи с оформлением, включая создание картинок, заняло у меня около 6 часов чистого времени.


Что будем делать в следующей статье.



Работы для завершения второй части еще предостаточно (совсем завершить разработку приложения невозможно, последний 1 процент разработки длится бесконечно). Минимум часа на 2.

Вот предполагаемый список:


  • оформить приложение в виде виджета на рабочем столе для N900;
  • сделать настройки адреса откуда грузить (сейчас «захардкожено»);
  • сделать делегата для отображения красивых пунктов и кнопочек (очень жду желающих дизайнеров поучавствовать);
  • сделать отображение от текущего момента времени, функционал «ближайшие электрички»;
  • сделать несколько закладок, несколько расписаний с настройками для каждой и реализовать кнопки «предыдущее» и «следующее» расписание;
  • отображать названия расписания и самого направления.



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



В комментариях жду отзывов, замечаний и предложений.

P.S.: исходные коды для данного первого этапа можно взять вот тут.



Перепечатано с разрешения автора с оригинала статьи.