.:Публикации:. [www.karlson.ru]


МОНИТОР #04/95

ОХОТА НА ВИДЖЕТА!

Кожекин Н.
В статье рассматривается программирование в среде X-Window с использованием X Toolkit Intrinsics (Xt) на примере написания программы, рисующей пучок разноцветных линий, бегающий по окну.


Используется компилятор g++ (GCC).
Блеснело. Склипкие лисцы
Вихрюжились во лже:
Буровращались мымрецы,
А утри лзлись уже.
Льюис Кэрролл. "Тарабардей"
Не секрет, что программирование вместе с XLib и Xt облегчает работу в мире X Window. А как это сделать - об этом данная статья. Здесь я привожу пример работы с Xt - написание программы, рисующей пучок цветных линий, красиво бегающий по окну. Программа корректно реагирует на изменение размеров окна, по следующему алгоритму : если событий нет, то добавляем новую линию и стираем старую, если получаем событие Expose (изменение размеров, положения и т.п.), то получаем новые параметры и выводим весь пучок линий.

Что такое Xt?


     Если кто-нибудь читал статью [3], то он наверняка заметил, что программирование в X Window только с XLib - занятие малоприятное. Однако всю рутинную работу программиста может взять на себя Xt- X Toolkit Intrinsics - библиотека простейших виджетов и набор процедур для работы с ними. Widget не английское слово - это комбинация двух слов : Window и Gadget (смотри [4] ). Вообще говоря это объект, но не в терминах объектно ориентированных языков ( напомню запуск этого проекта датирован 1984 годом, а первое издание книги Страуструпа о C++ 1986). Дерево виджетов Xt показано на рисунке [1]. На его базе создано множество коммерческих библиотек - X Motif(Open Software Foundation), OpenLook(Sun Microsystems), Aphena Widgets и т.д. Но Linux - это не коммерческая система, и поэтому надстройки виджетов пользователям Linux фактически не доступны. Впрочем Xt - это уже достаточно для программирования. Что бы использовать Xt Вы должны сделать следующие включения :

#include<X11/Intrinsic.h>
а также файлы, для используемых Вами виджетов, например, если Вы используете виджет "Core", то надо написать:
#include<X11/Core.h>
Инициализируется библиотека вызовом XtInitialize().

Процедуры обратного вызова (callback proc), процедуры действия (action proc), рабочие процедуры (work proc), процедуры обработчики событий (event handler), процедуры таймера (timer proc) и процедуры внешнего ввода (input data proc).


     Естественно, что к каждому виджету должны быть привязаны какие либо действия. Для осуществления этого Xt предлагает шесть механизмов, перечисленных в заглавии этого абзаца. Первый из них - процедуры обратного вызова, обозначает реакцию на зарегистрированные для этого виджета действия. Каждая callback-процедура должна иметь прототип:

void CallbackProc(Widget myWidget, XtPointer myData, XtPointer
 myCallbackdata);

     Каждый виджет имеет ресурс типа XtCallbackList, где хранится список процедур обратного вызова. Добавить свою процедуру можно так:

XtAddCallback( myWidget, myCallbackName, myXtCallbackProc, myData);
Рисунок 1. Иерархия виджетов XToolkit.

     Однако процедуры обратного вызова можно использовать только для зарегистрированных действий. Для остальных можно использовать action-процедуры, например так, как в примере [1]. При всем удобстве у этих двух моделей есть существенный недостаток - медленность вызова, ведь процедуру сначала нужно найти в списке. Для более быстрой работы используются рабочие процедуры(нет событий в стеке), обработчики событий (реакция на событие), таймерные процедуры (выполняются через данный промежуток времени). Например, в примере из листинга 2 IdleHook - рабочая процедура, а LookHook - обработчик события Expose. Естественно, мы не можем использовать методы объекта, поэтому параметр XtPointer UserData используется для указания на объект. Таймерные же процедуры используются для выполнения действий, через указанный промежуток времени. Таймерная процедура регистрируется обращением :

XtIntervalId XtAppAddTimeOut(XtAppContext myAppContext, unsigned long
 myInterval, XtTimerCallbackProc pmyProcedure, XtPointer pmyData);

     Каждая таймерная процедура должна иметь прототип :

void TimerProc(XtPointer pmyData, XtIntervalId *myId);

     InputData-процедуры используются для чтения данных из внешних устройств, и вызываются по мере готовности данных. Регистрация производится обращением :

