用PHP写桌面程序

写多了网页程序,是会对桌面程序抱有很深的怨念的。也不记得是哪天无意间搜到了PHP-GTK这种逆天的东西了,初看感觉道路一片光明(往后看你就会明白我为什么这么说了)。遂对它有了深深的执念。

下载了这货的二进制包,结果还搜了半天的教程才会使用它。而且官方手册没有中文包(虽然放了中文包的下载地址,不过居然404!满满的都是坑啊!),为此我还下了一番决定准备翻译官方文档呢。当然,目前为止我只是翻译了教程部分,关键的手册部分都木有开始就决定把这个计划腰斩了啊!如果有需要的同学可以点击这里:PHP-GTK 2教程。通篇教程是用MardownPad编写并输出的。你们不要问我为什么好好的分页教程被我活生生的整合到一个页面去了还不给打标签,我就是喜欢,你们能怎么样~恩哼!

简单说说PHP-GTK2这货怎么使用吧,其实没有官方手册讲的那么痛苦。首先去官方下载页面下载程序,你会发现很多各式各样英文名字很牛逼但是你完全不懂是什么的包,然后你最好遵从博主的建议去下载php-gtk-2.0.1 Windows binary pack这个文件。好了博主已经很好心的把你要下载的链接贴上来了,大家就快感恩戴德的去下载吧。下载好了之后把它解压到一个文件夹,比如就是php-gtk2这个文件夹好了。程序默认带了几个Demos,放置在demos文件夹下。打开CMD窗口进入到php-gtk2这个文件夹,运行php demos/phpgtk2-demo.php。然后很神奇的事情就发生了,哈哈!所以使用方法就是CMD到php-gtk2这个文件夹下然后带上你要运行的文件就好了。据说把文件夹加到环境变量中后不用cd到文件夹下也行,这个我没有多做测试,大家有兴趣可以测试一下。



大家可能要问我怎么将PHP程序打包成EXE程序了。目前我测试过许多方法,都不尽人意。表现最好的只有PriadoBlender这货了。什么?PriadoBlender这货是什么?对不起,虽然我很想贴一些简介来介绍一下它的,不过一个官网都死掉了的软件你想要官方简介?想多了吧亲!好吧,以上是调侃一下PHP-GTK2的穷途末路的,大家不要当真。PriadoBlender就是我说的打包程序了,它自己本身也是用PHP-GTK制作的。介于官网已死(有事烧纸都没用了!),博主我再次很好心的把它上传到了BOX上以供大家下载。新建工程输入工程名称、工程文件、工程输入文件夹、使用的PHP-GTK2版本,使用的PHP版本,点击编译稍等片刻就可以在输出文件夹发现一大片文件出现了,你的程序就编译成功了的说。顺带说一句,这货对中文的支持不是很好,工程名文件名文件夹名最好不要带中文,不要怪我没有提醒大家哦!



讲了如何使用如何打包,下面就要说说如何编写这个比较重要的问题了。不过这个问题其实我也是讲不了多少的,因为我本身也是在手册中学习的啊。所以还是比较推荐大家老老实实的把手册看一遍,这比什么都强啊。附上我今天花了一天的时间写的计算器代码,希望能帮助大家的学习。不要问我为什么一个简单的计算器还要写一点!木有办法,博主的智商博主自己也感到十分捉急啊!

<?php
/*
//检查是否包含gtk,试试就可以了这部分代码其实可以删掉的
if (!class_exists('gtk')) {
    die("Please load the php-gtk2 module in your php.ini\r\n");
}
*/

$wnd = new GtkWindow();
$wnd -> set_title('计算器'); //设置窗体的标题
$wnd -> set_default_size(228,322);  //设置窗体的默认大小
$wnd->connect_simple('destroy', array('gtk', 'main_quit')); //当关闭窗体的时候结束主程序
//看教程你就会知道为什么会有上面这句代码的存在

