Текущий раздел:

Описания RDS

Описание пользователя

Глава 3. Использование стандартных модулей автокомпиляции

§3.6. Принципы создания автокомпилируемых моделей блоков

§3.6.9. Всплывающие подсказки

# Описание пользователя

## Глава 3. Использование стандартных модулей автокомпиляции

### §3.6. Принципы создания автокомпилируемых моделей блоков

#### §3.6.9. Всплывающие подсказки

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

Если пользователь задержит курсор над изображением блока, модель этого блока может вывести всплывающую подсказку – окно с текстом, которое на некоторое время появится поверх изображения, и, через некоторое время, исчезнет. Всплывающие подсказки часто используются в Windows для вывода различных пояснений к кнопкам и полям ввода, когда такие пояснения заняли бы в самом окне слишком много места. В подсказках к блокам часто выводятся значения параметров этих блоков, сообщения об ошибках или комментарии. Модель может вывести подсказку ко всему блоку или к отдельному элементу его изображения, причем она может задать не только текст, который увидит пользователь, но и некоторые другие параметры подсказки, например, время ее нахождения на экране, или координаты зоны окна, при выходе курсора из которой подсказка автоматически исчезнет. Для того, чтобы при задержке курсора над блоком подсказка появилась на экране, должны быть одновременно выполнены два условия: во-первых, в параметрах блока должен быть разрешен вывод всплывающих подсказок и, во-вторых, модель блока, среагировав на событие вывода подсказки (ему соответствует константа RDS_BFM_POPUPHINT), должна передать текст подсказки в RDS функцией rdsSetHintText. Ниже будут рассмотрены два примера моделей, выводящих подсказки пользователю.

Начнем с простого примера, в котором подсказка выводится ко всему блоку целиком. В §3.6.6 рассматривается модель блока, выдающего на выход «y» произведение входа «x» и настроечного параметра «K». Поскольку параметр «K» скрыт внутри блока, пользователь может увидеть его, только вызвав окно настройки в режиме редактирования. Сделаем так, чтобы при задержке курсора над блоком значение «K» выводилось во всплывающей подсказке – так пользователю будет удобнее работать.

Прежде всего, добавим в модель реакцию на событие вывода всплывающей подсказки. Для этого на левой панели окна редактора выберем вкладку вкладку «события», раскроем на ней раздел «разное» и дважды щелкнем на его подразделе «всплывающая подсказка (RDS_BFM_POPUPHINT)» (рис. 440). При этом значок подраздела станет желтым, а в правой части окна появится новая пустая вкладка «подсказка». Программа на этой вкладке должна сформировать текст подсказки и передать его в RDS.

Введем на вкладке «подсказка» следующий текст:

      char buf[100]; // Здесь будет формироваться текст
      // Вывод текста в массив buf
      sprintf(buf,"K: %lf",K);
      // Передача сформированного текста в RDS
      rdsSetHintText(buf);

Рис. 440. Вывод подсказки в списке событий

Здесь мы для создания текста со значением параметра используем sprintf – стандартную библиотечную функцию языка C, выполняющую форматированный вывод в строку. Она записывает текст подсказки, в котором после буквы «K» и двоеточия выведено значение параметра K, во вспомогательный массив buf (его размера в сто символов хватит для выполняемого нами вывода текста). Затем вызывается функция rdsSetHintText, которая передает содержимое массива buf в RDS. Теперь, как только реакция модели на событие закончится, на экране возле курсора мыши должна будет появится подсказка с переданным нами текстом.