XtInputId XtAppAddInput(XtAppContext myAppContext, int mySource,
 XtPointer pCondition, XtInputCallbackProc myProcedure, 
 XtPointer pmyData);

     Причем каждая InputData-процедура должна иметь прототип:

void InputDataProc(XtPointer pmyData, int *Source, XtInputId
 *myIdentificator); 

Создание виджетов и Xt-программа


     Обычно Xt-программа строится по следующей модели: Создание виджетов; добавление аction-процедур, обработчиков событий и так далее; реализация виджетов.

     После этого, обычно, используется основной цикл обработки сообщений:

XtAppMainLoop( XtAppContext myAppContext );

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

void XtMyAppExtendedMainLoop( XtAppContext myAppContext);
{
 XEvent myEvent;
 for ( ; ; )
 {
  XtAppNextEvent(myAppContext, &myEvent);
  /* Здесь можно обработать некие события до диспетчера*/
  XtDispatchEvent( &myEvent);
  /* А здесь после диспетчера */
 }
}


Ресурсы для виджетов


     Ресурсы задаются виджетам директивами вида: ПУТЬ_К_РЕСУРСУ : ЗНАЧЕНИЕ_РЕСУРСА. При инициализации виджетам Xt обычно присваивает "разумные" значения, которые можно заменять в самой программе, например: XtVaSetValues( myWidget, XtNwidth, 200, NULL ); меняет у виджета myWidget значение ширины на 200 пикселей. Именно так это делается в программе из листинга 2. В принципе пользователь может сам поменять ресурсы у этой программы из командной строки, к примеру:

Xanimate -xrm Simple*background : blue
Пример 1. Работа с action-процедурами
...
static void KeyboardR( Widget userWidget, XEvent 
       *userEvent, String *psParams, 
         Cardinal *nNumParams) /* ActionProc */
...
static XtActionRec actions[] = {"PressMe" , KeyboardR };
static char UserTranslations[] = "Alt<Key>a : PressMe()";
...
XtAppAddActions(appContext,actions,1); /* добавление */
XtAugmentTranslations(userWidget,
         XtParseTranslationTable(UserTranslations));
...

Пример 2. Работа с ресурсами - на этот file должна указывать переменная среды XENVIRONMENT
...
XAnimate.Simple*background : white
XAnimate.Simple.Core.width : 300
XAnimate.Simple.Core.height : 300
XAnimate.Simple*font : 9x15
XAnimate.Simple.Core.boderwidth : 15
XAnimate.Simple.Core.title : X-Animate_v_1.0
XAnimate.Simple*foreground : black
... 

     Можно, правда, сделать и проще:

Xanimate -bg blue

     Но для больших приложений было бы удобнее, если ресурсы хранились бы в отдельных файлах, и такая возможность тоже есть. Программа будет читать ресурсы либо из файла ".Xdefaults", который ищется в домашней директории пользователя, либо из файла, на который указывает переменная среды XENVIRONMENT (смотри пример [2]). Это позволяет сделать программу более простой для адаптации (например к национальным языкам).

Несколько слов об утилите Make


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

  1. Система контроля версий RCS(проект GNU) - Revision Control System.
  2. Система автоматизации формирования бинарной поставки - make (GNU).

     Команда make выполняет программу, написанную на специальном командном языке, из файла "makefile" или "Makefile". Если же Вы хотите выполнить программу из другого файла, то Вам придется набрать команду вида:

make -f ThisIsMyMakeFileForMyProgram

     Командный язык make - это набор описаний вида:

цель1 [цель2...] : [зависимость1]
<<TAB>> [действие1]
...
<<TAB>> [действиеN]

     В этом языке так же допускается использование макроопределений, например: CC = g++ , что вызывается далее $(CC) или ${CC}. Часть макросов предопределены заранее - $*, $@, $? и $<. При этом $@ - полное имя текущего целевого файла, $? - цепочка имен файлов, более свежих, чем целевой. Макросы $*,$< используются в неявных правилах трансформации, и $* обозначает префикс имени целевого файла, $< - полное имя файла, к которому применяется правило. Типичный пример make-файла приведен в листинге 1. Он собирает программу из листинга 2.