//制造控件
//把所有需要的空间制造出来
//暂时需要一个Label控件显示数字,0-9,小数点,加号,减号,等于号,清除键这几个按键
$CLabel = new GtkLabel('0');
$CButton = array(); 
for($i=0;$i<10;$i++) $CButton[] = new GtkButton($i);
$CButton['plus'] = new GtkButton('+');
$CButton['minus'] = new GtkButton('-');
$CButton['equal'] = new GtkButton('=');
$CButton['point'] = new GtkButton('.');
$CButton['clean'] = new GtkButton('C');

//控件布局
//$wnd最后只能添加一个控件,所以我们必须先用一个大容器把所有的小控件包进来,目前大容器是$table
//table的五个参数:控件,左边距,右边距,上边距,下边距
$CLabel -> set_justify(Gtk::JUSTIFY_RIGHT);  //居右,但是好像这句不来事。
$table = new GtkTable(5,4);  
$table->attach($CLabel, 0, 4, 0, 1);
for($i=1;$i<10;$i++) {
    $quotient = floor($i/3);
    $rest = $i % 3;
    if($rest == 0) {
        $rest = 3;
        $quotient--;
    }
    $top = 3 - $quotient;
    $bottom = $top+1;
    $right = $rest;
    $left = $right-1;
    $table->attach($CButton[$i], $left, $right, $top, $bottom);
}
$table->attach($CButton[0], 0, 2, 4, 5);
$table->attach($CButton['point'], 2, 3, 4, 5);
$table->attach($CButton['plus'], 3, 4, 2, 3);
$table->attach($CButton['minus'], 3, 4, 3, 4);
$table->attach($CButton['equal'], 3, 4, 4, 5);
$table->attach($CButton['clean'], 3, 4, 1, 2);

//添加控件信号绑定
//信号以为当做一个动作的时候产生的信号,下面的简单解释就是当点击按钮出发相应的函数
//GtkButton::connect_simple的参数:信号(例如点击),触发函数, 传递参数1, 传递参数2...
foreach($CButton as $name => $button) {
    switch($name) {
        case 'plus':
            $button->connect_simple('clicked', 'Plus', $CLabel);
        break;
        case 'minus':
            $button->connect_simple('clicked', 'Minus', $CLabel);
        break;
        case 'equal':
            $button->connect_simple('clicked', 'Equal', $CLabel);
        break;
        case 'clean':
            $button->connect_simple('clicked', 'Clean', $CLabel);
        break;
        default:
            $button->connect_simple('clicked', 'Cal', $button, $CLabel);
        break;
    }
}

//定义了三个全局变量,$number是用来存储输入的数字的,$method是用来存储运算法则的,$status用来存储输入状态的
//$method是当点击加减等于等功能键的时候进行赋值或者清空
//$status默认是为真的,也及时在输入状态,一当点击功能键则返回假,即非输入状态。存在这两种状态是为了区别没有点击功能键的时候输入数字是添加在末尾的,点击功能键之后输入数字是要清空标签控件然后添加的。
$number = 0;
$method = '';
$status = true;
function Cal(GtkButton $button, GtkLabel $CLabel) {
    global $status;
    $Text = $button->get_label();
    $BeforeText = $CLabel->get_text();   
    if($status && $BeforeText != '0')   {
        $Text = $BeforeText.$Text;
    } else {
        $status = true;
    }
    $CLabel->set_text($Text);
}
function Calculate(GtkLabel $CLabel) {
    global $number, $method;
    $num = $CLabel->get_label();
    switch($method) {
        case '+':   
            $number += $num;
        break;
        case '-':   
            $number -= $num;
        break;
        default:
        break;  
    }
}
function Plus(GtkLabel $CLabel) {
    global $number, $method, $status;
    if($method != '' && $status) {
        Calculate($CLabel);
    } else {
        $number = $CLabel->get_label();
    }
    $method = '+';
    $status = false;
    $CLabel->set_label($number); 
}
function Minus(GtkLabel $CLabel) {
    global $number, $method, $status;
    if($method != '' && $status) {
        Calculate($CLabel);
    } else {
        $number = $CLabel->get_label();
    }
    $method = '-';
    $status = false;
    $CLabel->set_label($number); 
}
function Equal(GtkLabel $CLabel) {
    global $number, $method, $status;
    Calculate($CLabel);
    $method = '';
    $status = false;
    $CLabel->set_label($number);
}
function Clean(GtkLabel $CLabel) {
    global $number, $method, $status;
    $number = 0;
    $method = '';
    $status = true;
    $CLabel->set_label($number);
}