Во введенном фрагменте программы все написано верно, однако, если попытаться скомпилировать модель, будет выведено сообщение об ошибке с текстом, подобным «call to undefined function 'sprintf' in...» («вызов неизвестной функции 'sprintf'...», конкретный текст сообщения зависит от используемого компилятора). Дело в том, что функция sprintf, которую мы используем, описана в файле заголовков «stdio.h», а этот файл не включается в программу модели по умолчанию. Мы уже сталкивались с этим в примере из §3.6.2.5. Чтобы пользоваться функцией sprintf, необходимо включить этот файл вручную. Для этого следует на вкладке «события» левой панели редактора раскрыть раздел «описания» (это самый первый раздел в списке) и дважды щелкнуть на его подразделе «глобальные описания», после чего на открывшейся пустой вкладке «описания» в правой части окна ввести единственную строчку: «#include <stdio.h>». После этого ошибки при компиляции исчезнут.

Теперь в нашей модели есть реакция на вывод всплывающей подсказки, однако, если задержать курсор над блоком, никакой подсказки не появится. Необходимо разрешить блоку выводить подсказку – без этого созданная нами реакция не будет вызвана. Разрешить вывод подсказки можно двумя способами: через окно параметров блока и через окно групповой установки. В нашем случае, поскольку у нас только один блок с такой моделью, первый вариант удобнее: нужно открыть окно параметров блока (пункт «параметры» в его контекстном меню), и на вкладке «DLL» включить флажок «блок выводит всплывающую подсказку» (рис. 441).

Рис. 441. Разрешение всплывающей подсказки в окне параметров блока

Если бы наша модель была подключена к нескольким блокам, разрешить вывод подсказки удобнее было бы для всех них одновременно через окно групповой установки. Для этого нужно было бы открыть редактор модели и выбрать пункт меню «модель | установка параметров блоков». В открывшемся окне следовало бы выбрать вкладку «DLL», включить на ней флажок «всплывающая подсказка» и выбрать в выпадающем списке справа от него вариант «есть» (рис. 442).

Рис. 442. Разрешение всплывающей подсказки в окне групповой установки

Рис. 443. Всплывающая
подсказка к блоку

Теперь вывод подсказки разрешен и, задержав курсор над изображением блока, можно увидеть значение множителя «K», введенного в настройках этого блока (рис. 443).

В нашей подсказке в функции sprintf мы использовали формат «%lf», поэтому в значении «K» выведено слишком много незначащих нулей. Мы не можем знать заранее, в каком формате пользователь введет значение в настройках, поэтому не можем заложить в подсказку фиксированное число знаков в дробной части. Однако, мы можем воспользоваться функцией RDS rdsDtoA, которая способна сама отбросить незначащие нули – мы уже использовали ее в §3.6.5 в круглом индикаторе со шкалой.

Изменим текст программы на вкладке «подсказка» так, чтобы формат вывода «K» подбирался автоматически. Старый текст мы полностью сотрем, и введем вместо него следующий:

      // Преобразование K в строку с подбором формата
      char *val=rdsDtoA(K,-1,NULL);
      // Сложение строк "K:" и значения K
      char *hint=rdsDynStrCat("K: ",val,FALSE);
      // Передача текста подсказки в RDS
      rdsSetHintText(hint);
      // Освобождение динамических строк
      rdsFree(val);
      rdsFree(hint);

В первой строке этой программы мы присваиваем вспомогательной переменной val результат возврата функции rdsDtoA, в которую мы передаем вещественное число K и значение −1 в качестве числа знаков дробной части – отрицательное число во втором параметре заставит функцию автоматически подобрать для числа формат. NULL в третьем параметре передается вместо указателя на целую переменную, куда rdsDtoA могла бы записать длину сформированной строки. Сама строка, указатель на которую возвращается функцией, формируется в динамической памяти, и ее нужно будет потом обязательно освободить вызовом rdsFree.

Во второй строке мы, при помощи функции RDS rdsDynStrCat, объединяем строку с буквой «K» и двоеточием со строкой значения параметра, которую ранее вернула rdsDtoA, и присваиваем ее результат вспомогательной переменной hint – это и будет текст нашей подсказки. Функция rdsDynStrCat тоже формирует строку-результат в динамической памяти, поэтому эту строку тоже нужно будет освобождать при помощи rdsFree. Помимо объединяемых строк в первом и втором параметрах, в третьем параметре rdsDynStrCat передается логическое значение, разрешающее или запрещающее функции возвращать NULL если обе объединяемые строки пусты. В нашем случае первая строка гарантированно не пуста, поэтому не важно, что передается в третьем параметре (мы передаем FALSE).

