C++ CSS HTML Java JavaScript MySQL Oracle PERL PHP SQL Unix VBScript XHTML XML Сети
8. Экранные библиотеки и работа с видеопамятью.
 
8.17.  Функциональные клавиши большинства дисплеев посылают в линию не один,  а  нес-
колько  символов. Например на терминалах, работающих в системе команд стандарта ANSI,
кнопки со стрелками посылают такие последовательности:

	стрелка вверх  "\033[A"  кнопка Home  "\033[H"
	стрелка вниз   "\033[B"  кнопка End   "\033[F"
	стрелка вправо "\033[C"  кнопка PgUp  "\033[I"
	стрелка влево  "\033[D"  кнопка PgDn  "\033[G"

(поскольку первым символом управляющих  последовательностей  обычно  является  символ
'\033' (escape), то их называют еще escape-последовательностями).  Нам же в программе
удобно воспринимать такую последовательность как единственный дисплея на вводе).

____________________
   [**] Режимы преобразований, символы редактирования, и.т.п. управляются системным  вы-
зовом ioctl. Большой пример на эту тему есть в приложении.
   [*][*] Если ваша программа завершилась аварийно и моды терминала остались в "странном"
состоянии, то привести терминал в чувство можно командой stty sane

	Самым интересным является то, что одиночный символ '\033' тоже  может  прийти  с
клавиатуры  - его посылает клавиша Esc. Поэтому если мы строим распознаватель клавиш,
который при поступлении кода 033 начинает ожидать составную последовательность  -  мы
должны  выставлять таймаут, например alarm(1); и если по его истечении больше никаких
символов не поступило - выдавать код 033 как код клавиши Esc>= 0400.
Учтите, что разные типы дисплеев посылают разные последовательности от одних и тех же
функциональных  клавиш: предусмотрите настройку на систему команд ДАННОГО дисплея при
помощи библиотеки termcap -  возвращаем  код,  приписанный
этому листу):

	---> '\033' ---> '[' ---> 'A' --> выдать 0400
	|	\--> 'B' -->	0401
	|	\--> 'C' -->	0402
	|	\--> 'D' -->	0403
	\--> 'X' ----------->	0404
	...

Нужное дерево стройте при настройке на систему команд данного дисплея.
	Библиотека curses уже имеет такой встроенный  распознаватель.   Чтобы  составные
последовательности склеивались в специальные коды, вы должны установить режим keypad:

	int c; WINDOW *window;
	...
	keypad(window, TRUE);
	...
	c = wgetch(window);

