终端支持鼠标操作目前是一个比较高级的话题,很少有资料涉及。另一方面支持鼠标操作的终端实际上目前非常的少,除了xterm 类型的终端外,其余的终端都不支持。因此这部分大多数人可能不需要,所以本书开始不准备涉及这方面的讨论,但考虑到书籍的完整性,还是把这部分加入。 (Sco Unix 、 Solaris 等操作系统终端环境下都不支持鼠标 ) 。本章的示例程序可以在 Linux 下的 X Window 中通过。
鼠标能够让用户的操作更加方便和容易,但存在着一个非常严重的问题,即就是程序的移植性问题。curses 库的引入正是为了解决终端的移植问题,如果过多的使用鼠标支持,将使得程序的移植性非常的差,在那些不支持鼠标的终端上操作将无法进行,这将违背于 curses 库当初的设计。因此我们应该避免使用鼠标,对于那些使用鼠标的程序最好同时能够提供键盘解决方案,除非你能保证你使用的所有终端都能够支持鼠标。
鼠标操作将引入一个新的概念:事件。每一个鼠标操作都对应于一个鼠标事件,同时会触发该鼠标事件。一旦鼠标事件触发,我们就可以执行特定的程序。目前,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 中引入了鼠标事件队列。所有的鼠标事件触发后,它们将被压入鼠标事件队列中等候处理。实际上所有的鼠标函数都是通过鼠标事件队列获取相应的鼠标事件。
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);
■ int mouseinterval(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 ,则函数将把窗口坐标转换为屏幕相对坐标。
鼠标程序的开发一般遵循下面的一些步骤:
(1) 使用mousemask() 函数初始化需要获取的鼠标事件。
(2) 循环使用getch() 或者 wgetch() 函数获取键盘或者鼠标输入,如果 getch() 和 wgetch() 返回 KEY_MOUSE ,则表明是鼠标输入。
(3) 通过getmouse() 获取触发的鼠标事件,根据具体的事件进行处理。
下面我们根据这三个开发步骤看下面的一个示例程序。
下面的程序演示了如何使用鼠标进行菜单选择,
#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 < n_choices; ++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 < n_choices; ++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;
}
}