Далее сформированный текст подсказки передается в RDS уже знакомым нам вызовом rdsSetHintText, после чего обе динамических строки (hint и val) освобождаются вызовами rdsFree – они нам больше не нужны.

Рис. 444. Всплывающая
подсказка к блоку –
второй вариант

Новая версия текста подсказки не содержит лишних нулей в дробной части числа (рис. 444). При этом, если пользователь введет дробное значение «K», в подсказке отобразится столько знаков после десятичной точки, сколько он ввел.

В рассмотренном примере выводилась одна и та же подсказка при попадании курсора мыши в любую точку прямоугольной области окна, занимаемой блоком. Если требуется выводить разные подсказки к разным элементам изображения одного и того же блока, необходимо, во-первых, определять, какой именно элемент находится под курсором, и, во-вторых, ограничить зону действия подсказки размером этого элемента, чтобы при выходе курсора из нее подсказка могла снова появится уже для другого элемента. Чтобы сделать это возможным, в параметре HintData функции реакции на всплывающую подсказку передается указатель на структуру RDS_POPUPHINTDATA, при помощи которой модель считывает и устанавливает параметры подсказки. Многие из ее полей по смыслу совпадают с полями структуры RDS_DRAWDATA, используемой при рисовании. Кратко перечислим поля структуры и их назначение:

x, y(int)
  Координаты курсора мыши на рабочем поле окна подсистемы на момент вывода подсказки (уже с учетом масштаба подсистемы).
BlockX, BlockY(int)
  Координаты точки привязки блока на рабочем поле с учетом масштаба и возможной связи положения этого блока с переменными. Для блоков с векторной картинкой точка привязки – это положение начала координат этой картинки, для всех остальных – левый верхний угол прямоугольной области.
Left, Top, Width, Height(int)
  Координаты левого верхнего угла (Left, Top) прямоугольной области, занимаемой блоком, ее ширина (Width) и высота (Height) в текущем масштабе с учетом возможной связи положения блока с его переменными. Соответствуют одноименным полям структуры RDS_PDRAWDATA.
HZLeft, HZTop, HZWidth, HZHeight(int)
  Возвращаемая моделью зона действия подсказки: горизонтальная (HZLeft) и вертикальная (HZTop) координаты верхнего левого угла зоны, ее ширина (HZWidth) и высота (HZHeight). При выходе курсора мыши за пределы этой прямоугольной зоны будет запрошен повторный вывод подсказки. По умолчанию в этих полях записаны координаты и размер всей прямоугольной области блока, то есть они совпадают с полями Left, Top, Width и Height соответственно, поэтому подсказка для любой точки его изображения будет одной и той же. Модель может записать в эти поля другие значения, чтобы при выходе курсора из указанной зоны можно было вывести другую подсказку.
ReshowTimeout(int)
  Возвращаемое моделью время в миллисекундах после гашения подсказки, по истечении которого подсказку необходимо вывести снова. По умолчанию в этом поле записан ноль – подсказка не будет выведена повторно до тех пор, пока курсор мыши не покинет зону ее действия.
HideTimeout(int)
  Возвращаемое моделью время в миллисекундах после вывода подсказки, по истечении которого ее необходимо убрать с экрана. По умолчанию в этом поле записано стандартное для Windows значение.
IntZoom(int)
  Текущий масштаб окна родительской подсистемы блока в процентах (используется крайне редко).
DoubleZoom(double)
  Текущий масштаб окна родительской подсистемы блока в долях единицы: 1 – 100%, 0.5 – 50%, 2 – 200% и т.п. Соответствует одноименному полю структуры RDS_PDRAWDATA.