Без этого wgetch() считывает все символы поодиночке.   Символические  названия  кодов
для  функциональных  клавиш  перечислены в <curses.h> и имеют вид KEY_LEFT, KEY_RIGHT
и.т.п.  Если вы работаете с единственным окном размером с весь экран, то  в  качестве
параметра  window вы должны использовать стандартное окно stdscr (это имя предопреде-
лено в include-файле curses.h).

	# ======================================== Makefile для getch
	getch: getch.o
	cc getch.o -o getch -ltermlib

	getch.o: getch.c getch.h
	cc -g -DUSG -c getch.c



	
	#define INPUT_CHANNEL   0
	#define OUTPUT_CHANNEL  1

	#define KEY_DOWN	0400
	#define KEY_UP	0401
	#define KEY_LEFT	0402
	#define KEY_RIGHT	0403

	#define KEY_PGDN	0404
	#define KEY_PGUP	0405

	#define KEY_HOME	0406
	#define KEY_END	0407

	#define KEY_BACKSPACE 0410
	#define KEY_BACKTAB   0411

	#define KEY_DC	0412
	#define KEY_IC	0413

	#define KEY_DL	0414
	#define KEY_IL	0415

	#define KEY_F(n)	(0416+n)

	#define ESC	' 33'
	extern char *tgetstr();

	 lgetch(BOOLEAN);
	int ggetch(BOOLEAN);
	int kgetch(void);
	void _sigalrm(int n);
	void init_keytry(void);
	void add_to_try(char *str, short code);
	void keypad_on(void);
	void keypad_off(void);
	int dotest(void);
	void tinit(void);
	void main(void);


	/* ===================================================== getch.c
	*	The source version of getch.c file was
	*	written by Pavel Curtis.
	*
	*/

	<stdio.h>
	#include <signal.h>
	#include <setjmp.h>
	#include <termios.h>
	#include <ctype.h>
	#include <string.h>
	#include <locale.h>
	#include "getch.h"

	#define keypad_local   S[0]
	#define keypad_xmit	S[1]

	#define key_backspace  S[2]
	#define key_backtab	S[3]

	#define key_left	S[4]
	#define key_right	S[5]
	#define key_up	S[6]
	#define key_down	S[7]

	#define key_ic	S[8]
	#define key_dc	S[9]
	#define key_il	S[10]
	#define key_dl	S[11]

	
	#define key_f10	S[21]	/*  f0 */
	#define key_f11	S[22]	/* f11 */
	#define key_f12	S[23]	/* f12 */
	#define key_home	S[24]
	#define key_end	S[25]
	#define key_npage	S[26]
	#define key_ppage	S[27]

	#define TOTAL 28

	/* descriptors for keys */
	char *KEYS[TOTAL+1] = {
	"ke", "ks",

	"kb", "kB",
	"kl", "kr", "ku", "kd",
	"kI", "kD", "kA", "kL",

	"f1", "f2", "f3", "f4", "f5",
	"f6", "f7", "f8", "f9", "f0",
	"f.", "f-",

	"kh", "kH", "kN", "kP",

	NULL

	}, *S[TOTAL];
	void _put (char c)  { write( INPUT_CHANNEL, &c, 1 ); }
	void _puts(char *s) { tputs ( s, 1, _put ); }

	static int  _backcnt = 0;
	static char _backbuf[30];

	static struct try {
	struct try *child;
	struct try *sibling;
	char ch;
	short value;
	}	*_keytry;

	BOOLEAN keypadok = FALSE;

	struct termios new_modes;

	void keyboard_access_denied(){ printf( "Клавиатура недоступна.\n" ); exit(1); }
	char *strdup(const char *s)  { return strcpy((char *) malloc(strlen(s)+1), s); }




	/* Инициализация таблицы строк */
	void keyinit(){
	char *key, nkey[80], *p;
	register i;

	keyreset();
	for( i=0; i < TOTAL; i++ ){
	p = nkey;
	printf("tgetstr(%s)...", KEYS[i]);
	key = tgetstr(KEYS[i], &p);

	

	init_keytry();
	if( tcgetattr(INPUT_CHANNEL, &new_modes) < 0 ){
	keyboard_access_denied();
	}

	/* input flags */

	&= ~ICRNL;
	if ((new_modes.c_cflag & CSIZE) == CS8)  /* 8-битный код */
	new_modes.c_iflag &= ~ISTRIP;	/* отменить & 0177 на вводе */

	/* output flags */

	/* отменить TAB3 - замену табуляций '\t' на пробелы */
	/* отменить ONLCR - замену '\n' на пару '\r\n' на выводе */
	new_modes.c_oflag &= ~(TAB3 | ONLCR);

	/* local flags */

	/* выключить режим ICANON, включить CBREAK */
	/* выключить эхоотображение набираемых символов */
	new_modes.c_lflag &= ~(ICANON | ECHO);

	/* control chars */	/* при вводе с клавиш ждать не более ... */
	new_modes.c_cc[VMIN]  = 1;  /* 1 символа и */
	new_modes.c_cc[VTIME] = 0;  /* 0 секунд	*/
	/* Это соответствует режиму CBREAK */

	/* Символы, нажатие которых заставляет драйвер терминала послать сигнал
	* либо отредактировать набранную строку. Значение 0 означает,
	* что соответствующего символа не будет */
	new_modes.c_cc[VINTR]  = '\0'; /* символ, генерящий SIGINT  */
	new_modes.c_cc[VQUIT]  = '\0'; /* символ, генерящий SIGQUIT */
	new_modes.c_cc[VERASE] = '\0'; /* забой (отмена последнего символа)*/
	new_modes.c_cc[VKILL]  = '\0'; /* символ отмены строки	*/
	}

	/* Чтение одного символа непосредственно с клавиатуры */
	int getc_raw(){
	int n; char c;

	n = read(INPUT_CHANNEL, &c, 1);
	if (n <= 0) return EOF;
	return (c & 0xFF);
	}
	static BOOLEAN  _getback  = FALSE;
	static char	_backchar = '\0';

	/* Чтение символа - либо из буфера (если не пуст), либо с клавиатуры */
	#define nextc()	(_backcnt > 0  ?  _backbuf[--_backcnt]	: \
	_getback	?  _getback = FALSE, _backchar  : \
	getc_raw())

	#define putback(ch)   _backbuf[_backcnt++] = ch

	void keyreset(){
	_backcnt = 0; _backchar = '\0';
	_getback = FALSE;
	}
	/* Функция чтения составного символа */
	int getch вернет 0 и errno == EINTR.
	В этом случае getch() вернет '\0'.
	Чтобы избежать этой ситуации используется функция lgetch()
	*/
	int lgetch(BOOLEAN kpad) {
	int c;

	while((c = ggetch(kpad)) <= 0);
	return c;
	}
	int ggetch(BOOLEAN kpad) {
	int kgetch();

	if( kpad ) keypad_on();
	else	keypad_off();

	return keypadok ? kgetch() : nextc();
	}
	/*
	**	int kgetch()
	**
	**	Get an input character, but take care of keypad sequences, returning
	**	an appropriate code when one matches the input.  After each character
	**	is received, set a one-second alarm call.  If no more of the sequence
	**	is received by the time the alarm goes off, pass through the sequence
	**	gotten so far.
	**
	*/

	#define CRNL(c)	(((c) == '\r') ? '\n' : (c))

	/* борьба с русской клавиатурой */
	#if !defined(XENIX) || defined(VENIX)
	# define unify(c) ( (c)&(( (c)&0100 ) ? ~0240 : 0377 ))
	#else
	# define unify(c) (c)
	#endif





	/* ==================================================================== */
	#if !defined(XENIX) && !defined(USG) && !defined(M_UNIX) && !defined(unix)

	/* Для семейства BSD */

	static BOOLEAN   alarmed;
	jmp_buf	jbuf;

	int kgetch()
	{
	register struct try  *ptr;
	int	ch;
	char	buffer[10];	/* Assume no sequences longer than 10 */
	register char	*bufp = buffer;
	void	(*oldsig)();
	void	_sigalrm();

	ptr = _keytry;

	oldsig = signal(SIGALRM, _sigalrm);
	alarmed = FALSE;

	if( setjmp( jbuf )) /* чтоб свалиться сюда с read-а */
	ch = EOF;

	do
	{
	if( alarmed )
	break;
	ch = nextc();
	if (ch != EOF)	/* getc() returns EOF on error, too */
	*(bufp++) = ch;
	if (alarmed)
	break;

	while (ptr != (struct try *)NULL &&
	(ch == EOF || unify(CRNL(ptr->ch)) != unify(CRNL(ch))  ))
	ptr = ptr->sibling;

	if (ptr != (struct try *)NULL)
	{
	if (ptr->value != 0)
	{
	alarm(0);
	signal(SIGALRM, oldsig);
	return(ptr->value);
	}
	else
	{
	ptr = ptr->child;
	alarm(1);
	}
	}

	} while (ptr != (struct try *)NULL);

	alarm(0);
	signal(SIGALRM, oldsig);

	if (ch == EOF && bufp == buffer)
	return ch;

	while (--bufp > buffer)
	putback(*bufp);
	return(*bufp & 0377);
	}
	void _sigalrm(int n)
	{
	alarmed = TRUE;
	longjmp(jbuf, 1);
	}







	/* ==================================================================== */
	#else   /* XENIX or USG */

	/* Для семейства SYSTEM V */

	static  BOOLEAN alarmed;

	int kgetch()
	{
	register struct try  *ptr;
	int	ch;
	char	buffer[10];	/* Assume no sequences longer than 10 */
	register char	*bufp = buffer;
	void	(*oldsig)();
	void	_sigalrm();

	ptr = _keytry;

	oldsig = signal(SIGALRM, _sigalrm);
	alarmed = FALSE;

	do
	{
	ch = nextc();
	if (ch != EOF)	/* getc() returns EOF on error, too */
	*(bufp++) = ch;
	if (alarmed)
	break;

	while (ptr != (struct try *)NULL &&
	(ch == EOF || unify(CRNL(ptr->ch)) != unify(CRNL(ch))  ))
	ptr = ptr->sibling;

	if (ptr != (struct try *)NULL)
	{
	if (ptr->value != 0)
	{
	alarm(0);
	signal(SIGALRM, oldsig);
	return(ptr->value);
	}
	else
	{
	ptr = ptr->child;
	alarm(1);
	}
	}

	} while (ptr != (struct try *)NULL);

	alarm(0);
	signal(SIGALRM, oldsig);

	if (ch == EOF && bufp == buffer)
	return ch;
	while (--bufp > buffer)
	putback(*bufp);
	return(*bufp & 0377);
	}

	void _sigalrm(int n)
	{
	alarmed = TRUE;
	signal(SIGALRM, _sigalrm);
	}

	#endif /*XENIX*/
	/* ==================================================================== */
	/*
	**	init_keytry()
	**	Построение дерева разбора последовательностей символов.
	**
	*/

	void init_keytry()
	{
	_keytry = (struct try *) NULL;

	add_to_try(key_backspace, KEY_BACKSPACE);
	add_to_try("\b",	KEY_BACKSPACE);
	add_to_try("\177",	KEY_BACKSPACE);

	add_to_try(key_backtab,   KEY_BACKTAB);
	add_to_try(key_dc,	KEY_DC);
	add_to_try(key_dl,	KEY_DL);
	add_to_try(key_down,	KEY_DOWN);

		KEY_F(7));
	add_to_try(key_f8,	KEY_F(8));
	add_to_try(key_f9,	KEY_F(9));
	add_to_try(key_f10,	KEY_F(10));
	add_to_try(key_f11,	KEY_F(11));
	add_to_try(key_f12,	KEY_F(12));
	add_to_try(key_home,	KEY_HOME);
	add_to_try(key_ic,	KEY_IC);
	add_to_try(key_il,	KEY_IL);
	add_to_try(key_left,	KEY_LEFT);
	add_to_try(key_npage,	KEY_PGDN);
	add_to_try(key_ppage,	KEY_PGUP);
	add_to_try(key_right,	KEY_RIGHT);
	add_to_try(key_up,	KEY_UP);
	add_to_try(key_end,	KEY_END);
	}

	void add_to_try(char *str, short code)
	{
	static BOOLEAN  out_of_memory = FALSE;
	struct try	*ptr, *savedptr;

	if (str == NULL || out_of_memory)
	return;

	if (_keytry != (struct try *) NULL)
	{
	ptr = _keytry;

	for (;;)
	{
	while (ptr->ch != *str  &&  ptr->sibling != (struct try *)NULL)
	ptr = ptr->sibling;

	>ch == *str)
	{
	if (*(++str))
	{
	if (ptr->child != (struct try *)NULL)
	ptr = ptr->child;
	else
	break;
	}
	else
	{
	ptr->value = code;
	return;
	}
	}
	else
	{
	if ((ptr->sibling =
	(struct try *) malloc(sizeof *ptr)) == (struct try *)NULL)
	{
	out_of_memory = TRUE;
	return;
	}

	savedptr = ptr = ptr->sibling;
	ptr->child = ptr->sibling = (struct try *)NULL;
	ptr->ch = *str++;
	ptr->value = 0;

	break;
	}
	} /* end for (;;) */
	}
	else	/* _keytry == NULL :: First sequence to be added */
	{
	savedptr = ptr = _keytry = (struct try *) malloc(sizeof *ptr);

	if (ptr == (struct try *) NULL)
	{
	out_of_memory = TRUE;
	return;
	}

	ptr->child = ptr->sibling = (struct try *) NULL;

	ptr->ch = *(str++);
	ptr->value = 0;
	}

	/* at this point, we are adding to the try.  ptr->child == NULL */

	while (*str)
	{
	ptr->child = (struct try *) malloc(sizeof *ptr);

	ptr = ptr->child;

	if (ptr == (struct try *)NULL)
	{
	out_of_memory = TRUE;

	ptr = savedptr;
	while (ptr != (struct try *)NULL)
	{
	savedptr = ptr->child;
	free(ptr);
	ptr = savedptr;
	}

	return;
	}

	ptr->child = ptr->sibling = (struct try *)NULL;
	ptr->ch = *(str++);
	ptr->value = 0;
	}

	ptr->value = code;
	return;
	}
	/* Включение альтернативного режима клавиатуры */
	void keypad_on(){
	if( keypadok ) return;
	keypadok = TRUE;
	if( keypad_xmit ) _puts( keypad_xmit );
	}

	/* Включение стандартного режима клавиатуры */
	void keypad_off(){
	if( !keypadok ) return;
	keypadok = FALSE;
	if( keypad_local ) _puts( keypad_local );
	}


	/* Тестовая функция */
	int dotest()
	{
	struct termios saved_modes;
	int c;
	char *s;
	char keyname[20];

	if( tcgetattr(INPUT_CHANNEL, &saved_modes) < 0 ){
	err:	keyboard_access_denied();
	}
	if( tcsetattr(INPUT_CHANNEL, TCSADRAIN, &new_modes) < 0 )
	goto err;

	keyreset();

	for(;;){
	c = getch();

	
	case KEY_PGUP:	s = "K_PGUP"  ; break;
	case KEY_HOME:	s = "K_HOME"  ; break;
	case KEY_END:	s = "K_END"   ; break;
	case KEY_BACKSPACE: s = "K_BS"	; break;
	case '\t':	s = "K_TAB"   ; break;
	case KEY_BACKTAB:   s = "K_BTAB"  ; break;
	case KEY_DC:	s = "K_DEL"   ; break;
	case KEY_IC:	s = "K_INS"   ; break;
	case KEY_DL:	s = "K_DL"	; break;
	case KEY_IL:	s = "K_IL"	; break;

	case KEY_F(1):	s = "K_F1"	; break;
	case KEY_F(2):	s = "K_F2"	; break;
	case KEY_F(3):	s = "K_F3"	; break;
	case KEY_F(4):	s = "K_F4"	; break;
	case KEY_F(5):	s = "K_F5"	; break;
	case KEY_F(6):	s = "K_F6"	; break;
	case KEY_F(7):	s = "K_F7"	; break;
	case KEY_F(8):	s = "K_F8"	; break;
	case KEY_F(9):	s = "K_F9"	; break;
	case KEY_F(10):	s = "K_F10"   ; break;
	case KEY_F(11):	s = "K_F11"   ; break;
	case KEY_F(12):	s = "K_F12"   ; break;

	case ESC:	s = "ESC"	; break;
	case EOF:	s = "K_EOF"   ; break;
	case '\r':	s = "K_RETURN"; break;
	case '\n':	s = "K_ENTER" ; break;
	default:
	s = keyname;
	if( c >= 0400 ){
	sprintf(keyname, "K_F%d", c - KEY_F(0));
	} else if( iscntrl(c)){
	sprintf(keyname, "CTRL(%c)", c + 'A' - 1);
	} else {
	sprintf(keyname, "%c", c );

	}
	}
	printf("Клавиша: %s\n\r", s);

	if(c == ESC)
	break;
	}
	tcsetattr(INPUT_CHANNEL, TCSADRAIN, &saved_modes);
	}
	/* Функция настройки на систему команд дисплея */
	void tinit (void) {
	/* static */ char Tbuf[2048];
	/* Tbuf должен сохраняться все время, пока могут вызываться функции tgetstr().
	* Для этого он либо должен быть static, либо вызов функции keyinit()
	* должен находиться внутри tinit(), что и сделано.
	*/
	char *tname;
	extern char *getenv();

	if((tname = getenv("TERM")) == NULL){
	printf("TERM не определено: неизвестный тип терминала.\n");
	exit(2);
	}
	printf("Терминал: %s\n", tname);

	/* Прочесть описание терминала в Tbuf */
	switch (tgetent(Tbuf, tname)) {
	case -1:
	printf ("Нет файла TERMCAP (/etc/termcap).\n");
	exit (1);
	case 0:
	printf ("Терминал '%s' не описан.\n", tname);
	exit (2);
	case 1:
	break;	/* OK */
	}
	if(strlen(Tbuf) >= 1024)
	printf("Описание терминала слишком длинное - возможны потери в конце описания\n");

	keyinit();  /* инициализировать строки, пока Tbuf[] доступен */
	}
	void main(void){
	setlocale(LC_ALL, "");
	tinit();
	/* keyinit(); */
	dotest();
	exit(0);
	}

 В таком поисковом алгоритме не требуются таймауты,  необ-