Как работать с листингами?


     Вы должны понимать, что то, что лежит в "Монитор-Диске" не есть программы - это соответствующие им куски статей. Требования журнала "Монитор" накладывают на них ряд условностей. Например знаки <,> и знак @ в начале строки удвоены. Исправив это, вы должны разбить листинг на соответствующие файлы и стереть фразу Листинг ... в заголовках. Теперь еще скажем несколько слов в связи со спецификой Linux. В статье [1] было объяснено, как русифицировать Linux кодировкой КОИ-8. Файлы, предоставленные Вам, записаны в DOS-формате с альтернативной кодировкой. Перевести из DOS-формата в UNIX совсем просто - это умеет делать например JED (кстати в highliting'е JED'а есть одна ошибка : он не воспринимает комментарии разорванные на несколько строк ). Перевести из альтернативной кодировки в КОИ-8 тоже просто - смотри таблицу[1]. Нетрудно и просто перевести в клер, например популярной программой translit. Обратите внимание, что пробелы в makefile на строках 17 и 22 надо заменить табуляцией. После этого наберите, например, следующее:

make
startx &
<откройте любой терминал, и наберите:
Xanimate [ <- опции ->] 
Если у вас возникнут вопросы или предложения автору, то пишите по e-mail : (Никита Кожекин). Я всегда рад переписке, тем более, что разговор об Xt не закончен (я не коснулся например конверторов).

      В принципе теперь Вы можете приступать к собственным экспериментам с X Toolkit, хотя например меня не располагает к методу проб и ошибок ( на машине установлено только 4Mb RAM и все происходит так медленно, что располагает, наоборот, писать сразу правильно). Поэтому я рекомендую Вам тщательно ознакомиться с документацией (смотри, например, [5],[6],[7],[8]).
Рекомендуемая литература
1. В.Водолазкий. "Как без головной боли и нервотрепки установить Linux, часть вторая" Монитор 1.95.
2. Н.Кожекин. "Новое поколение выбирает UNIX!" Монитор 1.95.
3. Н.Кожекин. "Программируем под X Window" Монитор 2.95
4. The complete works of Lewis Carrol. Penguin books 1988.
5. Nye A. XLib programming Manual. - O'Reilly & Associates.
6. Nye A. XLib refernce Manual. - O'Reilly & Associates.
7. Nye A., O'Reilly T. X Toolkit Intrinsics Programming Manual. - O'Reilly & Associates.
8. Nye A., O'Reilly T. X Toolkit Intrinsics Refernce Manual. - O'Reilly & Associates.
Кодировка КОИ-8. Символы расположены в ячейках таблицы 192 по 254, по созвучию с соответствующими латинскими символами.
юабцдефгхийклмнопярстужвьызшэщч
ЮАБЦДЕФГХИЙКЛМНОПЯРСТУЖВЬЫЗШЭЩЧ
Листинг 1. file : makefile

# makefile для программы xanimate.c, напечатанной в 
# листинге 2. Выполняется командой make.
# цель - получение выполняемого файла Xanimate 

# необходимые переменные : 
# компилятор C,
# опции оптимизации для 486 процессора,
# и используемые библиотеки, соответственно.

CC = g++
CFLAGS = -O2 -m486 -fomit-frame-pointer
LLIBS = -lXt -lX11

# неявное правило преобразования .c файлов в .o.

.c.o:
        ${CC} -c ${CFLAGS} ${INCLUDES} ${DEFINES} $<

# цель 

Xanimate : xanimate.o
        ${CC} ${LDFLAGS} -o $@ xanimate.o ${LLIBS}

# !!! в обоих предыдущих случаях в начале строк вместо
# 8 пробелов должно стоять  - необходимое 
# требование программы make !!! 
Листинг 2. file : xanimate.c
// Xanimate version 0.0.1 - от 21 марта 1995.
// автор Никита Кожекин : karlson@sch57.mcn.msk.su
// программа xanimate.c - классический пример, в окне рисуется
// красивый, "бегающий" веер из разноцветных линий.  

// необходимые включения
      
#include <X11/Intrinsic.h> 
// собственно сам Xt - X Toolkit Intrinsics 
#include <X11/StringDefs.h>
#include <X11/Core.h>  /* widget Core */
#include <X11/Shell.h>  /* widget Shell */
#include <stdlib.h> /* необходимы для некоторых, */
#include <stdio.h>  /* использованных функций - */
                      /* - rand() и printf(). */ 

const int   NumberLines = 20 ; /* количество линий */

struct line {
       int            X1, Y1, X2, Y2;
       unsigned long  Color;
}; /* структура - линия : координаты начала (X,Y) и */
   /* конца (X,Y), цвет линии */

