简易便签工具

mfk 1小时前 7

win10上想用一个便签工具,系统上也没带。安装微软官方的便签工具让修复这修得哪的-放弃。干脆让AI写一个, 经过几次改错——不完善但凑活能用(自己入门小白能力有限,希望有能力有兴趣的大佬继续完善)。    ---感谢AI(用到了deepseek\workbuddy 智能体)

// 便签工具 - V1.1
// 保留:托盘常驻 + Ctrl+Alt+N 热键新建;SQLite 本地存储;置顶/编辑自动保存/启动恢复。
// 行为:「关」按钮=隐藏窗口(便签保留在库,下次启动恢复内容);右键菜单「删除便签」=软删除(真正移除)。
// 数据库 notes.db 生成在同目录。

import win.ui;
import win.util.tray;
import key.hotkey;
import sqlite;
import gdi;

// ---------------- 配置 ----------------
var DB_PATH = io.fullpath("notes.db");
var PALETTE = {"#FFF59D";"#C8E6C9";"#F8BBD0";"#BBDEFB";"#FFCC80";"#FFFFFF";"#E1BEE7"};
var SIZES = {12; 14; 16; 18; 20; 24};  // 字号选项

// ---------------- 颜色工具 ----------------
hexToColor = function(hex){
    if(!hex) hex = "#FFF59D";
    var m = string.match(hex, "#[0-9A-Fa-f]+");
    if(!m) hex = "#FFF59D"; else hex = m;
    var r = tonumber("0x" ++ string.sub(hex,2,3));
    var g = tonumber("0x" ++ string.sub(hex,4,5));
    var b = tonumber("0x" ++ string.sub(hex,6,7));
    return gdi.RGB(r,g,b);
}
darker = function(hex){
    if(!hex) hex = "#FFF59D";
    var m = string.match(hex, "#[0-9A-Fa-f]+");
    if(!m) hex = "#FFF59D"; else hex = m;
    var r = tonumber("0x" ++ string.sub(hex,2,3));
    var g = tonumber("0x" ++ string.sub(hex,4,5));
    var b = tonumber("0x" ++ string.sub(hex,6,7));
    r = math.floor(r*0.82); g = math.floor(g*0.82); b = math.floor(b*0.82);
    return gdi.RGB(r,g,b);
}
newId = function(){
    return tostring(time.now()) ++ tostring(math.random(100000));
}