//窗体添加控件
$wnd->add($table);
$wnd->show_all(); //显示所有控件
Gtk::main();  //进入主程序循环
?>

程序的主要思路我也已经比较详细的注释在代码中了,有不懂的也可以在评论中提出。这里附上我打包好的计算器程序给大家提供参考学习,同样还是放在BOX的。不过打包好的程序文件夹大小居然有18M,压缩了也有6M这是直接打击我学习下去的想法啊。而且好像网上吐槽PHP-GTK的人还是比较多的,大部分的孩子都是说“这是PHP年轻时候干的傻事”,“这货一时爽,长期死全家”之类的。而且PHP说到底只是一单线程语言,对内存的控制也不是很好,想要用来写桌面程序真的是有点力不从心的。介于最近又在看Python,PHP-GTK又万年太监,官方文档也不清楚(基本的函数的参数解释都不全),最终决定还是放弃PHP-GTK的研究。最后感谢Jclyn同学的片头歌曲推荐。以上。

<

p>本文相关文件下载地址:PHP-GTK 2 | PHP-GTK2中文教程 | PriadoBlender | 自制简易计算器

初探<audio>标签实现同步显示歌词

今天群里的小胖说用<audio>标签写了个听评书的网页给父母用,技术实在是低级真心非常有孝心的说!然后我也顺带去了解了一下audio标签现在的支持情况,一不小心就脑补了一些新知识。看到了audio标签有duration属性和currentTime属性,分别用来读取歌曲总时间和当前播放时间,正好弥补了我之前想写播放器同步显示歌词一直卡在的如何获取播放时间的问题上。所以就开始操刀了。

歌曲和歌词的准备

我暂时是写到了之前的虾米搜索页面里面,实现搜索获得歌曲用audio标签播放并显示歌词。所以歌曲和歌词文件自然就来自盗版帝国虾米了。由于虾米的歌曲文件都是MP3格式的,所以悲剧如FireFox等虽然支持了HTML5但是不支持MP3格式的文件这让我也是感到很捉急的,所以暂时是推荐大家使用Chrome浏览器制作并访问的。关于目前各浏览器的支持情况可以看这里:居然连IE都支持了!这里利用了一下虾米的API:http://www.xiami.com/song/playlist/id/歌曲ID 获取歌曲的相关信息,其中就有歌曲地址和歌词。获取到的XML文件中,歌曲地址是加密放置的,至于解析原理,大家可以参见虾米音乐文件绝对地址解析。详细的解释一下就是:

获取到的字符串第一个字母是矩阵的行数,除去第一个字母的字符总数除以行数得到的整数是每行放置多少个字符,余数则是前余数行要在末尾多放置一个字符。这样就能组成引文中的矩阵了。按照竖行读取,^替换为0的处理后就可以获得绝对地址了。

懒惰如我为了方便自己,我把解析的过程写成了函数。最开始我是将字符串打散成为二维数组,然后嵌套循环连接字符串。后来觉得打散成一位数组应该会更方便一点,所以为了算获取数组索引顺序的规律,死了好多个脑细胞啊,果然数学已经忘到姥姥家了。

歌词由于后期要用js处理,js又不能跨域获取什么的。所以只能劳烦PHP获取到歌词地址后远程抓取过来了。以上解析得到绝对地址和歌词都是在服务器端完成的。我写成了API的形式,http://imnerd.org/lab/search/api.php?id=歌曲ID可以查看获取到的内容格式。

audio歌曲播放与暂停

果然我还是对解码什么的比较感兴趣,一不小心就偏题了呢。好啦好啦,以上都不应该是本文的重点啦,下面我们开始好好的蹂躏学习一下audio标签啦。audio标签的简单写法为

<audio src="歌曲地址"></audio>