Помимо указателя HintData, в функцию реакции на вывод подсказки передается также ссылка на целую переменную Show, присвоение ей константы RDS_BFR_NOTPROCESSED отменит вывод подсказки. Обычно эта переменная в модели никак не используется, поскольку для того, чтобы отменить вывод подсказки, достаточно просто ничего не передавать в RDS функцией rdsSetHintText.

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

Рис. 445. Предполагаемый внешний вид блока и его связь с входной матрицей

Чтобы не усложнять пример, мы не будем закладывать в этот блок настройку отображаемого диапазона координат, цветов и размера окружности точек. Окружности будем рисовать радиусом в три точки для масштаба 100% (как всегда, увеличивая и уменьшая этот радиус вместе с масштабом), а диапазон определим по максимальным и минимальным значениям координат в матрице. Диапазону горизонтальных вещественных координат (то есть минимуму и максимуму значений в нулевом столбце матрицы) будет соответствовать вся ширина блока за вычетом двух радиусов окружности точки (иначе окружности крайних точек выйдут за пределы изображения блока), а диапазону вертикальных (минимуму и максимуму значений в первом столбце матрицы) – вся высота блока за вычетом тех же двух радиусов. На рис. 445 отображаемый диапазон изображен пунктирной линией внутри блока, на настоящем блоке эта линия рисоваться не будет.

У нашего блока, помимо двух обязательных сигналов, будет единственный вход: матрица вещественных чисел «A». Создадим в схеме новый блок, запускающийся по сигналу, и зададим ему следующую структуру статических переменных:

Имя | Тип | Вход/выход | Пуск | Начальное значение
--- | --- | --- | --- | ---
Start | Сигнал | Вход | ✓ | 1
Ready | Сигнал | Выход |  | 0
A | Матрица double | Вход | ✓ | [ ] 0

В модели этого блока будет две реакции: реакция на рисование блока, в которой мы будем рисовать точки и линии между ними, и реакция на всплывающую подсказку, в которой мы будем проверять попадание курсора мыши в одну из точек и выводить ее координаты. Очевидно, в этих реакциях будет много общего: в обеих нужно вычислять координаты точек, для чего нужно будет определять границы отображаемого диапазона, то есть минимальные и максимальные значения координат в матрице. Чтобы не писать в этих реакциях одни и те же вычисления два раза, мы введем в нашу модель дополнительную функцию расчета вспомогательных параметров, которая будет вызываться из обеих реакций. Эту функцию необходимо сделать членом класса блока, поскольку ей необходим доступ к переменной блока «A». Параметры, которые она вычислит, тоже сделаем полями класса – так к ним проще будет обращаться из реакций. Таким образом, и функцию, и все вспомогательные параметры мы будем вводить на вкладке описаний внутри класса. Откроем ее: на левой панели окна редактора выберем вкладку «события», раскроем на ней раздел «описания» и дважды щелкнем на его подразделе «описания внутри класса блока» (рис. 446).

Рис. 446. Описания внутри класса в списке событий

При этом значок подраздела станет желтым, а в правой части окна появится новая пустая вкладка «описания в классе». Введем на ней следующий текст:

      // Вспомогательные параметры, используемые и при рисовании,
      // и при выводе подсказки

      // Радиус круга точки с учетом масштаба
      int point_r;
      // Границы области рисования внутри блока
      int draw_x1,draw_y1,draw_x2,draw_y2;
      // Диапазон координат в матрице
      double xmin,xmax,ymin,ymax;

      // Функция вычисления перечисленных выше параметров
      void CalcParams(int left,int top,	// левый верхний угол блока
                      int width,int height,	// ширина и высота
                      double zoom) 		// масштаб
      {
        // Радиус круга точки
        point_r=zoom*3; // 3 точки с учетом масштаба

        // Зона рисования внутри блока (на радиус круга внутрь от рамки)
        draw_x1=left+point_r+1;
        draw_y1=top+point_r+1;
        draw_x2=left+width-point_r-1;
        draw_y2=top+height-point_r-1;

        // Определение минимума и максимума координат в матрице
        xmin=xmax=A[0][0];
        ymin=ymax=A[0][1];
        for(int i=1;i<A.Rows();i++)
          { if(xmin>A[i][0]) xmin=A[i][0];
            if(xmax<A[i][0]) xmax=A[i][0];
            if(ymin>A[i][1]) ymin=A[i][1];
            if(ymax<A[i][1]) ymax=A[i][1];
          }
        if(xmin==xmax) // Нет горизонтального диапазона
          { // Принудительно создаем его
            xmin-=1; xmax+=1;
          }
        if(ymin==ymax) // Нет вертикального диапазона
          { // Принудительно создаем его
            ymin-=1; ymax+=1;
          }
      }