class DrawAnimate {  /* объект рисующиеся линии */
      
      Dimension      MaxX; /* текущие размеры окна  */
      Dimension      MaxY; /* в котором происходит  */
                           /* рисование             */
      
      unsigned long  R,G,B; /* значения чистых      */
      /* цветов в палитре - Красный, Зеленый, Синий */
      
      int          DX1, DY1, DX2, DY2;
      /* сдвиг новой линии относительно предыдущей  */
      
      int          LastLine; /*индекс последней линии*/
      line         Lines[NumberLines]; /*массив линий*/
      
      Display      * userDisplay; /* Идентификаторы */
      Window       userWindow;    /* дисплея, окна,  */ 
      int          nScr;          /* экрана,         */
      GC           userGC; /* графического контекста */
      /* виджета, для которого выполняется рисование */
      
      Bool         madeGC;     /* флаг готовности GC */    
public:

      DrawAnimate( Widget userWidget)
      {   /* конструктор */
      
       XColor myColor;
       
       /* получение идентификатора дисплея и номера */
       /* экрана данного виджета */
       userDisplay = XtDisplay( userWidget );  
       nScr = DefaultScreen( userDisplay );
       
       /* выделение красной ячейки палитры */
       myColor.red = myColor.green = myColor.blue = 0;
       myColor.red = 255;
       myColor.flags = DoRed | DoGreen | DoBlue;
       XAllocColor(userDisplay, DefaultColormap( 
                        userDisplay, nScr), &myColor);
       R = myColor.pixel;
       
       /* выделение зеленой ячейки палитры */
       XParseColor( userDisplay, DefaultColormap(
               userDisplay, nScr), "Green", &myColor);
       XAllocColor(userDisplay, DefaultColormap(
                        userDisplay, nScr), &myColor);
       G = myColor.pixel;
       
       /* выделение голубой ячейки палитры */
       XAllocColorCells(userDisplay, DefaultColormap(
         userDisplay, nScr), False, NULL, 0, &B, 1);
       XParseColor( userDisplay, DefaultColormap(
         userDisplay, nScr), "Blue", &myColor);
       myColor.pixel = B;
         
       /* сохранение цвета */
       XStoreColor( userDisplay, DefaultColormap(
                     userDisplay, nScr), &myColor);
 
       /* установка первоначальных значений */
       DX1 = ((rand() % 3) + 1) * 2;
       DX2 = ((rand() % 3) + 1) * 2;
       DY1 = ((rand() % 3) + 1) * 2;
       DY2 = ((rand() % 3) + 1) * 2;
       LastLine = 0;
       memset((unsigned long *)Lines, 0x00,
                        (NumberLines*sizeof(line)));
       Lines[NumberLines-1].X1 = rand() % 200;
       Lines[NumberLines-1].Y1 = rand() % 200;
       Lines[NumberLines-1].X2 = rand() % 200;
       Lines[NumberLines-1].Y2 = rand() % 200;
       Lines[NumberLines-1].Color = 
                     WhitePixel(userDisplay,nScr);
       madeGC = False;
      
      } /* DrawAnimate */

      DrawLine(int Index, Bool Draw)
      { /* рисование данной линии из массива lines */
        
      /* в зависимости от параметра Draw рисуем или */
      /* стираем линию */
      if (Draw == True)
      XSetForeground(userDisplay, userGC, 
                             Lines[Index].Color);
      else
      XSetForeground(userDisplay, userGC,
                    WhitePixel( userDisplay, nScr));
      /* рисуем линию */
      XDrawLine(userDisplay,userWindow,userGC,
                  Lines[Index].X1,Lines[Index].Y1,
                  Lines[Index].X2,Lines[Index].Y2);
      /* с вот ТАКИМИ атрибутами */
      XSetLineAttributes( userDisplay, userGC, 1,
                LineSolid, userGC->values.cap_style,
                        userGC->values.join_style);

      } /* DrawLine */

      Next( int &A, int &DA, int Max)
      { /* следующий - увеличение данных */
      
      A = A + DA;
      if (A > ( Max - 1 )) {
         A = Max - 1;
         DA = (( -1 - (rand() % 3)) * 2); }
      else if (A < 1) { 
         A = 1;
         DA = (((rand() % 3) + 1) * 2); } 
      
      } /* Next */