// ---------------- 存储层 (SQLite) ----------------
var db = sqlite(DB_PATH);
if(!db.existsTable("notes")){
    db.exec("CREATE TABLE notes(id TEXT PRIMARY KEY, content TEXT, color TEXT, x INTEGER, y INTEGER, w INTEGER, h INTEGER, pinned INTEGER, fontSize INTEGER, createdAt TEXT, updatedAt TEXT, deletedAt TEXT)");
} else {
    // 数据迁移:旧表可能没有 fontSize 列,补上,避免 REPLACE 报错
    var cols = db.getTable("PRAGMA table_info(notes)");
    var hasFs = false;
    for(i=1; #cols){ if(cols[i].name == "fontSize") hasFs = true; }
    if(!hasFs) db.exec("ALTER TABLE notes ADD COLUMN fontSize INTEGER");
}
saveNote = function(n){
    n.updatedAt = tostring(time.now());
    if(n.fontSize == null) n.fontSize = 14;
    var cmd = db.prepare("REPLACE INTO notes(id,content,color,x,y,w,h,pinned,fontSize,createdAt,updatedAt,deletedAt) VALUES(@id,@content,@color,@x,@y,@w,@h,@pinned,@fontSize,@createdAt,@updatedAt,@deletedAt)");
    cmd.step(
        id = n.id; content = n.content; color = n.color;
        x = n.x; y = n.y; w = n.w; h = n.h;
        pinned = n.pinned; fontSize = n.fontSize;
        createdAt = n.createdAt; updatedAt = n.updatedAt; deletedAt = n.deletedAt
    );
    cmd.finalize();
}
loadNotes = function(){
    return db.getTable("SELECT * FROM notes WHERE deletedAt IS NULL");
}

// ---------------- 创建便签窗口 ----------------
var noteCount = 0;
var openCount = 0;  // 当前已打开的便签窗口数(用于"最后一个关闭则退出程序")
createNote = function(data){
    if(!data){
        data = {
            id = newId(); content = ""; color = PALETTE[1];
            x = 200 + noteCount*24; y = 150 + noteCount*24;
            w = 220; h = 180; pinned = 0; fontSize = 14;
            createdAt = tostring(time.now())
        };
    }
    noteCount++;
    openCount++;
    var x = data.x or 200, y = data.y or 150;
    var w = data.w or 220, h = data.h or 180;
    var fs = data.fontSize or 14;

    var winform = win.form(
        text = "便签";
        left = x; top = y; right = x + w; bottom = y + h;
        border = "none";
        bgcolor = hexToColor(data.color)
    );
    winform.add(
        caption = { cls="plus"; text="便签"; left=0; top=0; right=w-98; bottom=24;
                    background = darker(data.color); color = 0x333333;
                    font = LOGFONT(h=-12; name="Microsoft YaHei"); notify=1; dt=1; dl=1; dr=1; z=1 };
        btnColor = { cls="button"; text="色"; left=w-92; top=3; right=w-66; bottom=21; dr=1; dt=1; z=2 };
        btnPin   = { cls="button"; text="钉"; left=w-64; top=3; right=w-38; bottom=21; dr=1; dt=1; z=3 };
        btnClose = { cls="button"; text="关"; left=w-36; top=3; right=w-8;  bottom=21; dr=1; dt=1; z=4 };
        edit     = { cls="edit"; text=data.content or ""; left=4; top=26; right=w-4; bottom=h-16;
                     edge=1; multiline=1; vscroll=1; dl=1; dt=1; dr=1; db=1; z=5;
                     font = LOGFONT(h=-fs; name="Microsoft YaHei") };
        resizeHandle = { cls="plus"; left=w-16; top=h-16; right=w; bottom=h;
                         background = darker(data.color); dr=1; db=1; notify=1; z=6 }
    );

    if(data.pinned) win.setTopmost(winform.hwnd, true);
    winform.edit.bgcolor = hexToColor(data.color); // 编辑区背景同步便签色

    // 拖动:在标题栏按下即移动窗口
    winform.caption.onMouseDown = function(wParam,lParam){ winform.hitCaption(); };

    // 改色:循环切换(保留快速换色)
        winform.btnColor.oncommand = function(){
            var idx = 1;
            for(i=1; #PALETTE){ if(PALETTE[i] == data.color) idx = i; }
            data.color = PALETTE[(idx % #PALETTE) + 1];
            winform.bgcolor = hexToColor(data.color);
            winform.caption.background = darker(data.color);
            winform.resizeHandle.background = darker(data.color);
            winform.edit.bgcolor = hexToColor(data.color);
            saveNote(data);
        };

    // 钉住 / 取消置顶
    winform.btnPin.oncommand = function(){
        if(data.pinned) data.pinned = 0; else data.pinned = 1; // aardio 无 iif;0/null/false 均为假值,故 if(data.pinned) 即"已置顶"
        win.setTopmost(winform.hwnd, data.pinned);
        saveNote(data);
    };

    // 关闭 = 仅隐藏窗口(内容已在 onClose 中保存进数据库,便签保留,下次启动恢复)
    // 真正的删除请用右键菜单的「删除便签」
    winform.btnClose.oncommand = function(){
        winform.close();
    };

    // 编辑自动保存
    winform.edit.onChange = function(){
        data.content = winform.edit.text;
        saveNote(data);
    };

    // 关闭时保存位置 / 尺寸 / 字号;若这是最后一个窗口,则整个程序退出
    winform.onClose = function(){
        data.content = winform.edit.text; // 关闭前强制读取最新文字,确保最后输入的内容不丢
        var px, py, w0, h0 = winform.getPos(true); // getPos 返回 x,y,宽,高(true=屏幕坐标);aardio 无 getSize 方法
        data.x = px; data.y = py; data.w = w0; data.h = h0;
        saveNote(data);
        openCount--;
        if(openCount <= 0){
            win.quitMessage(); // 最后一个便签关闭 → 消息循环退出 → 进程结束
        }
    };

    // ---------- 右键菜单:颜色子菜单 + 字号子菜单(借鉴参考代码的子菜单与打勾)----------
    var colorMenu = win.ui.popmenu(winform);
    for(i=1; #PALETTE){
        var c = PALETTE[i];
        colorMenu.add(c, function(){
            data.color = c;
            winform.bgcolor = hexToColor(c);
            winform.caption.background = darker(c);
            winform.resizeHandle.background = darker(c);
            winform.edit.bgcolor = hexToColor(c);
            saveNote(data);
        });
    }
    var fontMenu = win.ui.popmenu(winform);
    for(i=1; #SIZES){
        var s = SIZES[i];
        fontMenu.add(tostring(s), function(){
            data.fontSize = s;
            winform.edit.font = LOGFONT(h=-s; name="Microsoft YaHei");
            saveNote(data);
        });
    }
    var ctx = win.ui.popmenu(winform);
    ctx.add("颜色", colorMenu);
    ctx.add("字号", fontMenu);
    ctx.add(); // 分隔线
    ctx.add("置顶/取消置顶", function(){
        if(data.pinned) data.pinned = 0; else data.pinned = 1; // aardio 无 iif;0/null/false 均为假值,故 if(data.pinned) 即"已置顶"
        win.setTopmost(winform.hwnd, data.pinned);
        saveNote(data);
    });
    ctx.add("删除便签", function(){
        data.deletedAt = tostring(time.now());
        saveNote(data);
        winform.close();
    });

    // 右键弹出(WM_RBUTTONUP)—— 弹出前刷新打勾(参考代码的 check + 0x400)
    winform.wndproc = function(hwnd, msg, wParam, lParam){
        if(msg == 0x205/*_WM_RBUTTONUP*/){
            for(i=1; #PALETTE){ colorMenu.check(i, data.color == PALETTE[i], 0x400); }
            for(i=1; #SIZES){ fontMenu.check(i, (data.fontSize or 14) == SIZES[i], 0x400); }
            var sx, sy = win.getCursorPos();
            ctx.popup(sx, sy, true);
            return true;
        }
        if(msg == 0x7B/*_WM_CONTEXTMENU*/){ return true; } // 阻止 edit 默认右键菜单
    };

    // ---------- 右下角 resize 手柄:拖拽调节窗口大小 + 调整光标(借鉴参考代码的 resize + 光标切换)----------
    var resizing = false;
    var startX, startY, startPx, startPy, startW, startH;
    winform.resizeHandle.onMouseDown = function(wParam, lParam){
        resizing = true;
        startX, startY = win.getMessagePos(lParam);
        startPx, startPy, startW, startH = winform.getPos(true); // getPos 返回 x,y,宽,高;替代不存在的 getSize
        win.setCapture(winform.resizeHandle.hwnd);
    };
    winform.resizeHandle.onMouseMove = function(wParam, lParam){
        if(resizing){
            var mx, my = win.getMessagePos(lParam);
            var nw = startW + (mx - startX);
            var nh = startH + (my - startY);
            if(nw < 180) nw = 180;   // 最小尺寸限制(借鉴参考代码边界处理)
            if(nh < 140) nh = 140;
            win.setPos(winform.hwnd, startPx, startPy, nw, nh);
            data.w = nw; data.h = nh;
        } else {
            ::SetCursor(::LoadCursor(null, topointer(32642))); // 悬停手柄显示右下调整光标
        }
    };
    winform.resizeHandle.onMouseUp = function(wParam, lParam){
        if(resizing){
            resizing = false;
            win.releaseCapture();
            saveNote(data); // 保存新尺寸
        }
    };
    winform.resizeHandle.onMouseLeave = function(){
        if(!resizing) ::SetCursor(::LoadCursor(null, topointer(32512))); // 恢复默认箭头
    };

    winform.show();
    return winform;
}

// ---------------- 主程序:托盘 + 全局热键 ----------------
var mainForm = win.form(text="便签工具"; right=0; bottom=0; border="none");
mainForm.show(false); // 主窗口隐藏,仅留托盘

// 托盘
mainForm.tray = win.util.tray(mainForm);
mainForm.tray.tip = "便签工具(左键新建 / 右键菜单)";
mainForm.tray.onClick = function(){ createNote(); };        // 左键:新建便签
mainForm.tray.onRButtonUp = function(){                     // 右键:菜单
    win.setForeground(mainForm.hwnd);  // 前置主窗口,避免菜单点了不消失(aardio 官方范例要求)
    var x, y = win.getCursorPos();
    var m = win.ui.popmenu(mainForm);
    m.add('新建便签', function(){ createNote() });
    m.add();
    m.add('退出', function(){ win.quitMessage() });
    m.popup(x, y, true);
};

// 全局热键新建便签:同时注册 Ctrl+N 与 Ctrl+Alt+N
// 注意:Ctrl+N 是全局热键,会覆盖其它软件的 Ctrl+N(如浏览器新建标签页),介意的话只用 Ctrl+Alt+N
var hk = key.hotkey();
hk.regStr("Ctrl+Alt+N", function(hwnd,...){ createNote() });
hk.regStr("Ctrl+N", function(hwnd,...){ createNote() });

// 启动时恢复已有便签;若库为空(首次运行),自动建一张便签,保证一启动就有可见窗口
var notes = loadNotes();
if(#notes == 0){
    createNote();
} else {
    for(i=1; #notes){ createNote(notes[i]); }
}

// 启动气泡提示:即使你没注意到托盘图标,也能看到这条反馈
mainForm.tray.pop("便签工具已启动\n左键托盘 / Ctrl+Alt+N 新建便签", "便签工具");

win.loopMessage();



最新回复 (0)
返回