В начале этого текста описываются добавляемые нами поля класса для вспомогательных параметров (эти описания станут именно полями класса, а не глобальными переменными, поскольку весь введенный нами текст будет вставлен внутрь описания класса блока). В целом поле point_r будет вычисляться радиус окружности точки с учетом текущего масштаба подсистемы. В целых полях draw_x1 и draw_y1 будут вычисляться координаты левого верхнего, а в draw_x2 и draw_y2 – правого нижнего углов области рисования внутри прямоугольника блока (то есть пунктирной области на рис. 445). Вещественные поля xmin, xmax, ymin и ymax будут содержать минимальные и максимальные значения координат из поступившей на вход блока матрицы.

Сразу за описаниями полей записана функция CalcParams, которая будет вычислять значения этих полей. В функцию передаются координаты левого верхнего угла (left, top) и размеры (width, height) прямоугольной области блока, а также масштабный коэффициент подсистемы zoom: в зависимости от того, из какой именно реакции будет вызываться эта функция, эти значения будут браться либо из структуры RDS_DRAWDATA (при рисовании), либо из структуры RDS_POPUPHINTDATA (при выводе подсказки).

Внутри функции мы сначала вычисляем действительный размер окружности точки point_r, умножая 3 (радиус окружности в масштабе 100% мы решили сделать равным трем точкам экрана) на масштабный коэффициент zoom. Затем вычисляются размеры области рисования точек внутри блока: из общих размеров блока, переданных в параметрах функции, со всех сторон вычитается по радиусу point_r с дополнительным запасом в одну точку. После этого, в цикле перебирая все точки матрицы на входе блока A, мы вычисляем диапазоны содержащихся в ней координат xmin, xmax, ymin и ymax. Горизонтальные координаты содержатся в нулевом столбце матрицы, то есть в A[…][0], а вертикальные – в первом столбце, то есть в A[…][1]. Проверка наличия в матрице двух столбцов и, по крайней мере, одной строки будет выполняться внутри реакций на события до вызова функции CalcParams, поэтому в самой функции нет никаких проверок – мы просто обращаемся к нулевому и первому столбцу, считая, что они в матрице есть.

После того, как диапазоны координат точек в матрице определены, нужно проверить, не совпадают ли минимальное и максимальное значения по каждой из координат. Для преобразования вещественных координат точки в ее целые координаты на экране нам придется делить значение координаты на размер соответствующего ей диапазона, и, если этот размер будет нулевым, мы не сможем выполнить преобразование. Например, чтобы вычислить горизонтальную координату точки на экране, нужно разделить ее вещественную координату на (xmax-xmin) и умножить результат на (draw_x2-draw_x1), а затем добавить координату левой границы блока – это обычное линейное преобразование диапазонов. При этом, очевидно, значение xmax не должно быть равным xmin.