      NextLine ()
      { /* добавление следующей линии */
       /* только если окно готово к выводу */
       if (madeGC == True) {
        
        DrawLine(LastLine, False);
        if (LastLine != 0)
         Lines[LastLine] = Lines[LastLine - 1];
        else
         Lines[LastLine] = Lines[NumberLines-1];
        
        Next(Lines[LastLine].X1, DX1, MaxX);
        Next(Lines[LastLine].X2, DX2, MaxX);
        Next(Lines[LastLine].Y1, DY1, MaxY);
        Next(Lines[LastLine].Y2, DY2, MaxY);
        
        /* случайный из 3-х чистых цветов */
        switch ( rand() % 3)  {
             case 0: Lines[LastLine].Color = R; break;
             case 1: Lines[LastLine].Color = G; break;
             case 2: Lines[LastLine].Color = B; break;
        }
       
        DrawLine(LastLine, True); /* рисуем ... */
        LastLine++;
        if (LastLine > NumberLines-1) LastLine = 0;
       }

      } /* NextLine */


      static Boolean IdleHook (XtPointer pUserData)
      { /* рабочая процедура */
      
       ((DrawAnimate *)(pUserData))->NextLine();
       return False;
      
      } /* IdleHook */

      static void LookHook(Widget userWidget, XtPointer 
        pUserData, XEvent *userEvent, Boolean *pbContinue)
      { /* обработчик события Expose */
       
        ((DrawAnimate *)(pUserData))->Look(userWidget,
                                           userEvent);
      } /* LookHook */

      void Look( Widget userWidget, XEvent *userEvent )
      { /* отслеживание изменения размеров окна и т.д*/

       XGCValues rVal;
       
       /* создание графического контекста */
       userDisplay = XtDisplay( userWidget ) ;
       userWindow = XtWindow( userWidget ) ;
       nScr = DefaultScreen( userDisplay );
       XtVaGetValues(userWidget, XtNheight, &MaxY,
                                XtNwidth, &MaxX, NULL);
       rVal.foreground = BlackPixel(userDisplay, nScr);
       rVal.background = WhitePixel(userDisplay, nScr);
       userGC = XCreateGC(userDisplay, userWindow ,
       (GCForeground | GCBackground), &rVal );
       madeGC = True;
       
       /* вывод всех линий сразу */
       int Index = LastLine;
       int I = -1;
       while ( I < (NumberLines - 1))
       {
        DrawLine( Index, True );
        Index++;
        if (Index > (NumberLines - 1) ) Index = 0; 
        I++;
       }

      } /* Look */

}; /* DrawAnimate */

int  main( int argc, char **argv)
{

  Widget  topLevelWidget, coreWidget ;
  XtAppContext  appContext;
  
  printf("Xanimate version 0.0.1 \n usage: Xanimate " 
         "<options> \n options are: \n -bg "
         "(-background) [String] \n -bd (-bodercolor) "
         "[String] \n -bw (-boderwidth) [Integer] \n "
         "-display [String] \n -fg (-foreground) "
         "[String] \n -fn (-font) [String] \n "
         "-geomtery [String] \n -iconic [None] \n "
         "-name [String] \n -rv (-reverse) [None] \n "
         "+rv [None] \n -title [String] \n -xrm "
         "[String] \n");
  
  fflush(stdout); /* вывод подсказки */
  
  /* инициализация виджетов */
  topLevelWidget = XtVaAppInitialize( &appContext,
     "Simple", NULL, 0, &argc, argv, NULL , NULL ) ;
  
  coreWidget = XtCreateManagedWidget( "Core" ,
       widgetClass, topLevelWidget , NULL , 0 ) ;
  
  XtVaSetValues( coreWidget, XtNwidth, 400, 
                   XtNheight, 400, NULL ) ;
  
  /* инициализация DrawAnimate */
  DrawAnimate anim( coreWidget) ;
  
  /* добавление рабочей процедуры и */ 
  /* обработчика событий            */
  XtAppAddWorkProc(appContext, 
                  (XtWorkProc)anim.IdleHook, &anim);
  XtAddEventHandler( coreWidget , ExposureMask, False,
                (XtEventHandler)anim.LookHook,&anim);
  
  /* реализация виджета topLevelWidget и его потомков*/
  XtRealizeWidget( topLevelWidget );
  /* основной цикл обработки сообщений */
  XtAppMainLoop( appContext );  
  
  return 0;
  /* конец программы, однако */
} /* main */

...домик на крыше...,поиск,гостевая книга,cv. Be free, use Linux!