ходимые  при  вводе  с  клавиатуры,  поскольку есть явные терминаторы строк - символы
'\0', которых нет при вводе с клавиатуры.   В  чем  эффективность  такого  алгоритма?
Сравним последовательный перебор при помощи strcmp и поиск в дереве букв:

	"zzzzzzzzzza"
	"zzzzzzzzzzb"
	"zzzzzzzzzzbx"
	"zzzzzzzzzzc"
	"zzzzzzzzzzcx"

Для линейного перебора (даже в отсортированном  массиве)  поиск  строки  zzzzzzzzzzcx
потребует

	zzzzzzzzzza	|	11 сравнений, отказ
	zzzzzzzzzzb	|	11 сравнений, отказ
	zzzzzzzzzzbx	|	12 сравнений, отказ
	zzzzzzzzzzc	|	11 сравнений, отказ
	zzzzzzzzzzcx	V	12 сравнений, успех

Всего: 57 шагов.  Для поиска в дереве:

	__z__z__z__z__z__z__z__z__z__z__a__\0
	|_b__\0
	|  |_x__\0
	|
	|_c__\0
	|_x__\0

потребуется проход вправо (вниз) на 10 шагов, потом выбор среди 'a','b','c', потом  -
выбор среди '\0' и 'x'.  Всего: 15 шагов. За счет того, что общий "корень" проходится
ровно один раз, а не каждый раз заново. Но это и требует  предварительной  подготовки
данных: превращения строк в дерево!

