第4章 鼠標支持
4.1鼠標支持簡介
終端支持鼠標操作目前是一個比較高級的話題,很少有資料涉及。另一方面支持鼠標操作的終端實際上目前非常的少,除了xterm類型的終端外,其余的終端都不支持。因此這部分大多數人可能不需要,所以本書開始不准備涉及這方面的討論,但考慮到書籍的完整性,還是把這部分加入。(Sco Unix、Solaris等操作系統終端環境下都不支持鼠標)。本章的示例程序可以在Linux下的X Window中通過。
鼠標能夠讓用戶的操作更加方便和容易,但存在着一個非常嚴重的問題,即就是程序的移植性問題。curses庫的引入正是為了解決終端的移植問題,如果過多的使用鼠標支持,將使得程序的移植性非常的差,在那些不支持鼠標的終端上操作將無法進行,這將違背於curses庫當初的設計。因此我們應該避免使用鼠標,對於那些使用鼠標的程序最好同時能夠提供鍵盤解決方案,除非你能保證你使用的所有終端都能夠支持鼠標。
4.2鼠標支持概念和數據結構
鼠標操作將引入一個新的概念:事件。每一個鼠標操作都對應於一個鼠標事件,同時會觸發該鼠標事件。一旦鼠標事件觸發,我們就可以執行特定的程序。目前,curses庫中支持的所有的鼠標事件如表4.1。
表4.1curses庫中的所有鼠標事件
鼠標事件
鼠標操作
BUTTON1_PRESSED
按下鼠標鍵1
BUTTON1_RELEASED
松開鼠標鍵1
BUTTON1_CLICKED
單擊鼠標鍵1
BUTTON1_DOUBLE_CLICKED
雙擊鼠標鍵1
BUTTON1_TRIPLE_CLICKED
連續三次點擊鼠標鍵1
BUTTON2_PRESSED
按下鼠標鍵2
BUTTON2_RELEASED
松開鼠標鍵2
BUTTON2_CLICKED
單擊鼠標鍵2
BUTTON2_DOUBLE_CLICKED
雙擊鼠標鍵2
BUTTON2_TRIPLE_CLICKED
連續三次點擊鼠標鍵2
BUTTON3_PRESSED
按下鼠標鍵3
BUTTON3_RELEASED
松開鼠標鍵3
BUTTON3_CLICKED
單擊鼠標鍵3
BUTTON3_DOUBLE_CLICKED
雙擊鼠標鍵3
BUTTON3_TRIPLE_CLICKED
連續三次點擊鼠標鍵3
BUTTON4_PRESSED
按下鼠標鍵4
BUTTON4_RELEASED
松開鼠標鍵4
BUTTON4_CLICKED
單擊鼠標鍵4
BUTTON4_DOUBLE_CLICKED
雙擊鼠標鍵4
BUTTON4_TRIPLE_CLICKED
連續三次點擊鼠標鍵4
BUTTON_SHIFT
在鼠標狀態改變期間按下SHIFT鍵
BUTTON_CTRL
在鼠標狀態改變期間按下CTRL鍵
BUTTON_ALT
在鼠標狀態改變期間按下ALT鍵
ALL_MOUSE_ENENT
報告所有的鼠標的狀態改變
REPORT_MOUSE_POSITION
鼠標移動
為了能夠描述每個鼠標事件,curses中使用結構MEVENT,它的結構如下:
typedef struct {
short id;
int x,y,z;
mmask_t bstate;
}MEVENT;
其中bstate是我們最感興趣的成員,它描述了事件發生的時候鼠標按鍵的狀態;
其余的id用來區分消息來源的不同的設備,比如鼠標,圖形板等等;x,y給出了事件發生時候的鼠標位置,至於z暫時沒有使用。
另一方面,為了能夠保存鼠標事件,curses中引入了鼠標事件隊列。所有的鼠標事件觸發后,它們將被壓入鼠標事件隊列中等候處理。實際上所有的鼠標函數都是通過鼠標事件隊列獲取相應的鼠標事件。
4.3開始使用鼠標
4.3.1鼠標操作函數
curses中的鼠標支持函數包括下面的七個:
■mmask_t mousemask(mmask_t newmask,mmask_t *oldmask);
這個函數是所有的鼠標函數中第一個調用的,它的作用相當於initscr(),它初始化鼠標系統,通知系統必須截獲並處理參數newmask指定的鼠標事件,指定之外的事件則可以忽略。如果設置之前系統指定的處理事件oldmask不為NULL,則原有的事件保存在oldmask中,這樣一旦處理結束后可以恢復為原來的設置。默認情況下,函數不對任何事件進行處理。因此如果需要使用鼠標進行處理,我們必須自己進行設置。如果我們對所有的鼠標事件都需要處理的話,那可以使用ALL_MOUSE_EVENT事件;如果需要關閉所有的鼠標事件,newmask可以設置為0。
如果函數執行錯誤,將返回0;否則返回設置的當前位置。如果我們需要處理鼠標的所有雙擊事件,那么函數用法可以如下:
mousemask(BUTTON1_DOUBLE_CLICKED|BUTTON2_DOUBLE_CLICKED|
BUTTON3_DOUBLE_CLICKED|BUTTON4_DOUBLE_CLICKED,
Old_mask);
■ intmouseinterval(int erval);
該函數用來設置鼠標一次點擊的時間間隔。一次鼠標點擊定義為在一定的時間間隔內鼠標被按下又被釋放的過程。默認情況下這個時間間隔為1/5秒。通過mouseinterval()函數我們可以修改這個時間間隔。erval就是需要設定的時間間隔。它的單位是毫秒。如果將鼠標點擊的時間間隔從默認值更改為1秒,則函數用法如下:
mouseinterval(1000);
如果函數執行成功,將返回OK,否則返回ERR。
■int getmouse(MEVENT *envnt);
■int ungetmouse(MEVENT *event);
與使用getch()和wgetch()從鍵盤接受輸入一樣,我們也同樣使用getch()和wgetch()從鼠標接受輸入。但是為了區別鍵盤輸入和鼠標輸入,我們定義了KEY_MOUSE常量。如果是鼠標輸入,getch()和wgetch()將返回KEY_MOUSE。因此我們可以通過判斷getch()和wgetch()的返回值是否為KEY_MOUSE判斷是否是鼠標輸入。
如果輸入為鼠標,則 getmouse()函數用來從鼠標事件隊列中獲取下一個鼠標事件。如果執行成功,它將根據鼠標事件填充結構mevent。因此通過獲取該event結構中的bstate成員可以知道具體的鼠標事件。
因此整個鼠標處理的代碼可以如下:
MEVENT event;
ch = getch();
if(ch == KEY_MOUSE)
if(getmouse(&event) == OK)
. /*事件處理代碼*/
.
.
■bool wenclose(WINDOW *win,int y,int x);
ungetmouse()的作用與ungetch()類似,它將KEY_MOUSE事件返回給getch()和wgetch()函數的輸入隊列,同時將鼠標事件返回給鼠標事件隊列。
■bool wmouse_trafo(const WINDOW *win,int *pY,int *pX,bool to_screen);
這兩個函數用來處理MEVENT結構中的x,y坐標。wenclose()用來判斷參數中的(x;y)坐標是否在給定的窗口win中。對於前面的函數getmouse(),它返回的MEVENT結構中的(x,y)坐標是相對於屏幕的左上角而言。而wenclose()是相對於指定窗口的。getmouse()用來獲取整個屏幕上的指定的鼠標事件,而wenclose()只能處理指定窗口內的鼠標事件。發生在窗口之外的其余的鼠標事件,wenclose()無法獲取。一旦在窗口內觸發鼠標事件,wmouse_trafo()函數將把屏幕坐標轉換為窗口坐標,這時候最后一個參數to_screen必需設置為FALSE;如果to_screen設置為TRUE,則函數將把窗口坐標轉換為屏幕相對坐標。
4.3.2 鼠標程序開發步驟
鼠標程序的開發一般遵循下面的一些步驟:
(1)使用mousemask()函數初始化需要獲取的鼠標事件。
(2)循環使用getch()或者wgetch()函數獲取鍵盤或者鼠標輸入,如果getch()和wgetch()返回KEY_MOUSE,則表明是鼠標輸入。
(3)通過getmouse()獲取觸發的鼠標事件,根據具體的事件進行處理。
下面我們根據這三個開發步驟看下面的一個示例程序。
4.3.3示例程序
下面的程序演示了如何使用鼠標進行菜單選擇,
#include
#define WIDTH 30
#define HEIGHT 10
int startx = 0;
int starty = 0;
char *choices[] = {
"Choice 1",
"Choice 2",
"Choice 3",
"Choice 4",
"Exit",
};
int n_choices = sizeof(choices) / sizeof(char *);
void print_menu(WINDOW *menu_win, int highlight);
void report_choice(int mouse_x, int mouse_y, int *p_choice);
int main()
{
int c, choice = 0;
WINDOW *menu_win;
MEVENT event;
initscr();
clear();
noecho();
cbreak();
/*在屏幕上輸出窗口*/
startx = (80 - WIDTH) / 2;
starty = (24 - HEIGHT) / 2;
attron(A_REVERSE);
mvprintw(23, 1, "Click on Exit to quit");
refresh();
attroff(A_REVERSE);
/* Print the menu for the first time */
menu_win = newwin(HEIGHT, WIDTH, starty, startx);
print_menu(menu_win, 1);
/* Get all the mouse events */
mousemask(ALL_MOUSE_EVENTS, NULL);
while(1)
{ c = wgetch(menu_win);
switch(c)
{ case KEY_MOUSE:
if(getmouse(&event) == OK)
{ /*一旦用戶按下左鍵*/
if(event.bstate & BUTTON1_PRESSED)
{ report_choice(event.x + 1, event.y + 1, &choice);
if(choice == -1) //Exit chosen
goto end;
mvprintw(22, 1, "Choice made is : %d String Chosen is /"%10s/"", choice, choices[choice - 1]);
refresh();
}
}
print_menu(menu_win, choice);
break;
}
}
end:
endwin();
return 0;
}
void print_menu(WINDOW *menu_win, int highlight)
{
int x, y, i;
x = 2;
y = 2;
box(menu_win, 0, 0);
for(i = 0; i
{ if(highlight == i + 1)
{ wattron(menu_win, A_REVERSE);
mvwprintw(menu_win, y, x, "%s", choices[i]);
wattroff(menu_win, A_REVERSE);
}
else
mvwprintw(menu_win, y, x, "%s", choices[i]);
++y;
}
wrefresh(menu_win);
}
void report_choice(int mouse_x, int mouse_y, int *p_choice)
{ int i,j, choice;
i = startx + 2;
j = starty + 3;
for(choice = 0; choice
if(mouse_y == j + choice && mouse_x >= i && mouse_x <= i + strlen(choices[choice]))
{ if(choice == n_choices - 1)
*p_choice = -1;
else
*p_choice = choice + 1;
break;
}
}