控制歌曲的播放和暂停也是非常简单的,只要利用play()和pause()事件就好了,代码如下:

m = document.getElementsByTagName('audio')[0];
m.play(); //歌曲的播放
m.pause(); //歌曲的暂停

想要了解更多audio标签的属性和事件的话可以点击这里:HTML 5 视频/音频参考手册

同步显示歌词的实现

我在开头已经讲了,audio的currentTime属性可以获取当前播放时间。又如果你刚才有去点击上面的参考手册的链接的话,你就会发现audio还有一个神奇的timeupdate事件,目的是当目前的播放位置已更改时做出动作。也就是说我们可以利用timeupdate事件在currentTime变化的时候做出判断,然后放置相应的歌词。

基本方法大概就是这个样子,播放器的当前播放时间我们已经获取到了,剩下的就是获取歌词的播放时间的问题了。由于标准的歌词每行应该是“[你好我是播放时间,我的格式是分:秒]你好我是该播放时间下的歌词”这种格式的,想必大家早就了解了吧。利用正则获取split分割等一系列方法将歌词分割成一个二维数组,一纬为每行歌词,二维为每行歌词的时间和每行歌词的内容。其中歌词的时间由于是分钟:秒的形式,需要转化为单位为秒的数字。下面给出歌词转化为二维数组的方法。方法可能坑爹不是最好的,但是基本凑合,这一部分大家可以随意发挥:

