четверг, 29 декабря 2011 г.

Promote widget в Qt Creator: что это такое, и как его использовать

При создании формы графического интерфейса в Qt Creator-е зачастую возникает необходимость изменить поведение размещённых на форме визуальных компонентов. Для этого в нём (Creator-е) существует механизм promote-инга (корректный перевод не знаю). В качестве примера рассмотрим добавление объекту QLabel возможности реагирования на отпускание на нём левой кнопки мыши (ЛКМ).
Выделив необходимый компонент на форме, в меню ПКМ выбираем пункт Promote to.... Откроется меню задания имени класса-обёртки, и его заголовочного файла. Заголовочный файл может находиться и не в рабочем каталоге с файлом формы (*.ui), можно задать ему либо абсолютный путь (возможны проблемы при переносе проекта между платформами), либо относительный, от рабочего каталога. Дадим классу имя TestPromoter (Promoted class name:), его заголовочный файл -- ./TestPromoter.hpp (Header file:). Нажимаем кнопку Add, и QLabel оказывается "обёрнут" в созданный класс promoter-а.
Теперь нужно вручную создать заголовочный файл, и файл реализации нового класса:
#include <QLabel>
#include <QMouseEvent>
//-------------------------------------------------------------------------
/*
 TestPromoter.hpp
*/
//-------------------------------------------------------------------------
class TestPromoter : public QLabel
{
 Q_OBJECT:
protected:
 virtual void mouseReleaseEvent(QMouseEvent* ev);
public:
 TestPromoter(QWidget* parent = 0,Qt::WindowFlags f = 0);
 TestPromoter(const QString& text,QWidget* parent = 0,Qt::WindowFlags f = 0);
};

#include "./TestPromoter.hpp"
//-------------------------------------------------------------------------
/*
 TestPromoter.cpp
*/
//-------------------------------------------------------------------------
TestPromoter::TestPromoter(QWidget* parent,Qt::WindowFlags f) : \
  QLabel(parent,f) \
{
 return;
}
TestPromoter::TestPromoter(const QString& text,QWidget* parent,Qt::WindowFlags f) : \
  QLabel(text,parent,f) \
{
 return;
}
void TestPromoter::mouseReleaseEvent(QMouseEvent* ev)
{
 if(ev->button() == Qt::LeftButton)
 {
  //Код, который нужно выполнить по указанному событию
 }
 QLabel::mouseReleaseEvent(ev);
 return;
}

Таким образом можно реализовать расширение функциональности визуальных объектов Qt Creator-а.

вторник, 8 ноября 2011 г.

Сборка и установка визуальных плагинов Qt под Windows

В процессе работы с библиотекой Qt возникла необходимость создания своих визуальных компонент-плагинов для Qt Creator-а. Эта задача разделяется на следующие: создание класса визуального компонента (как правило, наследника QWidget); создание класса регистрации плагина в Qt Creator-е; обеспечение возможности сборки как release-, так и debug-версии программы, в которой используется данный плагин. Рассмотрим решение каждой из этих задач для Qt 2010.05 for Windows.
Создание класса визуального компонента происходит обычно, за исключением необходимости добавления одного макроса. Выглядит это приблизительно так:
#include <QWidget>
#include <QtDesigner/QDesignerExportWidget>
//-----------------------------------------------
class QDESIGNER_WIDGET_EXPORT NewClass : public QWidget
{
private:
public:
 explicit NewClass(QWidget* parent = 0);
 ~NewClass(void);
};

Саму реализацию, для простоты, не привожу, можно отнаследоваться от QPushButton, чтобы результат был более нагляден.
Класс QDesignerExportWidget через этот макрос обеспечивает доступ Qt Designer-а к создаваемому классу плагина, и позволяет создавать его экземпляры "на лету", в процессе "таскания мышкой компонентов".
Для регистрации плагина создаётся отдельный класс. Вот как может выглядеть его заголовочный файл:
#include <QtPlugin>
#include <QDesignerCustomWidgetInterface>
//-----------------------------------------------
#include "./NewClass.hpp" //Оба класса находятся в одном каталоге
//-----------------------------------------------
class NewClassPlugin : public QObject,public QDesignerCustomWidgetInterface
{
  Q_OBJECT;
  Q_INTERFACES(QDesignerCustomWidgetInterface);
private:
  bool initialized;
public:
  NewClassPlugin(QObject* parent = 0);
  bool isContainer(void) const;
  bool isInitialized(void) const;
  QIcon icon(void) const;
  QString domXml(void) const;
  QString group(void) const;
  QString includeFile(void) const;
  QString name(void) const;
  QString toolTip(void) const;
  QString whatsThis(void) const;
  QWidget* createWidget(QWidget* parent);
  void initialize(QDesignerFormEditorInterface* core);
};
//-----------------------------------------------

