1. Введение
Xlib -- это библиотека, позволяющая программам на языке C рисовать на экране любого X-сервера -- локального или удаленного. Все, что для этого требуется -- вставить в исходный файл программы строку "include
Для примера рассмотрим как нарисовать окно на экране локального компьютера. Это можно сделать следующим образом:
Listing 1: http://gazette.linux.ru.net/lg78/articles/misc/tougher/example1.cpp.txt
#include#include main() { // Открыть дисплей Display *d = XOpenDisplay(0); if ( d ) { // Создать окно Window w = XCreateWindow(d, DefaultRootWindow(d), 0, 0, 200, 100, 0, CopyFromParent, CopyFromParent, CopyFromParent, 0, 0); // Нарисовать окно на экране XMapWindow(d, w); XFlush(d); // Выполнить задержку, достаточную // по времени, чтобы мы смогли увидеть окно sleep(10); } return 0; }
Скомпилируйте программу командой:
prompt$ g++ test.cpp -L/usr/X11R6/lib -lX11
Запустите:
prompt$ ./a.out
И, вуаля, в течение 10 секунд можете любоваться окном на экране:
Рисунок. Если очень хочется -- можнопосмотреть на http://gazette.linux.ru.net/lg78/articles/misc/tougher/screen_small.png
Цель данной статьи - познакомить вас с некоторыми простыми классами, которые можно использовать при разработке Xlib-приложений. Мы создадим приложение с одним окном и кнопкой в этом окне. Кнопку мы напишем сами, используя только библиотеку Xlib.
2. Почему не используются визуальные элементы (виджеты)?
Вы можете задаться вопросом: "А почему бы не использовать библиотеки визуальных элементов (виджетов), скажем QT (http://www.trolltech.com/) или GTK (http://www.gtk.org/)?". Законный вопрос. Я использую QT, и нахожу ее очень удобной для разработки приложений на C++ для платформы Linux.
Причина, по которой я пишу эти строки, заключается в намерении дать вам более глубокое понимание X Window System, а для этого нужно заглянуть под покров библиотек QT и GTK. Несколько раз уже я приходил к выводу, что умение писать Xlib приложения действительно полезно.
Я надеюсь, что эта статья поможет вам использовать имеющиеся классы в ваших приложениях.
3. Основы
В этом разделе мы пройдемся по основным особенностям библиотеки Xlib. Давайте сейчас рассмотрим исходный код примера.
3.1 Открытие дисплея
Первый класс, который создается в программе -- это класс display (http://gazette.linux.ru.net/lg78/articles/misc/tougher/display.hpp.txt), основная задача которого -- открытие и закрытие дисплея. Заметьте, что в примере examle1.cpp, дисплей не закрывается явно с помощью вызова XCloseDisplay(). Дисплей будет закрыт самим классом display перед завершением программы. Немножко усложним наш пример, и он уже выглядит вот так:
Listing 2: http://gazette.linux.ru.net/lg78/articles/misc/tougher/example2.cpp.txt
#include#include "xlib++/display.hpp" using namespace xlib; main() { try { // Открыть дисплей display d(""); // Создать окно Window w = XCreateWindow((Display*)d, DefaultRootWindow((Display*)d), 0, 0, 200, 100, 0, CopyFromParent, CopyFromParent, CopyFromParent, 0, 0); // Нарисовать окно на экране XMapWindow(d, w); XFlush(d); // Выполнить задержку, чтобы успеть увидеть окно sleep(10); } catch ( open_display_exception& e ) { std::cout << "Exception: " << e.what() << "\n"; } return 0; }
Собственно, ничего особенного. Все то же самое -- открывается и закрывается дисплей. Однако, вы наверняка заметили, что экземпляр класса display в данной реализации (http://gazette.linux.ru.net/lg78/articles/misc/tougher/display.hpp.txt) приводится к типу Display*, таким образом, создавая экземпляр этого класса , вы в действительности получаете указатель на Xlib Display.
Конечно же вы заметили и блок try/catch. Все классы в данной статье для извещения об ошибках порождают исключения.
3.2 Создание окна
Далее, я хотел бы упростить процесс создания окна, для этого я добавлю класс window (http://gazette.linux.ru.net/lg78/articles/misc/tougher/window.hpp.txt). Этот класс создает и отрисовывает окно в конструкторе, а "разрушает" окно в деструкторе. Теперь пример выглядит так (обратите внимание на класс event_dispatcher, который мы рассмотрим несколько ниже):
Listing 3 : http://gazette.linux.ru.net/lg78/articles/misc/tougher/example3.cpp.txt
#include "xlib++/display.hpp" #include "xlib++/window.hpp" using namespace xlib; class main_window : public window { public: main_window ( event_dispatcher& e ) : window ( e ) {}; ~main_window(){}; }; main() { try { // Открыть дисплей display d(""); event_dispatcher events ( d ); main_window w ( events ); // верхний уровень events.run(); } catch ( exception_with_text& e ) { std::cout << "Exception: " << e.what() << "\n"; } return 0; }
Обратите внимание на то, что наш класс main_window порожден от класса xlib::window. Когда создается объект main_window, вызывается базовый конструктор, который создает окно Xlib.
3.3 Обработка событий
Вы наверняка обратили внимание на класс event_dispatcher (http://gazette.linux.ru.net/lg78/articles/misc/tougher/event_dispatcher.hpp.txt ) в последнем примере. Этот класс получает события из очереди событий приложения и передает их требуемому окну.
Определен этот класс следующим образом:
Listing 4 : http://gazette.linux.ru.net/lg78/articlesmisc/tougher/event_dispatcher.hpp.txt
class event_dispatcher { // constructor, destructor, and others... [snip...] register_window ( window_base *p ); unregister_window ( window_base *p ); run(); stop(); handle_event ( event ); }
Класс event_dispatcher передает события классу окна через интерфейс класса window_base (http://gazette.linux.ru.net/lg78/articles/misc/tougher/window_base.hpp.txt). Все классы окон, в этой статье, являются наследниками именно этого класса и, после регистрации себя вызовом метода register_window, могут получать сообщения от диспетчера. Из объявления класса window_base (http://gazette.linux.ru.net/lg78/articles/misc/tougher/window_base.hpp.txt) следует, что все классы, порождаемые от него, смогут получать события, реализовав следующие методы:
Listing 5 : window_base.hpp (http://gazette.linux.ru.net/lg78/articles/misc/tougher/window_base.hpp.txt)
virtual void on_expose() = 0; virtual void on_show() = 0; virtual void on_hide() = 0; virtual void on_left_button_down ( int x, int y ) = 0; virtual void on_right_button_down ( int x, int y ) = 0; virtual void on_left_button_up ( int x, int y ) = 0; virtual void on_right_button_up ( int x, int y ) = 0; virtual void on_mouse_enter ( int x, int y ) = 0; virtual void on_mouse_exit ( int x, int y ) = 0; virtual void on_mouse_move ( int x, int y ) = 0; virtual void on_got_focus() = 0; virtual void on_lost_focus() = 0; virtual void on_key_press ( character c ) = 0; virtual void on_key_release ( character c ) = 0; virtual void on_create() = 0; virtual void on_destroy() = 0;
Давайте проверим, а так ли это в действительности? Попробуем обработать событие ButtonPress в нашем окне. Добавим в определение нашего класса main_window следующий код:
Listing 6 : example4.cpp (http://gazette.linux.ru.net/lg78/articles/misc/tougher/example4.cpp.txt)
class main_window : public window { public: main_window ( event_dispatcher& e ) : window ( e ) {}; ~main_window(){}; void on_left_button_down ( int x, int y ) { std::cout << "on_left_button_down()\n"; } };
Скомпилируйте и запустите приложение, а потом щелкните в окне мышкой. Код сработал! Класс event_dispatcher получил событие ButtonPress и передал его в наше окно через вызов предопределенного метода on_left_button_down.
3.4 Рисование
Теперь попробуем рисовать в нашем окне. Система X Window определяет концепцию "графического контекста" ("graphics context"), поэтому я, естественно, создаю класс graphics_context. Вот определение класса:
Listing 7 : graphics_context.hpp (http://gazette.linux.ru.net/lg78/articles/misc/tougher/graphics_context.hpp.txt)
class graphics_context { public: graphics_context ( display& d, int window_id ); ~graphics_context(); void draw_line ( line l ); void draw_rectangle ( rectangle rect ); void draw_text ( point origin, std::string text ); void fill_rectangle ( rectangle rect ); void set_foreground ( color& c ); void set_background ( color& c ); rectangle get_text_rect ( std::string text ); std::vectorget_character_widths ( std::string text ); int get_text_height (); long id(); private: display& m_display; int m_window_id; GC m_gc; };
Передав этому классу id окна и объект display, вы, используя для этого соответствующие методы, получаете возможность рисовать на поверхности окна. Давайте попробуем. Добавьте в наш пример следующий код:
Listing 8 : example5.cpp (http://gazette.linux.ru.net/lg78/articles/misc/tougher/example5.cpp.txt)
#include "xlib++/display.hpp" #include "xlib++/window.hpp" #include "xlib++/graphics_context.hpp" using namespace xlib; class main_window : public window { public: main_window ( event_dispatcher& e ) : window ( e ) {}; ~main_window(){}; void on_expose () { graphics_context gc ( get_display(),id() ); gc.draw_line ( line ( point(0,0), point(50,50) ) ); gc.draw_text ( point(0, 70), "I'm drawing!!" ); } };
Метод on_expose() вызывается всякий раз, когда окно выводится на экран. Внутри этого метода я разместил код, на поверхности окна (в клиентской его области) рисующий линию и выводящий некоторый текст. Когда вы скомпилируете и запустите этот пример, то вы должны увидеть примерно следующее:
Рисунок на http://gazette.linux.ru.net/lg78/articles/misc/tougher/draw_line.png
Класс graphics_context широко используется в данной статье.
Вы могли заметить в выше приведенном коде два вспомогательных класса: point и line (http://gazette.linux.ru.net/lg78/articles/misc/tougher/shapes.hpp.txt). Эти маленькие классы, которые я создал для упрощения построения фигур. Сейчас они не столь необходимы, но позднее, когда потребуется выполнять комплексные операции типа трансформации фигур, они окажутся полезными. Например, куда как проще написать "line.move_x(5)", чем "line_x += 5; line_y += 5;". И проще, и ниже вероятность допустить ошибку.
4. Создание кнопки
4.1 Требования к кнопке
Давайте приступим к созданию визуального элемента, который потом может быть использован в других наших программах. Требования к кнопке можно выразить так:
- должна иметь свое окно для приема событий
- должна иметь два состояния -- "нажатая кнопка" и "отпущенная кнопка"
- должна отображаться как "нажатая" при нажатии на нее кнопкой мыши (т.е. когда указатель мыши неподвижно расположен над кнопкой)
- должна отображаться как "ненажатая" если кнопка мыши отпущена, либо когда указатель мыши находится вне пределов кнопки
- должна иметь свойство text и методы get и set для управления им
- должна передавать клиенту событие "on_click()"
Выглядит довольно просто, но реализация всего этого не столь тривиальная задача.
4.2 Создание собственного окна
Для начала создается отдельное окно кнопки. Конструктор вызывает метод show, который в свою очередь передает управление методу create, ответственному за создание окна:
Listing 9 : command_button.hpp (http://gazette.linux.ru.net/lg78/articles/misc/tougher/command_button.hpp.txt)
virtual void create() { if ( m_window ) return; m_window = XCreateSimpleWindow ( m_display, m_parent.id(), m_rect.origin().x(), m_rect.origin().y(), m_rect.width(), m_rect.height(), 0, WhitePixel((void*)m_display,0), WhitePixel((void*)m_display,0)); if ( m_window == 0 ) { throw create_button_exception( "could not create the command button" ); } m_parent.get_event_dispatcher().register_window ( this ); set_background ( m_background ); }
Очень похоже на конструктор класса window, не так ли? Первым делом создается окно с помощью вызова Xlib API XCreateSimpleWindow(), затем окно регистрируется в event_dispatcher, включаясь тем самым в цикл обработки событий, и наконец -- устанавливается фон.
Примечательно, что в XCreateSimpleWindow() передается id родительского окна, тем самым сообщая Xlib, что кнопка является дочерним окном указанного родителя.
4.3 Реализация состояний "нажатая" и "отпущенная"
Поскольку кнопка регистрирует свое окно в event_dispatcher, появляется возможность при необходимости перерисовки получать события on_expose(). Для отображения обоих состояний кнопки используется класс graphics_context.
Ниже показан ход отображения "отпущенной" кнопки
Listing 10 : command_button.hpp (http://gazette.linux.ru.net/lg78/articles/misc/tougher/command_button.hpp.txt)
// нижняя грань gc.draw_line ( line ( point(0, rect.height()-1), point(rect.width()-1, rect.height()-1) ) ); // правая грань gc.draw_line ( line ( point ( rect.width()-1, 0 ), point ( rect.width()-1, rect.height()-1 ) ) ); gc.set_foreground ( white ); // верхняя грань gc.draw_line ( line ( point ( 0,0 ), point ( rect.width()-2, 0 ) ) ); // левая грань gc.draw_line ( line ( point ( 0,0 ), point ( 0, rect.height()-2 ) ) ); gc.set_foreground ( gray ); // серая полутень нижней грани gc.draw_line ( line ( point ( 1, rect.height()-2 ), point(rect.width()-2,rect.height()-2) ) ); // серая полутень правой грани gc.draw_line ( line ( point ( rect.width()-2, 1 ), point(rect.width()-2,rect.height()-2) ) );
После компиляции и запуска приложения, кнопка будет выглядеть примерно так:
Рисунок на http://gazette.linux.ru.net/lg78/articles/misc/tougher/command_button.png
Следующий фрагмент кода рисует "нажатую" кнопку:
Listing 11 : command_button.hpp (http://gazette.linux.ru.net/lg78/articles/misc/tougher/command_button.hpp.txt)
gc.set_foreground ( white ); // нижняя грань gc.draw_line ( line ( point(1,rect.height()-1), point(rect.width()-1,rect.height()-1) ) ); // правая грань gc.draw_line ( line ( point ( rect.width()-1, 1 ), point ( rect.width()-1, rect.height()-1 ) ) ); gc.set_foreground ( black ); // верхняя грань gc.draw_line ( line ( point ( 0,0 ), point ( rect.width()-1, 0 ) ) ); // левая грань gc.draw_line ( line ( point ( 0,0 ), point ( 0, rect.height()-1 ) ) ); gc.set_foreground ( gray ); // серая полутень верхней грани gc.draw_line ( line ( point ( 1, 1 ), point(rect.width()-2,1) ) ); // серая полутень левой грани gc.draw_line ( line ( point ( 1, 1 ), point( 1, rect.height()-2 ) ) );
Нажатая кнопка выглядит так:
Рисунок на http://gazette.linux.ru.net/lg78/articles/misc/tougher/command_button_pressed.png
4.4 Дополнительные аспекты при отображении состояния кнопки
Казалось бы, все довольно просто: когда над кнопкой нажимается клавиша мыши -- рисуется "нажатая" кнопка, а когда клавиша мыши отпускается -- рисуется "отпущенная". Однако это не совсем верно. Если над изображением кнопки нажимается, а затем удерживается в нажатом состоянии, левая клавиша мыши, а после этого указатель мыши перемещается за пределы кнопки, то, не смотря на то, что клавиша мыши остается нажатой, кнопка должна отобразить состояние "отпущенная".
Для обработки такой ситуации класс command_button (http://gazette.linux.ru.net/lg78/articles/misc/tougher/command_button.hpp.txt) имеет два поля -- m_is_down и m_is_mouse_over. Сначала, нажатие клавиши мыши над кнопкой (смотри on_left_button_down()) переводит ее в состояние "нажатая" и перерисовывает ее, затем, если курсор мыши выводится за пределы кнопки (смотри on_mouse_exit()), то поле m_is_mouse_over устанавливается в состояние false и кнопка опять перерисовывается, но уже как "отпущенная". Если теперь курсор мыши опять переместить на кнопку, то поле m_is_mouse_over перейдет в состояние true и кнопка будет перерисована как "нажатая". Когда клавиша мыши отпускается, то кнопка переводится в состояние "отпущенная" и перерисовывается.
4.5 Свойство "text"
Реализация свойства "text" -- довольно простая задача. Для управления этим свойством в распоряжение программиста предоставляется два метода: первый -- получить текст надписи на кнопке, второй -- изменить его:
Listing 12 : command_button.hpp (http://gazette.linux.ru.net/lg78/articles/misc/tougher/command_button.hpp.txt)
std::string get_name() { return m_name; } void set_name ( std::string s ) { m_name = s; refresh(); }
Вызов метода refresh() служит для отображения кнопки с обновленной надписью.
4.6 Генерация события "on_click()"
Теперь необходимо снабдить нашу кнопку возможностью порождать событие "on_click()" в тот момент, когда по ней производится щелчок мышью. Ниже приведено определение класса command_button_base:
Listing 13 : command_button_base.hpp (http://gazette.linux.ru.net/lg78/articles/misc/tougher/command_button_base.hpp.txt)
namespace xlib { class command_button_base : public window_base { public: virtual void on_click () = 0; }; };
По существу этот код утверждает: "кнопка поддерживает все события, которые поддерживает класс окна, плюс еще одно -- on_click()". В результате, породив дочерний класс, программист получает возможность реализовать метод on_click() для выполнения необходимых действий.
5. Заключение
Я надеюсь, что вам понравилась эта статья. Мы рассмотрели некоторые из свойств библиотеки Xlib и "завернули" их в классы C++, чтобы сделать разработку программ на основе Xlib проще. Если у вас есть какие либо вопросы, комментарии или предложения по данной статье или по работе с Xlib в целом, можете .
a. Ссылки
- www.xfree86.org- домашняя страничка XFree86, open-source реализация X Window System
- Xlib Programming Manual (http://tronche.com/gui/x/xlib/)- замечательный ресурс для тех, кто работает с Xlib.
- The X Protocol (http://www.x.org/about_x.htm) - информация об X протоколе на сайте www.x.org
b. Файлы
- Примеры
- Makefile (http://gazette.linux.ru.net/lg78/articles/misc/tougher/Makefile.txt) - для компиляции всех примеров
- Example1 (http://gazette.linux.ru.net/lg78/articles/misc/tougher/example1.cpp.txt) - создание простого окна с помощью Xlib
- Example2 (http://gazette.linux.ru.net/lg78/articles/misc/tougher/example2.cpp.txt) - использование класса display
- Example3 (http://gazette.linux.ru.net/lg78/articles/misc/tougher/example3.cpp.txt) - использование класса window
- Example4 (http://gazette.linux.ru.net/lg78/articles/misc/tougher/example4.cpp.txt) - обработка событий с помощью event_dispatcher
- Example5 (http://gazette.linux.ru.net/lg78/articles/misc/tougher/example5.cpp.txt) - рисование линий и текста с помощью класса graphics_context
- Example6 (http://gazette.linux.ru.net/lg78/articles/misc/tougher/example6.cpp.txt) - наша кнопка в действии
- классы xlib++
- shapes.hpp (http://gazette.linux.ru.net/lg78/articles/misc/tougher/shapes.hpp.txt) - содержит различные классы фигур
- character.hpp, (http://gazette.linux.ru.net/lg78/articles/misc/tougher/character.hpp.txt) color.hpp (http://gazette.linux.ru.net/lg78/articles/misc/tougher/color.hpp.txt) - вспомогательные классы
- command_button_base.hpp (http://gazette.linux.ru.net/lg78/articles/misc/tougher/command_button_base.hpp.txt) - абстрактный класс command_button_base
- command_button.hpp (http://gazette.linux.ru.net/lg78/articles/misc/tougher/command_button.hpp.txt) - класс command_button
- event_dispatcher.hpp (http://gazette.linux.ru.net/lg78/articles/misc/tougher/event_dispatcher.hpp.txt) - класс event_dispatcher для обработки событий
- display.hpp (http://gazette.linux.ru.net/lg78/articles/misc/tougher/display.hpp.txt) - класс display
- exceptions.hpp (http://gazette.linux.ru.net/lg78/articles/misc/tougher/exceptions.hpp.txt) - различные классы исключений
- graphics_context.hpp (http://gazette.linux.ru.net/lg78/articles/misc/tougher/graphics_context.hpp.txt) - класс для рисования graphics_context
- window_base.hpp (http://gazette.linux.ru.net/lg78/articles/misc/tougher/window_base.hpp.txt) - абстрактный класс window_base
- window.hpp (http://gazette.linux.ru.net/lg78/articles/misc/tougher/window.hpp.txt) - класс window для создания окна
- COPYING (http://gazette.linux.ru.net/lg78/articles/misc/tougher/COPYING.txt) - Информация об авторском праве на исходные тексты в данной статье
Rob Tougher
Роб -- пишущий на C++ программист из Нью-Йорка. В свободное от работы время его можно найти прогуливающимся по пляжу со своей девушкой Николь (Nicole) и их собакой Холли (Halley).