8.18.  Напишите функцию для  "экранного"  редактирования  вводимой  строки  в  режиме
CBREAK.  Напишите  аналогичную  функцию  на curses-е.  В curses-ной версии надо уметь
отрабатывать: забой (удаление символа перед курсором), отмену всей  строки,  смещение
влево/вправо  по строке, удаление символа над курсором, вставку пробела над курсором,
замену символа, вставку символа, перерисовку экрана.  Учтите, что параллельно с изме-
нением  картинки  в  окне,  вы  должны вносить изменения в некоторый массив (строку),
которая и будет содержать результат. Эта строка должна быть аргументом функции редак-
тирования.
	Забой можно упрощенно эмулировать как

	addstr( "\b \b" );
	или
	addch( '\b' ); delch();

Недостатком этих способов является некорректное поведение в начале строки (при x==0).
Исправьте это!

8.19.  На curses-е напишите функцию редактирования текста  в  окне.   Функция  должна
возвращать  массив  строк с обрезанными концевыми пробелами. Вариант: возвращать одну
строку, в которой строки окна разделяются символами '\n'.

8.20.  Напишите функцию, рисующую прямую линию из точки (x1,y1) в (x2,y2).  Указание:
используйте  алгоритм  Брезенхема  (минимального  отклонения).   Ответ: пусть функция
putpixel(x,y,color) рисует точку в координатах (x,y) цветом color.

	void line(int x1, int y1,   int x2, int y2,
	int color){
	int dx, dy, i1, i2, i, kx, ky;
	register int d; /* "отклонение" */
	register int x, y;
	short /* boolean */ l;
	dy = y2 - y1; dx = x2 - x1;
	if( !dx && !dy ){
	putpixel(x1,y1, color); return;
	}
	kx = 1; /* шаг по x */
	ky = 1; /* шаг по y */
	/* Выбор тактовой оси */
	if( dx < 0 ){ dx = -dx; kx = -1; } /* Y */
	else if( dx == 0 )	kx = 0;	/* X */
	if( dy < 0 ){ dy = -dy; ky = -1; }
	if( dx < dy ){ l = 0; d = dx; dx = dy; dy = d; }
	else	l = 1;

	i1 = dy + dy; d = i1 - dx; i2 = d - dx;
	x = x1; y = y1;

	for( i=0; i < dx; i++ ){
	putpixel( x, y, color );

	if( l ) x += kx; /* шаг по такт. оси   */
	else	y += ky;
	if( d < 0 ) /* горизонтальный шаг	*/
	d += i1;
	else{	/* диагональный шаг	*/
	d += i2;
	if( l ) y += ky; /* прирост высоты */
	else	x += kx;
	}
	}
	putpixel(x, y, color);  /* последняя точка  */
	}