Выйти из этого затруднения очень просто: поскольку мы не рисуем на блоке никаких шкал или координатных сеток, при совпадении максимального и минимального значений какой-либо координаты (то есть если данная координата совпадает у всех точек матрицы – они расположены на строго горизонтальной или вертикальной линии) можно принудительно расширить этот диапазон, добавив какую-либо константу к максимальному значению и вычтя ее же из минимального. При этом все точки матрицы окажутся в середине этого диапазона и будут нарисованы в середине изображения блока. Константу можно взять любой – мы будем добавлять и вычитать единицу. Два оператора if, расположенных после цикла перебора точек матрицы, выполняют именно эту функцию. Таким образом, на момент завершения функции CalcParams, диапазоны xmin…xmax и ymin…ymax гарантированно не будут иметь нулевой размер.

Теперь введем в нашу модель реакцию на событие рисования блока. На вкладке «события» левой панели окна редактора раскроем раздел «внешний вид блока» и дважды щелкнем на подразделе «рисование блока (RDS_BFM_DRAW)» (см. рис. 416) – в правой части окна появится новая пустая вкладка «рисование». Запишем не ней следующую программу:

      // Вспомогательные переменные
      int right,bottom,ix,iy;

      // Правый нижний угол области блока
      right=DrawData->Left+DrawData->Width;
      bottom=DrawData->Top+DrawData->Height;

      // Черная линия
      rdsXGSetPenStyle(0,PS_SOLID,DrawData->DoubleZoom,0,R2_COPYPEN);
      // Белый фон
      rdsXGSetBrushStyle(0,RDS_GFS_SOLID,0xffffff);
      // Прямоугольник - черная рамка и белый фон
      rdsXGRectangle(DrawData->Left,DrawData->Top,right,bottom);

      // Отключаем заливку
      rdsXGSetBrushStyle(RDS_GFSTYLE,RDS_GFS_EMPTY,0);

      if(A.Cols()<2 || A.Rows()<1 ) // Меньше двух столбцов или нет строк
        return;

      // Вычисляем вспомогательные параметры
      CalcParams(DrawData->Left,DrawData->Top,
                 DrawData->Width,DrawData->Height,
                 DrawData->DoubleZoom);

      // Рисуем точки и линии
      for(int i=0;i<A.Rows();i++)
        { // Вычисляем оконные координаты точки i
          ix=draw_x1+(A[i][0]-xmin)*(draw_x2-draw_x1)/(xmax-xmin);
          iy=draw_y2-(A[i][1]-ymin)*(draw_y2-draw_y1)/(ymax-ymin);
          // Рисуем окружность радиусом point_r
          rdsXGEllipse(ix-point_r,iy-point_r,ix+point_r,iy+point_r);
          // Соединяем линией с предыдущей точкой
          if(i) rdsXGLineTo(ix,iy);
          else rdsXGMoveTo(ix,iy);
        }

В этой программе, как и в моделях из §3.6.5, для рисования используются графические функции RDS. В ее начале описано несколько вспомогательных переменных, затем располагаются команды установки стиля линии, заливки и рисования прямоугольника размером во весь блок. После их выполнения блок будет выглядеть как белый прямоугольник с черной рамкой, поверх этого прямоугольника мы и будем рисовать точки из матрицы.

Прежде чем приступать к рисованию точек, необходимо проверить, соответствует ли матрица нашим ожиданиям. Чтобы она описывала хотя бы одну точку, в ней должна быть по крайней мере одна строка и два столбца. Если число столбцов в матрице A (A.Cols(), см. §3.6.2.2) меньше двух, или число строк (A.Rows()) меньше одной, мы немедленно завершаем реакцию – с такой матрицей ничего нельзя нарисовать. В противном случае мы вызываем написанную ранее функцию расчета вспомогательных параметров CalcParams, передавая ей координаты блока, его размеры и масштаб подсистемы, взятые из структуры RDS_DRAWDATA по указателю DrawData.

Теперь мы можем пользоваться полями класса, которые заполнила функция CalcParams. В цикле по всем точкам матрицы для каждой точки i мы вычисляем экранные координаты (ix,iy) по ее вещественным координатам (A[i][0],A[i][1]), рисуем круг радиусом point_r с центром в точке (ix,iy), и, если это не самая первая точка матрицы (то есть если i не равно нулю), соединяем эту точку с предыдущей вызовом rdsXGLineTo. Для самой первой точки вместо rdsXGLineTo вызывается функция rdsXGMoveTo, которая просто устанавливает координаты точки для последующих вызовов rdsXGLineTo.

