本篇内容依然比较简单,主要是优化窗口功能以及开发定时器应用程序。首先是优化窗口的切换功能,实现通过键盘和鼠标切换窗口,然后是实现通过鼠标关闭窗口。接着实现不同窗口输入状态的切换,最后是实现定时器的API与应用程序。
1. 窗口优化
前面已经完成了窗口的基本功能,现在需要对窗口功能进行进一步优化。首先是增加窗口的切换功能。
1.1 通过按键实现窗口切换
先从简单的开始,实现按下F11后将最下面的窗口放在最上面。F11的按键编码为0x57,只需要在主程序中添加如下代码:
……
if (i == 256 + 0x57 && shtctl->top > 2)
{ /* F11 */
sheet_updown(shtctl->sheets[1], shtctl->top - 1);
}
……
代码很简单,sheets[0]表示背景图层,sheets[1]即为最下层的窗口。top图层为鼠标图层,按下F11键后将最下层的窗口调整到鼠标图层下一层即可(因为不能覆盖鼠标)。
1.2 通过鼠标实现窗口切换
接下来实现通过鼠标点击实现的窗口切换。鼠标点击画面上的某个地方时,我们需要按照从上到下的顺序判断鼠标的位置落在哪个图层的范围内,并且还需要确保该位置不是透明色区域:
else if (512 <= i && i <= 767)
{ /* 鼠标数据 */
if (mouse_decode(&mdec, i - 512) != 0)
{
/* 鼠标指针移动 */
mx += mdec.x;
my += mdec.y;
if (mx < 0)
{
mx = 0;
}
if (my < 0)
{
my = 0;
}
if (mx > binfo->scrnx - 1)
{
mx = binfo->scrnx - 1;
}
if (my > binfo->scrny - 1)
{
my = binfo->scrny - 1;
}
sheet_slide(sht_mouse, mx, my);
if ((mdec.btn & 0x01) != 0)
{
/* 按下左键 */
/* 按照从上到下的顺序寻找鼠标所指向的图层 */
for (j = shtctl->top - 1; j > 0; j--)
{
sht = shtctl->sheets[j];
x = mx - sht->vx0;
y = my - sht->vy0;
if (0 <= x && x < sht->bxsize && 0 <= y && y < sht->bysize)
{
if (sht->buf[y * sht->bxsize + x] != sht->col_inv)
{
sheet_updown(sht, shtctl->top - 1);
break;
}
}
}
}
}
这一部分的逻辑也比较清楚。按照从上到下的顺序寻找鼠标点击的图层,如果鼠标点击的位置处于该图层的范围之内,就将该图层移动到最上面。如果将命令行窗口移动到最上面,则会遮挡其他窗口。不过这时又可以通过F11将下面的窗口切换上来。
1.3 窗口移动
窗口的切换功能基本完成了,接下来实现窗口的移动功能。之前单独实现了任务A窗口的实现功能,现在有了多个任务窗口,需要重新进行实现。
当鼠标左键点击窗口时,如果点击窗口的标题栏区域,则进入窗口移动模式,使窗口位置随着鼠标指针移动;而放开鼠标左键时,退出窗口移动模式,返回普通模式。实现窗口的移动需要记录鼠标移动的距离,这里添加了两个变量mmx和mmy,用于记录移动之前的坐标,并且规定mmx=-1时不处于窗口移动模式。
……
int j, x, y, mmx = -1, mmy = -1;
struct SHEET *sht = 0;
else if (512 <= i && i <= 767)
{ /* 鼠标数据 */
if (mouse_decode(&mdec, i - 512) != 0)
{
/*按下左键 */
mx += mdec.x;
my += mdec.y;
if (mx < 0)
{
mx = 0;
}
if (my < 0)
{
my = 0;
}
if (mx > binfo->scrnx - 1)
{
mx = binfo->scrnx - 1;
}
if (my > binfo->scrny - 1)
{
my = binfo->scrny - 1;
}
sheet_slide(sht_mouse, mx, my);
if ((mdec.btn & 0x01) != 0)
{
/* 按下左键 */
if (mmx < 0)
{
/* 处于通常模式,不处于窗口移动模式 */
/* 按照从上到下的顺序寻找鼠标指针所在的图层 */
for (j = shtctl->top - 1; j > 0; j--)
{
sht = shtctl->sheets[j];
x = mx - sht->vx0;
y = my - sht->vy0;
if (0 <= x && x < sht->bxsize && 0 <= y && y < sht->bysize)
{
if (sht->buf[y * sht->bxsize + x] != sht->col_inv)
{
sheet_updown(sht, shtctl->top - 1);
if (3 <= x && x < sht->bxsize - 3 && 3 <= y && y < 21)
{
mmx = mx; /* 进入窗口移动模式 */
mmy = my;
}
break;
}
}
}
}
else
{
/* 如果处于窗口移动模式 */
x = mx - mmx; /* 计算鼠标移动距离 */
y = my - mmy;
sheet_slide(sht, sht->vx0 + x, sht->vy0 + y);
mmx = mx; /* 更新移动后窗口坐标*/
mmy = my;
}
}
else
{
/* 没有按下左键 */
mmx = -1; /* 返回通常模式 */
}
}
}
虽然代码长了一些,但基本没有什么新东西,主要还是利用之前已经实现的内容与熟悉的方法。这样就可以通过鼠标移动窗口,更有操作系统的样子了。
1.4 用鼠标关闭窗口
有了前面用鼠标切换窗口的基础,用鼠标关闭窗口的实现也就顺理成章了。原理基本一致,只是需要增加判断鼠标点击的位置范围是否在窗口右上角的"X"号,并根据点击结束程序就可以了。
……
if (0 <= x && x < sht->bxsize && 0 <= y && y < sht->bysize)
{
if (sht->buf[y * sht->bxsize + x] != sht->col_inv)
{
sheet_updown(sht, shtctl->top - 1);
if (3 <= x && x < sht->bxsize - 3 && 3 <= y && y < 21)
{
mmx = mx;
mmy = my;
}
if (sht->bxsize - 21 <= x && x < sht->bxsize - 5 && 5 <= y && y < 19)
{
/* 点击"X"按钮 */
if (sht->task != 0)
{ /* 该窗口是否为应用程序窗口 */
cons = (struct CONSOLE *) *((int *) 0x0fec);
cons_putstr0(cons, "\nBreak(mouse) :\n");
io_cli(); /* 强制结束处理时禁止切换任务 */
task_cons->tss.eax = (int) &(task_cons->tss.esp0);
task_cons->tss.eip = (int) asm_end_app;
io_sti();
}
}
break;
}
}
……
1.5 切换输入到应用窗口
现在有了多个应用程序窗口,并且像walk这样的应用程序已经可以接受键盘输入了。应该使应用程序的窗口能够切换到输入状态,然后通过键盘进行输入。这里仍然通过tab键来切换多个应用程序窗口,使用key_win变量保存当前处于输入状态的窗口地址。另外如果应用程序的窗口处于输入状态时被关闭,这是就让操作系统自动切换到最上层的窗口。
if (256 <= i && i <= 511)
{ /* 键盘数据 */
……
if (s[0] != 0)
{ /* 一般字符 */
if (key_win == sht_win)
{ /* 发送给任务A */
if (cursor_x < 128)
{
/* 显示一个字符,光标后移一位 */
s[1] = 0;
putfonts8_asc_sht(sht_win, cursor_x, 28, COL8_000000, COL8_FFFFFF, s, 1);
cursor_x += 8;
}
}
else
{ /* 发送至命令行窗口 */
fifo32_put(&key_win->task->fifo, s[0] + 256);
}
}
if (i == 256 + 0x0e)
{ /* BackSpace键 */
if (key_win == sht_win)
{ /* 发送给任务A */
if (cursor_x > 8)
{
/* 空格擦除光标后后移一位 */
putfonts8_asc_sht(sht_win, cursor_x, 28, COL8_000000, COL8_FFFFFF, " ", 1);
cursor_x -= 8;
}
}
else
{ /* 发送至命令行窗口 */
fifo32_put(&key_win->task->fifo, 8 + 256);
}
}
if (i == 256 + 0x1c)
{ /* Enter */
if (key_win != sht_win)
{ /* 发送至任务A */
fifo32_put(&key_win->task->fifo, 10 + 256);
}
}
if (i == 256 + 0x0f)
{ /* Tab */
cursor_c = keywin_off(key_win, sht_win, cursor_c, cursor_x);
j = key_win->height - 1;
if (j == 0)
{
j = shtctl->top - 1;
}
key_win = shtctl->sheets[j];
cursor_c = keywin_on(key_win, sht_win, cursor_c);
}
……
}
key_winon与key_winoff两个函数用于控制串口标题栏的颜色与任务A窗口的光标:
int keywin_off(struct SHEET *key_win, struct SHEET *sht_win, int cur_c, int cur_x)
{
change_wtitle8(key_win, 0);
if (key_win == sht_win) {
cur_c = -1; /* 删除光标 */
boxfill8(sht_win->buf, sht_win->bxsize, COL8_FFFFFF, cur_x, 28, cur_x + 7, 43);
} else {
if ((key_win->flags & 0x20) != 0) {
fifo32_put(&key_win->task->fifo, 3); /* 命令行窗口光标OFF */
}
}
return cur_c;
}
int keywin_on(struct SHEET *key_win, struct SHEET *sht_win, int cur_c)
{
change_wtitle8(key_win, 1);
if (key_win == sht_win) {
cur_c = COL8_000000; /* 显示光标 */
} else {
if ((key_win->flags & 0x20) != 0) {
fifo32_put(&key_win->task->fifo, 2); /* 命令行窗口光标ON */
}
}
return cur_c;
}
通过tab键可以实现窗口输入的切换:
1.6 鼠标切换输入窗口
实现鼠标的窗口输入切换,只需要再增加一点改动:
……
if (0 <= x && x < sht->bxsize && 0 <= y && y < sht->bysize)
{
if (sht->buf[y * sht->bxsize + x] != sht->col_inv)
{
sheet_updown(sht, shtctl->top - 1);
/*鼠标点击的图层不是当前输入的图层,切换*/
if (sht != key_win)
{
cursor_c = keywin_off(key_win, sht_win, cursor_c, cursor_x);
key_win = sht;
cursor_c = keywin_on(key_win, sht_win, cursor_c);
}
if (3 <= x && x < sht->bxsize - 3 && 3 <= y && y < 21)
{
mmx = mx;
mmy = my;
}
……
}
}
2. 定时器API与应用程序
2.1 定时器应用程序
为了编写定时器的应用程序,先编写了定时器的API:
_api_alloctimer: ; int api_alloctimer(void);
MOV EDX,16
INT 0x40
RET
_api_inittimer: ; void api_inittimer(int timer, int data);
PUSH EBX
MOV EDX,17
MOV EBX,[ESP+ 8] ; timer
MOV EAX,[ESP+12] ; data
INT 0x40
POP EBX
RET
_api_settimer: ; void api_settimer(int timer, int time);
PUSH EBX
MOV EDX,18
MOV EBX,[ESP+ 8] ; timer
MOV EAX,[ESP+12] ; time
INT 0x40
POP EBX
RET
_api_freetimer: ; void api_freetimer(int timer);
PUSH EBX
MOV EDX,19
MOV EBX,[ESP+ 8] ; timer
INT 0x40
POP EBX
RET
……
else if (edx == 16)
{
reg[7] = (int) timer_alloc();
}
else if (edx == 17)
{
timer_init((struct TIMER *) ebx, &task->fifo, eax + 256);
}
else if (edx == 18)
{
timer_settime((struct TIMER *) ebx, eax);
}
else if (edx == 19)
{
timer_free((struct TIMER *) ebx);
}
……
分别实现了获取定时器、设置定时器的发送数据、设置定时器时间、释放定时器的API。应用程序如下:
#include <stdio.h>
int api_openwin(char *buf, int xsiz, int ysiz, int col_inv, char *title);
void api_putstrwin(int win, int x, int y, int col, int len, char *str);
void api_boxfilwin(int win, int x0, int y0, int x1, int y1, int col);
void api_initmalloc(void);
char *api_malloc(int size);
int api_getkey(int mode);
int api_alloctimer(void);
void api_inittimer(int timer, int data);
void api_settimer(int timer, int time);
void api_end(void);
void HariMain(void)
{
char *buf, s[12];
int win, timer, sec = 0, min = 0, hou = 0;
api_initmalloc();
buf = api_malloc(150 * 50);
win = api_openwin(buf, 150, 50, -1, "noodle");
timer = api_alloctimer();
api_inittimer(timer, 128);
for (;;) {
sprintf(s, "%5d:%02d:%02d", hou, min, sec);
api_boxfilwin(win, 28, 27, 115, 41, 7 /* 白色*/);
api_putstrwin(win, 28, 27, 0 /* 黑色 */, 11, s);
api_settimer(timer, 100); /* 1秒 */
if (api_getkey(1) != 128) {
break;
}
sec++;
if (sec == 60) {
sec = 0;
min++;
if (min == 60) {
min = 0;
hou++;
}
}
}
api_end();
}
实现的效果就是显示时间:
2.2 取消定时器
定时器超时时,会发送设置好的数据。但如果定时器超时之前应用程序已经退出了,定时器超时时会产生什么效果呢?
运行定时器程序,关闭应用程序。过1秒钟后(产生超时),命令行窗口中会出现一个异常字符。
为了解决这种异常的情况,我们需要再应用程序关闭后取消定时器。
首先编写用于取消指定定时器的函数:
int timer_cancel(struct TIMER *timer)
{
int e;
struct TIMER *t;
e = io_load_eflags();
io_cli(); /*设置过程中禁止改变定时器状态 */
if (timer->flags == TIMER_FLAGS_USING) { /* 是否需要取消? */
if (timer == timerctl.t0) {
/* 取消第一个定时器的处理 */
t = timer->next;
timerctl.t0 = t;
timerctl.next = t->timeout;
} else {
/* 非第一个定时器的取消处理 */
/* 找到timer前的一个定时器 */
t = timerctl.t0;
for (;;) {
if (t->next == timer) {
break;
}
t = t->next;
}
t->next = timer->next;
}
timer->flags = TIMER_FLAGS_ALLOC;
io_store_eflags(e);
return 1; /* 取消处理成功 */
}
io_store_eflags(e);
return 0; /* 不需要取消处理 */
}
在此基础上,再来编写应用结束时取消定时器的函数。同时需要在定时器上增加一个标记,防止命令行窗口中的光标定时器也被取消:
struct TIMER {
struct TIMER *next;
unsigned int timeout;
char flags, flags2;// 增加标志位
struct FIFO32 *fifo;
int data;
};
通常情况下将flags2置为0:
struct TIMER *timer_alloc(void)
{
int i;
for (i = 0; i < MAX_TIMER; i++) {
if (timerctl.timers0[i].flags == 0) {
timerctl.timers0[i].flags = TIMER_FLAGS_ALLOC;
timerctl.timers0[i].flags2 = 0;
return &timerctl.timers0[i];
}
}
return 0;
}
对于应用程序申请的定时器,将flags2置为1:
else if (edx == 16)
{
reg[7] = (int) timer_alloc();
((struct TIMER *) reg[7])->flags2 = 1;
}
这样就可以在程序结束取消不需要的定时器了:
void timer_cancelall(struct FIFO32 *fifo)
{
int e, i;
struct TIMER *t;
e = io_load_eflags();
io_cli();
for (i = 0; i < MAX_TIMER; i++) {
t = &timerctl.timers0[i];
if (t->flags != 0 && t->flags2 != 0 && t->fifo == fifo) {
timer_cancel(t);
timer_free(t);
}
}
io_store_eflags(e);
return;
}
运行定时器程序并关闭,超时也不会再出现异常字符了。
本篇的内容逻辑清楚,代码简单,但却使操作系统看起来更像样了。下一篇继续优化命令行窗口,敬请期待。