Day24
今天是完善多窗口的一些操作:窗口切换、移动、输入、关闭。此外添加了定时器API
一些感想
操纵窗口的方法
- 图层切换
只是回忆一下方法:用许久以前的sheet_updown()函数,来改变顺序。 - 窗口移动/关闭窗口
当鼠标点击时,按照图层从上到下的顺序,判断所在的图层。如果在该图层的标题栏/关闭按钮位置,则需要进行相应操作。
对于移动操作,在左键点击/释放的时候,进入/退出移动状态。 - 窗口输入
很久以前是实现了控制台输入,但是那个时候只有主函数和控制台,所以tab一下就知道FIFO该发给谁了。
现在做出了一定的改动,通过tab来从顶部窗口->底部窗口->最顶部窗口这样的一个循环。同时改成了记录应该发给哪个窗口(这样写起来也会更舒服吧。。)
因为改成了记录窗口地址,当用鼠标切换窗口的时候,也可以直接方便地指定地址了。
定时器API
这里说定时器API需要注意的地方。
当一个应用程序申请了定时器,但被强制终止的时候,定时器超时所发送的信息就不知去向了,于是会产生问题。
总的来说,应用程序申请的定时器,和内存一样,也是应用程序申请的资源。所以在应用程序结束的时候,检查一下是否有申请的定时器,有的话要取消掉。
窗口切换(1)
harib21a
遵循循序渐进的原则(雾),一开始只是实现一个较为简单的窗口切换:按下F11的时候,把最底下的窗口置顶。
if(i == 256 + 0x57 && shtctl->top > 2) // F11
{
sheet_updown(shtctl->sheets[1], shtctl->top - 1);
}
sheets[1]是sheets[0](背景图层)之上的那个图层,直接把它放到鼠标下面。
窗口切换(2)
harib21b
这次实现了鼠标点击某个可见图层的时候,将其置顶。简单来说就是判断一下鼠标点击坐标是否在当前这个图层。
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;
}
}
}
}
还附加了要确定该区域不是透明区域,没什么问题。
移动窗口
harib21c
移动窗口的话,在之前鼠标点击置顶窗口的基础上,如果仍然按住左键不放并且鼠标有位移,此时第二次就会进入移动的状态,进行移动。
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; // 返回通常模式
}
mmx,mmy记录的是鼠标之前点击后,移动到的位置。
用鼠标关闭窗口
harib21d
也是查找是否在关闭图标的位置了。
if(sht->bxsize - 21 <= x && x < sht->bxsize - 5 && 5 <= y && y < 19)
{
// 点击“×”按钮
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();
}
}
将输入切换到应用程序窗口
harib21e
也填了许久之前的一个坑。现在可以把FIFO的信息发给当前正在运行的任务了。
在struct SHEET里的flag,用于判断这个窗口是否是应用程序创建的(0x10),以及判断这个窗口是否需要光标(0x20)。这些都是按位记录的。
开始申请一个key_win变量,用于记录当前需要输入的任务。
同时sht_win记录的是当前图层,如果刚好是该任务与该图层,则会把FIFO的信息发进去。
struct SHEET *sht = 0, *key_win;
key_win = sht_win;
当窗口被关闭的时候,当前窗口转到下一个顶层窗口
if(key_win->flags == 0) // 输入窗口被关闭
{
key_win = shtctl->sheets[shtctl->top - 1];
cursor_c = keywin_on(key_win, sht_win, cursor_c);
}
然后是对于FIFO的处理。
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) // 退格键
{
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) // 回车键
{
if(key_win != sht_win) // 发送至命令行窗口
{
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);
}
简单总结一下,如果keywin == shtwin,就会发到相应窗口,否则发送至命令行窗口
Tab键中,有两个新增函数,用途是添加/删除当前窗口的光标
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;
}
其中chagne_wtitle的作用为将指定窗口的标题栏区域颜色显示为活动/非活动状态。
在这一小节开头提到了,0x10用来标记应用程序,0x01是之前正在使用的标记
于是可以修改关闭应用程序的设定了
if((sht->flags & 0x11) == 0x11 && sht->task == task)
{
// 找到应用程序残留的窗口
sheet_free(sht); // 关闭
}
此外,在hrb_api中,case 5(申请图层)中也增加了 flags |= 0x10的操作,标记是应用程序申请的图层。
用鼠标切换输入窗口
harib21f
这次并不用改太多,在找到图层之后,顺便把键盘输入也赋值就好了。
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);
}
定时器API
harib21g
添加应用程序用的定时器API
- 获取定时器(alloc)
EDX=16
EAX=定时器句柄(由操作系统返回) - 设置定时器的发送数据(init)
EDX=17
EBX=定时器句柄
EAX=数据 - 定时器时间设定(set)
EDX=18
EBX=定时器句柄
EAX=时间 - 释放定时器(free)
EDX=19
EBX=定时器句柄
case 16:
reg[7] = (int)timer_alloc();
break;
case 17:
timer_init((struct TIMER *)ebx, &task->fifo, eax + 256);
break;
case 18:
timer_settime((struct TIMER *)ebx, eax);
break;
case 19:
timer_free((struct TIMER *)ebx);
break;
汇编调用:
_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
测试程序:
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();
}
FIFO的128为定时器专用的数据,不会与其它产生冲突。
取消定时器
harib21h
在应用程序结束的时候,需要释放掉申请的定时器。
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; // 不需要取消处理
}
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;
}
cancelall用于在应用程序关闭之后检查一遍所有的定时器。
flags2为记录该API是否为应用程序申请的。若是的话需要在case 16里将flags2置1
struct TIMER
{
struct TIMER *next;
unsigned int timeout;
char flags, flags2;
struct FIFO32 *fifo;
int data;
};
然后修改cmd_app中强制关闭的部分
for(i = 0; i < MAX_SHEETS; ++i)
{
sht = &(shtctl->sheets0[i]);
if((sht->flags & 0x11) == 0x11 && sht->task == task)
{
// 找到应用程序残留的窗口
sheet_free(sht); // 关闭
}
}
timer_cancelall(&task->fifo);