Программа рисования написана – теперь введем в модель реакцию на вывод подсказки. Для этого раскроем на вкладке «события» раздел «разное» и дважды щелкнем на подразделе «всплывающая подсказка (RDS_BFM_POPUPHINT)» (см. рис. 440). В правой части окна появится пустая вкладка «подсказка», на которой мы введем следующий текст:

      // Индекс найденной точки
      int index=-1;
      // Экранные координаты найденной точки
      int ix,iy;

      if(A.Cols()<2 || A.Rows()<1) // Меньше двух столбцов или нет строк
        return;

      // Вычисляем вспомогательные параметры
      CalcParams(HintData->Left,HintData->Top,
                 HintData->Width,HintData->Height,
                 HintData->DoubleZoom);

      // Ищем попадание в круг точки
      for(int i=0;i<A.Rows();i++)
        { // Вычисляем оконные координаты точки i
          ix=draw_x1+(A[i][0]-xmin)*(draw_x2-draw_x1)/(xmax-xmin);
          iy=draw_y2-(A[i][1]-ymin)*(draw_y2-draw_y1)/(ymax-ymin);
          if(fabs(HintData->x-ix)<=point_r &&
             fabs(HintData->y-iy)<=point_r) // Попали
            { index=i;
              break;
            }
        }

      if(index>=0) // Нашли точку
        { char *hint,*val_x,*val_y,*val_n;
          // Левый верхний угол зоны подсказки
          HintData->HZLeft=ix-point_r;
          HintData->HZTop=iy-point_r;
          // Размеры зоны подсказки
          HintData->HZWidth=HintData->HZHeight=point_r*2;
          // Задержка гашения подсказки – одна минута
          HintData->HideTimeout=60000;
          //  текста подсказки
          val_n=rdsItoA(index,10,0);           // номер точки
          val_x=rdsDtoA(A[index][0],-1,FALSE); // x
          val_y=rdsDtoA(A[index][1],-1,FALSE); // y
          hint=rdsDynStrCat("Точка: ",val_n,FALSE);
          rdsAddToDynStr(&hint,"\nX: ",FALSE);
          rdsAddToDynStr(&hint,val_x,FALSE);
          rdsAddToDynStr(&hint,"\nY: ",FALSE);
          rdsAddToDynStr(&hint,val_y,FALSE);
          // Передача текста в RDS
          rdsSetHintText(hint);
          // Освобождение динамических строк
          rdsFree(hint);
          rdsFree(val_x);
          rdsFree(val_y);
          rdsFree(val_n);
        }

В начале этого текста описаны вспомогательные целые переменные index, ix и iy – в них будут занесены номер точки (строки матрицы) под курсором мыши и ее координаты в окне подсистемы соответственно. Переменная index инициализирована значением −1: если в процессе перебора точек матрицы ни одна из них не окажется близко к курсору мыши, значение index останется отрицательным, и это будет признаком того, что курсор не над точкой матрицы и подсказку выводить не нужно.

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

Теперь, когда диапазоны координат матрицы и границы области рисования внутри блока вычислены, мы можем проверить, попал ли курсор мыши в одну из точек матрицы A. Для этого мы перебираем все ее точки и вычисляем для каждой экранные координаты (ix,iy) по вещественным координатам (A[i][0],A[i][1]) при помощи тех же самых формул, которые использовались в программе рисования. Если расстояния между координатами курсора мыши (HintData->x,HintData->y) и точкой матрицы (ix,iy) и по горизонтали, и по вертикали не больше радиуса круга точки point_r, значит, курсор попал внутрь этого круга (точнее, внутрь квадрата, в который вписан этот круг, но для проверки это не принципиально). В этом случае мы присваиваем переменной index номер найденной точки и прерываем цикл перебора точек оператором break. В переменных ix и iy при этом останутся координаты этой точки на экране.