8.21.  Составьте программу, которая строит график функции sin(x) на отрезке от  0  до
2*пи.   Учтите  такие вещи: соседние точки графика следует соединять отрезком прямой,
чтобы график выглядел непрерывным; не забывайте приводить double к int, т.к.  коорди-
наты пикселов[*] - целые числа.

8.22.  Напишите функцию, которая заполняет в массиве байт count бит подряд, начиная с
x-ого бита от левого края массива:

	байт 0	|	байт 1
	7 6 5 4 3 2 1 0 | 7 6  5  4  3  2  1  0 : биты в байте
	0 1 2 3 4 5 6 7 | 8 9 10 11 12 13 14 15 : x
	==========================
	x=2, count=11

Такой алгоритм используется в растровой машинной графике для рисования горизонтальных
прямых  линий  (тогда  массив  - это видеопамять компьютера, каждый бит соответствует
пикселу на экране).
	Ответ (причем мы заполняем биты не просто единицами, а "узором" pattern):

	void horizLine(char *addr,int x,int count,char pattern){
	static char masks[8] = {
	0xFF, 0x7F, 0x3F, 0x1F, 0x0F, 0x07, 0x03, 0x01 };
	/* индекс в этом массиве равен числу 0-битов слева */
____________________
   [*] Пиксел (pixel, pel) - picture element, в машинной графике - точка растра на  эк-
ране.

	register i;
	char mask;
	short lbits, rbits; /* число битов слева и справа */
	short onebyte;	/* единственный байт ? */

	& 7 ];   /* x % 8 */
	if( count >= (rbits = 8 - lbits)){
	count -= rbits; onebyte = 0;
	}else{
	mask &= ~masks[ lbits = (x+count) & 7 ];
	onebyte = 1;
	}
	/* Первый байт */
	*addr = (*addr & ~mask) | (pattern & mask);
	addr++;
	/* Для pattern==0xFF можно просто
	*	*addr++ |= mask;
	* поскольку (a &~m)|(0xFF & m) = (a &~m) | m =
	*   (a|m) & (~m|m) = (a|m) & 0xFF = a | m
	* Почему здесь нельзя написать *addr++ = (*addr...) ?
	* Потому, что ++ может быть сделан ДО вычисления
	* правой части присваивания!
	*/
	if(onebyte) return;
	/* Средние байты */
	for(i = count/8; i > 0; --i)
	*addr++ = pattern;  /* mask==0xFF */
	/* Последний байт */
	if((lbits = count & 7) == 0) return;
	/* последний байт был полным */
	mask = ~masks[lbits];
	*addr = (*addr & ~mask) | (pattern & mask);
	}