//写成函数的形式大家可能会更好理解一点
function parseLyric(text) {
lyric = text.split('\r\n'); //先按行分割
var _l = lyric.length; //获取歌词行数
lrc = new Array(); //新建一个数组存放最后结果
for(i=0;i<_l;i++) {
    var d = lyric[i].match(/\[\d{2}:\d{2}((\.|\:)\d{2})\]/g);  //正则匹配播放时间
    var t = lyric[i].split(d); //以时间为分割点分割每行歌词,数组最后一个为歌词正文
    if(d != null) { //过滤掉空行等非歌词正文部分
        //换算时间,保留两位小数
        var dt = String(d).split(':'); //不知道为什么一定要转换时间为字符串之后才能split,难道之前正则获取的时间已经不是字符串了么? 
        var _t = Math.round(parseInt(dt[0].split('[')[1])*60+parseFloat(dt[1].split(']')[0])*100)/100; //这一步我自己都觉得甚是坑爹啊!
        lrc.push([_t, t[1]]);
    }
return lrc;
}

这样我们只要匹配歌词每行的时间和crrentTime就行了,如果当前播放时间超过了这一句歌词的时间,则显示这一句歌词,否则不显示。这是我们下面代码的关键判断。另外,这里我使用了一个自我感觉还不错的小技巧:显示完歌词之后立即将这行歌词从歌词数组中剔除出去,这样我们就永远只要将currentTime和lrc[1][0]比较就好了,而不用麻烦的去每个都去比较一下。

m.addEventListener('timeupdate', function() {
    if(audio.currentTime > lrc[1][0]) {
        document.getElementById('lrc').innerHTML = lrc[1][1];
        lrc.shift();
    }
},false);
//addEventListener等同于m.timeupdate(function() {...});

歌曲播放并显示歌词的思路大概就这个样子了,具体的代码可以去看我的DEMO。DEMO只是给出了大概的功能,UI方面都还木有美化,大家习惯就好,这个是在是力不从心啊!本人一向也不是很在意UI的。最后的最后,感谢一下Jclyn帮我做了测试并推荐了本文开头的歌曲。哦,又突然想起来了,DEMO的范例是用JQuery操作的,虽然跟Javascript有些不同,但是对于思路什么的是没有问题的,有不懂的可以留言提问哦,我会看心情耐心解答的。

PageCookery微信机器人

上传API文件到网站,微信添加PageCookery公共账号即可使用(下面有详细使用教程说明)。本机器人建立初功能比较薄弱,仅有绑定账号,发表状态功能,后续博主会再接再厉增加更多功能的。个人认为此微信公共账号变相地解决了PageCookery的手机客户端的问题。本账号还处于婴儿阶段,并未作过多的测试,欢迎大家反馈。

使用教程:

第一步:下载wechat.php并上传到PageCookery的根目录(请保证文件名为wechat.php)

wechat.php源代码(请自行保存以下代码并上传至PageCookery根目录):

<?php 
include"config.php";
function sql_query($sqlcon){
    $con=mysql_connect(DATABASE_HOST,DATABASE_USER,DATABASE_PSSWORD);
    mysql_select_db(DATABASE_DB_NAME);
    mysql_query("SET NAMES 'utf8'");
    $result=mysql_query($sqlcon);
    mysql_close($con);
    return $result;
}
if(!isset($_GET['do'])) $_GET['do'] = '';
switch($_GET['do']){
    case'auth':
        $user=sql_query("SELECT * FROM user WHERE id= '".$_POST['usr']."'");
        $user=mysql_fetch_array($user);
        $pwd=md5($user['username'].$user['password']);
        if($pwd===$_POST['auth']){
            $j = array('code'=>true, 'message'=>'绑定成功,现在你可以发一条消息试试了!');
        }else{
            $j = array('code'=>false, 'message'=>'绑定失败,请输入正确的信息!');
        }
        echo json_encode($j);
    break;
    case 'post':
        $user=sql_query("SELECT * FROM user WHERE id= '".$_POST['usr']."'");
        $user=mysql_fetch_array($user);
        $pwd=md5($user['username'].$user['password']);
        if($pwd===$_POST['auth']){
            $userid=$_POST['usr'];
            $content=$_POST['content'];
            $time=time();
            $res=sql_query("INSERT INTO entry VALUES ('','0','$content','$time','微信','$userid')");
            if($res){
                $j = array('code'=>true, 'message'=>'发送成功!');
            }else{
                $j = array('code'=>false, 'message'=>'发送失败请稍后再试');
            }
        }else{
            $j= array('code'=>false, 'message'=>'您还未绑定网站或者之前的绑定已失效,请重新绑定!');
        }
        echo json_encode($j);
    break;
    case 'postfromgtalk':
        $user=sql_query("SELECT * FROM user WHERE id= '".$_POST['usr']."'");
        $user=mysql_fetch_array($user);
        $pwd=md5($user['username'].$user['password']);
        if($pwd===$_POST['auth']){
            $userid=$_POST['usr'];
            $content=$_POST['content'];
            $time=time();
            $res=sql_query("INSERT INTO entry VALUES ('','0','$content','$time','Gtalk','$userid')");
            if($res){
                $j = array('code'=>true, 'message'=>'发送成功!');
            }else{
                $j = array('code'=>false, 'message'=>'发送失败请稍后再试');
            }
        }else{
            $j= array('code'=>false, 'message'=>'您还未绑定网站或者之前的绑定已失效,请重新绑定!');
        }
        echo json_encode($j);
    break;
    
    case 'signature':   
        echo '<meta charset="utf-8" http-equiv="content-type" content="">';
        if(!isset($_POST['username'])) {
            echo '<form method="post" action="./wechat.php?do=signature"><p>用户名:<input type="text" name="username" value="" /></p><p>密  码:<input type="password" name="password" value="" /></p><p><input type="submit" value="确认" />';
        } else {
            $user=sql_query("SELECT * FROM user WHERE username = '".$_POST['username']."'");
            $user=mysql_fetch_array($user);
            if(md5($_POST['password']) === $user['password']) {
                $sig = $user['id'].md5($user['username'].$user['password']);
                echo '你的识别码是"'.$sig.'",请发送"-sig '.$sig.'"给PageCookery微信机器人完成绑定!';
            } else {
                echo '用户名或密码错误!';
            }
        }
    break;
    default:
        $j = array('code'=>true, 'message'=>'微信机器人平台搭建成功!');
        echo json_encode($j);
    break;
}
?>

第二步:关注PageCookery微信机器人账号。微信中选择“朋友-添加朋友-查找微信公众账号”,输入pagecookery即可查到机器人账号,添加即可。或者直接扫描下面的二维码即可成功添加。
PageCookery微信机器人

第三步:绑定自己的网站和账号。首先发送消息“-url 你的网站地址”,会让你跳转到另外一个页面,复制地址到浏览器中打开输入你的账号和密码后会返回识别码,复制识别码到微信中发送过去就完成绑定过程了。具体使用见下图:

第一次关注会提示你绑定网站,输入“-url 你的网站地址”即可完成绑定。
绑定账号成功绑定账号成功

复制地址到浏览器中打开,并输入你的PageCookery账号和密码,网页会返回识别码,复制识别码发送给PageCookery微信机器人即可完成绑定。
绑定账号成功绑定账号成功绑定账号成功

如果发送错误的识别码过去,机器人会提示绑定失败。
绑定账号成功

绑定账号完成后直接输入消息就可以发布状态了。
发布状态

2012年小结之电影全记录

由于豆瓣API的无能,这次的统计颇为烦恼啊。本次统计使用http://imnerd.org/lab/douban 工具得到,欢迎大家使用。

寒戰 太极2:英雄崛起 Looper 人再囧途之泰囧 一路向西 Cloud Atlas Moonrise Kingdom
Intouchables Fantastic Mr. Fox 女朋友○男朋友 ももへの手紙 써니 パプリカ Life of Pi
太极1:从零开始 Abraham Lincoln: Vampire Hunter Ted 铜雀台 The Amazing Spider-Man 喜宴 苹果
El sexo de los ángeles The Expendables 2 Madagascar 3: Europe's Most Wanted Despicable Me Limitless Blitz The Expendables
名探偵コナン 11人目のストライカー The Dark Knight Rises Wrath of the Titans 搞定岳父大人 听风者 Safe 桃姐
搜索 转山 翻滚吧!阿信 賽德克·巴萊(下)彩虹橋 賽德克·巴萊(上)太陽旗 杀生 武侠
The Avengers The Pirates! In an Adventure with Scientists! The Hunger Games サムライチャンプルー テイルズ オブ ヴェスペリア ~The First Strike~ コクリコ坂から The Grey
Ronal Barbaren Happy Feet Two 劇場版 NARUTO-ナルト- ブラッド・プリズン House M.D. House M.D. Ausente Shame
Walk a Mile in My Pradas Killer Elite Sherlock Holmes: A Game of Shadows I Am Legend Mission: Impossible - Ghost Protocol We Bought a Zoo とある飛空士への追憶
Eating Out 5: The Open Weekend ぬらりひょんの孫 Les petits mouchoirs ぬらりひょんの孫 千年魔京 The Adventures of Tintin: The Secret of the Unicorn うさぎドロップ The Fluffer
Gone, But Not Forgotten Longhorns The Cost of Love I Want to Get Married Clapham Junction The Artist Interview with the Vampire: The Vampire Chronicles
The Help The Lord of the Rings: The Fellowship of the Ring Toast The Sixth Sense 蛍火の杜へ Dragons: Gift of the Night Fury 夏目友人帐 第二季
夏目友人帳 参 夏目友人帳 Cover boy: L'ultima rivoluzione Отец и сын Real Fiction Cibrâil - Eine Liebe in Berlin Drive
Lilies - Les feluettes Testosterone Tinker Tailor Soldier Spy Moneyball جدایی نادر از سیمین Conan the Barbarian The Three Musketeers
又见阿郎 Real Steel 志明與春嬌 Beginners Puss in Boots 再見阿郎 Mission: Impossible II
Mission: Impossible III Mission: Impossible Harry Potter and the Deathly Hallows: Part 2 Harry Potter and the Deathly Hallows: Part 1 Harry Potter and the Prisoner of Azkaban Harry Potter and the Goblet of Fire Harry Potter and the Order of the Phoenix
龙门飞甲 Harry Potter and the Chamber of Secrets Heat Harry Potter and the Sorcerer's Stone 步步惊心 Sherlock Sherlock S01E00 Pilot
Front of the Class Taare Zameen Par 金陵十三钗 喜羊羊与灰太狼之开心闯龙年 Minority Report The Boy in the Striped Pajamas