О "наполнении" этих функций можно посмотреть официальную документацию, никаких расхождений с ней не наблюдалось.
Теперь приступаем к сборке и установке плагина. Для ясности дальнейших действий немного о структуре разрабатываемого мной приложения, в котором будет использоваться плагин. Приложение состоит из загрузчика, и динамических библиотек-модулей, которые он загружает, предоставляя некое внутреннее API для функционирования. Как загрузчик, так и модули расположены в индивидуальных поддиректориях общей директории проекта. В директории плагина (пусть её название -- newPlugin) создаём файл проекта newPlugin.pro со следующим содержимым:
TEMPLATE = lib
TARGET = $$qtLibraryTarget($$TARGET)
CONFIG += designer plugin
QTDIR_build:DESTDIR = $$QT_BUILD_TREE/plugins/designer

DLL = $$TARGET".dll"

CONFIG(release, debug | release): QMAKE_POST_LINK = xcopy \
.\\release\\$$DLL ..\\loader\\release /Y

CONFIG(debug, debug | release): QMAKE_POST_LINK = xcopy \
.\\debug\\$$DLL ..\\loader\\debug /Y

# Input
HEADERS += ./newClass.hpp \
           ./newClassPlugin.hpp

SOURCES += ./newClass.cpp \
           ./newClassPlugin.cpp

target.path = $$[QT_INSTALL_PLUGINS]/designer
sources.files = $$HEADERS $$SOURCES *.pro
sources.path = $$[QT_INSTALL_EXAMPLES]/designer/newClass
INSTALLS += target sources

В строке 6 определяется переменная с именем создаваемой DLL, которое будет различным для release- и debug-версий. Строки 8-9 и 11-12 производят копирование средствами Windows собранных DLL плагина в соответствующие каталоги загрузчика. После выполнения команд qmake и mingw32-make all нужно произвести установку собранного плагина в соответствующие каталоги Qt Creator-а. Поскольку он собран, как release-версия, то установка производится командой mingw32-make release-install. Удаляется плагин, соответственно, командой mingw32-make release-uninstall.
И заключительный этап -- сборка модуля, использующего плагин. Поскольку в модуле будет использоваться форма, созданная с помощью Qt Creator-а, то явное подключение заголовочного файла плагина не потребуется. Для модуля создаём файл проекта moduleName.pro:
TEMPLATE = lib
TARGET = 
DEPENDPATH += .
INCLUDEPATH += . $$[QT_INSTALL_EXAMPLES]/designer/newPlugin
CONFIG += thread dll rtti stl exceptions

CONFIG(release, debug | release): LIBS += \
-L"../newPlugin/release" -lnewPlugin

CONFIG(debug, debug | release): LIBS += \
-L"../newPlugin/debug" -lnewPlugind

# Input
FORMS += ./formWithPlugin.ui

HEADERS += ./module.hpp

SOURCES += ./module.cpp

Здесь в строке 4 происходит задание директории, в которой находятся заголовочные файлы плагина. Строки 7-8 и 10-11 задают линковку для отладочной (-lnewPlugind) и релизной (-lnewPlugin) версий. В строке 14 подключается файл описания формы, созданной Qt Creator-ом. Выполнив команды qmake и mingw32-make all, получаем работающий и в release-, и в debug-версиях плагин.

Upd. По неясной пока причине статические библиотеки плагина начали собираться с постфиксом 4, т.е. вместо -lnewPlugin создаётся -lnewPlugin4. В файл проекта, использующего плагин, следует внести соответствующие изменения.

пятница, 28 октября 2011 г.

Количество page faults-ов, как индикатор проблем в работе приложения

В разрабатываемой модульной системе возникли проблемы: после нескольких часов непрерывной работы программа падала по исключению std::bad_alloc. А работать она должна в режиме 24/7 в течении нескольких недель. Первое, что я заподозрил, было утечка памяти, либо дескрипторов. Но проверка Process Explorer-ом ни того, ни другого не выявила. И тут я случайно увидел, что счётчик Page Faults принимает какие-то уж совсем запредельные значения, и непрерывно их увеличивает. В конце концов оказалось, что один из старых модулей при работе с потоками вместо join-а выполнял просто detach, что явно не проявлялось до момента падения программы. Ошибка была исправлена, счётчик Page Faults-ов перестал показывать внешний госдолг США, программа перестала падать на ровном месте.

четверг, 13 октября 2011 г.

Установка VirtualBox на Windows7 в случае вынесенного в сеть домашнего каталога пользователя

Решили наши администраторы вынести каталоги пользователей на отдельную машину в сети. Решили -- и вынесли. Всё было бы хорошо, да только некоторые программы отказывались устанавливаться. Одной из них и была VirtualBox. В процессе установки он говорил, что %UserName% не является корректным именем файла, и аварийно завершался. Решение оказалось простейшим: до установки отключить сетевой адаптер. Так я обновил VirtualBox.

