Г.Тяпичев
«Начальный курс быстрого программирования на СИ++». Глава 8.
Глава 8. Проекты
сложных программ
Создаем проект приложения WPSK1
Инициализационный файл Wpsk1.ini
Устанавливаем на форму компоненты
Как заполнить файл proba1.cpp
и доработать проект
Как заполнить файл main.cpp
в проекте
В этом разделе предлагается создать проект приложения,
предназначенного для проведения двухсторонней любительской радиосвязи видом
связи PSK31.
PSK31 является сравнительно новым
видом цифровой радиосвязи и пользуется достаточно большой популярностью у
радиолюбителей – коротковолновиков. Идею этого вида связи впервые предложил
польский радиолюбитель Pawel Jalocha (SP9VRC), а основную
разработку выполнил англичанин Peter Martinez (G3PLX). Это было в
конце 90-х годов прошлого столетия.
Большой популярностью среди
радиолюбителей пользуются программы для PSK31, разработанные американским радиолюбителем Moe Weatley (AE4JY), который создал очень удачный вариант подключаемой
библиотеки PSK31Core.dll. На базе этой
библиотеки многие другие радиолюбители стали создавать свои программы для PSK31. Рассматриваемый в этой главе мой вариант также
использует библиотеку PSK31Core.dll. Исходным материалом для меня послужила программа,
которую разработал англичанин Julian U Moss (G4ILO). Исходные коды его программы PSK31TEST можно
скачать в Интернете на моем сайте
Следует помнить,
что предлагаемый проект представляет собой сильно упрощенный вариант
действующей программы, вариант созданный специально для учебных целей. В этом
проекте я старался показать те особенности, которые являются специфическими для
всех радиолюбительских программ, предназначенных для проведения любительских
радиосвязей. Всё это делалось в надежде на тот случай, если книга попадет в
руки человека, пожелающего создать свою
программу для радиосвязи.
Последний вариант
библиотеки PSK31Core.dll можно
скачать в Интернете по адресу: http://www.qsl.net/ae4jy/ .
Создание проекта приложения
начнем, как обычно, с создания соответствующей директории, в которой будет
находиться весь комплект разрабатываемой документации. Например, D:\WPSK1\. Затем создадим
начальный вариант проекта, в котором файл с исходными кодами обзовем как proba1.cpp, а файл
проекта будет иметь название Wpsk1.bpr. Название файла proba1.cpp, по
моему мнению, должно подчеркнуть учебный и экспериментальный характер этого
проекта.
Практически во
всех программах для радиосвязи имеется специальный файл, в котором содержатся
все настроечные параметры программы, как, например, параметры частоты, скорости
передачи символов, позывные владельца и корреспондентов, их имена и т.д.. Такой файл должен создавать сам разработчик, C++Builder помогать не будет.
Файл, в котором сохраняются
настроечные характеристики программы носит название «конфигурационный файл» или
«инициализационный файл». В C++Builder придерживаются
второго варианта и мы также создадим текстовый файл Wpsk1.ini.
Напоминаю, что этот файл должен создаваться в простом текстовом редакторе типа NOTEPAD или в редакторе C++Builder.
В листинге 8.1 приведен
текст созданного мною инициализационного файла. Построение файла носит вполне
определенный характер, и C++Builder имеет
в своем составе необходимые инструменты для работы с подобными файлами,
имеющими расширение .ini.
Инициализационный файл
состоит из отдельных секций, при этом каждая секция имеет свой заголовок,
заключенный в квадратные скобки. Перечислим эти заголовки:
q
[Settings] – в этой секции содержатся различные установки общего
значения. Первым устанавливается текст заголовка, который будет находиться на
главном окне. Затем вводится позывной собственной радиостанции, и т.д..
q
[Display] – в этой секции устанавливается значение центральной
частоты.
q
TX Control] –
назначается COM-порт, который будет управлять
радиостанцией.
q
[Macros] – перечисляются кодовые выражения, которые в текстах
будут заменяться определенными значениями.
q
[Macro 1] … [Macro 10] –
приведены тексты, предназначенные для использования их при проведении
радиосвязей. По замыслу разработчиков PSK31,
этот вид связи предназначался для работы на передачу только с клавиатуры
компьютера. Но не все могут достаточно быстро печатать на клавиатуре, поэтому
часто применяются заранее подготовленные тексты, которые вставляются в передачу
по мере необходимости.
По этим макросам нужно иметь в виду
следующее. Каждый из макросов имеет строчки, начинающиеся со слов Caption и Hint. Эти строчки имеют смысл только тогда, когда каждый
из десяти макросов запускается кнопкой типа SpeedButton. При этом строка, начинающаяся с Caption содержит текст, который будет размещен на самой
кнопке, а строка, начинающаяся с Hint будет появляться в виде подсказки при наведении
курсора на эту клавишу.
Мною в этом
проекте для управления макросами используется компонент ComboBox, для которого эти строчки
совершенно не нужны. Но я не стал их
убирать в надежде на то, что кто-то из читателей решит использовать для
управления макросами кнопки. Так что буду надеяться на то, что эти строчки
пригодятся.
Листинг
8.1. Файл Wpsk1.ini
# Установка
конфигурации программы Wpsk1 C++Builder Application.
# Любой текст
после символа ‘#’ считается
комментариями.
# После
редактирования этого файла следует перезагрузить программу.
[Settings]
# Строка ниже
установит текст в области заголовка формы
Caption=WPSK1 +
Core.DLL C++Builder Application – RA3XB
-
# На следующей
строке введите Ваш callsign (позывной)
MyCall=RA3XB
# Строка ниже
устанавливает показ UTC времени в строке состояния
ShowUTCTime=1
#Редактируйте и
включите строку ниже, если нужно определить другой
# формат даты/времени
# DateFormat=
# Ниже
устанавливается скорость передачи некоторого текста телеграфом
# CWIDSpeed=1 (35.5wpm),
2 (18.75wpm), 3 (12.5wpm), 4 (9.375wpm)
CWIDSpeed=2
#
Устанавливается текст для передачи телеграфом как CW ID
CWID=de RA3XB
# AFC диапазон частоты (0= нет AFC)
AFCRange=10
[Display]
# Этот раздел
устанавливает задания для дисплея
# CenterFreq устанавливает частоту центра дисплеев водопада и specrum,
# величина которого не должна выходить из
диапазона 512 – 2048 Герц
CenterFreq=1000
[TX Control]
# Значение ComPort устанавливает COM-порт (1
… 4), который будет
# включать радиостанцию на передачу
ComPort=1
# Control должен иметь одно из следующих значений:
# 0 (нет
включения), 1 (используется RTS), 2 (для DTR), 3 (для RTS и DTR)
Control=1
[Macros]
# Следующие
разделы определяют тексты, загруженные в макро кнопки,
# пронумерованные от 1 до 10 горизонтально в
двух строках на форме #программы.
# Caption= надпись на кнопке (только для варианта с кнопками).
# Hint= появляющееся описание-подсказка (только для варианта с
кнопками).
# Stroka= текст непосредственно макросообщения
#
# Макрокоманды
могут включать маркеры, которые начинаются с символа '%'.
# Маркер
заменяется во время выполнения следующими строками:
# %c - позывной другой
станции, который введен в окно редактора CALL
# %d - Дата и время в
формате Windows Short Date
# %g - приветствие на
английском, в соответствии с временем суток (напр. # "Good morning")
# %h - имя
корреспондента, введенное в окно редактора NAME
# %i - выдать
телеграфный сигнал CWID при скорости CWIDSpeed
# %m - Ваш позывной,
записанный как MyCall
# %n - переход в
начало новой строки
# %r - RST для корреспондента, введенное в окне редактора
# %t - включается
немедленная передача (TX ON)
# %w - очищается окно
терминала
# %z - выключает
передачу (TX OFF)
[Macro 1]
Caption=Call CQ
Hint=Call CQ
Stroka=%t%w CQ CQ CQ de
%m %m %m %n CQ CQ CQ de %m %m %m %n pse kkk%i%z
[Macro 2]
Caption=Init over
Hint=Initial over with
greeting and details
Stroka=%c %c de %m %m %n
%g OM %h and thanks for the call.%n Your report is %r %r.%n My name is Gennady
Gennady.%n My QTH is Ludinowo nr Kaluga, RUSSIA.%n pse hw? %c de %m k %z
[Macro 3]
Caption=Eqpt details
Hint=Equipment details
Stroka=The equipment here
is:%n
[Macro 4]
Caption=
Hint=
Stroka=%t%n%c %c de %m %m
[Macro 5]
Caption=End over
Hint=Hand it back
Stroka=%nNow btu. %c %c
de %m %m pse kkk %n%z
[Macro 6]
Caption=Call stn
Hint=Call a station
Stroka=%t%n %c %c de %m
%m %m pse kkk%n%z
[Macro 7]
Caption=Start over
Hint=Start over
Stroka=%n%c %c de %m %m%n
[Macro 8]
Caption=Net details
Hint=Internet details
Stroka=%t%n QRZ QRZ QRZ
de %m %m pse k %i%z
[Macro 9]
Caption=
Hint=
Stroka=t%n %c %c de %m %m
%m pse kkk%n%z
[Macro 10]
Caption=Final end
Hint=End final over
Stroka=Thank you %h for a
nice QSO. My best wishes to you and your family.%n If you wish, please QSL via
the buro. 73 and all the best.%n%c de %m sk sk%n%z
[Persistent]
# Записанные
здесь параметры настройки сохраняются при закрытии программы
Squelch_Level=18
AFC=1
Net=1
//-----------------------------------------------------------------------
[Persistent] – заголовок последней секции, в которой записаны
параметры, которые должны сохраняться при закрытии программы.
Если вы решите на базе
этого проекта создать программу для практической работы в эфире, то нужно будет
создать также и свои макросы, которые будут вам наиболее удобны.
На рис. 8.1
представлен предложенный мною вариант установки компонентов на форму проекта.
Рис. 8.1. Форма проекта
Чтобы лучше разобраться с
тем вопросом, какие компоненты находятся на форме, я предлагаю вам
проанализировать файл proba1.h,
размещенный в листинге 8.2.
В начале файла, как обычно,
подключаются заголовочные файлы. Некоторые из них пришлось дописывать
разработчику.
Затем, начиная со строки const UM_DATARDY = (WM_USER + 1000); происходит объявление констант собственных сообщений
разработчика, использованных в проекте. Дело в том, что все управление окнами в
Windows
происходит на основании сообщений. Каждое из сообщений
имеет свой определенный номер, который является константой. Все номера
сообщений до определенной величины WM-USER
использует собственно Windows, а нам, разработчикам,
разрешено использовать номера констант начиная с WM-USER +
0 и выше. В данном случае библиотека DLL требует использовать константы WM-USER +
1000 и выше.
Дело в том, что
тема о сообщениях Windows и их обработке очень обширная и совсем не вписывается в книгу для
начинающих программистов. Эту тему нужно прорабатывать каждому самому, по мере
накопления опыта программирования для Windows. Поэтому я не буду больше касаться
этой темы, чтобы книга не вышла за рамки задуманного. Нельзя (в одной книжке)
объять необъятное!
В этом проекте
задействовано пять собственных констант, относящихся к функциям обработки сообщений.
Сами эти функции объявлены в разделе protected класса
TForm1. Объявление
первой из этих функций выглядит следующим образом:
void __fastcall
TForm1::FFTDataReady(TMessage& msg);
Перед этими объявлениями
(выше) выполнены объявления публичных функций нашего основного класса. Эти
объявления начинаются со строки:
void __fastcall ComboBox1Change(TObject *Sender);
В этих объявлениях нет
никаких особенностей, поэтому останавливаться на вопросе объявления функций нет
необходимости.
Обратимся к строке:
BEGIN_MESSAGE_MAP
Начиная с этой строки
выполняется создание карты Windows
сообщений, созданных разработчиком,
вернее сказать, навязанных разработчику проекта разработчиком DLL библиотеки.
В каждой из таких строк карты записан заголовок сообщения, снабженный
несколькими аргументами, представленными в круглых скобках. Последним из этих
аргументов является название функции, которая должна обрабатывать это
сообщение.
Заканчивается эта карта,
которая представляет собой определенный макрос, принадлежащий к системе Windows, следующей строкой:
END_MESSAGE_MAP(TComponent);
Все строки, перечисленные
мною выше и относящиеся к сообщениям и их обработке, должен вносить в этот файл
разработчик.
На этом файл proba1.h заканчивается.
Но работа с ним должна быть продолжена. Вам нужно для каждого из компонентов,
изображенных на форме (рис. 8.1), найти строку с объявлением этого
компонента. Подобная процедура не только поможет вам разобраться в сути файла proba1.h, но и избавит
от неправильного выбора компонента при собственной разработке.
Листинг
8.2. Файл
proba1.h
//---------------------------------------------------------------------
#ifndef Proba1H
#define Proba1H
//---------------------------------------------------------------------
#include <Classes.hpp>
#include
<Controls.hpp>
#include
<StdCtrls.hpp>
#include
<Forms.hpp>
#include
<Buttons.hpp>
#include
<ComCtrls.hpp>
#include
<ExtCtrls.hpp>
#include
<windows.h>
#include
<SysUtils.hpp>
#include
<Menus.hpp>
#include
"CSPIN.h"
#include
<vcl/messages.hpp>
#include
<windowsx.h>
#include
<vcl/classes.hpp>
#include
<winbase.h>
#include
<vcl/sysmac.h>
const UM_DATARDY =
(WM_USER + 1000);
const UM_CHARREADY =
(WM_USER + 1001);
const UM_STATUSCHANGE =
(WM_USER + 1002);
const UM_IMDRDY =
(WM_USER + 1003);
const UM_CLKERR =
(WM_USER + 1004);
unsigned int STANlasttime = 0;
//----------------------------------------------------------------------
class TForm1 : public
TForm
{
__published: // IDE-managed Components
TButton *Kclear;
TEdit *CallKor;
TEdit *NameKor;
TEdit *RSTKor;
TSpeedButton *TuneBtn;
TSpeedButton *TxBtn;
TStaticText *Kname;
TStaticText *Krst;
TRichEdit *Terminal;
TRichEdit *TxBuf;
TStaticText *Kcall;
TComboBox *ComboBox1;
TLabel *Label1;
TTimer *Timer1;
TRadioGroup *Mode;
TRadioButton *BPSK;
TRadioButton *QPSK;
TStatusBar *StatusBar1;
TLabel *IMD;
TLabel *Label2;
TPanel *Panel1;
TPaintBox *SpectrumDisplay;
TPanel *Panel2;
TPaintBox *WaterfallDisplay;
TButton *Button1;
TLabel *Label3;
TCSpinEdit *TxFreq;
void __fastcall ComboBox1Change(TObject
*Sender);
void __fastcall ModeClick(TObject
*Sender);
void __fastcall CallKorChange(TObject
*Sender);
void __fastcall NameKorChange(TObject
*Sender);
void __fastcall RSTKorChange(TObject
*Sender);
void __fastcall KclearClick(TObject
*Sender);
void __fastcall TxBtnClick(TObject
*Sender);
void __fastcall TuneBtnClick(TObject
*Sender);
void __fastcall TxBufKeyPress(TObject
*Sender, char &Key);
void __fastcall Timer1Timer(TObject
*Sender);
void __fastcall
SpectrumDisplayPaint(TObject *Sender);
void __fastcall
WaterfallDisplayPaint(TObject *Sender);
void __fastcall
WaterfallDisplayMouseUp(TObject *Sender,
TMouseButton Button, TShiftState
Shift, int X, int Y);
void __fastcall Button1Click(TObject
*Sender);
void __fastcall TxFreqChange(TObject
*Sender);
private: // User declarations
public: // User declarations
__fastcall TForm1(TComponent* Owner);
int __fastcall TForm1::TXMacros(String);
protected:
void __fastcall
TForm1::FFTDataReady(TMessage& msg);
void __fastcall
TForm1::CharReady(TMessage& msg);
void __fastcall TForm1::IMDReady(TMessage&
msg);
void __fastcall TForm1::ClkErr(TMessage&
msg);
void __fastcall
TForm1::StatusChange(TMessage& msg);
BEGIN_MESSAGE_MAP
MESSAGE_HANDLER(UM_DATARDY,TMessage,FFTDataReady);
MESSAGE_HANDLER(UM_CHARREADY,TMessage,CharReady);
MESSAGE_HANDLER(UM_STATUSCHANGE,TMessage,IMDReady);
MESSAGE_HANDLER(UM_IMDRDY,TMessage,ClkErr);
MESSAGE_HANDLER(UM_CLKERR,TMessage,StatusChange);
END_MESSAGE_MAP(TComponent);
};
//----------------------------------------------------------------------
extern PACKAGE TForm1
*Form1;
//----------------------------------------------------------------------
#endif
//----------------------------------------------------------------------
Настоятельно рекомендую
просмотреть в учебнике по C++ тему об
объявлении классов. Это поможет вам более уверенно чувствовать себя при работе
над этим проектом.
Теперь обратимся
непосредственно к заполненной компонентами форме. Практически все примененные
компоненты вам уже знакомы, за очень малым исключением.
q
В левом нижнем
углу формы (см. рис. 8.1) располагается компонент для расчета величины
рабочей частоты. Компонент носит название TCSpinEdit и располагается в библиотеке компонентов Samples.
Практически, здесь могут быть применены и другие компоненты.
q
Чуть выше
располагается компонент RadioGroup. Это очень простой
компонент, материал о нем следует почитать в главе 1.
q
В качестве
многострочных окон редактирования применены не известные вам компоненты Memo, а компоненты RichEdit. Такой замены можно было бы и не делать, потому что
никакие специфические свойства этих окон в данном проекте не задействованы.
Дело в том, что просто они остались от здесь от другого проекта, в котором были
задействованы их цветовые возможности.
q
Для рисования
изображений применен компонент PaintDox, расположенный в библиотеке компонентов System.
Посмотреть свойства этого компонента можно в Приложении 2 и Приложении 3.
Итак, с установкой компонентов на форму
разобрались. Нужно приступать к созданию файла с исходными кодами proba1.cpp.
В листинге 8.3 размещен
текст полностью готового файла proba1.cpp. Функции этого файла непременно пригодятся вам при
создании собственного аналогичного приложения.
Листинг
8.3. Файл
proba1.cpp
//-------------------------------------------------------------------
#include <vcl.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
#include
<sys/timeb.h>
#include
<Classes.hpp>
#pragma hdrstop
#include
"inifiles.hpp"
#include
"Proba1.h"
//--------------------------------------------------------------------
#pragma
package(smart_init)
#pragma link "CSPIN"
#pragma resource
"*.dfm"
TForm1 *Form1;
MSG msg;
typedef int
TFFTData[1024];
typedef TFFTData
*PFFTData;
typedef int
TVectorData[16];
typedef TVectorData
*PVectorData;
typedef int TPeakData[5];
typedef TPeakData
*PPeakData;
typedef int
TSyncData[16];
typedef TSyncData
*PSyncData;
typedef int
TRawData[2048];
typedef TRawData *PRawData;
//---------------------------------------------------------------------
В конце листинга имеется
несколько строчек, начинающихся с оператора typedef. Этот оператор является специальным средством в языке
Си, которое позволяет назначать имена новым типам данных. Все указанные в этих
строчках имена используются в качестве аргументов в импортируемых из библиотеки
функциях. Не допускаются какие либо корректировки и изменения.
Следующий листинг 8.4
начинается с объявления импортируемых функций. Вы, вероятно, уже заметили
отсутствие записи __declspec(dllimport)
в этих строках Дело в том, что используемая нами в этом проекте DLL библиотека
создана компилятором Microsoft Visual C++, который имеет в данном случае свои требования,
которых следует придерживаться.
Далее идут обычные
объявления различных переменных величин, задействованных в проекте программы.
Обратите внимание на объявление структуры
struct Macro{}. Это первая
встреча со структурой и следует почитать о структурах в Приложении 4.
Листинг
8.4. Продолжение 1 файла proba1.cpp
//---------------------------------------------------------------------
extern "C" int
__stdcall fnStartSoundCard(HWND h_Wnd, int cardnum, int numRXchannels);
extern "C" void
__stdcall fnStopSoundCard(void);
extern "C" void
__stdcall fnSetRXFrequency(int freq, int range, int channel);
extern "C" void
__stdcall fnSetRXPSKMode(int mode, int chan);
extern "C" int
__stdcall fnGetRXFrequency(int channel);
extern "C" void
__stdcall fnSetFFTMode(int ave, int maxscale, int mode);
extern "C" bool
__stdcall fnGetFFTData(PFFTData DataArray, int startpos, int endpos);
extern "C" void
__stdcall fnGetFFTPeaks(PPeakData PeakArray, int startpos, int endpos);
extern "C" void
__stdcall fnGetSyncData(PSyncData SyncArray, int channel);
extern "C" void
__stdcall fnGetVectorData(PVectorData VectorArray, int channel);
extern "C" int
__stdcall fnGetRawData(PRawData DataArray, int startpos, int endpos);
extern "C" void
__stdcall fnSetAFCLimit(int limit, int channel);
extern "C" void
__stdcall fnSetSquelchThreshold(int thresh, int mode, int channel);
extern "C" int
__stdcall fnGetSignalLevel(int channel);
extern "C" void
__stdcall fnStartTX(int mode);
extern "C" void
__stdcall fnStopTX(void);
extern "C" void
__stdcall fnAbortTX(void);
extern "C" void
__stdcall fnSetTXFrequency(int freq);
extern "C" void
__stdcall fnSetCWIDString(char * lpszIDStrg);
extern "C" int
__stdcall fnSendTXCharacter(int txchar, int cntrl);
extern "C" void
__stdcall fnSendTXString(char * lpszTXStrg);
extern "C" int
__stdcall fnGetTXCharsRemaining(void);
extern "C" void
__stdcall fnClearTXBuffer(void);
extern "C" void
__stdcall fnSetCWIDSpeed(int speed);
extern "C" bool
__stdcall fnSetComPort(int portnum, int mode);
extern "C" void
__stdcall fnSetClockErrorAdjustment(int ppm);
extern "C" int
__stdcall fnGetDLLVersion(void);
void LoadMacros();
struct Macro
{
String Caption;
String Hint;
String Stroka;
} Mk[10];
TRichEdit *TxBuf;
TRichEdit *Terminal;
TSpeedButton *TuneBtn;
TSpeedButton *TxBtn;
TFFTData FFTData;
TTimer *Timer1;
FILE *F;
Graphics::TBitmap *WaterfallBitmap;
TPaintBox *SpectrumDisplay;
TPaintBox *WaterfallDisplay;
TIniFile *Ini;
String sFile;
String mycall;
String korcall = "UA3XBI";
String korname = "Gena";
String korqth, korrst = "589";
String cwid;
String Caption;
String s, dtformat;
char buf[515];
int Mode;
int CenterFreq;
int ShowUTCTime, CWIDSpeed, AFCRange, ComPort,
Control,AFC,Net, Squelch_Level;
int centerfreq, RxFrequency, TxFrequency,
FFTMax, FFTScale, FFTBandwidth;
int WFMin,WFMax,WFBandwidth,WFOffset;
int afcrange, netrange;
bool SignalOverload;
//---------------------------------------------------------------------
Обратите
внимание на то, что некоторым переменным заранее даны какие то имена и числовые
значения. Это сделано чисто в учебных целях, чтобы соответствующие окна
редактирования были заполнены.
Листинг 8.5 начинается с
функции описания формы. В теле этой функции выполняются все действия,
которые могут использоваться в пределах
класса формы TForm1. Начинается
заполнения этой функции чтением данных из файла инициализации и сохранения этих
данных в памяти. Первым действием является создание переменной sFile,
которая обозначает как бы вспомогательное имя инициализационного файла.
Настоящим именем этого файла, которое будет работать дальше, является Ini. Все данные, записанные нами в инициализационном
файле, будут далее считываться именно из файла Ini. Например, посмотрите строку:
mycall = Ini->ReadString("Settings", "MyCall",
" ");
Точно таким же образом
считываются из инициализационного файла все необходимые данные. По мере
необходимости, из этих данных формируются другие переменные величины.
Листинг
8.5. Продолжение 2 файла proba1.cpp
//----------------------------------------------------------------------
__fastcall
TForm1::TForm1(TComponent* Owner)
// 1
: TForm(Owner)
{
// Чтение
данных из файла инициализации
sFile
= ChangeFileExt(Application->ExeName, +".ini");
if(FileExists(sFile)) Ini = new
TIniFile(sFile);
mycall =
Ini->ReadString("Settings", "MyCall", " ");
cwid =
Ini->ReadString("Settings", "CWID", " ");
Caption =
Ini->ReadString("Settings", "Caption", " ");
ShowUTCTime =
StrToInt(Ini->ReadString("Settings", "ShowUTCTime",
" "));
CWIDSpeed =
StrToInt(Ini->ReadString("Settings", "CWIDSpeed", "
"));
AFCRange =
StrToInt(Ini->ReadString("Settings","AFCRange", "
"));
CenterFreq =
StrToInt(Ini->ReadString("Display", "CenterFreq", "
"));
//
Устанавливаем величины частот приема и передачи
RxFrequency = CenterFreq;
TxFrequency = CenterFreq;
// Устанавливаем FFT параметры
FFTMax = (CenterFreq * 2048 / 4000)- 1;
if (FFTMax > 1023) FFTMax = 1023;
FFTScale = 100;
FFTBandwidth = (FFTMax + 1)* 8000 / 2048;
// Установка порта для включения передатчика и
проч. параметров
ComPort = StrToInt(Ini->ReadString("TX Control",
"ComPort", " "));
Control =
StrToInt(Ini->ReadString("TX Control", "Control", "
"));
if(Control != 0)
{ if(!fnSetComPort(ComPort, Control))
Application->MessageBoxA("COM-порт
выбран не верно!","Ошибка!",MB_OK);
}
AFC =
StrToInt(Ini->ReadString("Parsistent", "AFC", 1));
Terminal->Lines->Add(mycall);
Net =
StrToInt(Ini->ReadString("Parsistent", "Net", 1));
// Запишем версию
подключаемой библиотеки
s = IntToStr(fnGetDLLVersion());
StatusBar1->Panels->Items[3]->Text =
" PSKCORE.DLL ver. "+s;
// Установим параметры дисплея для Waterfall
WFMin
= (FFTMax / 2) - WaterfallDisplay->Width / 2;
WFMax = (FFTMax / 2) +
(WaterfallDisplay->Width / 2) -1;
WFBandwidth = (WFMax - WFMin)*8000/2048;
WFOffset = WFMin*8000/2048;
//--------------------------------------------------------------------
На этом считывание данных
закончилось и начинается подготовка к работе с DLL. Сначала очищаются от вспомогательных текстов окна Terminal и TxBuf, формируются в памяти тексты макросов.
Основная подготовка к
работе начинается с открытия DLL. Если
процесс открытия прошел без ошибок (err == 0), то в DLL передаются
и там устанавливаются все необходимые данные, в том числе и полученные из
инициализационного файла. Если процесс открытия не состоялся, то переключателем
switch(err) посредством диалогового
окна сообщается причина ошибки.
Листинг
8.6. Продолжение 3 файла proba1.cpp
//---------------------------------------------------------------------
Terminal->Clear();
TxBuf->Clear();
LoadMacros();
//
Инициализация и подготовка к работе
int err;
String errstr;
err = fnStartSoundCard(Handle,-1,1);
if(err == 0)
{
fnSetFFTMode(1,FFTScale,1);
fnSetRXPSKMode(Mode->ItemIndex,0);
fnSetAFCLimit(afcrange,0);
fnSetSquelchThreshold(15,1,0);
fnSetRXFrequency(RxFrequency,0,0);
fnSetCWIDSpeed(CWIDSpeed);
fnSetCWIDString(cwid.c_str());
}
else
{
switch(err)
{
case 10: errstr = "Ошибка распределения памяти"; break;
case 11: errstr = "Не открывается звуковая
карта"; break;
case 12: errstr = "Звуковая карта не обнаружена"; break;
case 13: errstr = "Звуковая карта уже в работе"; break;
default:
errstr =
"Неизвестная ошибка, код "+IntToStr(err);
}
Application->MessageBoxA(errstr.c_str(),
"Ошибка!", MB_OK);
}
StatusBar1->Panels->Items[2]->Text =
FormatDateTime(" hh:nn:ss",Now());
}
//-------------------------------------------------------------------
// Функция
заполняет в памяти структуру, в которой хранятся макросы
void
LoadMacros() // 2
{
int i;
String n;
for(i=1;i<=10;i++)
{
n = IntToStr(i);
Mk[i].Caption = Ini->ReadString("Macro
"+n,"Caption"," ");
Mk[i].Hint = Ini->ReadString("Macro
"+n,"Hint"," ");
Mk[i].Stroka = Ini->ReadString("Macro
"+n,"Stroka","");
}
}
//--------------------------------------------------------------------
// Функция
отыскивает в текстах макросов условные обозначения и заменяет // их необходимыми данными или выполняет какое –
то действие
int
__fastcall TForm1::TXMacros(String s1) // 3
{
int p, p0=1;
String s2,s3,g;
String NewString;
//
----------------------------------------------------------------
// процесс
декодирования условных обозначений
s2 = "%m"; //
"%m" - собственный позывной
p = s1.Pos(s2);
if(p>0)
{
s3 = mycall+" ";
if(mycall == "")
{InputQuery("InputBox","Введите
собственный позывной",NewString);
mycall = NewString;
}
p = s1.Pos(s2);
do
{
s1.Delete(p,s2.Length());
s1.Insert(s3,p);
p0 = p + s3.Length();
p = p0 -1 + s1.SubString(p0,255).Pos(s2);
}
while(p > p0);
}
//
------------------------------------------------------------------
s2 = "%h"; //
"%h" - имя корреспондента
p = s1.Pos(s2);
if(p>0)
{
s3 = korname+" ";
if(korrst == "")
{InputQuery("InputBox","Введите
имя коррреспондента",NewString);
korname = NewString;
}
p = s1.Pos(s2);
do
{
s1.Delete(p,s2.Length());
s1.Insert(s3,p);
p0 = p + s3.Length();
p = p0 -1 + s1.SubString(p0,255).Pos(s2);
}
while(p > p0);
}
//
--------------------------------------------------------------------
s2 = "%r"; //
"%r" - RST для корреспондента
p = s1.Pos(s2);
if(p>0)
{
s3 = korrst+" ";
if(korrst == "")
{InputQuery("InputBox","Введите
RST для коррреспондента",NewString);
korrst = NewString;
}
p = s1.Pos(s2);
do
{
s1.Delete(p,s2.Length());
s1.Insert(s3,p);
p0 = p + s3.Length();
p = p0 -1 + s1.SubString(p0,255).Pos(s2);
}
while(p > p0);
}
// -------------------------------------------------------------------
s2 = "%c"; //
"%c" - позывной корреспондента
p = s1.Pos(s2);
if(p>0)
{
s3 = korcall+" ";
if(korcall == "")
{InputQuery("InputBox","Введите
позывной корреспондента",NewString);
korcall = NewString;
}
p = s1.Pos(s2);
do
{
s1.Delete(p,s2.Length());
s1.Insert(s3,p);
p0 = p + s3.Length();
p = p0 -1 + s1.SubString(p0,255).Pos(s2);
}
while(p > p0);
}
//
--------------------------------------------------------------------
s2 = "%n"; // "%n" - перевод строки
p = s1.Pos(s2);
if(p>0)
{
do
{
s1[p] = 13; s1[p+1] = 32;
p0 = p;
p = p0 -1 + s1.SubString(p0,255).Pos(s2);
}
while(p > p0);
}
//
----------------------------------------------------------------
s2 = "%d"; //
"%d" - включить дату и время
p = s1.Pos(s2);
if(p>0)
{
s3 = FormatDateTime(dtformat, Now())+"
";
p = s1.Pos(s2);
do
{
s1.Delete(p,s2.Length());
s1.Insert(s3,p);
p0 = p + s3.Length();
p = p0 -1 + s1.SubString(p0,255).Pos(s2);
}
while(p > p0);
}
//------------------------------------------------------------------
s2 = "%g"; //
"%g" - приветствие на
английском
p = s1.Pos(s2);
if(p>0)
{
unsigned short Hour, Min, Sec, MSec;
DecodeTime(Now(), Hour, Min, Sec, MSec);
if(Hour > 2 && Hour <
12) s3 = "Good morning ";
if(Hour > 12 && Hour <
18) s3 = "Good afternoon ";
if(Hour > 18 && Hour <
24) s3 = "Good evening ";
p = s1.Pos(s2);
s1.Delete(p,s2.Length());
s1.Insert(s3,p);
}
//
-------------------------------------------------------------------
s2 = "%i"; // "%i" - включить передачу
CWID
p = s1.Pos(s2);
if(p>0)
{
s1[p] = 5; s1[p+1] = 32;
}
//--------------------------------------------------------------------
s2 = "%z"; // "%z" - закончить передачу
p = s1.Pos(s2);
if(p>0)
{
s1[p] = 3; s1[p+1] = 32;
}
//
------------------------------------------------------------------
s2 = "%w"; //
"%w" - очистить терминал - окно
RX
p = s1.Pos(s2);
if(p>0)
{
s1.Delete(p,s2.Length());
Terminal->Clear();
}
//---------------------------------------------------------------------
s2 = "%t"; // "%t" - начать передачу
p = s1.Pos(s2);
if(p>0)
{
s1.Delete(p,s2.Length());
TuneBtn->Down = false;
if(Net == 1)
{
TxFrequency = RxFrequency;
fnSetTXFrequency(TxFrequency);
TxBtn->Down = true;
fnStartTX(Mode->ItemIndex);
TxBtn->Font->Color = clRed;
}
}
//---------------------------------------
strcpy(buf,s1.c_str()); // копируем строку в спец. буфер
return(s1.Length()); // выдаем длину строки в буфере
}
//--------------------------------------------------------------------
Функция 3 получилась самой большой в этом файле.
Поиск условных обозначений (кодов), начинающихся с символа ‘%’, и последующая
замена этих обозначений заданными словами или значениями выполняется на основе
замечательных свойств строк типа AnsiString. Сначала, с
помощью определенных функций, в заданной строке отыскивается буквосочетание,
состоящее из двух символов, первым из которых является символ ‘%’. Запоминается
позиция этих символов. Затем кодовые символы заменяются необходимым текстом.
При этом на величину длины этой замены увеличивается число, составляющее общую
длину строки.
Если в строке макроса
встречается несколько кодовых выражений, то все они поочередно проходят преобразования. Все эти изменения
запоминаются, после чего обновленная строка записывается в буфер, из которого в
дальнейшем будет считываться на передачу.
Листинг 8.7 начинается с
функции выбора макроса. Для этой цели используется компонент ComboBox.
Свойство ItemIndex этого
компонента начинается с нуля, а нумерация макросов начинается с единицы.
Поэтому переменная величина i представляет
собой значение свойства ItemIndex плюс единица. Далее выбирается нужная строка
соответствующего макроса, функцией TXMacros(s1)
выполняется декодирование условных обозначений (функцией 3) и сохранение
обновленной строки в буфере. После этого строка s1 из буфера
копируется в окно TxBuf.
Листинг
8.7. Продолжение 4 файла proba1.cpp
//--------------------------------------------------------------------
// Функция для
выбора нужного макроса
void __fastcall TForm1::ComboBox1Change(TObject *Sender) // 4
{
int i, p;
char c;
i = (ComboBox1->ItemIndex +1);
String s1;
s1 = Mk[i].Stroka;
p = TXMacros(s1);
TxBuf->Perform(EM_SETSEL,9999,9999);
for(i=0;i<p;i++)
{
c = buf[i];
TxBuf->Perform(WM_CHAR,c,0);
}
}
//-------------------------------------------------------------------
// Функция
обработки сообщений от DLL. Считывает звуковые данные FFT
void __fastcall TForm1::FFTDataReady(TMessage& msg) // 5
{
if(fnGetFFTData(&FFTData,0,FFTMax) !=
SignalOverload)
{
StatusBar1->Panels->Items[0]->Text
= " ** Input Overload **";
SignalOverload = true;
}
else
{
StatusBar1->Panels->Items[1]->Text
= "*ПРИЕМ";
SignalOverload = false;
}
RxFrequency = msg.WParam;
SpectrumDisplayPaint(this);
TForm1::WaterfallDisplayPaint(this);
}
//-------------------------------------------------------------------
// Функция
обработки сообщения от DLL. Копирует в окно
Terminal буквы
void __fastcall TForm1::CharReady(TMessage
&msg) // 6
{
unsigned int c;
c = msg.WParam;
if(c==8||c==13||c>=32&& c<=
255)
{
Terminal->Perform(EM_SETSEL,30000,30000);
Terminal->Perform(WM_CHAR,c,0);
}
}
//--------------------------------------------------------------------
// Функция
обработки сообщения от DLL. Считывает IMD
void __fastcall TForm1::IMDReady(TMessage&
msg) // 7
{
if(msg.LParam <100) IMD->Caption =
IntToStr(msg.WParam)+"db";
else IMD->Caption = "";
}
//--------------------------------------------------------------------
// Функция
обработки сообщения от DLL. Считывает код ошибки
void __fastcall TForm1::ClkErr(TMessage& msg) // 8
{
Label1->Caption = "ClkErr = cообщение
LParam "+IntToStr(msg.LParam);
if(msg.LParam ==0)
StatusBar1->Panels->Items[0]->Text = "*|*";
else
StatusBar1->Panels->Items[0]->Text = "Ошибка :"
+IntToStr(msg.LParam)+"ppm";
}
//--------------------------------------------------------------------
// Функция
обработки сообщения от DLL. Считывает изменение состояния
void __fastcall TForm1::StatusChange(TMessage& msg) // 9
{
String errstr = "";
Label1->Caption = "StatusChange =
cообщение wParam " +IntToStr(msg.WParam);
switch(msg.WParam)
{
case 0:
StatusBar1->Panels->Items[1]->Text = "ПРИЕМ*";
if(TxBtn->Down)
{
TxBtn->Font->Color =
clWindowText;
TuneBtn->Enabled = true;
TxBuf->Clear();
}
break;
case 1:
StatusBar1->Panels->Items[1]->Text = "ПЕРЕДАЧА";
break;
case 2:
StatusBar1->Panels->Items[0]->Text = "** CPU занят
**";
break;
case 10:
errstr = "Ошибка распределения памяти";
break;
case 11:
errstr = "Не могу открыть звуковую карту";
break;
case 12:
errstr = "Звуковая карта недоступна";
break;
case 13:
errstr = "Звуковая карта уже работает";
break;
default:
errstr = "Неизвестная ошибка";
}
if(errstr != "")
Application->MessageBoxA(errstr.c_str(), "Ошибка!",MB_OK);
}
//----------------------------------------------------------------------
Начиная с функции 5 в
листинге 8.7 описаны функции, которые служат для обработки сообщений,
поступающих от DLL в процессе работы программы. Вполне возможно, что
из-за значительных сокращений в тексте программы, некоторые из этих функций
могут работать нестабильно.
Если вы внимательно изучили
содержание файла proba1.h, то
наверняка помните, что функции обработки сообщений от DLL объявлены в
разделе private. Кроме того, в этом файле содержится карта MESSAGE_MAP,
в которой записаны имена этих функций. Советую разыскать материал с описанием
работы с сообщениями Windows. Это
будет очень нужно, если вы будете продолжать разработку Windows приложений.
Листинг 8.8 начинается с
описания функции (10) установки вида модуляции – Mode. Функция передает в DLL величину,
записанную в свойстве ItemIndex.
Листинг
8.8. Продолжение 5 файла proba1.cpp
//----------------------------------------------------------------------
// Функция
реакции на переключение Mode
void __fastcall
TForm1::ModeClick(TObject *Sender)
// 10
{
fnSetRXPSKMode(Mode->ItemIndex,0);
}
//---------------------------------------------------------------------
// Функция ввода текста в окно Call
void __fastcall
TForm1::CallKorChange(TObject *Sender)
// 11
{
korcall = CallKor->Text;
}
//----------------------------------------------------------------------
// Функция ввода текста в окно Name
void __fastcall
TForm1::NameKorChange(TObject *Sender)
// 12
{
korname = NameKor->Text;
}
//---------------------------------------------------------------------
// Функция ввода текста в окно RST
void __fastcall
TForm1::RSTKorChange(TObject *Sender)
// 13
{
korrst = RSTKor->Text;
}
//---------------------------------------------------------------------
// Функция реагирует на нажатие клавиши Clear
void __fastcall
TForm1::KclearClick(TObject *Sender)
// 14
{
CallKor->Text = "";
NameKor->Text = "";
RSTKor ->Text = "";
}
//---------------------------------------------------------------------
Описанные в функциях 11 …
13 действия относятся к заполнению окон редактирования, расположенных рядом
друг с другом, в верхней части формы. Эти окна служат:
q
для ввода позывного радиостанции – вашего
корреспондента;
q
для ввода имени
вашего корреспондента;
q
для ввода
цифрового обозначения параметров радиосвязи – величин разбираемости, слышимости
и тона сигналов радиостанции вашего корреспондента.
Функция 14 очищает все вышеперечисленные
окна редактирования.
Листинг 8.9 начинается с
функции 15, которая является реакцией на нажатие клавиши Tx, которая
служит для начала передача введенного в окно TxBuf текста.
Следующая функция (16)
является реакцией на нажатие клавиши Тон.
Эта клавиша служит только целям контроля за тоном звукового сигнала.
Листинг
8.9. Продолжение 6 файла proba1.cpp
//---------------------------------------------------------------------
// Функция
реакции на нажатие клавиши Tx
void __fastcall
TForm1::TxBtnClick(TObject *Sender)
// 15
{
if(TxBtn->Down)
{
TuneBtn->Enabled = false;
if(Net == 1) TxFrequency = RxFrequency;
fnSetTXFrequency(TxFrequency);
fnStartTX(Mode->ItemIndex);
TxBtn->Font->Color = clRed;
}
else
{
if(fnGetTXCharsRemaining == 0) fnStopTX();
else
{
fnAbortTX();
fnClearTXBuffer();
}
TxBuf->Text = "";
TxBtn->Font->Color = clWindowText;
TuneBtn->Enabled = true;
}
}
//---------------------------------------------------------------------
// Функция
реакции на нажатие клавиши Тон
void __fastcall
TForm1::TuneBtnClick(TObject *Sender)
// 16
{
if(TuneBtn->Down)
{
TxBtn->Enabled = false;
if(Net == 1) TxFrequency = RxFrequency;
fnSetTXFrequency(TxFrequency);
fnStartTX(3);
TuneBtn->Font->Color = clRed;
}
else
{
fnStopTX();
TuneBtn->Font->Color = clWindowText;
TxBtn->Enabled = true;
}
}
//---------------------------------------------------------------------
// Функция для
передачи символа непосредственно с клавиатуры
void __fastcall
TForm1::TxBufKeyPress(TObject *Sender, char &Key) // 17
{
int c;
TxBuf->Perform(EM_SETSEL,9999,9999);
Application->ProcessMessages();
c = Key;
if(c==3)
{
fnSendTXCharacter(1,1);
}
else
{
if(c==5) fnSendTXCharacter(2,1);
fnSendTXCharacter(c,0);
}
}
//---------------------------------------------------------------------
//
void __fastcall
TForm1::Timer1Timer(TObject *Sender)
// 18
{
unsigned int cloctime = clock();
STANlasttime = cloctime;
StatusBar1->Panels->Items[2]->Text
= FormatDateTime(" hh:nn:ss",Now());
}
//---------------------------------------------------------------------
Функция 17 служит для
передачи буквенного символа непосредственно с клавиатуры. Чтобы задействовать
эту функцию, нужно щелкнуть мышкой на поле окна TxBuf (текстового буфера для передаваемого текста). После
этого символ любой нажатой на клавиатуре компьютера клавиши будет выдан на
передачу.
Функция 18 служит для
печати времени включения программы. Предлагается позже начинающему разработчику
самостоятельно доработать и расширить эту функцию.
Листинг 8.10 начинается с
функции (19) рисования в окне Spectrum. Функция 20 выполняет рисование в окне Waterfall. В
теле этих функций мною даны некоторые пояснения. Детальное описание процесса
рисования в среде C++Builder не
входит в задачи этой книги, потому что представляет собой очень обширную
специальную тему. Тем читателям, которые хотят подробнее познакомиться с
процессами рисования, рекомендую поработать с [1]. В этой замечательной книге
можно найти описания многих важных тем по C++Builder.
Листинг
8.10. Продолжение 7 файла proba1.cpp
//----------------------------------------------------------------------
// Функция рисования в окне Spectrum
void __fastcall
TForm1::SpectrumDisplayPaint(TObject *Sender) // 19
{
if(Application)
{
int bmwidth, bmheight, i, fftrange;
bmwidth = SpectrumDisplay->Width;
bmheight = SpectrumDisplay->Height;
SpectrumDisplay->Canvas->Brush->Color
= clBlack;
SpectrumDisplay->Canvas->FillRect(Rect(0,0,bmwidth,bmheight));
// Начало вывода на экран анализатора спектра
SpectrumDisplay->Canvas->Pen->Color
= clRed;
i = RxFrequency * bmwidth / FFTBandwidth;
SpectrumDisplay->Canvas->MoveTo(i,0);
SpectrumDisplay->Canvas->LineTo(i,bmheight-1);
// снова рисуем спектрум
SpectrumDisplay->Canvas->Pen->Color =
clLime; //clAqua;
SpectrumDisplay->Canvas->MoveTo(0,bmheight
- 1);
SpectrumDisplay->Canvas->LineTo(0,bmheight
- (FFTData[0] *
bmheight) / FFTScale);
fftrange = FFTMax;
for(i=1;i<bmwidth;i++)
{
SpectrumDisplay->Canvas->MoveTo(i,bmheight-1);
SpectrumDisplay->Canvas->LineTo(i,bmheight - (FFTData[i*fftrange /
bmwidth]
* bmheight) / FFTScale);
}
}
else
{
BitBlt(SpectrumDisplay->Canvas->Handle,0,0,SpectrumDisplay->Width,
SpectrumDisplay->Height,SpectrumDisplay->Canvas->Handle,0,0,SRCCOPY);
}
}
//----------------------------------------------------------------------
// Функция рисования окна Waterfall
void __fastcall
TForm1::WaterfallDisplayPaint(TObject *Sender) // 20
{
Graphics::TBitmap *TempBitmap = new
Graphics::TBitmap;
if(Application)
{
int bmwidth,bmheight, i, v, g;
bmwidth = WaterfallDisplay->Width;
bmheight = WaterfallDisplay->Height;
TempBitmap->Width = bmwidth;
TempBitmap->Height = bmheight;
// Начало рисования водопада
// Копировать
предыдущую строку водопада вниз, на другую строку
BitBlt(TempBitmap->Canvas->Handle,0,1,bmwidth,bmheight-1,
WaterfallDisplay->Canvas->Handle,0,0,SRCCOPY);
// Записать новые данные
в водопад
for(i=0;i<bmwidth-1;i++)
{
v = FFTData[WFMin+i];
g = v << 2;
if(g > 255) g = 255;
TempBitmap->Canvas->Pixels[i][0] =
RGB(v,g,v);
}
// Копировать
это в следующую строку на картинку
BitBlt(WaterfallDisplay->Canvas->Handle,0,0,bmwidth,bmheight,
TempBitmap->Canvas->Handle,0,0,SRCCOPY);
WaterfallDisplay->Canvas->Pen->Color
= clRed;
i = (RxFrequency - WFOffset) * bmwidth /
WFBandwidth;
WaterfallDisplay->Canvas->MoveTo(i,0);
WaterfallDisplay->Canvas->LineTo(i,bmheight-1);
}
else
{
BitBlt(WaterfallBitmap->Canvas->Handle,0,0,WaterfallDisplay->Width,
WaterfallDisplay->Height,TempBitmap->Canvas->Handle,0,0,SRCCOPY);
}
TempBitmap->Free();
}
//---------------------------------------------------------------------
// Функция
изменения частоты передачи путем щелканья мышкой в Waterfall
void __fastcall
TForm1::WaterfallDisplayMouseUp(TObject *Sender,
TMouseButton Button, TShiftState Shift,
int X, int Y) // 21
{
if(Button == mbLeft)
{
RxFrequency = (X+1)* WFBandwidth /
WaterfallDisplay->Width+
WFOffset - 2;
fnSetRXFrequency(RxFrequency,0,0);
}
if(!AFC)
{
fnSetAFCLimit(afcrange,0);
Sleep(1000);
fnSetAFCLimit(0,0);
RxFrequency = fnGetRXFrequency(0);
}
}
//---------------------------------------------------------------------
В функции 21, которая
является реакцией на щелканье мышкой в поле окна Waterfall, происходит изменение частоты, которая
используется для передачи сигнала. На
этой функции заканчиваются процессы работы с окнами Spectrum и Waterfall.
Функция 22 является
реакцией на нажатие клавиши Выход. После нажатия этой клавиши сначала
останавливается работа DLL библиотеки, после чего прекращается работа управляющей
программы.
Листинг
8.11. Продолжение 8 файла proba1.cpp
//----------------------------------------------------------------------
//
void __fastcall
TForm1::Button1Click(TObject *Sender)
// 22
{
fnStopSoundCard();
Close();
}
//---------------------------------------------------------------------
// Функция
изменения звуковой частоты
void __fastcall
TForm1::TxFreqChange(TObject *Sender)
// 23
{
TxFrequency = StrToInt(TxFreq->Text);
fnSetTXFrequency(TxFrequency);
RxFrequency = TxFrequency;
fnSetRXFrequency(TxFrequency,0,0);
}
//---------------------------------------------------------------------
Функция 23 является
завершающей в файле proba1.cpp. Она является реакцией на изменение значения звуковой
частоты в окне редактирования, расположенном в левом нижнем углу формы.
Итал, мы имеем
разработанную заготовку для всех файлов проекта. Следующим этапом нужно сделать
проект работоспособным, т.е. доработать заготовки файлов, создать необходимые
дополнительные файлы и подключить к проекту DLL библиотеку.
Рассмотрим весь процесс по порядку.
q
Нужно создать
инициализационный файл. Для этого создаем в простом текстовом редакторе файл
под именем Wpsk1.ini
и
переносим в этот файл весь текст из листинга 8.1. Готовый файл размещаем в
диоектории создаваемого проекта.
q
В листинге 8.2
мною приведен текст, который должен иметь файл proba1.h. В
заготовку этого файла, расположенную в составе нового проекта, нужно ввести
недостающие строки во все разделы, за исключением раздела __published:. Все строки
этого раздела будут корректироваться автоматически. Особенно обратить внимание
на то, чтобы раздел protected: и раздел public: файла proba1.h.,
находящиеся в составе проекта, ничем не отличался от private: и public: в листинге 8.2.
q
Самую большую
работу предстоит проделать с файлом proba1.cpp, который
находится в составе проекта. Если просто переписать все функции из листингов в
файл, то компилятор не будет знать, где находится функция, относящаяся к тому
или иному компоненту. Нужно поступать иначе, а именно – привязать каждую функцию к своему компоненту.
·
В файле proba1.cpp всю
начальную часть (до функции 1) дополнить
данными из листингов 8.3 и 8.4 (все до
функции1).
·
В функцию 1
создаваемого файла вписать все строки из функции 1, расположенной в листингах
8.5 и 8.6.
·
Функцию 2 и
функцию 3 переписать полностью из листинга 8.6.
·
Функция 4
представляет собой реакцию на выбор строки из компонента ComboBox.
Выполняем двойной щелчок на окне этого компонента, расположенного на форме, и в
появившемся текстовом редакторе заполняем тело функции с названием ComboBox1Change() строками из аналогичной функции (4), расположенной в
листинге 8.7.
·
Функции 5, 6, 7,
8 и 9 служат для обработки сообщений, поступающих от DLL. Тексты всех этих пяти функций следует полностью
переписать в новый файл proba1.cpp.
·
Функция 10
представляет собой реакцию на выбор радиокнопки Mode. Чтобы вызвать окно редактора с нужной заготовкой под
функцию, нужно выполнить двойной щелчок на слова Mode и дописать в тело новой функции строки из функции 10
(листинг 8.8).
·
Функции 11, 12 и
13 являются реакцией на запись данных в окно редактирования Call, Name и RST. Вызвать окно редактора с заготовкой под любую
из этих функций можно двойнфм щелчком на поле соответствующего окна
редактирования. В тело новой функции нужно вписать строки из соответствующих
функций листинга 8.8.
·
Функция 14
является реакцией на нажатие кнопки Clear. Двойной
щелчок на изображении этой кнопки и перед вами на экране появится окно
редактора с функцией, в тело которой следует вписать строчки их аналогичной
функции листинга 8.8.
·
Функции 15 и 16
из листинга 8.9 являются реакциями на нажатие кнопок TX
и Тон.
Обрабатывать их следует в точном соответствии с предыдущим пунктом (как функцию 14).
·
Функцию 17 нужно
создавать. Для этого необходимо выделить щелчком мышки окно TxBuf. В
окне Object Inspector нужно выбрать Events (события).
В перечне событий выбрать событие OnKeyPress и щелкнуть
мышкой в этой строке на квадратике справа. Появится дополнительный перечень
событий, в котором нужно выбрать TxBufKeyPress и щелкнуть
дважды на этом событии. Появится окно
редактора с созданной заготовкой для функции 17. Теперь нужно в тело этой
заготовки переписать строчки из аналогичной функции 17 в листинге 8.9.
·
Функция 18
относится к работе таймера. Для получения окна редактора с заготовкой
соответствующей функции, нужно выполнить двойной щелчок на пиктограмме
компонента Timer. В новую функцию следует переписать строки из
соответствующей функции листинга 8.9.
·
Функции 19 и 20
выполняют рисование в своих соответствующих окнах. Для вызова редактора с
заготовкой для функции 19 нужно выбрать щелчком мышки находящееся справа вверху
окно SpectrumDisplay, открыть в окне Object Inspector страничку событий Events и выбрать событие OnPaint.
Щелкнуть мышкой на маленьком квадратике в конце строки и из появившегося списка
событий выбрать SpectrumDicplayPaint. Двойной щелчок на этом событии вызывает окно
редактора с заготовкой под соответствующую функцию. Теперь следует все строчки
из функции 19 (листинг 8.10) пернести в тело новой функции. Точно также следует
обработать и функцию 20.
·
Почти также, как
и в предыдущем случае, можно обработать функцию 21. Но при этом следует выбрать
событие OnMouseUp и
далее уточненное событие WaterfallDisplayMouseUP. Все строчки из
тела функции 21 (листинг 8.10) нужно перенести в тело новой функции.
·
Функция 22
(листинг 8.11) является реакцией нажатия на кнопку Выход. Все должно быть ясно
из описания обработки предыдущих аналогичных функций.
·
Функция 23 (из
того же листинга) реагирует на изменения в компоненте CspinEdit,
расположенном в левом нижнем углу. Двойной щелчок на поле окна этого компонента
вызывает окно редактора с заготовкой для функции.
На этом работа по созданию файлов нового проекта
закончена.
Теперь следует убедиться в
отсутствии случайных ошибок выбором из главного меню ProjectèMake Wpsk1 и, в случае их обнаружения, исправить эти ошибки.
Далее следует создать исполняемый файл новой программы. Это делается при выборе
из меню ProjectèBuild Wpsk1.
Если файл proba1.cpp выполнен без ошибок, то выбирайте в главном меню команду RunèRun и на экране вашего компьютера должно появиться главное окно программы Wpsk1, представленное на рис. 8.2.
Рис. 8.2. Окно программы Wpsk1
Если кого то из
читателей этой книги заинтересуют вопросы совместной работы компьютера и
любительской радиостанции, то советую обратиться к специальной литературе. В моих книгах [5] и [7] можно найти описание
различных методов подсоединения компьютера к любительской радиостанции.
Кроме того, в Интернете на моем сайте,
расположенном по адресу http://r3xb.nm.ru/, к моменту выхода в свет
этой книги, будут располагаться исходные коды описанных в этой книге программ. Там же можно найти
исходные коды других программ, статьи по различным видам цифровой радиосвязи,
описание и схемы различных радиоаппаратов и антенн.
В этом разделе я предлагаю
вам описание процесса создания проекта не упрощенной учебной программы, а
полноценной программы для приема любительского телевидения с медленной
разверткой – SSTV.
После некоторых раздумий я выбрал для
завершения своей книги именно эту
программу. Дело в том, что эта программа имеет минимально возможные размеры для
подобного класса программ, хотя по качеству работы не уступает многим другим,
более популярным программам. Кроме того, созданием программ для SSTV могут
заинтересоваться не только радиолюбители.
Японский программист и
радиолюбитель Makoto Mori (JE3HHT) известен
многими своими программами, которые становятся все более популярными. Примерно
два года тому назад им была создана коммерческая программа MMSSTV,
предназначенная для приема телевидения с медленной разверткой. Программа
работает совместно с созданной им же DLL
библиотекой SSTVENG.DLL. В целях оказания помощи радиолюбителям –
программистам, которые хотели бы повторить сами подобную программу, им был
создан недавно «укороченный» вариант, названный MiniSSTV. Рассмотрением
создания проекта этой программы мы будем сейчас заниматься.
Создание проекта начинаем,
как обычно, с создания соответствующей директории. Пускай это будет D:\MiniSSTV\. Создаем в этой
директории проект программы, в котором файл с исходными кодами назовем main.cpp, а файл проекта назовем mini.bpr. Такие
необычные названия файлов применил автор программы и мне не хочется выдумывать
что – то свое. Так будет на протяжении всего рассмотрения создания этого
проекта.
В директорию нового проекта
следует скопировать файлы SSTVENG.DLL SSTVENG.LIB. Взять эти
файлы можно в Интернете либо на моем сайте, либо на сайте Makoto Mori, по адресу: http://www.qsl.net/mmhamsoft/.
При рассмотрении предыдущего проекта я
довольно много времени уделил инициализационному файлу. В этом проекте
инициализационный файл также имеется, но он будет создан автоматически самой
программой во время первого её запуска в работу.
На рис. 8.3 изображена
форма программы MiniSSTV с установленными на ней компонентами.
Рис. 8.3. Форма проекта
Слева на форме
располагается большое окно компонента PageControl, который находится в библиотеке компонентов Win32.
Компонент состоит из трёх страниц, на каждой из которых размещен компонент PaintBox,
находящийся в библиотеке System.
В верхней части справа
сначала стоит компонент Label, затем идут пиктограммы невидимых компонентов, среди
которых находятся (слева направо) :
q
компонент OpenDialog из библиотеки
компонентов Dialogs;
q
компонент Timer из
библиотеки System;
q
компонент MainMenu из библиотеки Standard;
q
компонент PopupMenu из библиотеки Standard.
Прямо под этой строчкой внизу
располагаются сразу три компонента Panel, на каждом из которых установлены компоненты PaintBox.
Еще ниже располагается компонент GroupBox с буквосочетанием RX, ниже
располагается точно такой же компонент с буквосочетанием TX.
В верхнем GroupBox ( RX) располагается окно ComboBox, пять кнопок SpeedButton и один компонент Label.
В нижнем GroupBox (TX) располагается также окно ComboBox, четыре кнопки SpeedButton и
одна метка Label.
Для того, чтобы не допустить ошибок при
заполнении формы, ниже (в листинге 8.12)
приводим текст файла main.h.
Как обычно в подобных
файлах, сначала идут подключения заголовочных файлов, затем объявляется класс MinMain.
В пределах этого класса
сначала объявляются компоненты, изменяемые автоматически в процессе работы по
заполнению формы.
Затем объявляются функции,
которые служат для обслуживания компонентов. Они также создаются автоматически
при двойном щелчке по изображению компонента.
Если компонент нужно убрать с формы, вначале
следует двойным щелчком войти в редактор функции, связанной с этим компонентом
и удалить все содержимое «тела» этой функции, т.е. все те строчки, которые
расположены между двумя главными фигурными скобками. И только после этого можно убирать с формы
ненужный компонент.
Листинг
8.12. Файл main.h
//----------------------------------------------------------------------
#ifndef MainH
#define MainH
//----------------------------------------------------------------------
#include
<Classes.hpp>
#include
<Controls.hpp>
#include
<StdCtrls.hpp>
#include <Forms.hpp>
#include
<ComCtrls.hpp>
#include
<ExtCtrls.hpp>
#include
<Buttons.hpp>
#include
<Menus.hpp>
#include
<Dialogs.hpp>
//
//---------------------------------------------------------------------
class TMiniMain : public TForm
{
__published: //
создаются автоматически при установке на форму
TPageControl *Page;
TTabSheet *TabSync;
TTabSheet *TabRX;
TTabSheet *TabTX;
TPanel *PanelSync;
TPaintBox *PBoxSync;
TPanel *PanelRX;
TPaintBox *PBoxRX;
TPanel *PanelTX;
TPaintBox *PBoxTX;
TTimer *Timer;
TPanel *PanelLevel;
TPaintBox *PBoxLevel;
TPanel *PanelSpec;
TPaintBox *PBoxSpec;
TPanel *PanelWater;
TPaintBox *PBoxWater;
TGroupBox *GBRX;
TComboBox *CBRMode;
TGroupBox *GBTX;
TComboBox *CBTMode;
TSpeedButton *SBRX;
TSpeedButton *SBTX;
TSpeedButton *SBPaste;
TMainMenu *MainMenu;
TMenuItem *KE;
TMenuItem *KO;
TMenuItem *KSet;
TSpeedButton *SBAFC;
TSpeedButton *SBLMS;
TSpeedButton *SBTune;
TLabel *LStat;
TSpeedButton *SBReSync;
TSpeedButton *SBSlant;
TLabel *LCall;
TSpeedButton *SBCopy;
TMenuItem *KEC;
TMenuItem *KEP;
TMenuItem *KV;
TMenuItem *KVFW1;
TMenuItem *KVFW2;
TMenuItem *KVFW3;
TOpenDialog *OpenDialog;
TSpeedButton *SBLoad;
TLabel *LMode;
TPopupMenu *PopupLMS;
TMenuItem *KLM1;
TMenuItem *KLM2;
TMenuItem *KLM3;
void __fastcall FormDestroy(TObject
*Sender);
void __fastcall PBoxTXPaint(TObject *Sender);
void __fastcall PBoxSyncPaint(TObject
*Sender);
void __fastcall TimerTimer(TObject
*Sender);
void __fastcall PBoxRXPaint(TObject
*Sender);
void __fastcall PBoxLevelPaint(TObject
*Sender);
void __fastcall PBoxSpecPaint(TObject
*Sender);
void __fastcall PBoxWaterPaint(TObject
*Sender);
void __fastcall SBRXClick(TObject *Sender);
void __fastcall CBRModeChange(TObject
*Sender);
void __fastcall SBTXClick(TObject *Sender);
void __fastcall CBTModeChange(TObject *Sender);
void __fastcall SBPasteClick(TObject
*Sender);
void __fastcall KSetClick(TObject *Sender);
void __fastcall SBAFCClick(TObject
*Sender);
void __fastcall SBLMSClick(TObject
*Sender);
void __fastcall SBTuneClick(TObject
*Sender);
void __fastcall SBReSyncClick(TObject
*Sender);
void __fastcall SBSlantClick(TObject
*Sender);
void __fastcall SBCopyClick(TObject
*Sender);
void __fastcall KEClick(TObject *Sender);
void __fastcall SBTuneMouseDown(TObject
*Sender, TMouseButton Button,
TShiftState Shift, int X, int Y);
void __fastcall KVFW1Click(TObject
*Sender);
void __fastcall KVClick(TObject *Sender);
void __fastcall SBLoadClick(TObject
*Sender);
void __fastcall
PBoxSyncMouseDown(TObject *Sender, TMouseButton Button,
TShiftState Shift, int X, int Y);
void __fastcall
PBoxSpecMouseDown(TObject *Sender, TMouseButton Button,
TShiftState Shift, int X, int Y);
void __fastcall PBoxSpecMouseMove(TObject
*Sender, TShiftState Shift,
int X, int Y);
void __fastcall PBoxSpecMouseUp(TObject
*Sender, TMouseButton Button,
TShiftState Shift, int X, int Y);
void __fastcall SBLMSMouseDown(TObject
*Sender, TMouseButton Button,
TShiftState Shift, int X, int Y);
void __fastcall KLM1Click(TObject *Sender);
private: //
доступны только в пределах этого класса
HBITMAP m_hbSpec;
HBITMAP m_hbWater;
HBITMAP m_hbLevel;
HBITMAP m_hbSync;
HBITMAP
m_hbRX;
HBITMAP m_hbTX;
int
m_DisEvent;
DWORD
m_JobCode;
int
m_SendY, m_RecvY;
LONG
m_TxCode;
LONG
m_RxCode;
LONG
m_TuneCode;
int
m_RXW, m_RXH;
int
m_TXW, m_TXH;
int
m_Timer;
int
m_ToneFreq;
int m_SpecBase;
int m_SpecWidth;
int m_NotchFreq;
int m_NotchMove;
void __fastcall UpdateBtn(void);
void __fastcall UpdateTXMode(void);
void __fastcall DrawTXCursor_(int y);
void __fastcall DrawTXCursor(int sw);
void __fastcall DrawBitmap(TPaintBox
*pBox,int xw,int yw,HBITMAP hb);
void __fastcall CopyRXImage(void);
void __fastcall DispSampFreq(void);
void __fastcall UpdateLMS(void);
public: // доступны всем
__fastcall TMiniMain(TComponent* Owner);
};
//---------------------------------------------------------------------
extern PACKAGE TMiniMain
*MiniMain;
//---------------------------------------------------------------------
#endif
//---------------------------------------------------------------------
В разделе private: объявляются
переменные величины и функции, которые могут быть задействованы только в
пределах этого класса. В том числе и функции, связанные с Windows сообщениями.
Рассмотрим содержание файла
Main.cpp из
состава проекта Mini. Мною не будут вноситься в этот файл какие то изменения,
все останется в авторском варианте, будут добавлены только некоторые
(минимальные) комментарии.
Файл начинается как обычно,
с подключения заголовочных файлов. Затем объявляется форма с необычным
названием MiniMain.
Функция 1 содержит описания
формы. В теле этой функции выполняются все действия, которые могут использоваться в пределах класса формы TMiniMain. В начале тела этой функции производится
назначение начальных параметров некоторым переменным величинам, назначается
заголовок для формы. В конце выполняется включение в работу DLL библиотеки
и включается таймер.
Листинг
8.13. Файл
main.cpp
//---------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#define UseLIB 1
#include
"sstveng.h"
#include "Main.h"
//----------------------------------------------------------------------
#pragma
package(smart_init)
#pragma resource
"*.dfm"
//----------------------------------------------------------------------
TMiniMain *MiniMain;
//----------------------------------------------------------------------
#define VER "1.04"
#define
TIMERINTERVAL 200 // интервал таймера 200 ms
//----------------------------------------------------------------------
// Функция описания формы
__fastcall
TMiniMain::TMiniMain(TComponent* Owner)
// 1
: TForm(Owner)
{
Timer->Enabled = FALSE;
Timer->Interval = TIMERINTERVAL;
m_hbLevel = NULL;
m_hbSpec = NULL;
m_hbWater = NULL;
m_hbSync = NULL;
m_hbRX = NULL;
m_hbTX = NULL;
m_Timer = 0;
m_DisEvent = 1;
m_SendY = m_RecvY = -1;
m_NotchFreq = 2400;
m_NotchMove = 0;
mmsCreate(Handle, 0); // Создать SSTV engine object
// mmsLanguage(1);
// mmsSetClearColor(RGB(0, 128, 128));
m_TXW = m_RXW = PBoxRX->Width;
m_TXH = m_RXH = PBoxRX->Height;
m_ToneFreq = 1750;
SBTune->Caption = m_ToneFreq;
char bf[256];
wsprintf(bf, "Mini SSTV Ver " VER
" (with SSTV engine Ver %s)", mmsGetVersion());
Caption = bf;
m_hbRX = mmsCreateDIB(800, 616);
mmsFillDIB(m_hbRX, mmsGetClearColor());
m_hbTX = mmsCreateDIB(800, 616);
m_hbSync = mmsCreateDIB(PBoxSync->Width,
PBoxSync->Height);
mmsSetBitmap(m_hbRX, m_hbSync, m_hbTX);
m_hbLevel = mmsCreateDIB(PBoxLevel->Width,
PBoxLevel->Height);
m_hbSpec = mmsCreateDIB(PBoxSpec->Width,
PBoxSpec->Height);
m_hbWater = mmsCreateDIB(PBoxWater->Width,
PBoxWater->Height);
mmsSetTuneBitmap(m_hbSpec, m_hbWater,
m_hbLevel);
m_SpecBase = 700; m_SpecWidth = 2000;
mmsSetSpecRange(m_SpecBase, m_SpecWidth);
int i;
LPCSTR pName;
for( i = 0; (pName = mmsGetModeName(i)) !=
NULL; i++ ){
CBRMode->Items->Add(pName);
CBTMode->Items->Add(pName);
}
CBRMode->ItemIndex =
CBRMode->Items->IndexOf("Scottie 1");
CBTMode->ItemIndex =
CBRMode->ItemIndex;
UpdateTXMode();
m_JobCode = 0;
m_RxCode = mmsSetRxControl(-1);
m_TxCode = mmsSetTxControl(-1);
m_TuneCode = mmsSetTuneControl(-1);
// m_TuneCode &= ~ucPRIORITY;
// m_TuneCode |= 0x30000000;
// mmsSetTuneControl(m_TuneCode);
// mmsSetRepeater(TRUE, 1750, 2, 1500, 500,
6000);
DispSampFreq();
UpdateBtn();
UpdateLMS();
mmsStart(); // задействовать SSTV DLL библиотеку
Timer->Enabled = TRUE;
m_DisEvent = 0;
}
//----------------------------------------------------------------------
// Функция прекращения работы программы
void __fastcall
TMiniMain::FormDestroy(TObject *Sender)
// 2
{
mmsDelete();
mmsDeleteDIB(m_hbSpec);
mmsDeleteDIB(m_hbWater);
mmsDeleteDIB(m_hbLevel);
mmsDeleteDIB(m_hbSync);
mmsDeleteDIB(m_hbRX);
mmsDeleteDIB(m_hbTX);
}
//----------------------------------------------------------------------
Функция 2 служит для
прекращения работы программы и
запускается при закрытии главного окна программы.
Функция 3 создает большое
окно программы pBox. В начале функции объявляются задействованные в
пределах этой функции переменные величины, относящиеся к параметрам
создаваемого окна. В конце срабатывает функция DLL библиотеки mmsDrawDIBdc(), которая создает
рабочие параметры окна pBox.
Функция 4 контролирует состояние кнопок SBRX, SBTX, SBAFC, SBLMS и
др. Всех кнопок, установленных в правой части формы. Здесь устанавливаются
правила «поведения» для всех кнопок, в
случае если одна из кнопок нажимается. Устанавливаются правила, по которым та
или иная кнопка может или не может быть задействованной.
Функция 5 срабатывает при
изменении параметров (режима) передачи сигналов.
Листинг
8.14. Продолжение 1 файла main.cpp
//----------------------------------------------------------------------
// Функция
создания большого окна pBox
void __fastcall TMiniMain::DrawBitmap(TPaintBox *pBox, int xw, int yw, HBITMAP hb) // 3
{
RECT dest;
RECT src;
dest.left = dest.top = 0;
dest.right = pBox->Width;
dest.bottom = pBox->Height;
src.left = src.top = 0;
src.right = xw;
src.bottom = yw;
mmsDrawDIBdc(pBox->Canvas->Handle,
&dest, hb, &src, HALFTONE);
}
//----------------------------------------------------------------------
// Функция
контроля за состоянием кнопок SBRX, SBTX, SBAFC, SBLMS и др.
void __fastcall
TMiniMain::UpdateBtn(void)
// 4
{
m_DisEvent++;
SBRX->Down = m_JobCode & jcRX;
SBTX->Down = (m_JobCode & jcTX)
&& !SBTune->Down;
SBTune->Enabled = !SBTX->Down ||
SBTune->Down;
CBTMode->Enabled = !SBTX->Down;
SBAFC->Down = m_RxCode & rcAFC;
SBLMS->Down = m_RxCode & rcLMS;
SBReSync->Enabled = SBRX->Down;
SBSlant->Enabled = ((m_RecvY >= 16)
&& (m_RxCode & 0x00300000)) ? TRUE : FALSE;
LCall->Enabled = m_RxCode & rcFSKID;
m_DisEvent--;
}
//----------------------------------------------------------------------
// Функция контроля за изменением включения передачи
void __fastcall
TMiniMain::UpdateTXMode(void)
// 5
{
int mode = CBTMode->ItemIndex;
m_TXW = mmsGetImageSize(mode);
m_TXH = m_TXW >> 16;
m_TXW &= 0x0000ffff;
PBoxTX->Height = (m_TXH < 256) ? 240
: 256;
PBoxTX->Invalidate();
int h, w;
w = mmsGetModeSize(mode);
h = w >> 16;
w &= 0x0000ffff;
char bf[256];
wsprintf(bf, "%ux%u %u(s)", w, h,
(mmsGetModeLength(mode)+500)/1000);
LMode->Caption = bf;
}
//---------------------------------------------------------------------
Листинг 8.15 начинается с
функции 6, которая служит для формирования панели RX в окне pBox.
Функции 7 и 8 формируют
точно таким же образом панели TX и Sync
в окне pBox. Все три функции построены по одному образцу.
Функция 9 организует работу
таймера. Функция сложная, имеет большое число различных вариантов срабатывания
таймера.
Листинг
8.15. Продолжение 2 файла main.cpp
//---------------------------------------------------------------------
// Функция
создания панели RX на окне pBox
void __fastcall
TMiniMain::PBoxRXPaint(TObject *Sender)
// 6
{
if( m_hbRX == NULL ) return;
if( PanelRX->Visible ){
DrawBitmap(PBoxRX, m_RXW, m_RXH,
m_hbRX);
}
}
//----------------------------------------------------------------------
// Функция
создания панели TX на окне pBox
void __fastcall
TMiniMain::PBoxTXPaint(TObject *Sender)
// 7
{
if(
m_hbTX == NULL ) return;
if( PanelTX->Visible ){
DrawBitmap(PBoxTX, m_TXW, m_TXH,
m_hbTX);
}
m_SendY = -1;
}
//---------------------------------------------------------------------
// Функция
создания панели Sync на окне pBox
void __fastcall
TMiniMain::PBoxSyncPaint(TObject *Sender)
// 8
{
if( m_hbSync == NULL ) return;
if( PanelSync->Visible ){
mmsDrawDIBdc(PBoxSync->Canvas->Handle, NULL, m_hbSync, NULL,
HALFTONE);
}
}
//----------------------------------------------------------------------
// Функция организации работы таймера
void __fastcall
TMiniMain::TimerTimer(TObject *Sender) // 9
{
DWORD
JobCode = m_JobCode;
m_JobCode = mmsDoJob(1);
m_RecvY = mmsGetPos(FALSE);
if( m_JobCode & jcStartRX ){ // Start RX
m_DisEvent++;
CBRMode->ItemIndex = mmsGetMode(0);
m_DisEvent--;
m_RXW =
mmsGetImageSize(CBRMode->ItemIndex);
m_RXH = m_RXW >> 16;
m_RXW &= 0x0000ffff;
m_JobCode |= jcUpdateRX;
}
else if( !(m_JobCode & jcRX) &&
(JobCode & jcRX) ){ // Stop RX
if( m_RecvY >= (m_RXH * 70 / 100) ){
CopyRXImage();
CBTMode->ItemIndex =
CBRMode->ItemIndex;
UpdateTXMode();
}
}
if( m_JobCode & jcUpdateRX ){ // изменение RX картинки
PBoxRXPaint(NULL);
PBoxSyncPaint(NULL);
SBSlant->Enabled = ((m_RecvY >=
16) && (m_RxCode & 0x00300000)) ? TRUE : FALSE;
DispSampFreq();
}
if( m_JobCode & jcSpec ){ // изменение картинки tune (тона)
PBoxSpecPaint(NULL);
PBoxWaterPaint(NULL);
}
if( m_JobCode & jcTX ){ // передача
DrawTXCursor(1);
}
else if( JobCode & jcTX ){ // Stop TX (передачи)
DrawTXCursor(0);
}
if( m_JobCode & jcFSKID ){ // (прием) RX FSKID
LCall->Caption = mmsGetFSKID();
}
PBoxLevelPaint(NULL);
LONG RxCode = mmsSetRxControl(-1);
if( (JobCode != m_JobCode) || (RxCode !=
m_RxCode) ){
m_RxCode = RxCode;
UpdateBtn();
}
if( !m_Timer ){
m_Timer = 1000/TIMERINTERVAL;
SBPaste->Enabled =
::IsClipboardFormatAvailable(CF_BITMAP);
}
m_Timer--;
// для отладки – запись в метку LStat
char bf[32];
wsprintf(bf, "RX:%08X TX:%08X
JOB:%08X", RxCode, m_TxCode, m_JobCode);
LStat->Caption = bf;
}
//---------------------------------------------------------------------
Листинг 8.16 начинается с
функции 10, которая служит для определения уровня сигнала в окне pBox.
Функция 11 служит для
рисования на панели pBoxSpec, следующая функция 12 выполняет аналогичную работу в
окне pBoxWater.
Листинг
8.16. Продолжение 3 файла main.cpp
//---------------------------------------------------------------------
// Функция
определения уровня сигнала в окне pBox
void __fastcall
TMiniMain::PBoxLevelPaint(TObject *Sender) // 10
{
if( m_hbLevel == NULL ) return;
if( PanelLevel->Visible ){
mmsDrawDIBdc(PBoxLevel->Canvas->Handle, NULL, m_hbLevel, NULL,
HALFTONE);
}
}
//---------------------------------------------------------------------
// Функция рисования в окне pBoxSpec
void __fastcall
TMiniMain::PBoxSpecPaint(TObject *Sender)
// 11
{
if( PanelSync->Visible ){
mmsDrawDIBdc(PBoxSpec->Canvas->Handle, NULL, m_hbSpec, NULL,
HALFTONE);
}
}
//---------------------------------------------------------------------
// Функция рисования в окне pBoxWater
void __fastcall
TMiniMain::PBoxWaterPaint(TObject *Sender) // 12
{
if( PanelWater->Visible ){
mmsDrawDIBdc(PBoxWater->Canvas->Handle, NULL, m_hbWater, NULL,
HALFTONE);
}
}
//----------------------------------------------------------------------
Функция 13 в листинге 8.17
обрабатывает событие при нажатии на кнопку RX. Точно такую же работу, но по отношению к клавише TX
выполняет функция 15.
Функция 14 является реакцией
на изменение CBRMode.
Функция 16 срабатывает при
нажатии на клавишу с надписью 1750.
При нажатии на эту клавишу выдается звуковой сигнал частотой 1750 Герц или 1200
Герц.
Листинг
8.17. Продолжение 4 файла main.cpp
//----------------------------------------------------------------------
// Функция
реакции нажатия кнопки SBRX
void __fastcall TMiniMain::SBRXClick(TObject *Sender) // 13
{
if( m_DisEvent ) return;
if( SBRX->Down ){
mmsStartScan(CBRMode->ItemIndex);
}
else {
mmsStopScan(TRUE);
}
}
//----------------------------------------------------------------------
// Функция изменения CBRMode
void __fastcall
TMiniMain::CBRModeChange(TObject *Sender) // 14
{
if( m_DisEvent ) return;
if( SBRX->Down ){
SBRXClick(NULL);
}
}
//----------------------------------------------------------------------
// Функция
реакции на нажатие кнопки SBTX
void __fastcall
TMiniMain::SBTXClick(TObject *Sender)
// 15
{
if( m_DisEvent ) return;
if( SBTune->Down ) mmsSendStop();
if( SBTX->Down ){
mmsSendPic(CBTMode->ItemIndex);
}
else {
mmsSendStop();
}
SBTune->Down = FALSE;
}
//----------------------------------------------------------------------
// Функция
реакции на нажатие кнопки Tune (тон)
void __fastcall
TMiniMain::SBTuneClick(TObject *Sender)
// 16
{
if( m_DisEvent ) return;
if( SBTune->Down ){
mmsSendTone(m_ToneFreq);
}
else {
mmsSendStop();
}
}
//----------------------------------------------------------------------
В следующем листинге (8.18)
сначала располагаются функции 17 и 18, реагирующие на перемещение курсора в
окне pBoxTX.
Функция 19 реагирует на
изменения в окне CBTMode.
Листинг
8.18. Продолжение 5 файла main.cpp
//----------------------------------------------------------------------
// Функция перемещения курсора по y
void __fastcall TMiniMain::DrawTXCursor_(int y) // 17
{
m_SendY = y;
PBoxTX->Canvas->MoveTo(0,
y);
int
Sop = ::SetROP2(PBoxTX->Canvas->Handle, R2_NOT);
PBoxTX->Canvas->LineTo(PBoxTX->Width,
y);
::SetROP2(PBoxTX->Canvas->Handle,
Sop);
}
//----------------------------------------------------------------------
// Функция перемещения курсора в панели TX
void
__fastcall TMiniMain::DrawTXCursor(int sw) // 18
{
if( !PanelTX->Visible ) return;
if( SBTune->Down ) return;
if( sw ){
int sy = mmsGetPos(TRUE) * PBoxTX->Height
/ m_TXH;
if( (m_SendY < 0) || (m_SendY != sy)
){
if( m_SendY >= 0 )
DrawTXCursor_(m_SendY);
DrawTXCursor_(sy);
}
}
else {
PBoxTX->Invalidate();
}
}
//---------------------------------------------------------------------
//Функция реакции на изменение Mode
void
__fastcall TMiniMain::CBTModeChange(TObject *Sender) // 19
{
if( m_DisEvent ) return;
UpdateTXMode();
}
//----------------------------------------------------------------------
// Функция реакции на нажатие кнопки SBPaste
void
__fastcall TMiniMain::SBPasteClick(TObject *Sender) // 20
{
Page->ActivePage = TabTX;
HBITMAP hbTX = mmsPasteDIB();
if( hbTX != NULL ){
RECT dest;
dest.left = dest.top = 0;
dest.right = m_TXW;
dest.bottom = m_TXH;
mmsDrawDIBbmp(m_hbTX, &dest, hbTX,
NULL, HALFTONE);
::DeleteObject(hbTX);
PBoxTX->Invalidate();
}
}
//----------------------------------------------------------------------
//
Функция реакции на нажатие кнопки KSet
void
__fastcall TMiniMain::KSetClick(TObject *Sender) // 21
{
if( mmsOption() ){
m_TxCode = mmsSetTxControl(-1);
DispSampFreq();
UpdateBtn();
}
}
//---------------------------------------------------------------------
//
Функция реакции на нажатие кнорки SBAFC
void
__fastcall TMiniMain::SBAFCClick(TObject *Sender) // 22
{
m_RxCode &= ~rcAFC;
if( SBAFC->Down ) m_RxCode |= rcAFC;
mmsSetRxControl(m_RxCode);
}
//---------------------------------------------------------------------
//
Функция реакции на нажатие кнопки SBLMS
void
__fastcall TMiniMain::SBLMSClick(TObject *Sender) // 23
{
m_RxCode &= ~rcLMS;
if( SBLMS->Down ) m_RxCode |= rcLMS;
mmsSetRxControl(m_RxCode);
}
//----------------------------------------------------------------------
//
Функция реакции на нажатие кнопки SBReSync
void
__fastcall TMiniMain::SBReSyncClick(TObject *Sender) // 24
{
mmsReSync();
}
//---------------------------------------------------------------------
// Функция реакции на нажатие кнопки SBSlant
void
__fastcall TMiniMain::SBSlantClick(TObject *Sender) // 25
{
mmsCorrectSlant();
DispSampFreq();
}
//---------------------------------------------------------------------
Функции 20 … 25 являются
реакциями на нажатие соответствующих клавиш, расположенных в правой стороне
формы.
Листинг 8.19 начинается с
функции 26, которая служит для копирования принятого рисунка. Это копирование
происходит по команде, которой служит нажатие клавиши Copy.
Функция 27 как раз и представляет собой реакцию на нажатие клавиши Copy.
Функция 28 получает от DLL
различные значения частот и выводит эти
значения на экран в метку Label.
Функция 29 разрешает работу
промежуточного буфера (clipboard).
Листинг
8.19. Продолжение 6 файла main.cpp
//---------------------------------------------------------------------
// Функция
копирования принятого (принимаемого) рисунка
void __fastcall TMiniMain::CopyRXImage(void) // 26
{
HBITMAP hbRX = mmsCreateDIB(m_RXW, m_RXH);
mmsDrawDIBbmp(hbRX, NULL, m_hbRX, NULL,
HALFTONE);
mmsCopyDIB(hbRX);
::DeleteObject(hbRX);
}
//---------------------------------------------------------------------
// Функция реакции на нажатие кнопки SBCopy
void __fastcall
TMiniMain::SBCopyClick(TObject *Sender)
// 27
{
CopyRXImage();
}
//----------------------------------------------------------------------
// Функция
выдает в метку частоту RX и TX
void __fastcall
TMiniMain::DispSampFreq(void)
// 28
{
char bf[256];
LONG mfq = mmsGetSampleFreq(0); // Master
LONG toff = mmsGetSampleFreq(1); // Tx offset
LONG rfq = mmsGetSampleFreq(2); // RX
wsprintf(bf, "RX - %u.%02uHz Mas - %u.%02uHz", rfq/100, rfq%100,
mfq/100, mfq%100);
GBRX->Caption = bf;
rfq = mfq + toff;
wsprintf(bf, "TX - %u.%02uHz",
rfq/100, rfq%100);
GBTX->Caption = bf;
}
//----------------------------------------------------------------------
// Функция
реакции на нажатие кнопки KE
void __fastcall
TMiniMain::KEClick(TObject *Sender)
// 29
{
SBPaste->Enabled = ::IsClipboardFormatAvailable(CF_BITMAP);
KEP->Enabled = SBPaste->Enabled;
}
//----------------------------------------------------------------------
Функция 30 (листинг 8.20)
является реакцией на действия мышкой над клавишей Tune, на которой фактически напечатаны цифры 1750 или 1200. Эти цифры означают верхнюю и нижнюю звуковые частоты
передаваемого сигнала.
Функции 31 и 32 также
предназначены для манипуляции с частотами путем воздействия на клавишу Tune.
Листинг
8.20. Продолжение 7 файла main.cpp
//----------------------------------------------------------------------
// функция
реакции на действие мышки
void __fastcall TMiniMain::SBTuneMouseDown(TObject *Sender,
TMouseButton Button, TShiftState Shift, int X, int Y) // 30
{
if( Button == mbRight ){
m_ToneFreq = m_ToneFreq != 1750 ? 1750 :
1200;
SBTune->Caption = m_ToneFreq;
if( SBTune->Down )
mmsSendTone(m_ToneFreq);
}
}
//----------------------------------------------------------------------
// Функция
реакции на нажатие кнопки KVFW1
void __fastcall
TMiniMain::KVFW1Click(TObject *Sender)
// 31
{
if( Sender == KVFW1 ){
m_TuneCode &= ~(ucTYPE);
}
else if( Sender == KVFW2 ){
m_TuneCode &= ~(ucTYPE|ucGAIN);
m_TuneCode |= 0x00000012;
}
else if( Sender == KVFW3 ){
m_TuneCode &= ~(ucTYPE|ucGAIN);
m_TuneCode |= 0x00000025;
}
mmsSetTuneControl(m_TuneCode);
}
//----------------------------------------------------------------------
// Функция реакции на нажатие KV
void __fastcall
TMiniMain::KVClick(TObject *Sender)
// 32
{
m_TuneCode = mmsSetTuneControl(-1);
switch((m_TuneCode>>4) &
0x00000003){
case 1:
KVFW2->Checked = TRUE;
break;
case 2:
KVFW3->Checked = TRUE;
break;
default:
KVFW1->Checked = TRUE;
break;
}
}
//----------------------------------------------------------------------
// Функция на нажатие кнопки SBLoad
void __fastcall
TMiniMain::SBLoadClick(TObject *Sender)
// 33
{
Page->ActivePage = TabTX;
if( OpenDialog->Execute() == TRUE ){
HBITMAP hbTX =
mmsLoadDIB(OpenDialog->FileName.c_str());
if( hbTX != NULL ){
RECT dest;
dest.left = dest.top = 0;
dest.right = m_TXW;
dest.bottom = m_TXH;
mmsDrawDIBbmp(m_hbTX, &dest,
hbTX, NULL, HALFTONE);
::DeleteObject(hbTX);
PBoxTX->Invalidate();
}
}
}
//---------------------------------------------------------------------
Функция 33 является реакцией
на нажатие кнопки Load. Открывается диалоговое окно
для поиска файла, который нужно считать в память.
В листинге 8.21
представлены функции 34, 35, 36 и 37, которые являются реакцичми на действия
мышкой в различных панелях окна pBox.
Листинг
8.21. Продолжение 8 файла main.cpp
//---------------------------------------------------------------------
// Функция
реакции на действия мышки в окне pBoxSync
void __fastcall
TMiniMain::PBoxSyncMouseDown(TObject *Sender,
TMouseButton Button, TShiftState Shift,
int X, int Y) // 34
{
mmsAdjustPhase(X, (Button == mbLeft) ?
PBoxSync->Width : 0);
}
//----------------------------------------------------------------------
// Функция
реакции на действия мышки в окне pBoxSpec
void __fastcall
TMiniMain::PBoxSpecMouseDown(TObject *Sender,
TMouseButton Button, TShiftState Shift,
int X, int Y) // 35
{
int freq;
if( Button == mbLeft ){
freq = m_SpecBase + ((X * m_SpecWidth)
/ PBoxSpec->Width);
m_NotchFreq = freq;
m_NotchMove = TRUE;
}
else if( mmsSetNotch(-1) ){
freq = 0;
}
else {
freq = m_NotchFreq;
}
mmsSetNotch(freq);
}
//---------------------------------------------------------------------
// Функция
реакции на действие мышки в окне pBoxSpec
void __fastcall
TMiniMain::PBoxSpecMouseMove(TObject *Sender,
TShiftState Shift, int X, int Y) // 36
{
if( m_NotchMove ){
m_NotchFreq = m_SpecBase + ((X *
m_SpecWidth) / PBoxSpec->Width);
mmsSetNotch(m_NotchFreq);
}
}
//---------------------------------------------------------------------
// Функция
реакции на действие мышки в окне pBoxSpec
void __fastcall
TMiniMain::PBoxSpecMouseUp(TObject *Sender,
TMouseButton Button, TShiftState Shift,
int X, int Y) // 37
{
m_NotchMove = 0;
}
//---------------------------------------------------------------------
Функция 38 (листинг 8.22)
является реакцией на изменение надписи на кнопке LMS. На этой кнопке могут быть надписи либо ANF, либо ANS. Это
зависит от предварительной установки. По умолчанию печатается надпись LMS.
Листинг
8.22. Продолжение 9 файла main.cpp
//---------------------------------------------------------------------
// Функция
реакции на изменения надписи на кнорке LMS
void __fastcall
TMiniMain::UpdateLMS(void)
// 38
{
switch(mmsSetLMS(-1)){
case 1:
SBLMS->Caption =
"ANF";
SBLMS->Font->Color = clRed;
break;
case 2:
SBLMS->Caption =
"ANS";
SBLMS->Font->Color = clRed;
break;
default:
SBLMS->Caption =
"LMS";
SBLMS->Font->Color = clBlack;
break;
}
}
//---------------------------------------------------------------------
// Функция
реакции на действия мышки на кнопку SBLMS
void __fastcall
TMiniMain::SBLMSMouseDown(TObject *Sender,
TMouseButton Button, TShiftState Shift,
int X, int Y) // 39
{
if( Button == mbRight ){
switch(mmsSetLMS(-1)){
case 1:
KLM2->Checked = TRUE;
break;
case 2:
KLM3->Checked = TRUE;
break;
default:
KLM1->Checked = TRUE;
break;
}
int x = Left + GBRX->Left + SBLMS->Left
+ (SBLMS->Width / 2);
int y = Top + GBRX->Top +
SBLMS->Top + (Height - ClientHeight);
PopupLMS->Popup(x, y);
}
}
//----------------------------------------------------------------------
// Функция
реакции на нажатие кнопки KLM1
void __fastcall
TMiniMain::KLM1Click(TObject *Sender)
// 40
{
int sw = 0;
if( Sender == KLM2 ){
sw = 1;
}
else if( Sender == KLM3 ){
sw = 2;
}
mmsSetLMS(sw);
UpdateLMS();
}
//----------------------------------------------------------------------
Функция 39 является
реакцией на действия мышки при различных предварительных установках надписи на
клавише LMS.
Функция 40 является
реакцией на нажатие клавиши LMS в
зависимости от различных предварительных установок.
Итак, нами очень даже
кратко рассмотрен листинг файла main.cpp. Теперь
нужно выполнить заполнение этого файла, находящегося в составе проекта. Если
просто переписать все функции из листингов в файл, то компилятор не будет
знать, где находится функция, относящаяся к тому или иному компоненту. Нужно
поступать иначе, а именно – привязать каждую функцию к своему компоненту.
1.
Необходимо
доработать файл main.h. В этом
файле нужно заполнить раздел private: , для этого из раздела private: в листинге 8.12
все строчки объявлений и переменных и функций переписать в этот же раздел вновь
созданного в новом проекте файла main.h. Другие
разделы оставить без изменений. Они будут корректироваться автоматически.
2.
В файле main.cpp всю
начальную часть (до функции 1) дополнить
данными из начала листинга 8.13.
3.
Функцию 1
создаваемого файла дополнить данными из функции 1 листинга 8.13.
4.
Функцию 2 нужно
создавать. Для этого необходимо выделить щелчком мышки собственно форму. В окне
Object Inspector нужно выбрать Events (события).
В перечне событий выбрать событие OnDestroy и
щелкнуть мышкой в этой строке на квадратике справа. Появится дополнительный
перечень событий, в котором нужно выбрать
FormDestroy и
щелкнуть дважды на этом событии.
Появится окно редактора с созданной заготовкой для функции 2. Теперь
нужно в тело этой заготовки переписать строчки из аналогичной функции 2 в
листинге 8.13.
5.
Весь текст
функции 3 нужно без ошибок переписать в новый файл main.cpp. Точно
также нужно поступить с функциями 4 и 5. Дело в том, что эти функции, как и все
другие, объявленные в разделе private: файла main.h, созданы автором и автоматически изменяться не будут.
6.
Функции 6, 7 и 8
в новом файле создаются одинаково. Для функции 6 следует на главной форме
выбрать мышкой панель RX в окне pBox. В окне Object Inspector нужно выбрать Events (события). В событиях выбирается строка OnPaint, а в
этой строке выбирается дополнительное событие PBoxRXPaint и
выполняется двойной щелчок мышкой. В появившуюся заготовку переписывается весь
текст из аналогичной функции, расположенной в листинге 8.15. Точно такие же
действия выполняются для функций 7 и 8.
7.
Для выхода на
заполнение функции 9 следует выполнить двойной щелчок мышкой на пиктограмме
компонента Timer и перенести в тело появившейся заготовки весь текст из
аналогичной функции в листинге 8.15.
8.
Функции 10, 11 и
12 в новом файле создаются одинаково и по методике, описанной выше, в пункте 6.
При этом дополнительное событие должно соответствовать слову, имеющемуся в
названии функции.
9.
Функции 13, 15 и
16 в новом файле создаются очень просто. Например, функция 13 носит название SBRXClick(), где SBRX обозначает
компонент SpeedButton с именем RX, а в
сочетании со словом Click обозначает
событие – нажатие на кнопку RX. Следовательно, чтобы вызвать на экран редактор с заготовкой
для функции 13 нужно выполнить двойной щелчок на кнопке RX и внести в тело создаваемой функции все строки из
соответстыующей функции листинга 8.17. Точно также нужно поступить и в случае с
функциями 15 и 16, только двойной щелчок выполняется на соответствующей кнопке.
10.
Функция 14
является реакцией на изменения в окне компонента ComboBox, расположенном в группе компонентов RX. Двойной
щелчок на этом компоненте вызывает окно редактора с заготовкой, в которую нужно
вписать все строки из соответствующей функции листинга 8.17.
11.
В листинге 8.18
функции 17 и 18 переписываются в новый файл полностью.
12.
В том же листинге
функция 19 обрабатывается точно также, как аналогичная функция 14 из пункта 10.
13.
В том же листинге
функции 20, 21, 22, 23, 24 и 25 обрабатываются по методике пункта 9 (реакция на
нажатие соответствующей кнопки).
14.
В листинге 8.19
функции 26 и 28 нужно аккуратно полностью переписать в новый файл. Это функции,
созданные автором.
15.
В том же листинге
функции 27 и 29 являются реакцией на нажатие кнопки и обрабатываются по
методике пункта 9.
16.
Функция 30 в
листинге 8.20 является реакцией на действия мышкой для кнопки Tune (1750). Выбирается
кнопка Tune, для этой кнопки выбираем событие OnMouseDown,
а затем дополнительное событие SBTuneMouseDown, после двойного щелчка на этом событии появляется
заготовка функции, которую нужно заполнить. Все делается как и в пункте 4.
17.
В том же листинге
8.20 имеются функции 31 и 32, которые также являются реакцией на нажатие кнопки
Tune. Но затем выбирается событие OnClick, котором
выбираются дополнительные события, записанные в названиях этих функций.
18.
Функция 33 в том
же листинге является реакцией на нажатие кнопки Load, которая располагается в правом нижнем углу формы.
Обрабатывается обычным образом (см. пункт 9).
19.
В листинге 8.21
функции 34, 35, 36 и 37 обрабатываются по методике пункта 16, но при этом
события выбираются для различных панелей окна pBox.
20.
В листинге 8.22
функцию 38 следует просто переписать в новый файл. Фкункция 39 является
реакцией на действия мышки для кнопки LMS, а функция 40 – реакция на нажатие (OnClick), нужно выбрать событие, соответствующее названию
функции.
Дальнейшие действия обычные.
q
Сначала выбираем в меню ProjectèMake MINI и
исправляем ошибки;
q
затем выбираем ProjectèBuild MINI – создаем исполняемый файл;
q
Запускаем
программу командой RunèRun. Наэкране
должно появиться главное окно программы, изображенное на рис. 8.4.
Рис. 8.4. Главное окно программы MiniMain
Если вы удачно справились с
созданием проекта программы MiniMain, то должны еще раз прочитать Заключение и продолжать
занятия программированием.