Заметим, что для быстродействия подобные алгоритмы обычно пишутся на ассемблере.

8.23.  Напишите при помощи curses-а "электронные часы",  отображающие  текущее  время
большими  цифрами  (например, размером 8x8 обычных символов) каждые 5 секунд. Исполь-
зуйте alarm(), pause().

8.24.  Составьте программу, реализующую простой диалоговый интерфейс,  основанный  на
меню. Меню хранятся в текстовых файлах вида:



	файл menu2_12
	команда2_1
	команда2_2  #комментарий
	команда2_3
	альтернатива_3
	>menu2_2	#это переход в другое меню
	альтернатива_4
	>>menu3_7	#хранимое в файле menu3_7
	...
	...
	-----------------------------------------------

Программа должна обеспечивать: возврат к предыдущему меню по клавише Esc  (для  этого
следует  хранить "историю" вызовов меню друг из друга, например в виде "полного имени
меню":

	.rootmenu.menu1_2.menu2_4.menu3_1

где menuI_J - имена файлов с меню), обеспечить выход из программы по клавишам  'q'  и
ESC,  выдачу подсказки по F1, выдачу полного имени меню по F2.  Вызов меню при помощи
> означает замещение текущего меню новым, что соответствует замене  последней  компо-
ненты  в  полном  имени  меню.  Вызов  >> означает вызов меню как функции, т.е. после
выбора в новом меню и выполнения нужных действий автоматически должно быть выдано  то
меню, из которого произошел вызов (такой вызов соответствует удлинению полного имени,
а возврат из вызова - отсечению последней компоненты).  Этот вызов может быть показан
на  экране  как  появление нового "выскакивающего" окна поверх окна с предыдущим меню
(окно возникает чуть сдвинутым - скажем, на y=1 и x=-2), а возврат - как исчезновение
этого окна.  Заголовок меню должен высвечиваться в верхней строке меню:

	|-------------------
	|--ЗАГОЛОВОК_МЕНЮ---- |
	|  альтернатива_1   | |
	|  альтернатива_2   | |
	| *альтернатива_3   | |
	|  альтернатива_4   |--
	---------------------

