Day24

今天是完善多窗口的一些操作:窗口切换、移动、输入、关闭。此外添加了定时器API

一些感想

操纵窗口的方法

  1. 图层切换
    只是回忆一下方法:用许久以前的sheet_updown()函数,来改变顺序。
  2. 窗口移动/关闭窗口
    当鼠标点击时,按照图层从上到下的顺序,判断所在的图层。如果在该图层的标题栏/关闭按钮位置,则需要进行相应操作。
    对于移动操作,在左键点击/释放的时候,进入/退出移动状态。
  3. 窗口输入
    很久以前是实现了控制台输入,但是那个时候只有主函数和控制台,所以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);