После выполнения цикла перебора в переменной index останется начальное значение −1, если курсор мыши не оказался в пределах круга ни одной из точек. Если же курсор попал в один из кругов, значение index будет большим или равным нулю, и нам нужно вывести подсказку, сформировав текст из номера точки (index) и двух ее координат (A[index][0] и A[index][1]). Кроме того, нам нужно ограничить зону действия подсказки данной точкой, чтобы при выходе курсора за ее круг и попадании его в круг другой точки подсказка вывелась бы снова, уже с другими координатами. Этим мы и займемся.

Сначала зададим левый верхний угол зоны действия подсказки – он будет находиться левее и выше самой точки (ix,iy) на радиус ее круга point_r. В поля HintData->HZLeft и HintData->HZTop мы записываем ix-point_r и iy-point_r соответственно. Ширина и высота зоны должны быть равны диаметру, то есть двум радиусам, круга, поэтому в поля HintData->HZWidth и HintData->HZHeight мы записываем значение point_r*2. Теперь зона действия подсказки установлена. В нашей подсказке будет три числа, и, чтобы пользователь гарантированно успел ее прочесть, увеличим время ее показа до одной минуты: в поле HintData->HideTimeout мы записываем 60000 (шестьдесят тысяч миллисекунд – это одна минута).

Теперь нужно сформировать текст. Можно воспользоваться для этого стандартной функцией sprintf, заранее отведя под формируемую строку буфер заведомо большего размера, но мы будем использовать функции RDS, работающие с динамическими строками. Сначала мы формируем динамические строки из целого номера точки при помощи функции rdsItoA. У этой функции три параметра: преобразуемое число, система счисления (мы передаем 10, то есть используем десятичную систему) и минимальное число разрядов в числе (нам не нужно дополнять число ведущими нулями до заданного количества разрядов, поэтому мы передаем ноль). Указатель на динамическую строку, который возвращает функция, мы записываем во вспомогательную переменную val_n, потом эту строку обязательно нужно будет освободить вызовом rdsFree. Координаты точки мы преобразуем в строки при помощи функции rdsDtoA, которая уже встречалась нам ранее. Динамические строки с символьным представлением этих координат записываются во вспомогательные переменные val_x и val_y, их тоже нужно будет освободить функцией rdsFree. Далее начинается формирование самого текста подсказки: прежде всего мы объединяем строку «Точка:» и строку val_n с номером точки в новую динамическую строку hint при помощи функции rdsDynStrCat (мы уже использовали ее выше). Затем мы по очереди добавляем в конец к этой динамической строке строки « X:», val_x, « Y:» и val_y (символы «\n», как всегда в C, означают перевод строки – текст всплывающей подсказки может состоять из нескольких строк). Добавление осуществляется при помощи функции rdsAddToDynStr:

      rdsAddToDynStr(&hint, // Куда добавляем
                     "\nX: ",  // Что добавляем
                     FALSE);   // Заменять пустую строку на NULL?

В первом параметре этой функции передается указатель на переменную, в которой хранится указатель на динамическую строку, к которой дописывается указанный текст. В данном случае мы передаем &hint, то есть, после последнего вызова rdsAddToDynStr, в hint будет находиться указатель на полностью сформированный текст подсказки. Этот текст мы передаем в RDS вызовом rdsSetHintText, после чего освобождаем более не нужные нам динамические строки hint, val_n, val_x и val_y при помощи rdsFree.

Теперь в нашей модели есть все необходимые реакции, но нужно еще переключить сам блок на программное рисование и разрешить ему вывод всплывающих подсказок. Проще всего сделать это в окне параметров блока (см. рис. 417 и рис. 441), но можно также вызвать групповую установку параметров блоков из редактора модели и включить рисование и подсказки там.

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

Рис. 447. Тестирование блока с разными подсказками к разным точкам изображения