Terminal Programming: 두 판 사이의 차이
imported>Drake 잔글편집 요약 없음 |
imported>Drake (→화면 이동) |
||
| (같은 사용자의 중간 판 14개는 보이지 않습니다) | |||
| 696번째 줄: | 696번째 줄: | ||
int readch(); | int readch(); | ||
/* main함수는 터미널을 설정하기 위해 init_keyboard를 호출하고 나서 1초에 1번씩 kbhit을 실행한다. 'q'를 입력하면 close_keyboard가 호출되어 터미널을 정상적으로 설정한 후에 프로그램을 종료한다. | /* main함수는 터미널을 설정하기 위해 init_keyboard를 호출하고 나서 1초에 1번씩 kbhit을 실행한다. 'q'를 입력하면 close_keyboard가 호출되어 터미널을 정상적으로 설정한 후에 프로그램을 종료한다. */ | ||
int main() | int main() | ||
{ | { | ||
| 752번째 줄: | 752번째 줄: | ||
} | } | ||
/* 눌린 문자는 다음번 readch함수가 읽어들이고, 다음 루프를 위해 peek_character를 -1로 설정 | /* 눌린 문자는 다음번 readch함수가 읽어들이고, 다음 루프를 위해 peek_character를 -1로 설정 */ | ||
int readch() | int readch() | ||
{ | { | ||
| 764번째 줄: | 764번째 줄: | ||
read(0, &ch, 1); | read(0, &ch, 1); | ||
return ch; | return ch; | ||
} | |||
</source> | |||
== ncurses == | |||
=== Hello world === | |||
<source lang="c"> | |||
/* curses_hello.c */ | |||
#include <unistd.h> | |||
#include <curses.h> | |||
int main() { | |||
initscr(); /* Init curses */ | |||
move(5, 15); | |||
printw("%s", "Hello World"); | |||
refresh(); | |||
sleep(2); | |||
endwin(); /* de-init curses */ | |||
return 0; | |||
} | |||
</source> | |||
=== 초기화와 종료 === | |||
<source lang="c"> | |||
#include <curses.h> | |||
WINDOW *initscr(void); | |||
int endwin(void); | |||
</source> | |||
* initscr은 한번만 호출되어야 함. 성공시 stdscr 구조체 반환, 실패하면 에러메세지를 내고 종료 | |||
* endwin은 성공시 OK 실패시 ERR 반환. | |||
* clearok(stdscr, 1)이랑 refresh를 호출해서 재개가능하다. | |||
* curses는 WINDOW구조체 내부에 직접 접근하는 방법을 제공하지 않는다. | |||
=== 출력 === | |||
<source lang="c"> | |||
#include <curses.h> | |||
int addch(const chtype char_to_add); | |||
int addchstr(chtype *const string_to_add); | |||
int printw(char *format, ...); | |||
int refresh(void); | |||
int box(WINDOW *win_ptr, chtype vertical_char, chtype horizontal_char); | |||
int insch(chtype char_to_insert); | |||
int insertln(void); | |||
int delch(void); | |||
int deleteln(void); | |||
int beep(void); | |||
int flash(void); | |||
</source> | |||
* curses에는 이상한 데이터 타입으로 chtype이 있다. unsigned long 타입임. | |||
* addch, addchstr은 현재위치에 출력, printw는 printw처럼 쓰면 된다. | |||
* refresh는 화면갱신. 성공하면 OK, 에러는 ERR 반환. box는 윈도우 둘레에 상자를 그림. | |||
* insch는 문자 삽입하고 오른쪽에 문자들을 밀어버린다. | |||
* delete는 문자지우기. beep는 소리내기, flash함수는 깜빡이기. | |||
=== 읽기 === | |||
<source lang="c"> | |||
#include <curses.h> | |||
chtype inch(void); | |||
int instr(char *string); | |||
int innstr(char *string, int number_of_characters); | |||
</source> | |||
* inch는 현재 커서가 가리키는 좌표와 문자를 반환 | |||
* instr, innstr은 읽어들인 문자를 char 배열에 기록한다. | |||
=== 화면 지우기 === | |||
<source lang="c"> | |||
#include <curses.h> | |||
int erase(void); | |||
int clear(void); | |||
int clrtobot(void); | |||
int clrtoeol(void); | |||
</source> | |||
* erase, clear는 화면 전체를 공백으로 채운다. clear는 리프레시가 추가된것. | |||
* clrtobot는 커서 위치부터 화면 끝까지(세로), clrtoeol은 커서 위치부터 줄 끝까지(가로) 지운다. | |||
=== 화면 이동 === | |||
<source lang="c"> | |||
#include <curses.h> | |||
int move(int new_y, int new_x); | |||
int leaveok(WINDIW *window_ptr, bool leave_flag); | |||
</source> | |||
* move는 지정한 위치로 이동시킨다. 좌측상단 구석은 0,0 이다. | |||
* leaveok는 화면갱신후 물리커서를 어디에 둘건지 제어한다. default는 false. [[Win32API]]의 Active Window 개념이라고 보면 쉬울듯 | |||
=== attribute === | |||
<source lang="c"> | |||
#include <curses.h> | |||
int attron(chtype attribute); | |||
int attroff(chtype attribute); | |||
int attrset(chtype attribute); | |||
int standout(void); | |||
int standend(void); | |||
</source> | |||
* 이런 속성이 있다. | |||
# A_BLINK - 깜빡임 | |||
# A_BOLD - 볼드체 | |||
# A_DIM - 어두운색 | |||
# A_REVERSE - 역상. <font style="color:#FFFFFF; background-color:#000000;">Reverse</font> 이런것 | |||
# A_STANDOUT - 밝은색 | |||
# A_UNDERLINE - 밑줄 | |||
* attrset은 curses의 속성을 설정 | |||
* attron, attroff는 지정 속성을 켜거나 끈다. | |||
<source lang="c"> | |||
/* moveadd.c */ | |||
#include <unistd.h> | |||
#include <curses.h> | |||
int main() { | |||
const char witch_one[] = " First Witch "; | |||
const char witch_two[] = " Second Witch "; | |||
const char *scan_ptr; | |||
initscr(); | |||
/* 세개의 텍스트 집합이 간격을 두고 화면상에 출력된다. 테스트 속성도 on, off한다. */ | |||
move(5, 15); | |||
attron(A_BOLD); | |||
printw("%s", "Macbeth"); | |||
attroff(A_BOLD); | |||
refresh(); | |||
sleep(1); | |||
move(8, 15); | |||
attron(A_DIM); | |||
printw("%s", "Thunder and Lightning"); | |||
attroff(A_DIM); | |||
refresh(); | |||
sleep(1); | |||
move(10, 10); | |||
printw("%s", "when shall we three meet again"); | |||
move(11, 23); | |||
printw("%s", "In thunder, ligning, or in rain?"); | |||
move(13, 10); | |||
printw("%s", "When the hurlyburly's done,"); | |||
move(14,23); | |||
printw("%s", "When the battle's lost and won."); | |||
refresh(); | |||
sleep(1); | |||
attron(A_DIM); | |||
scan_ptr = witch_one + strlen(witch_one); | |||
while(scan_ptr != which_two) { | |||
move(13, 10); | |||
insch(*scan_ptr--); | |||
} | |||
attroff(A_DIM); | |||
refresh(); | |||
sleep(1); | |||
endwin(); | |||
return 0; | |||
} | |||
</source> | |||
=== 키보드 === | |||
<source lang="c"> | |||
#include <curses.h> | |||
int getch(void); | |||
int getstr(char *string); | |||
int getnstr(char *string, int number_of_characters); | |||
int scanw(char *format, ...); | |||
</source> | |||
* getch는 getchar, getstr랑 getnstr은 gets, scanw는 scanf랑 사용법이 같다. | |||
<source lang="c"> | |||
#include <unistd.h> | |||
#include <curses.h> | |||
#include <string.h> | |||
#define PW_LEN 32 | |||
#define NAME_LEN 256 | |||
int main() { | |||
char name[NAME_LEN]; | |||
char password[PW_LEN]; | |||
char *real_password = "xyzzy"; | |||
int i = 0; | |||
initscr(); | |||
move(5, 10); | |||
printw("%s", "Please login: "); | |||
move(7, 10); | |||
printw("%s", "User name: "); | |||
getstr(name); | |||
move(9, 10); | |||
printw("%s", "Password: "); | |||
refresh(); | |||
/* 비번이 화면에 보이면 망하니까 안보이게 하자. */ | |||
cbreak(); | |||
noecho(); | |||
memset(password, '\0', sizeof(password)); | |||
while(i < PW_LEN) { | |||
password[i] = getch(); | |||
move(9, 20 + i); | |||
addch('*'); | |||
refresh(); | |||
if(password[i] = '\n') break; | |||
if(strcmp(password, real_password) == 0) break; | |||
i++; | |||
} /* while */ | |||
/* 키보드를 다시 원래대로 복구하고 메세지 출력 */ | |||
echo(); | |||
nocbreak(); | |||
move(11, 10); | |||
if(strcmp(password, real_password) == 0) printw("%s", "Correct"); | |||
else printw("%s", "Wrong"); | |||
refresh(); | |||
sleep(3); | |||
endwin(); | |||
return 0; | |||
} | |||
</source> | |||
=== 다중 윈도우 === | |||
<source lang="c"> | |||
#include <curses.h> | |||
WINDOW *newwin(int num_of_lines, int num_of_cols, int start_y, int start_x); | |||
int delwin(WINDOW *window_to_delete); | |||
</source> | |||
* newwin 함수는 새로운 윈도우를 생성한다. 윈도우의 좌측 상단은 (start_x, start_y)가 되고, 'num_of_lines'와 'num_of_cols'만큼의 크기를 가진다. 성공하면 윈도우에 대한 포인터를 반환하고, 실패하면 null을 반환한다. | |||
* 윈도우가 영역을 벗어나면 실패한다. | |||
* delwin은 newwin으로 만들어진 윈도우를 없애버린다. | |||
<source lang="c"> | |||
#include <curses.h> | |||
int waddch(WINDOW *window_pointer, const char char); | |||
int mvwaddch(WINDOW *window_pointer, int y, int x, const chtype char); | |||
int wprintw(WINDOW *window_pointer, char *format, ...); | |||
int mvwprintw(WINDOW *window_pointer, int y, int x, char *format, ...); | |||
int mvwin(WINDOW *window_to_move, int new_y, int new_x); | |||
int wrefresh(WINDOW *window_ptr); | |||
int wclear(WINDOW *window_ptr); | |||
int werase(WINDOW *window_ptr); | |||
int touchwin(WINDOW *window_ptr); | |||
int scrollok(WINDOW *window_ptr, bool scroll_flag); | |||
int scroll(WINDOW *window_ptr); | |||
</source> | |||
* waddch, mvwaddch, wprintw, mvwprintw, wrefresh, wclear, werase는 각각 addch, mvaddch, printw, mvprintw, refresh, clear, erase의 윈도우 버전이라고 생각하면 된다. | |||
* mvwin은 윈도우 위치를 변경한다. 범위를 벗어나면 실패. | |||
* touchwin은 Active Window를 선택 | |||
* scrollok에 scroll_flag에 true를 세우면 스크롤이 가능하다. | |||
<source lang="c"> | |||
/* multiw1.c */ | |||
#include <unistd.h> | |||
#include <curses.h> | |||
int main() | |||
{ | |||
WINDOW *new_window_ptr; | |||
WINDOW *popup_window_ptr; | |||
int x_loop; | |||
int y_loop; | |||
char a_letter = 'a'; | |||
initscr(); | |||
/* 기본 윈도우를 문자로 채우고 refresh. */ | |||
move(5,5); | |||
printw("%s", "Testing multiple windows"); | |||
refresh(); | |||
for(x_loop = 0; x_loop < COLS -1; x_loop++) { | |||
for(y_loop = 0; y_loop < LINES -1; y_loop++) { | |||
mvaddch(stdscr, y_loop, x_loop, a_letter); | |||
a_letter++; | |||
if(a_letter > 'z') a_letter = 'a'; | |||
} | |||
} | |||
refresh(); | |||
sleep(2); | |||
/* 10x20 크기의 새로운 윈도우를 만들고 텍스트를 조금 넣어본다. */ | |||
new_window_ptr = newwin(10, 20, 5, 5); | |||
mvprintw(new_window_ptr, 2, 2, "%s", "Hello World"); | |||
mvprintw(new_window_ptr, 5, 2, "%s", "Notice how very long lines wrap inside the window"); | |||
wrefresh(new_window_ptr); | |||
sleep(2); | |||
/* 배경윈도우의 내용을 바꾸고 화면을 리플레시할 때 new_window_ptr이 가리키는 윈도우는 숨겨진다. */ | |||
a_letter = '0'; | |||
for(x_loop = 0; x_loop < COLS -1; x_loop++) { | |||
for(y_loop = 0; y_loop < LINES -1; y_loop++) { | |||
mvaddch(stdscr, y_loop, x_loop, a_letter); | |||
a_letter++; | |||
if(a_letter > '9') a_letter = '0'; | |||
} | |||
} | |||
refresh(); | |||
sleep(2); | |||
/* 새로운 윈도우를 전혀 변경하지 않았으므로, 새로운 윈도우를 리프레시해도 아무것도 변하지 않는다. */ | |||
wrefresh(new_window_ptr); | |||
sleep(2); | |||
/* 윈도우를 touch해서 Active Window라고 하고 리프레시하면 짠 */ | |||
touchwin(new_window_ptr); | |||
wrefresh(new_window_ptr); | |||
sleep(2); | |||
/* 윈도우를 하나 만들어 겹치게 한다. */ | |||
popup_window_ptr = newwin(10, 20, 8, 8); | |||
box(popup_window_ptr, '|', '-'); | |||
mvwprintw(popup_window_ptr, 5, 2, "%s", "Pop Up Window!"); | |||
wrefresh(popup_window_ptr); | |||
sleep(2); | |||
/* 좀더 뭔가 해보고 윈도우를 날려보자. */ | |||
touchwin(new_window_ptr); | |||
wrefresh(new_window_ptr); | |||
sleep(2); | |||
wclear(new_window_ptr); | |||
wrefresh(new_window_ptr); | |||
sleep(2); | |||
delwin(new_window_ptr); | |||
touchwin(popup_window_ptr); | |||
wrefresh(popup_window_ptr); | |||
sleep(2); | |||
delwin(popup_window_ptr); | |||
touchwin(stdscr); | |||
refresh(); | |||
sleep(2); | |||
endwin(); | |||
return 0; | |||
} | |||
</source> | |||
=== 보조윈도우 === | |||
<source lang="c"> | |||
#include <curses.h> | |||
WINDOW *subwin(WINDOW *parent, int num_of_lines, int num_of_cols, int start_y, int start_x); | |||
</source> | |||
* 보조윈도우는 윈도우 안에 들어가는 윈도우다. MFC 기본 프로그램을 보면 창 안에 창이 여러개 들어가는걸 보여준다. 이것도 비슷한 개념이다. | |||
* newwin처럼 쓰면 된다. | |||
<source lang="c"> | |||
/* subscl.c */ | |||
#include <unistd.h> | |||
#include <curses.h> | |||
int main() | |||
{ | |||
WINDOW *sub_window_ptr; | |||
int x_loop; | |||
int y_loop; | |||
int counter; | |||
char a_letter = '1'; | |||
initscr(); | |||
for(x_loop = 0; x_loop < COLS -1; x_loop++) { | |||
for(y_loop = 0; y_loop < LINES -1; y_loop++) { | |||
mvaddch(stdscr, y_loop, x_loop, a_letter); | |||
a_letter++; | |||
if(a_letter > '9') a_letter = '1'; | |||
} | |||
} | |||
/* 스크롤 할 보조윈도우를 만들고, 리프레시하기전에 한번 만진다. */ | |||
sub_window_ptr = subwin(stdscr, 10, 20, 10, 10); | |||
scrollok(sub_window_ptr, 1); | |||
touchwin(stdscr); | |||
refresh(); | |||
sleep(1); | |||
/* 보조 윈도우의 내용을 지우고 보조 윈도우에 텍스트를 출력하고 리프레시한다. 스크롤하는 텍스트는 루프에 의해 만들어짐ㅋ */ | |||
werase(sub_window_ptr); | |||
mvwprintw(sub_window_ptr, 2, 0, "%s", "This window will now scroll"); | |||
wrefresh(sub_window_ptr); | |||
sleep(1); | |||
for(counter = 1; counter < 10; counter++) { | |||
wprintw(sub_window_ptr, "%s", "This text is both wrapping and scrolling."); | |||
wrefresh(sub_window_ptr); | |||
scroll(1); | |||
} | |||
/* 루프가 끝나면 보조윈도우를 제거하고 기본화면을 리프레시한다. */ | |||
delwin(sub_window_ptr); | |||
touchwin(stdscr); | |||
refresh(); | |||
sleep(1); | |||
endwin(); | |||
return 0; | |||
} | |||
</source> | |||
=== 키패드 === | |||
<source lang="c"> | |||
#include <curses.h> | |||
int keypad(WINDOW *window_ptr, bool keypad_on); | |||
</source> | |||
* keypad 함수에서 keypad_on을 true로 세우면 Keypad mode가 세팅되고, curses는 시퀀스키를 처리할 수 있다. | |||
* Keypad mode에서는 제한사항이 몇가지가 있었다.<s>요즘 컴터중에 텍스트 처리 한다고 느려지는건 ARM걸리는 ARM밖에 없지</s> | |||
# 이스케이프 시퀀스를 인식하는건 타이밍에 의존하게 되는 경우도 있는데, 예전에 느린 네트워크에서는 MTU때문에 오동작하던 때가 있었다. | |||
# 이스케이프 시퀀스의 처리는 일반적인 처리보다 많은 연산을 필요로 하는데, 요즘 PC에서는 신경쓰지 않아도 된다. | |||
# 중복 이스케이프 시퀀스 처리는 불가능하다. 하지만 그거야 raw data를 건들지 않으면 거의 발생하지 않을 문제다. | |||
<source lang="c"> | |||
/* keypad.c */ | |||
#include <curses.h> | |||
#define LOCAL_ESCAPE_KEY 27 | |||
int main() | |||
{ | |||
int key; | |||
initscr(); | |||
crmode(); | |||
keypad(stdscr, TRUE); | |||
/* echo off하고 약간 메세지를 출력한다. 프로그램은 키입력을 기다려서 'q'면 종료하고, 에러가 나지 않았으면 뭐가 눌렸는지 대충 출력해본다. */ | |||
noecho(); | |||
clear(); | |||
mvprintw(5, 5, "Key pad demonstration. Press 'q' to quit."); | |||
move(7, 5); | |||
refresh(); | |||
key = getch(); | |||
while(key != ERR && key != 'q') { | |||
move(7, 5); | |||
clrtoeol(); | |||
if((key >= 'A' && key <= 'Z') || (key >= 'a' && key <= 'z')) { | |||
printw("Key was %c", (char)key); | |||
} | |||
else { | |||
switch(key) { | |||
case LOCAL_ESCAPE_KEY: printw("%s", "Escape key"); break; | |||
case KEY_END: printw("%s", "END key"); break; | |||
case KEY_BEG: printw("%s", "BEGINNING key"); break; | |||
case KEY_RIGHT: printw("%s", "RIGHT key"); break; | |||
case KEY_LEFT: printw("%s", "LEFT key"); break; | |||
case KEY_UP: printw("%s", "UP key"); break; | |||
case KEY_DOWN: printw("%s", "DOWN key"); break; | |||
default: printw("Unmatched - %d", key); break; | |||
} /* switch */ | |||
} /* else */ | |||
refresh(); | |||
key = getch(); | |||
} /* while */ | |||
endwin(); | |||
return 0; | |||
} | |||
</source> | |||
=== 색깔 === | |||
* 원래 curses는 흑백이었다. | |||
<source lang="c"> | |||
#include <curses.h> | |||
bool has_colors(void); | |||
int start_color(void); | |||
int init_pair(short pair_number, int foreground, int background); | |||
int COLOR_PAIR(int pair_number); | |||
int pair_content(short pair_number, short *foreground, short *background); | |||
</source> | |||
* has_color는 색을 지원하는 터미널인지 확인하고 지원되면 true, 지원안되거나 에러면 false를 반환한다. | |||
* start_color는 제대로 초기화되면 OK, 실패하면 ERR을 반환한다. | |||
* start_color가 초기화되면 COLOR_PAIRS 변수에터미널이 제공할 수 있는 최대한의 색상 페어를 저장한다. | |||
* 색깔을 쓰려면 그 색상 페어를 초기화해야 한다. | |||
* 녹색 배경에 빨간 문자색을 색상페어로 지정하려면 다음과 같이 하면 된다. | |||
<source lang="c"> | |||
init_pair(1, COLOR_RED, COLOR_GREEN); | |||
</source> | |||
* 그리고 COLOR_PAIR로 제어할 수 있다. | |||
<source lang="c"> | |||
wattron(window_ptr, COLOR_PAIR(1)); | |||
</source> | |||
* 이렇게 하면 init_pair로 지정한 색으로 세팅된다. | |||
* 다음처럼 조합도 가능하다. | |||
<source lang="c"> | |||
wattron(window_ptr, COLOR_PAIR(1) | A_BOLD); | |||
</source> | |||
* 잘 와닿지 않으면 다음을 보자. | |||
<source lang="c"> | |||
/* color.c */ | |||
#include <unistd.h> | |||
#include <stdio.h> | |||
#include <curses.h> | |||
int main() { | |||
int i; | |||
initscr(); | |||
if(!has_colors()) { | |||
endwin(); | |||
fprintf(stderr, "Error - no color support on this terminal\n"); | |||
return 1; | |||
} | |||
if(start_color() != OK) { | |||
endwin(); | |||
fprintf(stderr, "Error - could not initialize colors\n"); | |||
return 2; | |||
} | |||
/* 색상 페어로 7개의 페어를 만들어서 보여준다. */ | |||
clear(); | |||
mvprintw(5, 5, "There are %d COLORS, and %d COLOR_PAIRS available", COLORS, COLOR_PAIRS); | |||
refresh(); | |||
init_pair(1, COLOR_RED, COLOR_BLACK); | |||
init_pair(2, COLOR_RED, COLOR_GREEN); | |||
init_pair(3, COLOR_GREEN, COLOR_RED); | |||
init_pair(4, COLOR_YELLOW, COLOR_BLUE); | |||
init_pair(5, COLOR_BLACK, COLOR_WHITE); | |||
init_pair(6, COLOR_MAGENTA, COLOR_BLUE); | |||
init_pair(7, COLOR_CYAN, COLOR_WHITE); | |||
for(i = 1; i <= 7; i++) { | |||
attroff(A_BOLD); | |||
attrset(COLOR_PAIR(i)); | |||
mvprintw(5 + i, 5, "Color pair %d", i); | |||
attrset(COLOR_PAIR(i) | A_BOLD); | |||
mvprintw(5 + i, 25, "Bold color pair %d", i); | |||
refresh(); | |||
sleep(1); | |||
} | |||
endwin(); | |||
return 0; | |||
} | |||
</source> | |||
=== 패드 윈도우 === | |||
<source lang="c"> | |||
#include <curses.h> | |||
WINDOW *newpad(int number_of_lines, int number_of_columns); | |||
int prefresh(WINDOW *pad_ptr, int pad_row, int pad_column, int screen_row_min, int screen_col_min, int screen_row_max, int screen_col_max); | |||
</source> | |||
* newpad는 newwin이랑 사용법이 같다. 삭제도 delwin으로 가능하다. | |||
<source lang="c"> | |||
/* pad.c */ | |||
/* 패드를 초기화하고 패드를 만든다. 그리고 패드에 문자를 채워넣는다. 패드는 보통의 터미널보다 크다. */ | |||
#include <unistd.h> | |||
#include <curses.h> | |||
int main() | |||
{ | |||
WINDOW *pad_ptr; | |||
int x, y; | |||
int pad_lines; | |||
int pad_cols; | |||
char disp_char; | |||
initscr(); | |||
pad_lines = LINES + 50; | |||
pad_cols = COLS + 50; | |||
pad_ptr = newpad(pad_lines, pad_cols); | |||
disp_char = 'a'; | |||
for (x = 0; x< pad_lines; x++) { | |||
for (y = 0; y < pad_cols; y++) { | |||
mvwaddch(pad_ptr, x, y, disp_char); | |||
if (disp_char == 'z') disp_char = 'a'; | |||
else disp_char++; | |||
} | |||
} | |||
/* 이제 패드의 영역을 화면상의 특정위치로 쓴다. */ | |||
prefresh(pad_ptr, 5, 7, 2, 2, 9, 9); | |||
sleep(1); | |||
prefresh(pad_ptr, LINES +5, COLS +7, 5, 5, 21, 19); | |||
sleep(1); | |||
delwin(pad_ptr); | |||
endwin(); | |||
return 0; | |||
} | |||
</source> | |||
=== 종합 === | |||
<source lang="c"> | |||
/* cdapp.c */ | |||
#include <unistd.h> | |||
#include <stdlib.h> | |||
#include <stdio.h> | |||
#include <string.h> | |||
#include <curses.h> | |||
#define MAX_STRING (80) /* 제일 긴 문자열 */ | |||
#define MAX_ENTRY (1024) /* 제일 긴 데이터베이스 엔트리 */ | |||
#define MESSAGE_LINE 6 /* 기타 메세지 표현할 라인 */ | |||
#define ERROR_LINE 22 /* 에러를 표시할 라인 */ | |||
#define Q_LINE 20 /* 질문을 표시할 라인 */ | |||
#define PROMPT_LINE 18 /* 입력 프롬프트를 표시할 라인 */ | |||
/* 몇가지 전역변수를 정의한다. current_cd는 현재 CD의 제목을 저장, 초기화는 null로, 선택된 CD가 없다는걸 보여준다. current_cat는 현재 CD의 카탈로그 번호를 기록하는데 사용 */ | |||
static char current_cd[MAX_STRING] = "\0"; | |||
static char current_cat[MAX_STRING]; | |||
/* 몇몇 파일 이름을 정의한다. 프로그램을 간단하게 하기 위해 파일 이름을 고정시킨다. */ | |||
const char *title_file = "title.cdb"; | |||
const char *tracks_file = "tracks.cdb"; | |||
const char *temp_file="cdb.tmp"; | |||
/* 프로그램에서 필요한 함수 원형 */ | |||
void clear_all_screen(void); | |||
void get_return(void); | |||
int get_confirm(void); | |||
int getchoice(char *greet, char *choices[]); | |||
void draw_menu(char *options[], int highlight, int start_row, int start_col); | |||
void insert_title(char *cdtitle); | |||
void get_string(char *string); | |||
void add_record(void); | |||
void count_cds(void); | |||
void find_cd(void); | |||
void list_tracks(void); | |||
void remove_tracks(void); | |||
void remove_cd(void); | |||
void update_cd(void); | |||
/* 메뉴를 출력하기 위한 문자열 배열. 첫번째 문자는 메뉴가 선택됐을때 반환될 문자고, 나머지 텍스트는 출력될 문자다. 현재 선택된 CD가 있을 경우에는 extended_menu에 있는 메뉴를 출력한다. */ | |||
char *main_menu[] = { | |||
"aadd new CD", | |||
"ffind CD", | |||
"ccount CDs and tracks in the catalog", | |||
"qquit", | |||
0, | |||
}; | |||
char *extended_menu[] = { | |||
"aadd new CD", | |||
"ffind CD", | |||
"ccount CDs and tracks in the catalog", | |||
"llist tracks on current CD", | |||
"rremove current CD", | |||
"uupdate track information", | |||
"qquit", | |||
0, | |||
}; | |||
/* main은 메뉴를 선택하고 q를 누르면 종료한다. */ | |||
int main() | |||
{ | |||
int choice; | |||
initscr(); | |||
do { | |||
choice = getchoice("Options:", current_cd[0] ? extended_menu : main_menu); | |||
switch(choice) { | |||
case 'q': | |||
break; | |||
case 'a': | |||
add_record(); | |||
break; | |||
case 'c': | |||
count_cds(); | |||
break; | |||
case 'f': | |||
find_cd(); | |||
break; | |||
case 'l': | |||
list_tracks(); | |||
break; | |||
case 'r': | |||
remove_cd(); | |||
break; | |||
case 'u': | |||
update_cd(); | |||
break; | |||
} | |||
} while (choice != 'q'); | |||
endwin(); | |||
return 0; | |||
} | |||
/* getchoice 함수는 main에서 호출되는 함수고, greet랑 choices가 인자로 전달된다. choices는 main_menu나 extended_menu에 대한 포인터다. */ | |||
int getchoice(char *greet, char *choices[]) | |||
{ | |||
static int selected_row = 0; | |||
int max_row = 0; | |||
int start_screenrow = MESSAGE_LINE, start_screencol = 10; | |||
char **option; | |||
int selected; | |||
int key = 0; | |||
option = choices; | |||
while(*option) { | |||
max_row++; | |||
option++; | |||
} | |||
if(selected_row >= max_row) | |||
selected_row = 0; | |||
clear_all_screen(); | |||
mvprintw(start_screenrow - 2, start_screencol, greet); | |||
keypad(stdscr, TRUE); | |||
cbreak(); | |||
noecho(); | |||
key = 0; | |||
while (key != 'q' && key != KEY_ENTER && key != '\n') { | |||
if(key == KEY_UP) { | |||
if(selected_row == 0) | |||
selected_row = max_row -1; | |||
else | |||
selected_row--; | |||
} | |||
if(key == KEY_DOWN) { | |||
if(selected_row == (max_row -1)) | |||
selected_row = 0; | |||
else | |||
selected_row++; | |||
} | |||
selected = *choices[selected_row]; | |||
draw_menu(choices, selected_row, start_screenrow, start_screencol); | |||
key = getch(); | |||
} | |||
keypad(stdscr, FALSE); | |||
nocbreak(); | |||
echo(); | |||
if(key == 'q') | |||
selected = 'q'; | |||
return (selected); | |||
} | |||
/* getchoice 함수 내부에서 호출된 clear_all_screen, draw_menu 함수 */ | |||
void draw_menu(char *options[], int current_highlight, int start_row, int start_col) | |||
{ | |||
int current_row = 0; | |||
char **option_ptr; | |||
char *txt_ptr; | |||
option_ptr = options; | |||
while (*option_ptr) { | |||
if(current_row == current_highlight) { | |||
mvaddch(start_row + current_row, start_col - 3, ACS_BULLET); | |||
mvaddch(start_row + current_row, start_col + 40, ACS_BULLET); | |||
} else { | |||
mvaddch(start_row + current_row, start_col -3, ' '); | |||
mvaddch(start_row + current_row, start_col + 40, ' '); | |||
} | |||
txt_ptr = options[current_row]; | |||
txt_ptr++; | |||
mvprintw(start_row + current_row, start_col, "%s", txt_ptr); | |||
current_row++; | |||
option_ptr++; | |||
} | |||
mvprintw(start_row + current_row + 3, start_col, "Move highlight then press Enter."); | |||
refresh(); | |||
} | |||
/* clear_all_screen은 화면을 지우고 제목을 다시 적는다. CD가 선택되면 해당 정보가 출력된다. */ | |||
void clear_all_screen() | |||
{ | |||
clear(); | |||
mvprintw(2, Q_LINE, "%s", "CD Database Application"); | |||
if(current_cd[0]) { | |||
mvprintw(ERROR_LINE, 0, "Current CD: %s: %s\n", current_cat, current_cd); | |||
} | |||
refresh(); | |||
} | |||
/* 새로 CD레코드를 추가하는 함수 */ | |||
void add_record() | |||
{ | |||
char catalog_number[MAX_STRING]; | |||
char cd_title[MAX_STRING]; | |||
char cd_type[MAX_STRING]; | |||
char cd_artist[MAX_STRING]; | |||
char cd_entry[MAX_STRING]; | |||
int screenrow = MESSAGE_LINE; | |||
int screencol = 10; | |||
clear_all_screen(); | |||
mvprintw(screenrow, screencol, "Enter new CD details"); | |||
screenrow += 2; | |||
mvprintw(screenrow, screencol, "Catalog Number: "); | |||
get_string(catalog_number); | |||
screenrow ++; | |||
mvprintw(screenrow, screencol, " CD Title: "); | |||
get_string(cd_title); | |||
screenrow ++; | |||
mvprintw(screenrow, screencol, " CD Type: "); | |||
get_string(cd_type); | |||
screenrow ++; | |||
mvprintw(screenrow, screencol, " Artist: "); | |||
get_string(cd_artist); | |||
screenrow ++; | |||
mvprintw(15, 5, "About to add this new entry:"); | |||
sprintf(cd_entry, "%s, %s, %s, %s", catalog_number, cd_title, cd_type, cd_artist); | |||
mvprintw(17, 5, "%s", cd_entry); | |||
refresh(); | |||
move(PROMPT_LINE, 0); | |||
if(get_confirm()) { | |||
insert_title(cd_entry); | |||
strcpy(current_cd, cd_title); | |||
strcpy(current_cat, catalog_number); | |||
} | |||
} | |||
/* get_string 함수는 현재 화면 위치에서 문자열을 읽는다. */ | |||
void get_string(char *string) | |||
{ | |||
int len; | |||
wgetnstr(stdscr, string, MAX_STRING); | |||
len = strlen(string); | |||
if(len > 0 && string[len - 1] == '\n') | |||
string[len - 1] = '\0'; | |||
} | |||
/* get_confirm 함수는 사용자의 의사를 확인한다. */ | |||
int get_confirm() | |||
{ | |||
int confirmed = 0; | |||
char first_char = 'N'; | |||
mvprintw(Q_LINE, 5, "Are you sure? "); | |||
clrtoeol(); | |||
refresh(); | |||
cbreak(); | |||
first_char = getch(); | |||
if(first_char == 'Y' || first_char == 'y') { | |||
confirmed = 1; | |||
} | |||
nocbreak(); | |||
if(!confirmed) { | |||
mvprintw(Q_LINE, 1, " Cancelled"); | |||
clrtoeol(); | |||
refresh(); | |||
sleep(1); | |||
} | |||
return confirmed; | |||
} | |||
/* insert_title 함수는 타이틀을 추가한다. 타이틀 문자열을 타이틀 파일의 마지막에 추가하는 방식을 사용. */ | |||
void insert_title(char *cdtitle) | |||
{ | |||
FILE *fp = fopen(title_file, "a"); | |||
if(!fp) { | |||
mvprintw(ERROR_LINE, 0, "cannot open CD titles database"); | |||
} else { | |||
fprintf(fp, "%s\n", cdtitle); | |||
fclose(fp); | |||
} | |||
} | |||
/* 화면 창을 표현하기 위한 전역 상수 몇개를 선언한다. */ | |||
#define BOXED_LINES 11 | |||
#define BOXED_ROWS 60 | |||
#define BOX_LINE_POS 8 | |||
#define BOX_ROW_POS 2 | |||
/* update_cd는 CD 트랙을 수정한다. */ | |||
void update_cd() | |||
{ | |||
FILE *tracks_fp; | |||
char track_name[MAX_STRING]; | |||
int len; | |||
int track = 1; | |||
int screen_line = 1; | |||
WINDOW *box_window_ptr; | |||
WINDOW *sub_window_ptr; | |||
clear_all_screen(); | |||
mvprintw(PROMPT_LINE, 0, "Re-entering tracks for CD. "); | |||
if(!get_confirm()) | |||
return; | |||
move(PROMPT_LINE, 0); | |||
clrtoeol(); | |||
remove_tracks(); | |||
mvprintw(MESSAGE_LINE, 0, "Enter a blank line to finish"); | |||
tracks_fp = fopen(tracks_file, "a"); | |||
box_window_ptr = subwin(stdscr, BOXED_LINES + 2, BOXED_ROWS + 2, BOX_LINE_POS - 1, BOX_ROW_POS - 1); | |||
if(!box_window_ptr) | |||
return; | |||
box(box_window_ptr, ACS_VLINE, ACS_HLINE); | |||
sub_window_ptr = subwin(stdscr, BOXED_LINES, BOXED_ROWS, BOX_LINE_POS, BOX_ROW_POS); | |||
if(!sub_window_ptr) | |||
return; | |||
scrollok(sub_window_ptr, TRUE); | |||
werase(sub_window_ptr); | |||
touchwin(stdscr); | |||
do { | |||
mvwprintw(sub_window_ptr, screen_line++, BOX_ROW_POS + 2, "Track %d: ", track); | |||
clrtoeol(); | |||
refresh(); | |||
wgetnstr(sub_window_ptr, track_name, MAX_STRING); | |||
len = strlen(track_name); | |||
if(len > 0 && track_name[len - 1] == '\n') | |||
track_name[len - 1] = '\0'; | |||
if(*track_name) | |||
fprintf(tracks_fp, "%s, %d, %s\n", current_cat, track, track_name); | |||
track++; | |||
if(screen_line > BOXED_LINES - 1) { | |||
/* time to start scrolling */ | |||
scroll(sub_window_ptr); | |||
screen_line--; | |||
} | |||
} while(*track_name); | |||
delwin(sub_window_ptr); | |||
fclose(tracks_fp); | |||
} | |||
/* 다음은 remove_cd */ | |||
void remove_cd() | |||
{ | |||
FILE *titles_fp, *temp_fp; | |||
char entry[MAX_ENTRY]; | |||
int cat_length; | |||
if(current_cd[0] == '\0') | |||
return; | |||
clear_all_screen(); | |||
mvprintw(PROMPT_LINE, 0, "About to remove CD %s: %s. ", current_cat, current_cd); | |||
if(!get_confirm()) | |||
return; | |||
cat_length = strlen(current_cat); | |||
/* 타이틀 파일을 임시파일로 복사한다 */ | |||
titles_fp = fopen(title_file, "r"); | |||
temp_fp = fopen(temp_file, "w"); | |||
while(fgets(entry, MAX_ENTRY, titles_fp)) { | |||
/* 카타로그 넘버랑 복사된 엔트리랑 체크 */ | |||
if(strncmp(current_cat, entry, cat_length) != 0) | |||
fputs(entry, temp_fp); | |||
} | |||
fclose(titles_fp); | |||
fclose(temp_fp); | |||
/* 타이틀 파일을 삭제하고, 임시파일을 타이틀파일로 덮어쓴다. */ | |||
unlink(title_file); | |||
rename(temp_file, title_file); | |||
/* 트랙 파일도 마찬가지 작업을 한다. */ | |||
remove_tracks(); | |||
/* 현재 CD는 없는 CD라고 입력해둔다. */ | |||
current_cd[0] = '\0'; | |||
} | |||
/* remove_tracks는 트랙을 제거한다. update_cd랑 remove_cd에서 호출됨. */ | |||
void remove_tracks() | |||
{ | |||
FILE *tracks_fp, *temp_fp; | |||
char entry[MAX_ENTRY]; | |||
int cat_length; | |||
if(current_cd[0] == '\0') | |||
return; | |||
cat_length = strlen(current_cat); | |||
tracks_fp = fopen(tracks_file, "r"); | |||
temp_fp = fopen(temp_file, "w"); | |||
while(fgets(entry, MAX_ENTRY, tracks_fp)) { | |||
/* 카타로그 넘버랑 복사된 엔트리랑 체크 */ | |||
if(strncmp(current_cat, entry, cat_length) != 0) | |||
fputs(entry, temp_fp); | |||
} | |||
fclose(tracks_fp); | |||
fclose(temp_fp); | |||
unlink(tracks_file); | |||
rename(temp_file, tracks_file); | |||
} | |||
/* count_cds는 디비를 뒤져서 타이틀과 트랙의 갯수를 구한다. */ | |||
void count_cds() | |||
{ | |||
FILE *titles_fp, *tracks_fp; | |||
char entry[MAX_ENTRY]; | |||
int titles = 0; | |||
int tracks = 0; | |||
titles_fp = fopen(title_file, "r"); | |||
if(titles_fp) { | |||
while(fgets(entry, MAX_ENTRY, titles_fp)) | |||
titles++; | |||
fclose(titles_fp); | |||
} | |||
tracks_fp = fopen(tracks_file, "r"); | |||
if(tracks_fp) { | |||
while(fgets(entry, MAX_ENTRY, tracks_fp)) | |||
tracks++; | |||
fclose(tracks_fp); | |||
} | |||
mvprintw(ERROR_LINE, 0, "Database contains %d titles, with a total of %d tracks.", titles, tracks); | |||
get_return(); | |||
} | |||
/* 트랙 목록 검색해서 CD 타이틀을 찾을 수 있다 */ | |||
void find_cd() | |||
{ | |||
char match[MAX_STRING], entry[MAX_ENTRY]; | |||
FILE *titles_fp; | |||
int count = 0; | |||
char *found, *title, *catalog; | |||
mvprintw(Q_LINE, 0, "Enter a string to search for in CD titles: "); | |||
get_string(match); | |||
titles_fp = fopen(title_file, "r"); | |||
if(titles_fp) { | |||
while(fgets(entry, MAX_ENTRY, titles_fp)) { | |||
/* 이전 카다록 스킵 */ | |||
catalog = entry; | |||
if(found = strstr(catalog, ",")) { | |||
*found = 0; | |||
title = found + 1; | |||
/* 콤마 지우기 */ | |||
if(found = strstr(title, ",")) { | |||
*found = '\0'; | |||
if(found = strstr(title, match)) { | |||
count++; | |||
strcpy(current_cd, title); | |||
strcpy(current_cat, catalog); | |||
} | |||
} | |||
} | |||
} | |||
fclose(titles_fp); | |||
} | |||
if(count != 1) { | |||
if(count == 0) | |||
mvprintw(ERROR_LINE, 0, "Sorry, no matching CD found. "); | |||
if(count > 1) { | |||
mvprintw(ERROR_LINE, 0, "Sorry, match is ambiguous: %d CDs found. ", count); | |||
} | |||
current_cd[0] = '\0'; | |||
get_return(); | |||
} | |||
} | |||
void list_tracks() | |||
{ | |||
FILE *tracks_fp; | |||
char entry[MAX_ENTRY]; | |||
int cat_length; | |||
int lines_op = 0; | |||
WINDOW *track_pad_ptr; | |||
int tracks = 0; | |||
int key; | |||
int first_line = 0; | |||
if (current_cd[0] == '\0') { | |||
mvprintw(ERROR_LINE, 0, "You must select a CD first. ", stdout); | |||
get_return(); | |||
return; | |||
} | |||
clear_all_screen(); | |||
cat_length = strlen(current_cat); | |||
/* 현재 CD의 카운트 */ | |||
tracks_fp = fopen(tracks_file, "r"); | |||
if(!tracks_fp) | |||
return; | |||
while(fgets(entry, MAX_ENTRY, tracks_fp)) { | |||
if(strncmp(current_cat, entry, cat_length) == 0) | |||
tracks++; | |||
} | |||
fclose(tracks_fp); | |||
/* 새 패드를 만든다. */ | |||
track_pad_ptr = newpad(tracks + 1 + BOXED_LINES, BOXED_ROWS + 1); | |||
if(!track_pad_ptr) | |||
return; | |||
tracks_fp = fopen(tracks_file, "r"); | |||
if(!tracks_fp) | |||
return; | |||
mvprintw(4, 0, "CD Track Listening\n"); | |||
/* 트랙 정보를 패드에 작성 */ | |||
while(fgets(entry, MAX_ENTRY, tracks_fp)) { | |||
/* 카다록 넘버랑 나머지를 출력 */ | |||
if(strncmp(current_cat, entry, cat_length) == 0) { | |||
mvwprintw(track_pad_ptr, lines_op++, 0, "%s", entry + cat_length + 1); | |||
} | |||
} | |||
fclose(tracks_fp); | |||
if(lines_op > BOXED_LINES) { | |||
mvprintw(MESSAGE_LINE, 0, "Cursor keys to scroll, Enter or q to exit"); | |||
} else { | |||
mvprintw(MESSAGE_LINE, 0, "Enter or q to exit"); | |||
} | |||
wrefresh(stdscr); | |||
keypad(stdscr, TRUE); | |||
cbreak(); | |||
noecho(); | |||
key = 0; | |||
while(key != 'q' && key != KEY_ENTER && key != '\n') { | |||
if(key == KEY_UP) { | |||
if(first_line > 0) | |||
first_line--; | |||
} | |||
if(key == KEY_DOWN) { | |||
if(first_line + BOXED_LINES + 1 < tracks) | |||
first_line++; | |||
} | |||
/* 이제 패드에서 적절한 부분을 떼서 그린다. */ | |||
prefresh(track_pad_ptr, first_line, 0, BOX_LINE_POS, BOX_ROW_POS, BOX_LINE_POS + BOXED_LINES, BOX_ROW_POS + BOXED_ROWS); | |||
/* wrefresh(stdscr); */ | |||
key = getch(); | |||
} | |||
delwin(track_pad_ptr); | |||
keypad(stdscr, FALSE); | |||
nocbreak(); | |||
echo(); | |||
} | |||
/* get_return 함수는 리턴문자가 입력될때까지 프롬프트를 출력 */ | |||
void get_return() | |||
{ | |||
int ch; | |||
mvprintw(23, 0, "%s", " Press Enter "); | |||
refresh(); | |||
while((ch = getchar()) != '\n' && ch != EOF); | |||
} | } | ||
</source> | </source> | ||
2015년 4월 24일 (금) 17:28 기준 최신판
메뉴[편집]
Basic[편집]
<source lang="c"> /* menu1.c */ /* getchoice 원형 정리 */
- include <stdio.h>
char *menu[] = {
"a - add new record", "d - delete record", "q - quit", NULL,
};
- menu를 출력하고 사용자의 입력을 받는다.
- main은 menu를 사용하여 getchoice를 호출
int getchoice(char *greet, char *choices[]) {
int chosen = 0; int selected; char **option;
do {
printf("Choice: %s\n", greet);
option = choices;
while(*option) {
printf("%s\n", *option);
option++;
}
selected = getchar();
option = choices;
while(*option) {
if(selected == *option[0]) {
chosen =1;
break;
}
option++;
}
if(!chosen) {
printf("Incorrect choice, select again\n");
}
} while(!chosen);
return selected;
}
/* main 함수는 menu를 사용하여 getchoice를 호출한다. */ int main() {
int choice = 0;
do
{
choice = getchoice("Please select an action", menu);
printf("You have chosen: %c\n", choice);
} while (choice != 'q');
return 0;
} </source>
- 기본적인 메뉴 프로그램이다.
문제가 많다
isatty[편집]
<source lang="c">
- include <unistd.h>
int isatty(int fildes); </source>
- isatty는 fildes가 터미널이면 1, 아니면 0을 반환한다.
<source lang="c"> /* menu2.c */ /* getchoice 원형 정리 */
- include <unistd.h>
- include <stdio.h>
char *menu[] = {
"a - add new record", "d - delete record", "q - quit", NULL,
};
- menu를 출력하고 사용자의 입력을 받는다.
- main은 menu를 사용하여 getchoice를 호출
int getchoice(char *greet, char *choices[]) {
int chosen = 0; int selected; char **option;
do {
printf("Choice: %s\n", greet);
option = choices;
while(*option) {
printf("%s\n", *option);
option++;
}
selected = getchar();
option = choices;
while(*option) {
if(selected == *option[0]) {
chosen =1;
break;
}
option++;
}
if(!chosen) {
printf("Incorrect choice, select again\n");
}
} while(!chosen);
return selected;
}
int main() {
int choice = 0;
if(!isatty(fileno(stdout))) {
fprintf(stderr, "You are not a terminal!\n");
return 1;
}
do {
choice = getchoice("Please select an action", menu);
printf("You have chosen: %c\n", choice);
} while (choice != 'q');
return 0;
} </source>
- 콘솔이 아닐때 에러를 뱉어준다.
<source lang="c">
- include <stdio.h>
- include <unistd.h>
char *menu[] = {
"a - add new record", "d - delete record", "q - quit", NULL,
};
int getchoice(char *greet, char *choices[], FILE *in, FILE *out); {
int chosen = 0; int selected; char **option;
do {
fprintf(out, "Choice: %s\n", greet);
option = choices;
while(*option) {
fprintf(out, "%s\n", *option);
option++;
}
do {
selected = fgetc(in);
} while (selected == '\n');
option = choices;
while(*option) {
if(selected == *option[0]) {
chosen = 1;
break;
}
option++;
}
if(!chosen) {
fprintf(out, "Incorrect choice, select again\n");
}
} while(!chosen);
return selected;
}
int main() {
int choice = 0;
FILE *input;
FILE *output;
if(!isatty(fileno(stdout))) {
fprintf(stderr, "You are not a terminal, OK.\n");
}
input = fopen("/dev/tty", "r");
output = fopen("/dev/tty", "w");
if(!input || !output) {
fprintf(stderr, "Unable to open /dev/tty\n");
return 1;
}
do {
choice = getchoice("Please select an action", menu, input, output);
printf("You have chosen: %c\n", choice);
} while(choice != 'q');
return 0;
} </source>
- 이제 리다이렉션해도 터미널 출력으로만 나오게 된다.
tcgetattr, tgsetattr[편집]
- 이건 ncurses 라이브러리가 필요. gcc 컴파일시에 -lncurses 옵션을 붙이면 된다.
<source lang="c">
- include <termios.h>
struct termios {
tcflag_t c_iflag; tcflag_t c_oflag; tcflag_t c_cflag; tcflag_t c_lflag; cc_t c_cc[NCCS];
}
int tcgetattr(int fildes, struct termios *termios_p); int tcsetattr(int fildes, int actions, const struct termios *termios_p); </source>
- tcgetattr은 fildes가 가리키고 있는 터미널 정보를 가져와 termios_p 구조체에 들어간다.
- tcsetattr의 actions는 다음중 하나로 지정
- TCSANOW - 즉시 값 변경
- TCSADRAIN - 출력이 끝났을때 값 변경
- TCSAFLUSH - 출력이 끝났을때 값 변경, 유효한 입력이나 read호출에서 리턴되지 않은 입력은 취소.
- 입력모드 c_iflag에서 사용할 수 있는 매크로는 다음과 같다.
- BRKINT - 라인에서 break 조건이 감지되면 인터럽트를 발생
- IGNBRK - 라인에서 break 조건을 무시
- ICRNL - 입력된 캐리지 리턴을 뉴라인으로 변환
- IGNCR - 캐리지 리턴은 무시
- INLGR - 입력된 새 라인을 캐리지 리턴으로 변환
- IGNPAR - 입력된 문자중 패리티 에러가 있는 문자는 무시
- INPCK - 입력 문자들에 대해 패리티 체크를 한다.
- PARMRK - 패리티 에러 표시
- ISTRIP - 입력되는 모든 문자들을 7비트로 strip
- IXOFF - 입력시에 소프트웨어 흐름 제어
- IXON - 출력시 소프트웨어 흐름 제어
- BRKINT나 IGNBRK가 설정되지 않으면 break 조건은 0x00으로 읽힘
- 출력모드 c_oflag에서 사용할 수 있는 매크로는 다음과 같다.
- OPOST - 출력시 처리. 세팅 안하면 밑엣것들 전부 무시.
- ONLCR - 출력시 뉴 라인을 캐리지 리턴과 라인피드로 변환
- OCRNL - 출력시 캐리지 리턴을 뉴라인으로 변환
- ONOCR - 첫문자일땐 캐리지 리턴을 출력하지 않음
- ONLRET - 뉴라인을 캐리지 리턴으로 취급
- OFILL - 지연을 위해 채움문자로 보냄
- OFDEL - 채움 문자로 NULL 대신 DEL을 사용
- NLDLY - 새 라인시 딜레이
- CRDLY - 캐리지리턴시 딜레이
- TABDLY - 탭시 딜레이
- BSDLY - 백스페이스 딜레이
- VTDLY - 수직탭 딜레이
- FFDLY - 폼피드 딜레이
- 컨트롤 모드 c_cflag에서 사용할 수 있는 매크로는 다음과 같다.
- CLOCAL - 상태라인 무시
- CREAD - 문자 읽기 가능
- CS5 - 문자 보내고 받을때 5비트 사용
- CS6 - 문자 보내고 받을때 6비트 사용
- CS7 - 문자 보내고 받을때 7비트 사용
- CS8 - 문자 보내고 받을때 8비트 사용
- CSTOPB - 정지비트 2비트
- HUPCL - 전송 끊기, Hang-up 세팅.
- PARENB - 패리티 사용
- PARODD - 홀수 패리티 사용
- 로컬 모드 c_lflag에서 사용할 수 있는 매크로는 다음과 같다.
- ECHO - 입력되는 문자의 로컬 echo를 가능하게 한다.
- ECHOE - erase 문자를 받으면 backspace, space, backspace를 수행.
- ECHOK - kill 문자를 받으면 라인을 지운다.
- ECHONL - 새 라인 문자도 echo한다.
- ICANON - 정규 입력 처리를 가능하게 한다.
- IEXTEN - 입력 처리시 특별히 정의한 함수를 사용가능하게
- IESIG - 시그널을 가능하게 한다.
- NOFLSH - 큐는 플러시 안한다.
- TOSTOP - 쓰기시도시 백그라운드 프로세스에 시그널 보냄
- 제어모드에서 ICANON 세팅여부에 따라 특수제어모드 세팅이 가능
- ICANON이 세팅되었을때 특수제어모드 c_cc배열의 인덱스는 다음과 같다.
- VEOF - EOF문자
- VEOL - EOL문자
- VERASE - ERASE 문자
- VINTR - INTR문자
- VKILL - KILL 문자
- VQUIT - QUIT 문자
- VSUSP - SUSP 문자
- VSTART - START 문자
- VSTOP - STOP 문자
- ICANON이 세팅되어 있지 않을 때 특수제어모드 c_cc배열의 인덱스는 다음과 같다.
- VINTR - INTR 문자
- VMIN - MIN 값
- VQUIT - QUIT문자
- VSUSP - SUSP문자
- VTIME - TIME값
- VSTART - START 문자
- VSTOP - STOP문자
- 문자에 대한 설명은 다음과 같다.
- INTR - 터미널 드라이버가 터미널과 연결된 프로세스에 SIGINT 시그널을 보내도록 한다.
- QUIT - 터미널 드라이버가 터미널과 연결된 프로세스에 SIGQUIT 시그널을 보내도록 한다.
- ERASE - 터미널 드라이버가 라인의 마지막 문자를 지우도록 한다.
- KILL - 터미널 드라이버가 라인을 전부 지우도록 한다.
- EOF - 터미널 드라이버가 라인의 모든 문자들을 응용프로그램의 입력으로 전달한다. 라인이 비었다면 READ호출은 파일의 끝에서 시도한 것처럼 0문자를 리턴
- EOL - 라인중단. 뉴라인 문자가 사용된다.
- SUSP - 터미널 드라이버가 터미널과 연결된 프로세스에 SIGSUSP시그널을 보내도록 한다.
- STOP - 터미널에 출력되지 않도록 흐름정지시킨다. XON/XOFF 흐름제어 제공. 보통 Ctrl-S로 설정
- START - STOP 이후에 다시 출력하게 한다. 보통 아스키 XON문자 사용
- Time과 Min값
- Min = 0, Time = 0 - read는 즉시 리턴. 읽을것이 없으면 읽지 않음
- Min = 0, Time > 0 - read는 읽어들일 문자가 있거나 Time/10이 경과했을 때 리턴. 정해진 시간동안 문자가 읽혀지지 않는다면 read는 0 리턴
- Min > 0, Time = 0 - read는 min개의 문자를 읽을때까지 기다리고, 읽어들인 문자의 갯수 리턴. 파일의 끝이라면 0 리턴
- Min > 0, Time > 0 - read가 호출되면 읽어들일 문자를 기다린다. read는 min개의 문자를 읽어들였거나 time/10초만큼 경과했을때 리턴. 이건 ESC키를 눌렀을때 코드값과 기능키 눌렀을때 첫번째 코드값의 차이점을 알아내는데 유용하게 사용될수 있다. 네트워크 통신이나 상위 프로세서는 이러한 타이머 정보를 쉽게 지울수 있다.
- ICANON으로 설정하지 않고 min과 time값을 사용하면 프로그램은 입력되는 모든 글자를 처리할 수 있다.
쉘에서 터미널 모드 접근[편집]
- 터미널의 termios 설정값을 알아보려면 다음의 명령으로 알아볼 수 있다.
<source lang="bash"> $ stty -a </source>
- 리눅스에서 출력은 다음과 같다.
drake@debian:~$ stty -a speed 38400 baud; rows 27; columns 93; line = 0; intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>; eol2 = <undef>; swtch = <undef>; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V; flush = ^O; min = 1; time = 0; -parenb -parodd cs8 -hupcl -cstopb cread -clocal -crtscts -ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff -iuclc -ixany -imaxbel -iutf8 opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0 isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt echoctl echoke drake@debian:~$
- EOF문자가 Ctrl+D고, echo가 설정되어 있음을 알 수 있다.
- 테스트하다가 이상해지면 다음과 같이 복구할 수 있다.
<source lang="bash"> $ stty sane </source>
- 쉘스크립트가 한번에 한문자씩 처리하도록 하려면 min을 1로 설정하고 time을 0으로 설정하고 icanon을 빼야 한다.
<source lang="bash"> $ stty -icanon min 1 time 0 </source>
- 비번검사를 한다든지 할땐 프롬프트를 보내기 전에 echo를 끌 수 있다.
<source lang="bash"> $ stty -echo </source>
- 나중에 다시 가능하게 하려면
<source lang="bash"> $ stty echo </source>
cfgetispeed, cfgetospeed, cfsetispeed, cfsetospeed[편집]
<source lang="c">
- include <termios.h>
speed_t cfgetispeed(const sturct termios *); speed_t cfgetospeed(const struct termios *); int cfsetispeed(struct termios *, speed_t speed); int cfsetospeed(struct termios *, speed_t speed); </source>
- tcsetattr로 termios를 만들어 써야 한다.
- speed는 다음과 같다.
- B0 - 접속 끊음
- B1200 - 1200baud
- B2400 - 2400baud
- B9600 - 9600baud
- B38400 - 38400baud
- 표준속도는 38400이 최대, 비표준 속도는 setserial로 사용
tcdrain, tcflow, tcflush[편집]
<source lang="c">
- include <termios.h>
int tcdrain(int fildes); int tcflow(int fildes, int flowtype); int tcflush(int fldes, int in_out_selector); </source>
- tcdrain은 출력이 다 될때까지 기다리는 함수
- tcflow는 출력을 중단하거나 재개한다.
- tcflush는 입력이나 출력을 지워버린다.
비밀번호 입력 프로그램[편집]
<source lang="c"> /* password.c */
- include <termios.h>
- include <stdio.h>
- define PASSWORD_LEN 32
int main() {
struct termios initialrsettings, newrsettings; char password[PASSWORD_LEN + 1]; /* 다음 표준 입력의 현재 설정값을 얻고, 이전에 만들둔 termios 구조체에 그걸 복사한다. */ tcgetattr(fileno(stdin), &initialrsettings); /* 마지막 설정을 복구하기 위해 원래의 설정을 복사하고, newrsettings에서 echo를 끄고 사용자에게 비밀번호를 물어보다. */ newrsettings = initialrsettings; newrsettings.c_lflag &= ~ECHO;
printf("Enter password: ");
/* 터미널 속성을 newrsettings으로 설정하고 비번을 읽어들인다. 마지막에는 터미널 속성을 원래의 설정값으로 복구하고 비번을 출력한다. */
if(tcsetattr(fileno(stdin), TCSAFLUSH, &newrsettings) != 0) {
fprintf(stderr, "Could not set attributes\n");
}
else {
fgets(password, PASSWORD_LEN, stdin);
tcsetattr(fileno(stdin), TCSANOW, &initialrsettings);
fprintf(stdout, "\nYou entered %s\n", password);
}
exit(0);
} </source>
문자를 하나씩 읽어들이기[편집]
<source lang="c"> /* menu4.c */
- include <stdio.h>
- include <unistd.h>
- include <termios.h>
char *menu[] = {
"a - add new record", "d - delete record", "q - quit", NULL,
};
int getchoice(char *greet, char *choices[], FILE *in, FILE *out); {
int chosen = 0;
int selected;
char **option;
do {
fprintf(out, "Choice: %s\n", greet);
option = choices;
while(*option) {
fprintf(out, "%s\n", *option);
option++;
}
do {
selected = fgetc(in);
} while (selected == '\n' || selected == '\r');
/* 1글자씩 받는 모드에서는 엔터키가 '\r'이 된다 */
option = choices;
while(*option) {
if(selected == *option[0]) {
chosen = 1;
break;
}
option++;
}
if(!chosen) {
fprintf(out, "Incorrect choice, select again\n");
}
} while(!chosen);
return selected;
}
int main() {
int choice = 0; FILE *input; FILE *output; struct termios initial_settings, new_settings;
if(!isatty(fileno(stdout))) {
fprintf(stderr, "You are not a terminal, OK.\n");
}
input = fopen("/dev/tty", "r");
output = fopen("/dev/tty", "w");
if(!input || !output) {
fprintf(stderr, "Unable to open /dev/tty\n");
return 1;
}
/* 문자를 한글자씩 받기 위해 터미널의 특성을 변경 */
tcgetattr(fileno(input), &initial_settings);
new_seettings = initial_settings;
new_settings.c_lflag &= ~ICANON;
new_settings.c_lflag &= ~ECHO;
new_settings.c_cc[VMIN] =1;
new_settings.c_cc[VTIME] = 0;
new_settings.c_lflag &= ~ISIG;
if(tcsetattr(fileno(input), TCSANOW, &new_settings) != 0) {
fprintf(stderr, "Could not set attributes\n");
}
do {
choice = getchoice("Please select an action", menu, input, output);
printf("You have chosen: %c\n", choice);
} while(choice != 'q');
/* 문자를 한글자씩 받으려고 터미널 특성 변경했던걸 복구 */ tcsetattr(fileno(input), TCSANOW, &initial_settings); return 0;
} </source>
- 이제 좀 즉시 처리되는 형태가 된것 같다.
터미널 형태 식별[편집]
- 터미널은 여러 종류가 있다. 터미널 에뮬레이터에서 어떤 방식으로 에뮬레이트할건지 정해본적이 있을 거다. vt100이나 vt220같은것들..
- 다음과 같이 입력하면 터미널의 종류를 식별할 수 있다.
<source lang="bash"> drake@debian:~$ echo $TERM linux drake@debian:~$ </source>
- 얼마전까지는 xterm이었으나, 많은 터미널 에뮬레이터가 linux를 지원해주는 추세라 옮겨가고 있다.
- 예전엔 프로그래머가 터미널의 종류에 따라 다 신경써야 했지만, terminfo라는게 생겨서, 아예 그 안에 웬만한건 다 들어있다.
- terminfo에 대한 정보는, 원래는 /usr/lib/terminfo/v/vt100 이런 경로에 있었지만, 현재는 /usr/share/terminfo/v/vt100 이런 경로로 정의되어 있고, 컴파일된 파일로 제공된다.
- 속도때문인지 /lib/share/terminfo/v/vt100 과 같이, 기본 경로에 제공되기도 한다.
- 추후 원본 파일을 볼 수 있는 방법을 찾아 기술하도록 함
setupterm[편집]
- 마찬가지로, ncurses 라이브러리를 사용한다. gcc 컴파일시 -lncurses 옵션을 붙여서 컴파일해야 한다.
<source lang="c">
- include <term.h>
int setupterm(char *term, int fildes, int *errret); </source>
- setupterm은 터미널 형태를 변수 term으로 세팅한다. term이 널 포인터라면 기본값을 사용한다. fildes는 터미널용 File Descriptor다. 함수의 결과는 errret가 널이 아닐 경우 errret가 가리키는 정수 변수에 저장된다. 그 값은 다음과 같다.
- -1 - terminfo 데이터베이스가 없음
- 0 - terminfo 데이터베이스에 해당하는게 없음
- 1 - 성공
- setupterm함수는 성공시 OK, 실패시 ERR을 반환한다.
<source lang="c"> /* badterm.c */
- include <stdio.h>
- include <nterm.h>
- include <ncurses.h>
int main() {
setupterm("unlisted", fileno(stdout), (int *)0);
printf("Done.\n");
return 0;
} </source>
- 여기서 눈여겨보아야 할 것은, Done이 출력되지 않는다는 점이다.
tigetflag, tigetnum, tigetstr[편집]
<source lang="c">
- include <term.h>
int tigetflag(char *capname); int tigetnum(char *capname); char *tigetstr(char *capname); </source>
- ini 파일좀 다뤄봤다면 어느정도 이해가 빠를거다.
- tigetflag는 플래그를 반환. 실패시 -1 반환
- tigetnum은 정수 반환. 실패시 -2 반환
- tigetstr은 문자열 반환. 실패시 (char *) -1 반환
<source lang="c"> /* sizeterm.c */
- include <stdio.h>
- include <term.h>
- include <ncurses.h>
int main() {
int nrows, ncolumns;
setupterm(NULL, fileno(stdout), (int *)0);
nrows = tigetnum("lines");
ncolumns = tigetnum("cols");
printf("This terminal has %d columns and %d rows\n", ncolumns, nrows);
return 0;
} </source>
- 이 어플리케이션은 터미널 크기를 반환한다.
tparm[편집]
<source lang="c">
- include <term.h>
char *tparm(char *cap, long p1, long p2, ..., long p9); </source>
- terminfo의 각 항을 바꾼다. 거의 쓰이지 않음.
존나 쓸까말까 고민했는데, 걍 있다는거 정도는 알아야 된다고 생각
putp, tputs[편집]
<source lang="c">
- include <term.h>
int putp(char *const str); int tputs(char *const str, int affcnt, int (*putfunc)(int)); </source>
- putp는 터미널 제어 문자열을 취해서 stdout으로 보낸다. 성공시 OK를, 실패시 ERR 반환.
개같은 putfunc 어떻게 설명하라고
<source lang="c"> char *cursor; char *esc_sequence; cursor = tigetstr("cup"); esc_sequence = tparm(cursor, 5, 30); putp(esc_sequence); </source>
- 아래로 다섯칸, 오른쪽으로 30칸 이동한다.
<source lang="c"> /* screenmenu.c */
- include <stdio.h>
- include <unistd.h>
- include <termios.h>
- include <term.h>
- inlcude <curses.h>
static FILE *output stream = (FILE *)0;
char *menu[] = {
"a - add new record", "d - delete record", "q - quit", NULL,
};
int char_to_terminal(int char_to_write) {
if (output_stream) putc(char_to_write, output_stream); return 0;
}
int getchoice(char *greet, char *choices[], FILE *in, FILE *out); {
int chosen = 0; int selected; int screenrow, screencol = 10;
char **option; char *cursor, *clear;
output_stream = out;
setupterm(NULL, fileno(out), (int *)0);
cursor = tigetstr("cup");
clear = tigetstr("clear");
screenrow = 4;
tputs(clear, 1, char_to_terminal);
tputs(tparm(cursor, screenrow, screencol), 1, char_to_terminal);
fprintf(out, "Choice: %s", greet);
screenrow += 2;
option = choices;
while(*option) {
tputs(tparm(cursor, screenrow, screencol), 1, char_to_terminal);
fprintf(out, "%s", *option);
screenrow++;
option++;
}
do {
selected = fgetc(in);
option = choices;
while(*option) {
if(selected == *option[0]) {
chosen = 1;
break;
}
option++;
}
if(!chosen) {
tputs(tparm(cursor, screenrow, screencol), 1, char_to_terminal);
fprintf(out, "Incorrect choice, select again\n");
}
} while(!chosen);
tputs(clear, 1, char_to_terminal);
return selected;
}
int main() {
int choice = 0;
FILE *input;
FILE *output;
struct termios initial_settings, new_settings;
if(!isatty(fileno(stdout))) {
fprintf(stderr, "You are not a terminal, OK.\n");
}
input = fopen("/dev/tty", "r");
output = fopen("/dev/tty", "w");
if(!input || !output) {
fprintf(stderr, "Unable to open /dev/tty\n");
return 1;
}
/* 문자를 한글자씩 받기 위해 터미널의 특성을 변경 */
tcgetattr(fileno(input), &initial_settings);
new_seettings = initial_settings;
new_settings.c_lflag &= ~ICANON;
new_settings.c_lflag &= ~ECHO;
new_settings.c_cc[VMIN] =1;
new_settings.c_cc[VTIME] = 0;
new_settings.c_lflag &= ~ISIG;
if(tcsetattr(fileno(input), TCSANOW, &new_settings) != 0) {
fprintf(stderr, "Could not set attributes\n");
}
do {
choice = getchoice("Please select an action", menu, input, output);
printf("You have chosen: %c\n", choice);
sleep(1); /* 선택된 화면이 너무 빠르게 지나가버리기 때문에, 1초간 대기하여 사람이 볼 수 있을 정도로 지나가게 함.. */
} while(choice != 'q');
/* 문자를 한글자씩 받으려고 터미널 특성 변경했던걸 복구 */
tcsetattr(fileno(input), TCSANOW, &initial_settings);
return 0;
} </source>
입력[편집]
kbhit[편집]
- MSDOS에서 겜같은거 만들때 꼭 쓰던놈
- 현재 눌려진 키가 뭔지 확인하는 기능을 한다.
<source lang="c"> /* kbhit.c */ /* 표준 헤더파일과 터미널 설정을 위해 2개의 구조체를 정의 */
- include <stdio.h>
- include <termios.h>
- include <term.h>
- include <curses.h>
- include <unistd.h>
static struct termios initial_settings, new_settings; static int peek_character = -1;
void init_keyboard(); void close_keyboard(); int kbhit(); int readch();
/* main함수는 터미널을 설정하기 위해 init_keyboard를 호출하고 나서 1초에 1번씩 kbhit을 실행한다. 'q'를 입력하면 close_keyboard가 호출되어 터미널을 정상적으로 설정한 후에 프로그램을 종료한다. */ int main() {
int ch = 0;
init_keyboard();
while(ch != 'q') {
printf("looping\n");
sleep(1);
if(kbhit()) {
ch = readch();
printf("You hit %c\n", ch);
}
}
close_keyboard();
return 0;
}
/* init_keyboard와 close_keyboard는 프로그램의 시작과 끝에서 터미널을 세팅한다. */ void init_keyboard() {
tcgetattr(0, &initial_settings); new_settings.c_lflag &= ~ICANON; new_settings.c_lflag &= ~ECHO; new_settings.c_lflag &= ~ISIG; new_settings.c_cc[VMIN] = 1; new_settings.c_cc[VTIME] = 0; tcsetattr(0, TCSANOW, &new_settings);
}
void close_keyboard() {
tcsetattr(0, TCSANOW, &initial_settings);
}
/* kbhit함수가 키보드가 눌렸는지 검사한다. */ int kbhit() {
char ch; int nread;
if(peek)character != -1) return 1; new_settings.c_cc[VMIN] = 0; tcsetattr(0, TCSANOW, &new_settings); nread = read(0, &ch, 1); new_settings.c_cc[VMIN] = 1; tcsetattr(0, TCSANOW, &new_settings);
if(nread == 1) {
peek_character = ch;
return 1;
}
return 0;
}
/* 눌린 문자는 다음번 readch함수가 읽어들이고, 다음 루프를 위해 peek_character를 -1로 설정 */ int readch() {
char ch;
if(peek_character != -1) {
ch = peek_character;
peek_character = -1;
return ch;
}
read(0, &ch, 1);
return ch;
} </source>
ncurses[편집]
Hello world[편집]
<source lang="c"> /* curses_hello.c */
- include <unistd.h>
- include <curses.h>
int main() {
initscr(); /* Init curses */
move(5, 15);
printw("%s", "Hello World");
refresh();
sleep(2); endwin(); /* de-init curses */ return 0;
} </source>
초기화와 종료[편집]
<source lang="c">
- include <curses.h>
WINDOW *initscr(void); int endwin(void); </source>
- initscr은 한번만 호출되어야 함. 성공시 stdscr 구조체 반환, 실패하면 에러메세지를 내고 종료
- endwin은 성공시 OK 실패시 ERR 반환.
- clearok(stdscr, 1)이랑 refresh를 호출해서 재개가능하다.
- curses는 WINDOW구조체 내부에 직접 접근하는 방법을 제공하지 않는다.
출력[편집]
<source lang="c">
- include <curses.h>
int addch(const chtype char_to_add); int addchstr(chtype *const string_to_add); int printw(char *format, ...); int refresh(void); int box(WINDOW *win_ptr, chtype vertical_char, chtype horizontal_char); int insch(chtype char_to_insert); int insertln(void); int delch(void); int deleteln(void); int beep(void); int flash(void); </source>
- curses에는 이상한 데이터 타입으로 chtype이 있다. unsigned long 타입임.
- addch, addchstr은 현재위치에 출력, printw는 printw처럼 쓰면 된다.
- refresh는 화면갱신. 성공하면 OK, 에러는 ERR 반환. box는 윈도우 둘레에 상자를 그림.
- insch는 문자 삽입하고 오른쪽에 문자들을 밀어버린다.
- delete는 문자지우기. beep는 소리내기, flash함수는 깜빡이기.
읽기[편집]
<source lang="c">
- include <curses.h>
chtype inch(void); int instr(char *string); int innstr(char *string, int number_of_characters); </source>
- inch는 현재 커서가 가리키는 좌표와 문자를 반환
- instr, innstr은 읽어들인 문자를 char 배열에 기록한다.
화면 지우기[편집]
<source lang="c">
- include <curses.h>
int erase(void); int clear(void); int clrtobot(void); int clrtoeol(void); </source>
- erase, clear는 화면 전체를 공백으로 채운다. clear는 리프레시가 추가된것.
- clrtobot는 커서 위치부터 화면 끝까지(세로), clrtoeol은 커서 위치부터 줄 끝까지(가로) 지운다.
화면 이동[편집]
<source lang="c">
- include <curses.h>
int move(int new_y, int new_x); int leaveok(WINDIW *window_ptr, bool leave_flag); </source>
- move는 지정한 위치로 이동시킨다. 좌측상단 구석은 0,0 이다.
- leaveok는 화면갱신후 물리커서를 어디에 둘건지 제어한다. default는 false. Win32API의 Active Window 개념이라고 보면 쉬울듯
attribute[편집]
<source lang="c">
- include <curses.h>
int attron(chtype attribute); int attroff(chtype attribute); int attrset(chtype attribute); int standout(void); int standend(void); </source>
- 이런 속성이 있다.
- A_BLINK - 깜빡임
- A_BOLD - 볼드체
- A_DIM - 어두운색
- A_REVERSE - 역상. Reverse 이런것
- A_STANDOUT - 밝은색
- A_UNDERLINE - 밑줄
- attrset은 curses의 속성을 설정
- attron, attroff는 지정 속성을 켜거나 끈다.
<source lang="c"> /* moveadd.c */
- include <unistd.h>
- include <curses.h>
int main() {
const char witch_one[] = " First Witch "; const char witch_two[] = " Second Witch "; const char *scan_ptr;
initscr();
/* 세개의 텍스트 집합이 간격을 두고 화면상에 출력된다. 테스트 속성도 on, off한다. */
move(5, 15);
attron(A_BOLD);
printw("%s", "Macbeth");
attroff(A_BOLD);
refresh();
sleep(1);
move(8, 15);
attron(A_DIM);
printw("%s", "Thunder and Lightning");
attroff(A_DIM);
refresh();
sleep(1);
move(10, 10);
printw("%s", "when shall we three meet again");
move(11, 23);
printw("%s", "In thunder, ligning, or in rain?");
move(13, 10);
printw("%s", "When the hurlyburly's done,");
move(14,23);
printw("%s", "When the battle's lost and won.");
refresh();
sleep(1);
attron(A_DIM);
scan_ptr = witch_one + strlen(witch_one);
while(scan_ptr != which_two) {
move(13, 10);
insch(*scan_ptr--);
}
attroff(A_DIM);
refresh(); sleep(1);
endwin(); return 0;
} </source>
키보드[편집]
<source lang="c">
- include <curses.h>
int getch(void); int getstr(char *string); int getnstr(char *string, int number_of_characters); int scanw(char *format, ...); </source>
- getch는 getchar, getstr랑 getnstr은 gets, scanw는 scanf랑 사용법이 같다.
<source lang="c">
- include <unistd.h>
- include <curses.h>
- include <string.h>
- define PW_LEN 32
- define NAME_LEN 256
int main() {
char name[NAME_LEN]; char password[PW_LEN]; char *real_password = "xyzzy"; int i = 0;
initscr();
move(5, 10);
printw("%s", "Please login: ");
move(7, 10);
printw("%s", "User name: ");
getstr(name);
move(9, 10);
printw("%s", "Password: ");
refresh();
/* 비번이 화면에 보이면 망하니까 안보이게 하자. */ cbreak(); noecho();
memset(password, '\0', sizeof(password));
while(i < PW_LEN) {
password[i] = getch();
move(9, 20 + i);
addch('*');
refresh();
if(password[i] = '\n') break;
if(strcmp(password, real_password) == 0) break;
i++;
} /* while */
/* 키보드를 다시 원래대로 복구하고 메세지 출력 */ echo(); nocbreak();
move(11, 10);
if(strcmp(password, real_password) == 0) printw("%s", "Correct");
else printw("%s", "Wrong");
refresh();
sleep(3);
endwin(); return 0;
} </source>
다중 윈도우[편집]
<source lang="c">
- include <curses.h>
WINDOW *newwin(int num_of_lines, int num_of_cols, int start_y, int start_x); int delwin(WINDOW *window_to_delete); </source>
- newwin 함수는 새로운 윈도우를 생성한다. 윈도우의 좌측 상단은 (start_x, start_y)가 되고, 'num_of_lines'와 'num_of_cols'만큼의 크기를 가진다. 성공하면 윈도우에 대한 포인터를 반환하고, 실패하면 null을 반환한다.
- 윈도우가 영역을 벗어나면 실패한다.
- delwin은 newwin으로 만들어진 윈도우를 없애버린다.
<source lang="c">
- include <curses.h>
int waddch(WINDOW *window_pointer, const char char); int mvwaddch(WINDOW *window_pointer, int y, int x, const chtype char); int wprintw(WINDOW *window_pointer, char *format, ...); int mvwprintw(WINDOW *window_pointer, int y, int x, char *format, ...); int mvwin(WINDOW *window_to_move, int new_y, int new_x); int wrefresh(WINDOW *window_ptr); int wclear(WINDOW *window_ptr); int werase(WINDOW *window_ptr); int touchwin(WINDOW *window_ptr); int scrollok(WINDOW *window_ptr, bool scroll_flag); int scroll(WINDOW *window_ptr); </source>
- waddch, mvwaddch, wprintw, mvwprintw, wrefresh, wclear, werase는 각각 addch, mvaddch, printw, mvprintw, refresh, clear, erase의 윈도우 버전이라고 생각하면 된다.
- mvwin은 윈도우 위치를 변경한다. 범위를 벗어나면 실패.
- touchwin은 Active Window를 선택
- scrollok에 scroll_flag에 true를 세우면 스크롤이 가능하다.
<source lang="c"> /* multiw1.c */
- include <unistd.h>
- include <curses.h>
int main() {
WINDOW *new_window_ptr; WINDOW *popup_window_ptr; int x_loop; int y_loop; char a_letter = 'a';
initscr();
/* 기본 윈도우를 문자로 채우고 refresh. */
move(5,5);
printw("%s", "Testing multiple windows");
refresh();
for(x_loop = 0; x_loop < COLS -1; x_loop++) {
for(y_loop = 0; y_loop < LINES -1; y_loop++) {
mvaddch(stdscr, y_loop, x_loop, a_letter);
a_letter++;
if(a_letter > 'z') a_letter = 'a';
}
}
refresh(); sleep(2);
/* 10x20 크기의 새로운 윈도우를 만들고 텍스트를 조금 넣어본다. */ new_window_ptr = newwin(10, 20, 5, 5); mvprintw(new_window_ptr, 2, 2, "%s", "Hello World"); mvprintw(new_window_ptr, 5, 2, "%s", "Notice how very long lines wrap inside the window");
wrefresh(new_window_ptr); sleep(2);
/* 배경윈도우의 내용을 바꾸고 화면을 리플레시할 때 new_window_ptr이 가리키는 윈도우는 숨겨진다. */
a_letter = '0';
for(x_loop = 0; x_loop < COLS -1; x_loop++) {
for(y_loop = 0; y_loop < LINES -1; y_loop++) {
mvaddch(stdscr, y_loop, x_loop, a_letter);
a_letter++;
if(a_letter > '9') a_letter = '0';
}
}
refresh(); sleep(2);
/* 새로운 윈도우를 전혀 변경하지 않았으므로, 새로운 윈도우를 리프레시해도 아무것도 변하지 않는다. */ wrefresh(new_window_ptr); sleep(2);
/* 윈도우를 touch해서 Active Window라고 하고 리프레시하면 짠 */ touchwin(new_window_ptr); wrefresh(new_window_ptr); sleep(2);
/* 윈도우를 하나 만들어 겹치게 한다. */ popup_window_ptr = newwin(10, 20, 8, 8); box(popup_window_ptr, '|', '-'); mvwprintw(popup_window_ptr, 5, 2, "%s", "Pop Up Window!"); wrefresh(popup_window_ptr); sleep(2);
/* 좀더 뭔가 해보고 윈도우를 날려보자. */ touchwin(new_window_ptr); wrefresh(new_window_ptr); sleep(2);
wclear(new_window_ptr); wrefresh(new_window_ptr); sleep(2);
delwin(new_window_ptr); touchwin(popup_window_ptr); wrefresh(popup_window_ptr); sleep(2);
delwin(popup_window_ptr);
touchwin(stdscr); refresh(); sleep(2);
endwin(); return 0;
} </source>
보조윈도우[편집]
<source lang="c">
- include <curses.h>
WINDOW *subwin(WINDOW *parent, int num_of_lines, int num_of_cols, int start_y, int start_x); </source>
- 보조윈도우는 윈도우 안에 들어가는 윈도우다. MFC 기본 프로그램을 보면 창 안에 창이 여러개 들어가는걸 보여준다. 이것도 비슷한 개념이다.
- newwin처럼 쓰면 된다.
<source lang="c"> /* subscl.c */
- include <unistd.h>
- include <curses.h>
int main() {
WINDOW *sub_window_ptr; int x_loop; int y_loop; int counter; char a_letter = '1';
initscr();
for(x_loop = 0; x_loop < COLS -1; x_loop++) {
for(y_loop = 0; y_loop < LINES -1; y_loop++) {
mvaddch(stdscr, y_loop, x_loop, a_letter);
a_letter++;
if(a_letter > '9') a_letter = '1';
}
}
/* 스크롤 할 보조윈도우를 만들고, 리프레시하기전에 한번 만진다. */ sub_window_ptr = subwin(stdscr, 10, 20, 10, 10); scrollok(sub_window_ptr, 1);
touchwin(stdscr); refresh(); sleep(1);
/* 보조 윈도우의 내용을 지우고 보조 윈도우에 텍스트를 출력하고 리프레시한다. 스크롤하는 텍스트는 루프에 의해 만들어짐ㅋ */ werase(sub_window_ptr); mvwprintw(sub_window_ptr, 2, 0, "%s", "This window will now scroll"); wrefresh(sub_window_ptr); sleep(1);
for(counter = 1; counter < 10; counter++) {
wprintw(sub_window_ptr, "%s", "This text is both wrapping and scrolling.");
wrefresh(sub_window_ptr);
scroll(1);
}
/* 루프가 끝나면 보조윈도우를 제거하고 기본화면을 리프레시한다. */ delwin(sub_window_ptr);
touchwin(stdscr); refresh(); sleep(1);
endwin(); return 0;
} </source>
키패드[편집]
<source lang="c">
- include <curses.h>
int keypad(WINDOW *window_ptr, bool keypad_on); </source>
- keypad 함수에서 keypad_on을 true로 세우면 Keypad mode가 세팅되고, curses는 시퀀스키를 처리할 수 있다.
- Keypad mode에서는 제한사항이 몇가지가 있었다.
요즘 컴터중에 텍스트 처리 한다고 느려지는건 ARM걸리는 ARM밖에 없지
- 이스케이프 시퀀스를 인식하는건 타이밍에 의존하게 되는 경우도 있는데, 예전에 느린 네트워크에서는 MTU때문에 오동작하던 때가 있었다.
- 이스케이프 시퀀스의 처리는 일반적인 처리보다 많은 연산을 필요로 하는데, 요즘 PC에서는 신경쓰지 않아도 된다.
- 중복 이스케이프 시퀀스 처리는 불가능하다. 하지만 그거야 raw data를 건들지 않으면 거의 발생하지 않을 문제다.
<source lang="c"> /* keypad.c */
- include <curses.h>
- define LOCAL_ESCAPE_KEY 27
int main() {
int key;
initscr(); crmode(); keypad(stdscr, TRUE);
/* echo off하고 약간 메세지를 출력한다. 프로그램은 키입력을 기다려서 'q'면 종료하고, 에러가 나지 않았으면 뭐가 눌렸는지 대충 출력해본다. */ noecho();
clear(); mvprintw(5, 5, "Key pad demonstration. Press 'q' to quit."); move(7, 5); refresh();
key = getch();
while(key != ERR && key != 'q') {
move(7, 5);
clrtoeol();
if((key >= 'A' && key <= 'Z') || (key >= 'a' && key <= 'z')) {
printw("Key was %c", (char)key);
}
else {
switch(key) {
case LOCAL_ESCAPE_KEY: printw("%s", "Escape key"); break;
case KEY_END: printw("%s", "END key"); break;
case KEY_BEG: printw("%s", "BEGINNING key"); break;
case KEY_RIGHT: printw("%s", "RIGHT key"); break;
case KEY_LEFT: printw("%s", "LEFT key"); break;
case KEY_UP: printw("%s", "UP key"); break;
case KEY_DOWN: printw("%s", "DOWN key"); break;
default: printw("Unmatched - %d", key); break;
} /* switch */
} /* else */
refresh(); key = getch(); } /* while */
endwin(); return 0;
} </source>
색깔[편집]
- 원래 curses는 흑백이었다.
<source lang="c">
- include <curses.h>
bool has_colors(void); int start_color(void);
int init_pair(short pair_number, int foreground, int background); int COLOR_PAIR(int pair_number); int pair_content(short pair_number, short *foreground, short *background); </source>
- has_color는 색을 지원하는 터미널인지 확인하고 지원되면 true, 지원안되거나 에러면 false를 반환한다.
- start_color는 제대로 초기화되면 OK, 실패하면 ERR을 반환한다.
- start_color가 초기화되면 COLOR_PAIRS 변수에터미널이 제공할 수 있는 최대한의 색상 페어를 저장한다.
- 색깔을 쓰려면 그 색상 페어를 초기화해야 한다.
- 녹색 배경에 빨간 문자색을 색상페어로 지정하려면 다음과 같이 하면 된다.
<source lang="c"> init_pair(1, COLOR_RED, COLOR_GREEN); </source>
- 그리고 COLOR_PAIR로 제어할 수 있다.
<source lang="c"> wattron(window_ptr, COLOR_PAIR(1)); </source>
- 이렇게 하면 init_pair로 지정한 색으로 세팅된다.
- 다음처럼 조합도 가능하다.
<source lang="c"> wattron(window_ptr, COLOR_PAIR(1) | A_BOLD); </source>
- 잘 와닿지 않으면 다음을 보자.
<source lang="c"> /* color.c */
- include <unistd.h>
- include <stdio.h>
- include <curses.h>
int main() {
int i;
initscr();
if(!has_colors()) {
endwin();
fprintf(stderr, "Error - no color support on this terminal\n");
return 1;
}
if(start_color() != OK) {
endwin();
fprintf(stderr, "Error - could not initialize colors\n");
return 2;
}
/* 색상 페어로 7개의 페어를 만들어서 보여준다. */ clear(); mvprintw(5, 5, "There are %d COLORS, and %d COLOR_PAIRS available", COLORS, COLOR_PAIRS); refresh();
init_pair(1, COLOR_RED, COLOR_BLACK); init_pair(2, COLOR_RED, COLOR_GREEN); init_pair(3, COLOR_GREEN, COLOR_RED); init_pair(4, COLOR_YELLOW, COLOR_BLUE); init_pair(5, COLOR_BLACK, COLOR_WHITE); init_pair(6, COLOR_MAGENTA, COLOR_BLUE); init_pair(7, COLOR_CYAN, COLOR_WHITE);
for(i = 1; i <= 7; i++) {
attroff(A_BOLD);
attrset(COLOR_PAIR(i));
mvprintw(5 + i, 5, "Color pair %d", i);
attrset(COLOR_PAIR(i) | A_BOLD);
mvprintw(5 + i, 25, "Bold color pair %d", i);
refresh();
sleep(1);
}
endwin(); return 0;
} </source>
패드 윈도우[편집]
<source lang="c">
- include <curses.h>
WINDOW *newpad(int number_of_lines, int number_of_columns); int prefresh(WINDOW *pad_ptr, int pad_row, int pad_column, int screen_row_min, int screen_col_min, int screen_row_max, int screen_col_max); </source>
- newpad는 newwin이랑 사용법이 같다. 삭제도 delwin으로 가능하다.
<source lang="c"> /* pad.c */ /* 패드를 초기화하고 패드를 만든다. 그리고 패드에 문자를 채워넣는다. 패드는 보통의 터미널보다 크다. */
- include <unistd.h>
- include <curses.h>
int main() {
WINDOW *pad_ptr; int x, y; int pad_lines; int pad_cols; char disp_char;
initscr();
pad_lines = LINES + 50; pad_cols = COLS + 50;
pad_ptr = newpad(pad_lines, pad_cols);
disp_char = 'a';
for (x = 0; x< pad_lines; x++) {
for (y = 0; y < pad_cols; y++) {
mvwaddch(pad_ptr, x, y, disp_char);
if (disp_char == 'z') disp_char = 'a';
else disp_char++;
}
}
/* 이제 패드의 영역을 화면상의 특정위치로 쓴다. */ prefresh(pad_ptr, 5, 7, 2, 2, 9, 9); sleep(1);
prefresh(pad_ptr, LINES +5, COLS +7, 5, 5, 21, 19); sleep(1);
delwin(pad_ptr); endwin(); return 0;
} </source>
종합[편집]
<source lang="c"> /* cdapp.c */
- include <unistd.h>
- include <stdlib.h>
- include <stdio.h>
- include <string.h>
- include <curses.h>
- define MAX_STRING (80) /* 제일 긴 문자열 */
- define MAX_ENTRY (1024) /* 제일 긴 데이터베이스 엔트리 */
- define MESSAGE_LINE 6 /* 기타 메세지 표현할 라인 */
- define ERROR_LINE 22 /* 에러를 표시할 라인 */
- define Q_LINE 20 /* 질문을 표시할 라인 */
- define PROMPT_LINE 18 /* 입력 프롬프트를 표시할 라인 */
/* 몇가지 전역변수를 정의한다. current_cd는 현재 CD의 제목을 저장, 초기화는 null로, 선택된 CD가 없다는걸 보여준다. current_cat는 현재 CD의 카탈로그 번호를 기록하는데 사용 */ static char current_cd[MAX_STRING] = "\0"; static char current_cat[MAX_STRING];
/* 몇몇 파일 이름을 정의한다. 프로그램을 간단하게 하기 위해 파일 이름을 고정시킨다. */ const char *title_file = "title.cdb"; const char *tracks_file = "tracks.cdb"; const char *temp_file="cdb.tmp";
/* 프로그램에서 필요한 함수 원형 */ void clear_all_screen(void); void get_return(void); int get_confirm(void); int getchoice(char *greet, char *choices[]); void draw_menu(char *options[], int highlight, int start_row, int start_col); void insert_title(char *cdtitle); void get_string(char *string); void add_record(void); void count_cds(void); void find_cd(void); void list_tracks(void); void remove_tracks(void); void remove_cd(void); void update_cd(void);
/* 메뉴를 출력하기 위한 문자열 배열. 첫번째 문자는 메뉴가 선택됐을때 반환될 문자고, 나머지 텍스트는 출력될 문자다. 현재 선택된 CD가 있을 경우에는 extended_menu에 있는 메뉴를 출력한다. */ char *main_menu[] = {
"aadd new CD", "ffind CD", "ccount CDs and tracks in the catalog", "qquit", 0,
};
char *extended_menu[] = {
"aadd new CD", "ffind CD", "ccount CDs and tracks in the catalog", "llist tracks on current CD", "rremove current CD", "uupdate track information", "qquit", 0,
};
/* main은 메뉴를 선택하고 q를 누르면 종료한다. */ int main() {
int choice;
initscr();
do {
choice = getchoice("Options:", current_cd[0] ? extended_menu : main_menu);
switch(choice) {
case 'q':
break;
case 'a':
add_record();
break;
case 'c':
count_cds();
break;
case 'f':
find_cd();
break;
case 'l':
list_tracks();
break;
case 'r':
remove_cd();
break;
case 'u':
update_cd();
break;
}
} while (choice != 'q');
endwin();
return 0;
}
/* getchoice 함수는 main에서 호출되는 함수고, greet랑 choices가 인자로 전달된다. choices는 main_menu나 extended_menu에 대한 포인터다. */ int getchoice(char *greet, char *choices[]) {
static int selected_row = 0; int max_row = 0; int start_screenrow = MESSAGE_LINE, start_screencol = 10; char **option; int selected; int key = 0;
option = choices;
while(*option) {
max_row++;
option++;
}
if(selected_row >= max_row) selected_row = 0; clear_all_screen(); mvprintw(start_screenrow - 2, start_screencol, greet);
keypad(stdscr, TRUE); cbreak(); noecho();
key = 0;
while (key != 'q' && key != KEY_ENTER && key != '\n') {
if(key == KEY_UP) {
if(selected_row == 0)
selected_row = max_row -1;
else
selected_row--;
}
if(key == KEY_DOWN) {
if(selected_row == (max_row -1))
selected_row = 0;
else
selected_row++;
}
selected = *choices[selected_row]; draw_menu(choices, selected_row, start_screenrow, start_screencol); key = getch(); }
keypad(stdscr, FALSE); nocbreak(); echo();
if(key == 'q') selected = 'q';
return (selected);
}
/* getchoice 함수 내부에서 호출된 clear_all_screen, draw_menu 함수 */ void draw_menu(char *options[], int current_highlight, int start_row, int start_col) {
int current_row = 0; char **option_ptr; char *txt_ptr;
option_ptr = options;
while (*option_ptr) {
if(current_row == current_highlight) {
mvaddch(start_row + current_row, start_col - 3, ACS_BULLET);
mvaddch(start_row + current_row, start_col + 40, ACS_BULLET);
} else {
mvaddch(start_row + current_row, start_col -3, ' ');
mvaddch(start_row + current_row, start_col + 40, ' ');
}
txt_ptr = options[current_row]; txt_ptr++; mvprintw(start_row + current_row, start_col, "%s", txt_ptr); current_row++; option_ptr++; }
mvprintw(start_row + current_row + 3, start_col, "Move highlight then press Enter.");
refresh();
}
/* clear_all_screen은 화면을 지우고 제목을 다시 적는다. CD가 선택되면 해당 정보가 출력된다. */ void clear_all_screen() {
clear();
mvprintw(2, Q_LINE, "%s", "CD Database Application");
if(current_cd[0]) {
mvprintw(ERROR_LINE, 0, "Current CD: %s: %s\n", current_cat, current_cd);
}
refresh();
}
/* 새로 CD레코드를 추가하는 함수 */ void add_record() {
char catalog_number[MAX_STRING]; char cd_title[MAX_STRING]; char cd_type[MAX_STRING]; char cd_artist[MAX_STRING]; char cd_entry[MAX_STRING];
int screenrow = MESSAGE_LINE; int screencol = 10;
clear_all_screen(); mvprintw(screenrow, screencol, "Enter new CD details"); screenrow += 2;
mvprintw(screenrow, screencol, "Catalog Number: "); get_string(catalog_number); screenrow ++;
mvprintw(screenrow, screencol, " CD Title: "); get_string(cd_title); screenrow ++;
mvprintw(screenrow, screencol, " CD Type: "); get_string(cd_type); screenrow ++;
mvprintw(screenrow, screencol, " Artist: "); get_string(cd_artist); screenrow ++;
mvprintw(15, 5, "About to add this new entry:"); sprintf(cd_entry, "%s, %s, %s, %s", catalog_number, cd_title, cd_type, cd_artist); mvprintw(17, 5, "%s", cd_entry); refresh();
move(PROMPT_LINE, 0);
if(get_confirm()) {
insert_title(cd_entry);
strcpy(current_cd, cd_title);
strcpy(current_cat, catalog_number);
}
}
/* get_string 함수는 현재 화면 위치에서 문자열을 읽는다. */ void get_string(char *string) {
int len;
wgetnstr(stdscr, string, MAX_STRING); len = strlen(string); if(len > 0 && string[len - 1] == '\n') string[len - 1] = '\0';
}
/* get_confirm 함수는 사용자의 의사를 확인한다. */ int get_confirm() {
int confirmed = 0; char first_char = 'N';
mvprintw(Q_LINE, 5, "Are you sure? "); clrtoeol(); refresh();
cbreak();
first_char = getch();
if(first_char == 'Y' || first_char == 'y') {
confirmed = 1;
}
nocbreak();
if(!confirmed) {
mvprintw(Q_LINE, 1, " Cancelled");
clrtoeol();
refresh();
sleep(1);
}
return confirmed;
}
/* insert_title 함수는 타이틀을 추가한다. 타이틀 문자열을 타이틀 파일의 마지막에 추가하는 방식을 사용. */ void insert_title(char *cdtitle) {
FILE *fp = fopen(title_file, "a");
if(!fp) {
mvprintw(ERROR_LINE, 0, "cannot open CD titles database");
} else {
fprintf(fp, "%s\n", cdtitle);
fclose(fp);
}
}
/* 화면 창을 표현하기 위한 전역 상수 몇개를 선언한다. */
- define BOXED_LINES 11
- define BOXED_ROWS 60
- define BOX_LINE_POS 8
- define BOX_ROW_POS 2
/* update_cd는 CD 트랙을 수정한다. */ void update_cd() {
FILE *tracks_fp; char track_name[MAX_STRING]; int len; int track = 1; int screen_line = 1; WINDOW *box_window_ptr; WINDOW *sub_window_ptr; clear_all_screen(); mvprintw(PROMPT_LINE, 0, "Re-entering tracks for CD. "); if(!get_confirm()) return; move(PROMPT_LINE, 0); clrtoeol(); remove_tracks(); mvprintw(MESSAGE_LINE, 0, "Enter a blank line to finish");
tracks_fp = fopen(tracks_file, "a");
box_window_ptr = subwin(stdscr, BOXED_LINES + 2, BOXED_ROWS + 2, BOX_LINE_POS - 1, BOX_ROW_POS - 1); if(!box_window_ptr) return; box(box_window_ptr, ACS_VLINE, ACS_HLINE);
sub_window_ptr = subwin(stdscr, BOXED_LINES, BOXED_ROWS, BOX_LINE_POS, BOX_ROW_POS); if(!sub_window_ptr) return; scrollok(sub_window_ptr, TRUE); werase(sub_window_ptr); touchwin(stdscr);
do {
mvwprintw(sub_window_ptr, screen_line++, BOX_ROW_POS + 2, "Track %d: ", track);
clrtoeol();
refresh();
wgetnstr(sub_window_ptr, track_name, MAX_STRING);
len = strlen(track_name);
if(len > 0 && track_name[len - 1] == '\n')
track_name[len - 1] = '\0';
if(*track_name)
fprintf(tracks_fp, "%s, %d, %s\n", current_cat, track, track_name);
track++;
if(screen_line > BOXED_LINES - 1) {
/* time to start scrolling */
scroll(sub_window_ptr);
screen_line--;
}
} while(*track_name);
delwin(sub_window_ptr);
fclose(tracks_fp);
}
/* 다음은 remove_cd */ void remove_cd() {
FILE *titles_fp, *temp_fp; char entry[MAX_ENTRY]; int cat_length;
if(current_cd[0] == '\0') return;
clear_all_screen(); mvprintw(PROMPT_LINE, 0, "About to remove CD %s: %s. ", current_cat, current_cd); if(!get_confirm()) return;
cat_length = strlen(current_cat);
/* 타이틀 파일을 임시파일로 복사한다 */
titles_fp = fopen(title_file, "r");
temp_fp = fopen(temp_file, "w");
while(fgets(entry, MAX_ENTRY, titles_fp)) {
/* 카타로그 넘버랑 복사된 엔트리랑 체크 */
if(strncmp(current_cat, entry, cat_length) != 0)
fputs(entry, temp_fp);
}
fclose(titles_fp);
fclose(temp_fp);
/* 타이틀 파일을 삭제하고, 임시파일을 타이틀파일로 덮어쓴다. */ unlink(title_file); rename(temp_file, title_file);
/* 트랙 파일도 마찬가지 작업을 한다. */ remove_tracks();
/* 현재 CD는 없는 CD라고 입력해둔다. */ current_cd[0] = '\0';
}
/* remove_tracks는 트랙을 제거한다. update_cd랑 remove_cd에서 호출됨. */ void remove_tracks() {
FILE *tracks_fp, *temp_fp; char entry[MAX_ENTRY]; int cat_length;
if(current_cd[0] == '\0') return;
cat_length = strlen(current_cat);
tracks_fp = fopen(tracks_file, "r"); temp_fp = fopen(temp_file, "w");
while(fgets(entry, MAX_ENTRY, tracks_fp)) {
/* 카타로그 넘버랑 복사된 엔트리랑 체크 */
if(strncmp(current_cat, entry, cat_length) != 0)
fputs(entry, temp_fp);
}
fclose(tracks_fp);
fclose(temp_fp);
unlink(tracks_file); rename(temp_file, tracks_file);
}
/* count_cds는 디비를 뒤져서 타이틀과 트랙의 갯수를 구한다. */ void count_cds() {
FILE *titles_fp, *tracks_fp; char entry[MAX_ENTRY]; int titles = 0; int tracks = 0;
titles_fp = fopen(title_file, "r");
if(titles_fp) {
while(fgets(entry, MAX_ENTRY, titles_fp))
titles++;
fclose(titles_fp);
}
tracks_fp = fopen(tracks_file, "r");
if(tracks_fp) {
while(fgets(entry, MAX_ENTRY, tracks_fp))
tracks++;
fclose(tracks_fp);
}
mvprintw(ERROR_LINE, 0, "Database contains %d titles, with a total of %d tracks.", titles, tracks);
get_return();
}
/* 트랙 목록 검색해서 CD 타이틀을 찾을 수 있다 */ void find_cd() {
char match[MAX_STRING], entry[MAX_ENTRY]; FILE *titles_fp; int count = 0; char *found, *title, *catalog;
mvprintw(Q_LINE, 0, "Enter a string to search for in CD titles: ");
get_string(match);
titles_fp = fopen(title_file, "r");
if(titles_fp) {
while(fgets(entry, MAX_ENTRY, titles_fp)) {
/* 이전 카다록 스킵 */
catalog = entry;
if(found = strstr(catalog, ",")) {
*found = 0;
title = found + 1;
/* 콤마 지우기 */
if(found = strstr(title, ",")) {
*found = '\0';
if(found = strstr(title, match)) {
count++;
strcpy(current_cd, title);
strcpy(current_cat, catalog);
}
}
}
}
fclose(titles_fp);
}
if(count != 1) {
if(count == 0)
mvprintw(ERROR_LINE, 0, "Sorry, no matching CD found. ");
if(count > 1) {
mvprintw(ERROR_LINE, 0, "Sorry, match is ambiguous: %d CDs found. ", count);
}
current_cd[0] = '\0';
get_return();
}
}
void list_tracks() {
FILE *tracks_fp; char entry[MAX_ENTRY]; int cat_length; int lines_op = 0; WINDOW *track_pad_ptr; int tracks = 0; int key; int first_line = 0;
if (current_cd[0] == '\0') {
mvprintw(ERROR_LINE, 0, "You must select a CD first. ", stdout);
get_return();
return;
}
clear_all_screen(); cat_length = strlen(current_cat);
/* 현재 CD의 카운트 */
tracks_fp = fopen(tracks_file, "r");
if(!tracks_fp)
return;
while(fgets(entry, MAX_ENTRY, tracks_fp)) {
if(strncmp(current_cat, entry, cat_length) == 0)
tracks++;
}
fclose(tracks_fp);
/* 새 패드를 만든다. */
track_pad_ptr = newpad(tracks + 1 + BOXED_LINES, BOXED_ROWS + 1);
if(!track_pad_ptr)
return;
tracks_fp = fopen(tracks_file, "r");
if(!tracks_fp)
return;
mvprintw(4, 0, "CD Track Listening\n");
/* 트랙 정보를 패드에 작성 */
while(fgets(entry, MAX_ENTRY, tracks_fp)) {
/* 카다록 넘버랑 나머지를 출력 */
if(strncmp(current_cat, entry, cat_length) == 0) {
mvwprintw(track_pad_ptr, lines_op++, 0, "%s", entry + cat_length + 1);
}
}
fclose(tracks_fp);
if(lines_op > BOXED_LINES) {
mvprintw(MESSAGE_LINE, 0, "Cursor keys to scroll, Enter or q to exit");
} else {
mvprintw(MESSAGE_LINE, 0, "Enter or q to exit");
}
wrefresh(stdscr);
keypad(stdscr, TRUE);
cbreak();
noecho();
key = 0;
while(key != 'q' && key != KEY_ENTER && key != '\n') {
if(key == KEY_UP) {
if(first_line > 0)
first_line--;
}
if(key == KEY_DOWN) {
if(first_line + BOXED_LINES + 1 < tracks)
first_line++;
}
/* 이제 패드에서 적절한 부분을 떼서 그린다. */
prefresh(track_pad_ptr, first_line, 0, BOX_LINE_POS, BOX_ROW_POS, BOX_LINE_POS + BOXED_LINES, BOX_ROW_POS + BOXED_ROWS);
/* wrefresh(stdscr); */
key = getch();
}
delwin(track_pad_ptr);
keypad(stdscr, FALSE);
nocbreak();
echo();
}
/* get_return 함수는 리턴문자가 입력될때까지 프롬프트를 출력 */ void get_return() {
int ch;
mvprintw(23, 0, "%s", " Press Enter "); refresh(); while((ch = getchar()) != '\n' && ch != EOF);
} </source>