Сначала реализуйте версию, в которой каждой "альтернативе" соответствует единственная
строка   "команда".	Команды  следует  запускать  при  помощи  стандартной  функции
system(команда).
	Усложните функцию выбора в меню так, чтобы альтернативы можно было  выбирать  по
первой букве при помощи нажатия кнопки с этой буквой (в любом регистре):

	Compile
	Edit
	Run program
8.25.  Напишите на curses-е функцию, реализующую выбор в меню  -  прямоугольной  таб-
лице:
	слово1   слово4   слово7
	слово2  *слово5   слово8
	слово3   слово6

Строки - элементы меню - передаются в функцию выбора в  виде  массива  строк.   Число
элементов  меню  заранее  неизвестно и должно подсчитываться внутри функции.  Учтите,
что все строки могут не поместиться в таблице, поэтому надо предусмотреть  "прокручи-
вание"  строк  через  таблицу  при  достижении  края меню (т.е. таблица служит как бы
"окошком" через которое мы обозреваем таблицу большего  размера,  возможно  перемещая
окно  над ней).  Предусмотрите также случай, когда таблица оказывается заполненной не
полностью (как на рисунке).

8.26.  Используя библиотеку curses, напишите программу, реализующую клеточный автомат
Конвея  "Жизнь". Правила: есть прямоугольное поле (вообще говоря бесконечное, но при-
нято в конечной модели замыкать края в кольцо), в котором живут  "клетки"  некоторого
организма.  Каждая имеет 8 соседних полей. Следующее поколение "клеток" образуется по
таким правилам:
-	если "клетка" имеет 2 или 3 соседей - она выживает.
-	если "клетка" имеет меньше 2 или больше 3 соседей - она погибает.
-	в пустом поле, имеющем ровно 3х живых соседей, рождается новая "клетка".

Предусмотрите: редактирование поля, случайное заполнение  поля,  останов  при  смерти
всех "клеток", останов при стабилизации колонии.

8.27.  При помощи curses-а напишите экранный редактор кодов доступа к файлу (в  форме
rwxrwxrwx).   Расширьте  программу, позволяя редактировать коды доступа у группы фай-
лов, изображая имена файлов и коды доступа в виде таблицы:

	НАЗВАНИЕ   КОДЫ ДОСТУПА
	файл1	rwxrw-r--
	файл2	rw-r-xr-x
	файл3	rwxrwxr--

Имена файлов задавайте как аргументы для main().  Указание: используйте для получения
текущих  кодов  доступа  системный вызов stat(), а для их изменения - системный вызов
chmod().

[Назад] [Содержание] [Вперед]

Главная