понедельник, 26 сентября 2011 г.

Контролируемый выход из цикла в потоке

В своей деятельности я часто использую потоки. Как правило, из библиотеки boost::thread. На пару с boost::bind возможности получаются впечатляющие. Если поток однократно выполняет какое-либо действие, и сам завершается -- всё хорошо. Но зачастую требуется, чтобы поток выполнял это действие в цикле, через заданный временной интервал, и был способен получать уведомления о завершении своей работы от основного потока приложения. Для подобных целей я использую следующий механизм, основанный на совместном использовании boost::shared_mutex и boost::shared_lock:
#define BOOST_THREAD_USE_LIB
//-------------------------------------------------------------------------
#include <boost/bind.hpp>
#include <boost/thread.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
//-------------------------------------------------------------------------
#include <string>
#include <iostream>
//-------------------------------------------------------------------------
/*
 g++ -Wall -o ./main ./main.cpp -lboost_thread
*/
//-------------------------------------------------------------------------
class Example
{
private:
 typedef boost::shared_mutex WaiterSharedMutex;
 typedef boost::shared_lock<WaiterSharedMutex> WaiterSharedLock;
 typedef boost::defer_lock_t DeferredLock;
 boost::thread workThread;
 WaiterSharedMutex waiterMutex;
 boost::posix_time::time_duration sleepTime;
 void WorkThread(void)
 {
  while(true)
  {
   WaiterSharedLock wsl(waiterMutex,DeferredLock());
   if(wsl.timed_lock(boost::get_system_time() + sleepTime))
   {
    std::cerr << "Forced exit from thread" << std::endl;
    return;
   }
   std::cerr << "Work of thread" << std::endl;
  }
  return;
 }
public:
 Example(std::size_t timeToSleep) : sleepTime(boost::posix_time::milliseconds(timeToSleep))
 {
  waiterMutex.lock();
  return;
 }
 ~Example(void)
 {
  waiterMutex.unlock();
  workThread.join();
  return;
 }
 bool Start(void)
 {
  bool result = false;
  if(workThread.joinable() == false)
  {
   waiterMutex.try_lock();
   workThread = boost::thread(boost::bind(&Example::WorkThread,this));
   result = true;
  }
  return result;
 }
 bool Stop(void)
 {
  bool result = false;
  waiterMutex.unlock();
  if(workThread.joinable() == true)
  {
   workThread.join();
  }
  return result;
 }
};
//-------------------------------------------------------------------------
int main(int argc,char** argv)
{
 std::string input;
 Example example(50000);
 while(true)
 {
  input.clear();
  std::cout << ":> ";
  std::cin >> input;
  if(!input.compare("quit"))
  {
   break;
  }
  else if(!input.compare("start"))
  {
   example.Start();
  }
  else if(!input.compare("stop"))
  {
   example.Stop();
  }
 }
 return 0;
}
Временная задержка выполнения потока обеспечивается ожиданием на mutex-е функцией timed_lock, а возможность немедленного завершения потока тем, что как сам mutex, так и его блокировка являются разделяемыми (shared_mutex и shared_lock). У разделяемого mutex-а есть две возможных блокировки: эксклюзивная, и разделяемая. Если на mutex-е эксклюзивная блокировка -- заблокировать его разделяемой будет невозможно. Обратное тоже верно. Но эксклюзивную блокировку может создавать только один поток, а разделяемую -- несколько. В представленном примере на mutex ставится эксклюзивная блокировка в конструкторе класса (строка 40), и перед попыткой пересоздать поток (строка 54). Поток (потоки), которому требуется выполнять в цикле некую функцию, пытается получить разделяемую блокировку на mutex (строка 28). Завершиться успехом это может только в случае, если основной поток отпустит эксклюзивную блокировку (строка 63, и вызов функции Stop в деструкторе класса). После чего требуется лишь дождаться завершения потока (потоков) функцией join.

пятница, 8 июля 2011 г.

Файл конфигурации Emacs и Windows 7

При эксплуатации Emacs под Windows 7 у меня возникла проблема с его файлом .emacs. При первоначальном запуске он располагался в C:\Users\%Username%\AppData\Roaming, но по непонятным причинам иногда обнулялся (машина находится в домене). При переносе .emacs в несистемный каталог, и добавлении переменной окружения %HOME%, указывающей на этот каталог, затирание этого файла прекратились. Что странно, на Windows XP такого поведения замечено не было.
Upd: Переменную %HOME% увидели и некоторые другие кроссплатформенные приложения, например, VirtualBox теперь складывает свою конфигурацию в каталог, указанный в ней. Так что в случае использования компьютера более, чем одним человеком, эту переменную лучше записывать, как переменную среды пользователя, а не системную.