django+jquery 实现级联选择菜单

最近在做一个基于django框架的项目,需要实现级联选择菜单,比如省-市-区,记录下来,希望能帮助碰到同样问题的人。

--------------------------------------------------------------------------------------------

无代码无真相,直接上代码:

js代码:

<script src="/site_media/js/jquery-1.5.2.min.js" type="text/javascript"></script> 
<script type="text/javascript"> 
	function getCityOptions(province_id){  
		$.ajax({   
			type: "GET",
			url: "/getCityList?provinceID="+province_id,	   
			dataType:'json',   
			success: function(data,textStatus){
				var citySelect = document.getElementById("id_city");
				for ( var i=citySelect.options.length-1; i>-1; i--){   
					citySelect[i] = null;   
				}     
				if(data.length > 0) {
					$("#id_city").show();  
					for(i=0;i<data.length;i++){   
						citySelect.options[i] = new Option();   
						citySelect.options[i].text = data[i].label;   
						citySelect.options[i].value = data[i].text; 
					}
				}else
					$("#id_city").hide();  
				 
			}    
		})   
	}  
</script> 

模板代码:

<label>省市:</label>	
        {{form.province}}
        {{form.city}}

Form.py:

provinces = Province.objects.all()
PROVINCE_CHOICES = []
for province in provinces:
    PROVINCE_CHOICES.append([province.id, province.provinceName])
class myForm(forms.Form): province = forms.ChoiceField(widget = forms.Select(attrs={'class':'select', 'onChange':'getCityOptions(this.value)'}), choices = PROVINCE_CHOICES, label= u'选择省') city = forms.ChoiceField(widget = forms.Select(attrs={'class':'select', 'onChange':'getDistrictOptions(this.value)','style':'display:none'}), label = u'选择市')

model.py:

class Province(models.Model):
   provinceName = models.CharField(max_length = 20)
class City(models.Model):
   cityName = models.CharField(max_length = 20)
   provinceID = models.ForeignKey(Province)

view.py

def city_list(request):
    city_list = []
    province = request.GET['provinceID']
    citys = City.objects.filter(provinceID = province)
    for city in citys:
        c = {}
        c['label'] = city.cityName
        c['text'] = city.id
        city_list.append(c)
    return HttpResponse(simplejson.dumps(city_list), mimetype='application/json')

 

Posted by 陈着 Apr 04, 2011 07:29:29 PM


ubuntu10.10+django+apache2+(mod-python 或 wsgi)

这几天一直在折腾django在apache上的部署,网上的资料鱼龙混杂,特将经验记录下来,希望能帮助更多的人。

mod-python:

1.安装apache,mod-python:

sudo apt-get install apache2 libapache2-mod-python

2.在/etc/apache2/available/目录下建立配置文件:mytest

<VirtualHost *:80>
	ServerName localhost
	DocumentRoot /home/sarlmolapple/workspace/django-site/mytest
	<Directory "/home/sarlmolapple/workspace/django-site">
		Allow from all
		SetHandler python-program
    		PythonHandler django.core.handlers.modpython
    		SetEnv DJANGO_SETTINGS_MODULE mytest.settings
    		PythonDebug On
    		PythonPath "['/home/sarlmolapple/workspace/django-site', '/home/sarlmolapple/workspace/django-site/mytest', '/home/sarlmolapple/workspace/django-site/mytest/mysite'] + sys.path"
	</Directory>
</VirtualHost>

注:django项目为mytest, 路径为:/home/sarlmolapple/workspace/django-site/mytest,

‘/home/sarlmolapple/workspace/django-site/mytest/mysite’为mytest中的app所在路径

3.运行命令:

sudo a2ensite mytest
sudo /etc/init.d/apache2 reload

然后你在浏览器中输入http://localhost就可以看到你想要看到的页面了

WSGI:

1.安装apache,mod-python:

sudo apt-get install apache2 libapache2-wsgi-python

2.在/etc/apache2/available/目录下建立配置文件:mytest

<VirtualHost *:80>
    ServerName wsgi.mytest
    DocumentRoot /home/sarlmolapple/workspace/django-site/mytest
    <Directory /home/sarlmolapple/workspace/django-site/mytest>
        Order allow,deny
        Allow from all
	PythonPath "['/home/sarlmolapple/workspace/django-site', '/home/sarlmolapple/workspace/django-site/mytest', '/home/sarlmolapple/workspace/django-site/mytest/mysite'] + sys.path"
    </Directory>
    WSGIDaemonProcess wsgi.mytest processes=2 threads=15 display-name=%{GROUP}
    WSGIProcessGroup wsgi.mytest
    WSGIScriptAlias / /home/sarlmolapple/workspace/django-site/mytest/apache/django.wsgi
</VirtualHost>

3.在mytest目录下建立apache/django.wsgi

import os
import sys

sys.path.append('/home/sarlmolapple/workspace/django-site')
sys.path.append('/home/sarlmolapple/workspace/django-site/mytest')
sys.path.append('/home/sarlmolapple/workspace/django-site/mytest/mysite')

os.environ['DJANGO_SETTINGS_MODULE'] = 'mytest.settings'

import django.core.handlers.wsgi
application = django.core.handlers.wsgi.WSGIHandler()

注: mytest路径为:/home/sarlmolapple/workspace/django-site/mytest,‘/home/sarlmolapple/workspace/django-site/mytest/mysite’为mytest中的app所在路径

4.运行命令:

sudo a2ensite mytest
sudo /etc/init.d/apache2 reload

同理,你在浏览器中输入http://localhost就可以看到你想要看到的页面了。

Posted by 陈着 Mar 12, 2011 04:22:44 PM


对数据库索引的理解

最近在写一个项目,其中涉及表test的访问,我想设计复合索引以提高查询效率。

表test如下:

id: INT primary key
x1: INT 
x2: INT 
x3: INT 

我用存储过程生成200w行数据

建立索引:

create index x1_x2_x3 on test(x1, x2, x3);

查询语句:

select * from test where x1 > xx and x2 < xx and x3 > xx;

explain之后发现type为all,也就是扫描全表,索引x1_x2_x3并没有用到,这让我百思不得其解;经过不断的纠结与实验后,基本找到了答案:索引需要对每一列有一个范围约束,比如:select *  from test where x1 > xx and x1 < xx...;这样type才能为range,ps:range不能小于总行数的四分之一。所以查询应该是类似:

select * from test where x1 > xx and x1 < xx and x2 < xx and x2 > xx and x3 > xx;

本来到此结束了,我又蛋疼的想分析下单独索引与复合索引的效率:

测试一组数据:

x1,x2,x3分别索引                             0.01s    扫描了48686行
X1,X2分别索引                                 0.10s    
x1索引                                         0.18s
x1_x2_x3复合索引                              0.20s   扫描了40117行
无索引                                         0.34s

我个人感觉复合索引虽然扫描行数少,但是有一部分资源消耗,导致没有单独索引快。

 

Posted by 陈着 Mar 09, 2011 03:57:46 PM


关于sina api上传图片接口(upload) c#版

    之前我写了sinaapi c#的sdk:opensinaapi,没有实现上传图片的接口。因为要忙实验室和其他项目,一直没有去研究。期间很多人询问过我有关上传图片接口的问题,最近刚好有些时间,花了一个晚上实现出来,需要的同学可以去http://code.google.com/p/opensinaapi/downloads/list下载。

    在研究的过程中我发现相关的资料非常少,基本上不能找到有用的信息。相对来说,twitter api的upload借口实现有其借鉴意义,但是其并不适用在sina api上,如果有人去研究的话应该会发现大多会返回401错误(未能授权)。原因是:sina api和twitter api中post的参数不一样,sina api中多了一个参数:source,少了oauth_verify。POST的参数和Update的接口一样,需要将status加入签名。

    我在研究过程中主要是抓取upload的POST包,对包结构进行分析,从而用c#构造POST包(抓包工具HttpAnalyzer)。以下为一个真实的POST包:

 

POST /statuses/upload.json HTTP/1.1
Authorization: OAuth oauth_consumer_key="3587646579", oauth_signature_method="HMAC-SHA1",oauth_timestamp="1289113593", oauth_nonce="7472747", oauth_version="1.0", oauth_token="9f273ceccf51e528b302a3a47053badd",oauth_signature="O5b4XTaazF6T50YqSsyLM6RtRsk%3d"
User-Agent: Jakarta Commons-HttpClient/3.1
Content-Type: multipart/form-data; boundary=bb9f588b-0eaf-4d95-aaec-b10f057bccd7
Host: api.t.sina.com.cn
Content-Length: 26509

--bb9f588b-0eaf-4d95-aaec-b10f057bccd7
Content-Disposition: form-data; name="status"
Content-Type: text/plain; charset=US-ASCII
Content-Transfer-Encoding: 8bit

%e6%88%91%e6%98%af%e9%99%88%e7%9d%80
--bb9f588b-0eaf-4d95-aaec-b10f057bccd7
Content-Disposition: form-data; name="source"
Content-Type: text/plain; charset=US-ASCII
Content-Transfer-Encoding: 8bit

3587646579
--bb9f588b-0eaf-4d95-aaec-b10f057bccd7
Content-Disposition: form-data; name="pic"; filename="sample_image.png"
Content-Type: application/octet-stream; charset=UTF-8
Content-Transfer-Encoding: binary
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
--bb9f588b-0eaf-4d95-aaec-b10f057bccd7


XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX为二进制文件串

以下是构造POST包的c#代码

 

public string oAuthWebRequestWithPic(Method method, string url, string postData,string filepath,string picName)
        {
            var UploadApiUrl = url;
            string status = postData.Split('=').GetValue(1).ToString();
            postData += "&source=" + _consumerKey;//多了一个source参数

            if (postData.Length > 0)
            {
                NameValueCollection qs = HttpUtility.ParseQueryString(postData);
                postData = "";
                foreach (string key in qs.AllKeys)
                {
                    if (postData.Length > 0)
                    {
                        postData += "&";
                    }
                    qs[key] = HttpUtility.UrlEncode(qs[key]);
                    qs[key] = this.UrlEncode(qs[key]);
                    postData += (key + "=" + qs[key]);

                }
                if (url.IndexOf("?") > 0)
                {
                    url += "&";
                }
                else
                {
                    url += "?";
                }
                url += postData;
            }

            var oauthSignaturePattern = "OAuth oauth_consumer_key=\"{0}\", oauth_signature_method=\"HMAC-SHA1\",oauth_timestamp=\"{1}\", oauth_nonce=\"{2}\", oauth_version=\"1.0\", oauth_token=\"{3}\",oauth_signature=\"{4}\"";
            
            var contentEncoding = "iso-8859-1";

            string normalizedString, normalizedParameters;
            var timestamp = this.GenerateTimeStamp();
            var nounce = this.GenerateNonce();

            var signature = this.GenerateSignature(
                                new Uri(url),
                                this.ConsumerKey,
                                this.ConsumerSecret,
                                this.Token,
                                this.TokenSecret,
                                method.ToString(),
                                timestamp,
                                nounce,
                                out normalizedString,
                                out normalizedParameters);
            signature = HttpUtility.UrlEncode(signature);

            var boundary = Guid.NewGuid().ToString();
            var request = (HttpWebRequest)System.Net.WebRequest.Create(UploadApiUrl);

            request.PreAuthenticate = true;
            request.AllowWriteStreamBuffering = true;
            request.Method = method.ToString();
            request.UserAgent = "Jakarta Commons-HttpClient/3.1";
            var authorizationHeader = string.Format(
                                        CultureInfo.InvariantCulture,
                                        oauthSignaturePattern,
                                        this.ConsumerKey,
                                        timestamp,
                                        nounce,
                                        this.Token,
                                        signature);
            request.Headers.Add("Authorization", authorizationHeader);

            var header = string.Format("--{0}", boundary);
            var footer = string.Format("--{0}--", boundary);

            var contents = new StringBuilder();
            request.ContentType = string.Format("multipart/form-data; boundary={0}", boundary);
            contents.AppendLine(header);
            contents.AppendLine(String.Format("Content-Disposition: form-data; name=\"{0}\"", "status"));
            contents.AppendLine("Content-Type: text/plain; charset=US-ASCII");
            contents.AppendLine("Content-Transfer-Encoding: 8bit");
            contents.AppendLine();
            contents.AppendLine(status);

            contents.AppendLine(header);
            contents.AppendLine(string.Format("Content-Disposition: form-data; name=\"{0}\"", "source"));
            contents.AppendLine("Content-Type: text/plain; charset=US-ASCII");
            contents.AppendLine("Content-Transfer-Encoding: 8bit");
            contents.AppendLine();
            contents.AppendLine(this.ConsumerKey);


            contents.AppendLine(header);
            string fileHeader = string.Format("Content-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"", "pic", picName);
            string fileData = System.Text.Encoding.GetEncoding(contentEncoding).GetString(File.ReadAllBytes(@filepath));

            contents.AppendLine(fileHeader);
            contents.AppendLine("Content-Type: application/octet-stream; charset=UTF-8");
            contents.AppendLine("Content-Transfer-Encoding: binary");
            contents.AppendLine();
            contents.AppendLine(fileData);
            contents.AppendLine(footer);

            byte[] bytes = Encoding.GetEncoding(contentEncoding).GetBytes(contents.ToString());
            request.ContentLength = bytes.Length;
            
            var requestStream = request.GetRequestStream();           
            try
            {
                requestStream.Write(bytes, 0, bytes.Length);
            }
            catch
            {
                throw;
            }
            finally
            {
                requestStream.Close();
                requestStream = null;
            }
            return WebResponseGet(request);
        }

其次,需要修改oAuthBase.cs,这是因为签名参数的问题:多了source少了oauth_verify

 

将
public string GenerateSignatureBase(Uri url, string consumerKey, string token, string tokenSecret, string httpMethod, string timeStamp, string nonce, string signatureType, out string normalizedUrl, out string normalizedRequestParameters)中

if (!string.IsNullOrEmpty(oauth_verifier))
改为
if (!string.IsNullOrEmpty(oauth_verifier) && httpMethod == "GET")

有个比较奇怪的问题,sina api的update接口可以不需要将oauth_verify换为source,而upload接口则必须将oauth_verify换为source。

最后,本版本的sdk中,upload的status必须为英文,暂时还没解决中文问题。

注:由于本人时间有限,而sdk还相当不完善,如有同学想加入opensinaapi,请联系sarlmolapple@gmail.com,不胜荣幸,希望能帮助更多的人。

Posted by 陈着 Nov 07, 2010 10:26:13 PM


75道逻辑题

 

   【1】假设有一个池塘,里面有无穷多的水。现有2个空水壶,容积分别为5升和6升。问题是如何只用这2个水壶从池塘里取得3升的水。

    由满6向空5倒,剩1升,把这1升倒5里,然后6剩满,倒5里面,由于5里面有1升水,因此6只能向5倒4升水,然后将6剩余的2升,倒入空的5里面,再灌满6向5里倒3升,剩余3升。

   

    【2】周雯的妈妈是豫林水泥厂的化验员。一天,周雯来到化验室做作业。做完后想出去玩。"等等,妈妈还要考你一个题目,"她接着说,"你看这6只做化验用的玻璃杯,前面3只盛满了水,后面3只是空的。你能只移动1只玻璃杯,就便盛满水的杯子和空杯子间隔起来吗?"爱动脑筋的周雯,是学校里有名的"小机灵",她只想了一会儿就做到了。请你想想看,"小机灵"是怎样做的?

    设杯子编号为ABCDEF,ABC为满,DEF为空,把B中的水倒进E中即可。

   

    【3】三个小伙子同时爱上了一个姑娘,为了决定他们谁能娶这个姑娘,他们决定用手枪进行一次决斗。小李的命中率是30%,小黄比他好些,命中率是50%,最出色的枪手是小林,他从不失误,命中率是100%。由于这个显而易见的事实,为公平起见,他们决定按这样的顺序:小李先开枪,小黄第二,小林最后。然后这样循环,直到他们只剩下一个人。那么这三个人中谁活下来的机会最大呢?他们都应该采取什么样的策略?

    小林在轮到自己且小黄没死的条件下必杀黄,再跟菜鸟李单挑。所以黄在林没死的情况下必打林,否则自己必死。小李经过计算比较(过程略),会决定自己先打小林。于是经计算,小李有873/2600≈33.6%的生机;小黄有109/260≈41.9%的生机;小林有24.5%的生机。哦,这样,那小李的第一枪会朝天开,以后当然是打敌人,谁活着打谁;小黄一如既往先打林,小林还是先干掉黄,冤家路窄啊!最后李,黄,林存活率约38:27:35;菜鸟活下来抱得美人归的几率大。李先放一空枪(如果合伙干中林,自己最吃亏)黄会选林打一枪(如不打林,自己肯定先玩完了)林会选黄打一枪(毕竟它命中率高)李黄对决0.3:0.280.4可能性李林对决0.3:0.60.6可能性成功率0.73;李和黄打林李黄对决0.3:0.40.7*0.4可能性李林对决0.3:0.7*0.6*0.70.7*0.6可能性成功率0.64。


    【4】一间囚房里关押着两个犯人。每天监狱都会为这间囚房提供一罐汤,让这两个犯人自己来分。起初,这两个人经常会发生争执,因为他们总是有人认为对方的汤比自己的多。后来他们找到了一个两全其美的办法:一个人分汤,让另一个人先选。于是争端就这么解决了。可是,现在这间囚房里又加进来一个新犯人,现在是三个人来分汤。必须寻找一个新的方法来维持他们之间的和平。该怎么办呢?按:心理问题,不是逻辑问题

    是让甲分汤,分好后由乙和丙按任意顺序给自己挑汤,剩余一碗留给甲。这样乙和丙两人的总和肯定是他们两人可拿到的最大。然后将他们两人的汤混合之后再按两人的方法再次分汤。

   

    【5】在一张长方形的桌面上放了n个一样大小的圆形硬币。这些硬币中可能有一些不完全在桌面内,也可能有一些彼此重叠;当再多放一个硬币而它的圆心在桌面内时,新放的硬币便必定与原先某些硬币重叠。请证明整个桌面可以用4n个硬币完全覆盖。

    要想让新放的硬币不与原先的硬币重叠,两个硬币的圆心距必须大于直径。也就是说,对于桌面上任意一点,到最近的圆心的距离都小于2,所以,整个桌面可以用n个半径为2的硬币覆盖。把桌面和硬币的尺度都缩小一倍,那么,长、宽各是原桌面一半的小桌面,就可以用n个半径为1的硬币覆盖。那么,把原来的桌子分割成相等的4块小桌子,那么每块小桌子都可以用n个半径为1的硬币覆盖,因此,整个桌面就可以用4n个半径为1的硬币覆盖。

    【6】一个球、一把长度大约是球的直径2/3长度的直尺。你怎样测出球的半径?

    

    【7】五个大小相同的一元人民币硬币。要求两两相接触,应该怎么摆?

 

    底下放一个1,然后2 3放在1上面,另外的4 5竖起来放在1的上面。

    

    【8猜牌问题S先生、P先生、Q先生他们知道桌子的抽屉里有16张扑克牌:红桃A、Q、4黑桃J、8、4、2、7、3草花K、Q、5、4、6方块A、5。约翰教授从这16张牌中挑出一张牌来,并把这张牌的点数告诉P先生,把这张牌的花色告诉Q先生。这时,约翰教授问P先生和Q先生:你们能从已知的点数或花色中推知这张牌是什么牌吗?于是,S先生听到如下的对话:P先生:我不知道这张牌。Q先生:我知道你不知道这张牌。P先生:现在我知道这张牌了。Q先生:我也知道了。听罢以上的对话,S先生想了一想之后,就正确地推出这张牌是什么牌。请问:这张牌是什么牌?

    方块5

    【9】一个教授逻辑学的教授,有三个学生,而且三个学生均非常聪明!一天教授给他们出了一个题,教授在每个人脑门上贴了一张纸条并告诉他们,每个人的纸条上都写了一个正整数,且某两个数的和等于第三个!(每个人可以看见另两个数,但看不见自己的)教授问第一个学生:你能猜出自己的数吗?回答:不能,问第二个,不能,第三个,不能,再问第一个,不能,第二个,不能,第三个:我猜出来了,是144!教授很满意的笑了。请问您能猜出另外两个人的数吗?

    经过第一轮,说明任何两个数都是不同的。第二轮,前两个人没有猜出,说明任何一个数都不是其它数的两倍。现在有了以下几个条件:1.每个数大于02.两两不等3.任意一个数不是其他数的两倍。每个数字可能是另两个之和或之差,第三个人能猜出144,必然根据前面三个条件排除了其中的一种可能。假设:是两个数之差,即x-y=144。这时1(x,y>0)和2(x!=y)都满足,所以要否定x+y必然要使3不满足,即x+y=2y,解得x=y,不成立(不然第一轮就可猜出),所以不是两数之差。因此是两数之和,即x+y=144。同理,这时1,2都满足,必然要使3不满足,即x-y=2y,两方程联立,可得x=108,y=36。

    这两轮猜的顺序其实分别为这样:第一轮(一号,二号),第二轮(三号,一号,二号)。这样分大家在每轮结束时获得的信息是相同的(即前面的三个条件)。

    那么就假设我们是C,来看看C是怎么做出来的:C看到的是A的36和B的108,因为条件,两个数的和是第三个,那么自己要么是72要么是144(猜到这个是因为72的话,108就是36和72的和,144的话就是108和36的和。这样子这句话看不懂的举手):

    假设自己(C)是72的话,那么B在第二回合的时候就可以看出来,下面是如果C是72,B的思路:这种情况下,B看到的就是A的36和C的72,那么他就可以猜自己,是36或者是108(猜到这个是因为36的话,36加36等于72,108的话就是36和108的和):

    如果假设自己(B)头上是36,那么,C在第一回合的时候就可以看出来,下面是如果B是36,C的思路:这种情况下,C看到的就是A的36和B的36,那么他就可以猜自己,是72或者是0(这个不再解释了):如果假设自己(C)头上是0,那么,A在第一回合的时候就可以看出来,下面是如果C是0,A的思路:这种情况下,A看到的就是B的36和C的0,那么他就可以猜自己,是36或者是36(这个不再解释了),那他可以一口报出自己头上的36。(然后是逆推逆推逆推),现在A在第一回合没报出自己的36,C(在B的想象中)就可以知道自己头上不是0,如果其他和B的想法一样(指B头上是36),那么C在第一回合就可以报出自己的72。现在C在第一回合没报出自己的36,B(在C的想象中)就可以知道自己头上不是36,如果其他和C的想法一样(指C头上是72),那么B在第二回合就可以报出自己的108。现在B在第二回合没报出自己的108,C就可以知道自己头上不是72,那么C头上的唯一可能就是144了。

 

   【10】某城市发生了一起汽车撞人逃跑事件,该城市只有两种颜色的车,蓝15%绿85%,事发时有一个人在现场看见了,他指证是蓝车,但是根据专家在现场分析,当时那种条件能看正确的可能性是80%那么,肇事的车是蓝车的概率到底是多少?

    15%*80%/(85%×20%+15%*80%)

 

   【11】有一人有240公斤水,他想运往干旱地区赚钱。他每次最多携带60公斤,并且每前进一公里须耗水1公斤(均匀耗水)。假设水的价格在出发地为0,以后,与运输路程成正比,(即在10公里处为10元/公斤,在20公里处为20元/公斤......),又假设他必须安全返回,请问,他最多可赚多少钱?

    f(x)=(60-2x)*x,当x=15时,有最大值450。 450×4

 

   【12】现在共有100匹马跟100块石头,马分3种,大型马;中型马跟小型马。其中一匹大马一次可以驮3块石头,中型马可以驮2块,而小型马2头可以驮一块石头。问需要多少匹大马,中型马跟小型马?(问题的关键是刚好必须是用完100匹马)

    6种结果

 

   【13】1=5,2=15,3=215,4=2145那么5=?

    因为1=5,所以5=1.

 

   【14】有2n个人排队进电影院,票价是50美分。在这2n个人当中,其中n个人只有50美分,另外n个人有1美元(纸票子)。愚蠢的电影院开始卖票时1分钱也没有。问:有多少种排队方法使得每当一个拥有1美元买票时,电影院都有50美分找钱

注:1美元=100美分拥有1美元的人,拥有的是纸币,没法破成2个50美分

 

    本题可用递归算法,但时间复杂度为2的n次方,也可以用动态规划法,时间复杂度为n的平方,实现起来相对要简单得多,但最方便的就是直接运用公式:排队的种数=(2n)!/[n!(n+1)!]。

    如果不考虑电影院能否找钱,那么一共有(2n)!/[n!n!]种排队方法(即从2n个人中取出n个人的组合数),对于每一种排队方法,如果他会导致电影院无法找钱,则称为不合格的,这种的排队方法有(2n)!/[(n-1)!(n+1)!](从2n个人中取出n-1个人的组合数)种,所以合格的排队种数就是(2n)!/[n!n!]- (2n)!/[(n-1)!(n+1)!] =(2n)!/[n!(n+1)!]。至于为什么不合格数是(2n)!/[(n-1)!(n+1)!],说起来太复杂,这里就不讲了。

 

   【15】一个人花8块钱买了一只鸡,9块钱卖掉了,然后他觉得不划算,花10块钱又买回来了,11块卖给另外一个人。问他赚了多少?

    2元

 

    【16】有一种体育竞赛共含M个项目,有运动员A,B,C参加,在每一项目中,第一,第二,第三名分别的X,Y,Z分,其中X,Y,Z为正整数且X>Y>Z。最后A得22分,B与C均得9分,B在百米赛中取得第一。求M的值,并问在跳高中谁得第二名。

    因为ABC三人得分共40分,三名得分都为正整数且不等,所以前三名得分最少为6分,40=5*8=4*10=2*20=1*20,不难得出项目数只能是5,即M=5。A得分为22分,共5项,所以每项第一名得分只能是5,故A应得4个一名一个二名.22=5*4+2,第二名得1分,又B百米得第一,所以A只能得这个第二。B的5项共9分,其中百米第一5分,其它4项全是1分,9=5+1=1+1+1,即B除百米第一外全是第三,跳高第二必定是C所得。

    【17】前提:

1 有五栋五种颜色的房子

2 每一位房子的主人国籍都不同

3 这五个人每人只喝一种饮料,只抽一种牌子的香烟,只养一种宠物

4 没有人有相同的宠物,抽相同牌子的香烟,喝相同的饮料

   提示:1  英国人住在红房子里

2  瑞典人养了一条狗

3  丹麦人喝茶

4  绿房子在白房子左边

5  绿房子主人喝咖啡

6  抽PALL MALL烟的人养了一只鸟

7  黄房子主人抽DUNHILL烟

8  住在中间那间房子的人喝牛奶

9  挪威人住第一间房子

10 抽混合烟的人住在养猫人的旁边

11 养马人住在抽DUNHILL烟的人旁边

12 抽BLUE MASTER烟的人喝啤酒

13 德国人抽PRINCE烟

14 挪威人住在蓝房子旁边

15 抽混合烟的人的邻居喝矿泉水

    问题是:谁养鱼???

    第一间是黄房子,挪威人住,喝矿泉水,抽DUNHILL香烟,养猫;第二间是蓝房子,丹麦人住,喝茶,抽混合烟,养马;第三间是红房子,英国人住,喝牛奶,抽PALL MALL烟,养鸟;第四间是绿房子,德国人住,喝咖啡,抽PRINCE烟,养猫、马、鸟、狗以外的宠物;第五间是白房子,瑞典人住,喝啤酒,抽BLUE  MASTER烟,养狗。

 

    【18】5个人来自不同地方,住不同房子,养不同动物,吸不同牌子香烟,喝不同饮料,喜欢不同食物。根据以下线索确定谁是养猫的人。

1 红房子在蓝房子的右边,白房子的左边(不一定紧邻)

2 黄房子的主人来自香港,而且他的房子不在最左边。

3 爱吃比萨的人住在爱喝矿泉水的人的隔壁。

4 来自北京的人爱喝茅台,住在来自上海的人的隔壁。

5 吸希尔顿香烟的人住在养马人的右边隔壁。

6 爱喝啤酒的人也爱吃鸡。

7 绿房子的人养狗。

8 爱吃面条的人住在养蛇人的隔壁。

9 来自天津的人的邻居(紧邻)一个爱吃牛肉,另一个来自成都。

10.养鱼的人住在最右边的房子里。

11.吸万宝路香烟的人住在吸希尔顿香烟的人和吸“555”香烟的人的中间(紧邻)

12.红房子的人爱喝茶。

13.爱喝葡萄酒的人住在爱吃豆腐的人的右边隔壁。

14.吸红塔山香烟的人既不住在吸健牌香烟的人的隔壁,也不与来自上海的人相邻。

15.来自上海的人住在左数第二间房子里。

16.爱喝矿泉水的人住在最中间的房子里。

17.爱吃面条的人也爱喝葡萄酒。

18.吸“555”香烟的人比吸希尔顿香烟的人住的靠右

    第一间是兰房子,住北京人,养马,抽健牌香烟,喝茅台,吃豆腐;第二间是绿房子,住上海人,养狗,抽希尔顿,喝葡萄酒,吃面条;第三间是黄房子,住香港人,养蛇,抽万宝路,喝矿泉水,吃牛肉;第四间是红房子,住天津人,抽555,喝茶,吃比萨;第五间是白房子,住成都人,养鱼,抽红塔山,喝啤酒,吃鸡。

 

    【19】斗地主附残局

地主手中牌2、K、Q、J、10、9、8、8、6、6、5、5、3、3、3、3、7、7、7、7

长工甲手中牌大王、小王、2、A、K、Q、J、10、Q、J、10、9、8、5、5、4、4

长工乙手中牌2、2、A、A、A、K、K、Q、J、10、9、9、8、6、6、4、4

    三家都是明手,互知底牌。要求是:在三家都不打错牌的情况下,地主必须要么输要么赢。问:哪方会赢?

    无解 地主怎么出都会输

 

    【20】一楼到十楼的每层电梯门口都放着一颗钻石,钻石大小不一。你乘坐电梯从一楼到十楼,每层楼电梯门都会打开一次,只能拿一次钻石,问怎样才能拿到最大的一颗?

    先拿下第一楼的钻石,然后在每一楼把手中的钻石与那一楼的钻石相比较,如果那一楼的钻石比手中的钻石大的话那就把手中的钻石换成那一层的钻石。

 

    【21】U2合唱团在17分钟 内得赶到演唱会场,途中必需跨过一座桥,四个人从桥的同一端出发,你得帮助他们到达另一端,天色很暗,而他们只有一只手电筒。一次同时最多可以有两人一起 过桥,而过桥的时候必须持有手电筒,所以就得有人把手电筒带来带去,来回桥两端。手电筒是不能用丢的方式来传递的。四个人的步行速度各不同,若两人同行则 以较慢者的速度为准。Bono需花1分钟过桥,Edge需花2分钟过桥,Adam需花5分钟过桥,Larry需花10分钟过桥。他们要如何在17分钟内过 桥呢?

    2+1先过                          2

    然后1回来送手电筒                 1

    5+10再过                         10

    2回来送手电筒                      2

    2+1过去                           2

总共2+1+10+2+2=17分钟

 

    【22】一个家庭有两个小孩,其中有一个是女孩,问另一个也是女孩的概率(假定生男生女的概率一样)

    1/3

样本空间为(男男)(女女)(男女)(女男)

A=(已知其中一个是女孩)=)(女女)(男女)(女男)

B=(另一个也是女孩)=(女女)

于是P(B/A)=P(AB)/P(A)=(1/4)/(3/4)=1/3

     

    【23】为什么下水道的盖子是圆的?

    不会掉下去

    【24】有7克、2克砝码各一个,天平一只,如何只用这些物品三次将140克的盐分成50、90克各一份?

    140->70+70  70->35+35

    35+70=105

    105->50+7 + 55+2

    55+35=90

 

    【25】芯片测试:有2k块芯片,已知好芯片比坏芯片多.请设计算法从其中找出一片 好芯片,说明你所用的比较次数上限. 其中:好芯片和其它芯片比较时,能正确给出另一块芯片是好还是坏. 坏芯片和其它芯片比较时,会随机的给出好或是坏。

    把第一块芯片与其它逐一对比,看看其它芯片对第一块芯片给出的是好是坏,如果给出是好的过半,那么说明这是好芯片,完毕。如果给出的是坏的过半,说明第一块芯片是坏的,那么就要在那些在给出第一块芯片是坏的芯片中,重复上述步骤,直到找到好的芯片为止。

 

    【26】12个球一个天平,现知道只有一个和其它的重量不同,问怎样称才能用三次就找到那个球。13个呢?(注意此题并未说明那个球的重量是轻是重)

    12个时可以找出那个是重还是轻,13个时只能找出是哪个球,轻重不知。
  把球编为①②③④⑤⑥⑦⑧⑨⑩⑾⑿。(13个时编号为⒀)
  第一次称:先把①②③④与⑤⑥⑦⑧放天平两边,
    ㈠如相等,说明特别球在剩下4个球中。
      把①⑨与⑩⑾作第二次称量,
      ⒈如相等,说明⑿特别,把①与⑿作第三次称量即可判断是⑿是重还是轻
      ⒉如①⑨<⑩⑾说明要么是⑩⑾中有一个重的,要么⑨是轻的。
        把⑩与⑾作第三次称量,如相等说明⑨轻,不等可找出谁是重球。
      ⒊如①⑨>⑩⑾说明要么是⑩⑾中有一个轻的,要么⑨是重的。
        把⑩与⑾作第三次称量,如相等说明⑨重,不等可找出谁是轻球。
    ㈡如左边<右边,说明左边有轻的或右边有重的
      把①②⑤与③④⑥做第二次称量
      ⒈如相等,说明⑦⑧中有一个重,把①与⑦作第三次称量即可判断是⑦与⑧中谁是重球
      ⒉如①②⑤<③④⑥说明要么是①②中有一个轻的,要么⑥是重的。
        把①与②作第三次称量,如相等说明⑥重,不等可找出谁是轻球。
      ⒊如①②⑤>③④⑥说明要么是⑤是重的,要么③④中有一个是轻的。
        把③与④作第三次称量,如相等说明⑤重,不等可找出谁是轻球。
    ㈢如左边>右边,参照㈡相反进行。
  当13个球时,第㈠步以后如下进行。
    把①⑨与⑩⑾作第二次称量,
    ⒈如相等,说明⑿⒀特别,把①与⑿作第三次称量即可判断是⑿还是⒀特别,但判断不了轻重了。
    ⒉不等的情况参见第㈠步的⒉⒊

 

    【27】100个人回答五道试题,有81人答对第一题,91人答对第二题,85人答对第三题,79人答对第四题,74人答对第五题,答对三道题或三道题以上的人算及格, 那么,在这100人中,至少有( )人及格。

    首先求解原题。每道题的答错人数为(次序不重要):26,21,19,15,9

    第3分布层:答错3道题的最多人数为:(26+21+19+15+9)/3=30

    第2分布层:答错2道题的最多人数为:(21+19+15+9)/2=32

    第1分布层:答错1道题的最多人数为:(19+15+9)/1=43

    Max_3=Min(30, 32, 43)=30。因此答案为:100-30=70。

    其实,因为26小于30,所以在求出第一分布层后,就可以判断答案为70了。

     

    要让及格的人数最少,就要做到两点:

    1. 不及格的人答对的题目尽量多,这样就减少了及格的人需要答对的题目的数量,也就只需要更少的及格的人

    2. 每个及格的人答对的题目数尽量多,这样也能减少及格的人数

    由1得每个人都至少做对两道题目

    由2得要把剩余的210道题目分给其中的70人: 210/3 = 70,让这70人全部题目都做对,而其它30人只做对了两道题

    也很容易给出一个具体的实现方案:

    让70人答对全部五道题,11人仅答对第一、二道题,10人仅答对第二、三道题,5人答对第三、四道题,4人仅答对第四、五道题

    显然稍有变动都会使及格的人数上升。所以最少及格人数就是70人!

     

    【28】陈奕迅有首歌叫十年吕珊有首歌叫3650夜那现在问,十年可能有多少天?

    十年可能包含2-3个闰年,3652或3653天。

    1900年这个闰年就是28天,1898~1907这10年就是3651天,闰年如果是整百的倍数,如1800,1900,那么这个数必须是400的倍数才有29天,比如1900年2月有28天,2000年2月有29天 。

 

    【29】1,11,21,1211,111221,下一个数是什么?

    下行是对上一行的解释 所以新的应该是3个1 2个2 1个1 :312211

 

    【30】烧一根不均匀的绳要用一个小时,如何用它来判断半个小时?烧一根不均匀的绳,从头烧到尾总共需要1个小时。现在有若干条材质相同的绳子,问如何用烧绳的方法来计时一个小时十五分钟呢?(微软的笔试题)

    一,一根绳子从两头烧,烧完就是半个小时。

    二,一根要一头烧,一根从两头烧,两头烧完的时候(30分),将剩下的一根另一端点着,烧尽就是45分钟。再从两头点燃第三根,烧尽就是1时15分。

 

 

 

 

 

 

 

Posted by 陈着 Oct 06, 2010 07:23:15 AM


无边框GTkWindow的信号处理

今天继续改进osd-lyrics的scroll-mode显示,希望最终的效果图为:

首要把标准的gtkwindow的框去掉,设置成无窗口模式,ol_scroll_window继承自gtkwindow可以使用:

 gtk_window_set_decorated (GTK_WINDOW(scroll),FALSE);

如下图显示:

但是此时,ol_scroll_window不会响应button_press,button_release和motion_notify信号,也就是无法移动ol_scroll_window和改变ol_scroll_window的大小,经过测试:key_press等信号是可以响应的,于是怀疑gtkwindow将button_press,button_release和motion_notify信号屏蔽掉了。通过阅读gtkwindow.c源代码,发现其gtk_window_realize (GtkWidget *widget)中对无边框窗口屏蔽了button_press,button_release和motion_notify信号。(比较悲剧,不懂gtkwindow为什么这样设计)

解决方案为:

在ol_scroll_window_init (OlScrollWindow *self)中添加事件响应

  gtk_widget_add_events (GTK_WIDGET (self), GDK_BUTTON_PRESS_MASK|GDK_BUTTON_RELEASE_MASK|GDK_POINTER_MOTION_MASK);

但是问题解决得并不完美,发现在移动窗口的时候,在碰到其他窗口时scroll_window坐标会大幅度变动. google一下得到解决方案:

 gtk_widget_add_events (GTK_WIDGET (self), GDK_POINTER_MOTION_HINT_MASK);

最终问题得到解决,继续进一步的改进。

Posted by 陈着 Jul 12, 2010 12:15:11 AM


Bloom Filter概念和原理

转载自:http://blog.csdn.net/jiaomeng/archive/2007/01/27/1495500.aspx



        Bloom Filter是一种空间效率很高的随机数据结构,它利用位数组很简洁地表示一个集合,并能判断一个元素是否属于 这个集合。Bloom Filter的这种高效是有一定代价的:在判断一个元素是否属于某个集合时,有可能会把不属于这个集合的元素误 认为属于这个集合(false positive)。因此,Bloom Filter不适合那些“零错误”的应用场合。而在能容忍低错误率的应用场合下,Bloom Filter通过极少的错误换取了存储空间的极大节省。

集合表示和元素查询

下面我们具体来看Bloom Filter是如何用位数组表示集合的。初始状态时,Bloom Filter是一个包含m位的位 数组,每一位都置为0

为了表达S={x1, x2,…,xn}这样一个n个元素 的集合,Bloom Filter使用k个相互独立的哈希函数(Hash Function),它们分别将集合中的每个元素映射到{1,…,m}的范围中。对任意一个元素x,第i个哈希函数映射的位置hi(x)就会被置为11ik)。注意,如果一个位置多次被置为1,那 么只有第一次会起作用,后面几次将没有任何效果。在下图中,k=3,且有两个哈希函数选中同一 个位置(从左边数第五位)。  

在判断y是否 属于这个集合时,我们对y应用k次哈希函数,如果所有hi(y)的位置都是11ik),那么我们就认为y是集合 中的元素,否则就认为y不是集合中的元素。下图中y1就不是集合中的元素。y2或者属于这个集合,或者刚好是一个false positive


错误率估计

前面我们已经提到了,Bloom Filter在判断一个元素是否属于它表示的集合时会有一定的错误率(false positive rate),下面我们就来估计错误率的大小。在估计之前为了简化模型,我们假设kn<m且各个哈希函数是完全随机的。当集合S={x1, x2,…,xn}的所有元素都被k个哈希 函数映射到m位的位数组中时,这个位数组中某一位还是0的概率是:

其中1/m表示任意一个哈希函数选中这 一位的概率(前提是哈希函数是完全随机的),(1-1/m)表示哈希一次没有选中这一位 的概率。要把S完全映射到位数组中,需要做kn次哈 希。某一位还是0意味着kn次哈希都没有选中它,因此这 个概率就是(1-1/m)的kn次方。令p = e-kn/m是为了简化运算,这里用到了计算e时常用的近似:

 

令ρ为位数组中0的比例,则ρ的数学 期望E(ρ)= p’。在ρ已知的情况 下,要求的错误率(false positive rate)为:

 

(1-ρ)位数组中1的比例,(1-ρ)k就表示k次哈希都刚好选中1的区域,即false positive rate。上式中第二步近似在前面已经提到了,现在来看第一步近似。p’只是ρ的数学期望, 在实际中ρ的值有可能偏离它的数学期望值。M. Mitzenmacher已经证明[2] ,位数组中0的比例非常集中地分布在它的数学期望值的附近。因此,第一步的近似得以成立。分别将pp’代入上式中,得:

 


相比p’f’,使用pf通常在分析中更为方便。 

最优的哈希函数个数 

既然Bloom Filter要靠多个哈希函数将集合映射到位数组中,那么应该选择几个哈希函数才能使元素查询时的错误率降到最低 呢?这里有两个互斥的理由:如果哈希函数的个数多,那么在对一个不属于集合的元素进行查询时得到0的概 率就大;但另一方面,如果哈希函数的个数少,那么位数组中的0就多。为了得到最优的哈希函数个 数,我们需要根据上一小节中的错误率公式进行计算。

先用pf进行计算。注意到f = exp(k ln(1 − e−kn/m)),我们令g = k ln(1 − e−kn/m),只要让g取到最 小,f自然也取到最小。由于p = e-kn/m,我们可以将g写成

根据对称性法则可以很容易看出当p = 1/2,也就是k = ln2· (m/n)时,g取得最小值。在这种情况下,最小错误率f等于(1/2)k (0.6185)m/n。另外,注意到p是位数组中某一位仍是0的概率,所以p = 1/2对应着位数组中0和1各一半。换句话说,要想保持错误率低,最好让位数组有一半还空着。

需要强调的一点是,p = 1/2时错误率最小这个结果并不依赖于近似值pf。同样对于f’ = exp(k ln(1 − (1 − 1/m)kn))g’ = k ln(1 − (1 − 1/m)kn)p’ = (1 − 1/m)kn,我们可以将g’写成

同样根据对称性法则可以得到当p’ = 1/2时,g’取得最小值。

位数组的大小 

下面我们来看看,在不超过一定错误率的情况下,Bloom Filter至少需要多少位才能表示全集中任意n个元 素的集合。假设全集中共有u个元 

素,允许的最大错误率为є,下面 我们来求位数组的位数m

假设X为全 集中任取n个元素的集合,F(X)是 表示X的位数组。那么对于集合X中任意 一个元素x,在s = F(X)中查询x都能得到肯定的结果,即s能够接 受x。显然,由于Bloom Filter引入了错误,s能够接 受的不仅仅是X中的元素,它还能够є (u - n)false positive

因此,对于一个确定的位数组来说,它能 够接受总共n + є (u - n)个元素。在n + є (u - n)个元素中,s真正表 示的只有其中n个,所以一个确定的位数组可以表示

个集合。m位的位数组共有2m个不同的组合,进而可以推出,m位的位 数组可以表示

个集合。全集中n个元素 的集合总共有

个,因此要让m位的位 数组能够表示所有n个元素的集合,必须有

即:


 

上式中的近似前提是nєu相比很小,这也是实际情况中常常发生的。根据上式,我们得出结论:在错误率不大于є的情况下,m至少要 等于n log2(1/є)才能表示任意n个元素 的集合。

上一小节中我们曾算出当k = ln2· (m/n)时错误率f最小, 这时f = (1/2)k = (1/2)mln2 / n。现在令fє,可以推出


这个结果比前面我们算得的下界n log2(1/є)大了log2 e 1.44倍。这说明在哈希函数的个数取到最优时,要让错误率不超过єm至少需要取到最小值的1.44倍。

总结: 

在计算机科学中,我们常常会碰到时间换空间或者空间换时间的情况, 即为了达到某一个方面的最优而牺牲另一个方面。Bloom Filter在时间空间这两个 因素之外又引入了另一个因素:错误率。在使用Bloom Filter判断一个元素是否 属于某个集合时,会有一定的错误率。也就是说,有可能把不属于这个集合的元素误认为属于这个集合(False Positive),但不会把属于这个集合的元素误认为不属于这个集合(False Negative)。在增加了错误率这个因素之后,Bloom Filter通过允许少量的错误来节省大量的存储空间。

自从Burton Bloom70年代提出Bloom Filter之后,Bloom Filter就被广 泛用于拼写检查和数据库系统中。近一二十年,伴随着网络的普及和发展,Bloom Filter在网络领域获得了新生,各种Bloom Filter变种和新的应用不断出现。可以预见,随着网络应用的不断深入,新的变种和应用将会继续出现,Bloom Filter必将获得更大的发展。

Posted by 陈着 May 13, 2010 12:55:30 AM


ububtu10.04下“华为e169” 3G上网卡 上网设置

在网上找了下资料,貌似”华为e169“对ubuntu的支持相当好,很多人直接插入网卡就可以识别。

但在ubuntu10.04下不是如此,按照网上的资料:

第一步安装wcdia:

keen@sarlmolapple:~$sudo apt-get install wvdia

 第二步安装usb-modeswitch:

keen@sarlmolapple:~$sudo apt-get install usb-modeswitch

 这时候运行

keen@sarlmolapple:~$sudo usb_modeswitch -W

会显示找不到 /etc/usb-modeswitch.conf

问题就出在这里,其实在/etc/usb_modeswitch.d/目录下保存着usb_modeswitch支持的无线网卡,e169的标识应该是:12d1:1001

于是将/etc/usb_modeswitch.d/12d1:1001中的内容复制到/etc/usb-modeswitch.conf中,然后运行

 

keen@sarlmolapple:~$sudo usb_modeswitch -W

在/dev/目录下会出现 ttyUSB0,ttyUSB1,ttyUSB2

在网络管理器里会出现移动宽带连接

Posted by 陈着 May 09, 2010 06:10:40 AM


用Xlib库进行基本图形编程(4)

8、创建一个简单的窗口-我们的“hello world”程序
在我们获得了一些有关我们的屏幕的基本信息之后,我们可以开始创建我们第一个窗口。Xl
ib提供数个函数来创建新窗口,其中的一个是XCreateSimpleWindow()。这个函数或者少量
几个决定窗口的大小和位置等的参数。这有一个这些参数的完整列表:

Display* display
    指向display结构的指针
Window parent
    窗口的父窗口
int x
    窗口左上角的点的x坐标
int y
    窗口左上角的点的x坐标
unsigned int width
    窗口的宽度
unsigned int height
    窗口的宽度
unsigned int border_width
    窗口边框的宽度
unsigned long border
    窗口边框的颜色
unsigned long background
    窗口的背景
让我们创建一个简单窗口,它的宽是屏幕宽的1/3,高是屏幕高的1/3,背景色是白的,边框
颜色是黑色的,而且边框宽2象素。窗口将会放置在屏幕的左上角。
/* this variable will store the ID of the newly created window. */
Window win;
/* these variables will store the window's width and height. */
int win_width;
int win_height;
/* these variables will store the window's location. */
int win_x;
int win_y;
/* calculate the window's width and height. */
win_width = DisplayWidth(display, screen_num) / 3;
win_height = DisplayHeight(display, screen_num) / 3;
/* position of the window is top-left corner - 0,0. */
win_x = win_y = 0;
/* create the window, as specified earlier. */
win = XCreateSimpleWindow(display,
                          RootWindow(display, screen_num),
                          win_x, win_y,
                          win_width, win_height,
              win_border_width, BlackPixel(display, screen_num),
                          WhitePixel(display, screen_num));
我们创造了窗口的事实并不意味着它会被画在屏幕上。缺省的,新创建的窗口不会被映射于
屏幕之上 - 它们是不可见的。为了使得我们的窗口可见,我们使用XMapWindow()函数,如
下:
XMapWindow(win);
要看我们至今积累写出的所有代码,看看simple-window.c程序。你将看到至今没有解释的
两个另外的函数 - XFlush()和XSync()函数用来清除仍未发送给X服务器的请求 - 很想用
来清除标准输出的fflush()函数。XSync()函数也清除所有仍未发送给X服务器的消息,而且
等待X服务器结束处理所有这些请求。在一个通常的程序中,这将不会是必要的(你可以看到
为什么在我们开始写一个普通的X程序的时候),但对于现在我们把它放在那儿。尝试着有和
去掉这些函数调用来编译程序,以观察它们行为上的不同点。
9.在窗口中绘图
在窗口中绘图能够通过使用各种图形函数来完成 - 画点,线,圆,矩形,等。为了能够在
窗口中绘图,我们首先需要定义几种通用的绘图参数 - 线宽使用多少的,绘图的颜色是什
么,等。这个是用图形上下文(GC)来完成的。
分配图形上下文(GC)

如我所说,图形上下文给出几个用于绘图函数的属性。因此,我们定义一个图形上下文。我
们能够在一个窗口中使用多余一个的图形上下文,以达到用多种风格(不同的颜色,线宽,
等)绘图。分配一个新的GC是通过使用XCreateGC()函数来完成的,如下(在这个代码片段中
,我们假定“display”是一个只想Display结构的指针,而起“win”是先前创建的窗口的I
D):
/* this variable will contain the handle to the returned graphics context. */
GC gc;
/* these variables are used to specify various attributes for the GC. */
/* initial values for the GC. */
XGCValues values = CapButt | JoinBevel;
/* which values in 'values' to check when creating the GC. */
unsigned long valuemask = GCCapStyle | GCJoinStyle;
/* create a new graphical context. */
gc = XCreateGC(display, win, valuemask, &values);
if (gc < 0) {
    fprintf(stderr, "XCreateGC: \n");
}
注意“valuesmask”和“values”的角色。因为图形上下文有n多属性,并且我们不想定义 它们中的一些,我们需要能够告诉XCreateGC()哪些属性是我们想要设置的。这就是“value smask”变量的用处。我们然后使用“values”变量来指定我们在“valuesmask”中定义的 属性的值。因而,对于每个在“values”中使用的常量,我们将使用在“valuesmask”中相 应的常量。在此例中,我们用两个属性定义图形上下文:    1、当在画多部分的线的时候,线应该以‘Bevelian’风格连接起来。    2、线的终点将被直的画出来(与以圆角结束线相对,如果它的宽度大于一个象素)。 这个GC的剩余属性将由它们的缺省值设定。 一旦我们创建了一个图形上下文,我们能够在绘图函数中使用它。我们还能够各种函数修改 它的参数。这儿有几个例子:
/* change the foreground color of this GC to white. */
XSetForeground(display, gc, WhitePixel(display, screen_num));
/* change the background color of this GC to black. */
XSetBackground(display, gc, BlackPixel(display, screen_num));
/* change the fill style of this GC to 'solid'. */
XSetFillStyle(display, gc, FillSolid);
/* change the line drawing attributes of this GC to the given values. */
/* the parameters are: Display structure, GC, line width (in pixels), */
/* line drawing  style, cap (line's end) drawing style, and lines     */
/* join style.                                                        */
XSetLineAttributes(display, gc, 2, LineSolid, CapRound, JoinRound);
要获关于在图形上下文中有的各种属性的完整信息,参考XCreateGC()的手册页。我们将在 我们的教程中仅仅使用几个简单的属性,以避搞得过度复杂了。 基本绘图-点,线,框,圆... 在我们创建了GC之后,我们能够使用这个GC在窗口上用一套Xlib函数绘画了,这些函数合成 为“基本绘图函数”。废话不多说了,让我们看看它们是如何使用的吧。我们假定”gc“是 先前初始化了的GC,而且‘win’包含了先前创建的窗口的句柄。
/* draw a pixel at position '5,60' (line 5, column 60) of the given window. */
XDrawPoint(display, win, gc, 5, 5);
/* draw a line between point '20,20' and point '40,100' of the window. */
XDrawLine(display, win, gc, 20, 20, 40, 100);
/* draw an arc whose center is at position 'x,y', its width (if it was a     */
/* full ellipse) is 'w', and height is 'h'. Start the arc at angle 'angle1'  */
/* (angle 0 is the hour '3' on a clock, and positive numbers go              */
/* counter-clockwise. the angles are in units of 1/64 of a degree (so 360*64 */
/* is 360 degrees).                                                          */
int x = 30, y = 40;
int h = 1, w = 45;
int angle1 = 0, angle2 = 2.109;
XDrawArc(display, win, gc, x-(w/2), y-(h/2), w, h, angle1, angle2);
/* now use the XDrawArc() function to draw a circle whose diameter */
/* is 15 pixels, and whose center is at location '50,100'.         */
XDrawArc(display, win, gc, 50-(15/2), 100-(15/2), 15, 15, 0, 360*64);
/* the XDrawLines() function draws a set of consecutive lines, whose     */
/* edges are given in an array of XPoint structures.                     */
/* The following block will draw a triangle. We use a block here, since  */
/* the C language allows defining new variables only in the beginning of */
/* a block.                                                              */
  {
    /* this array contains the pixels to be used as the line's end-points. */
    XPoint points[] = {
      {0, 0},
      {15, 15},
      {0, 15},
      {0, 0}
    };
    /* and this is the number of pixels in the array. The number of drawn */
    /* lines will be 'npoints - 1'.                                       */
    int npoints = sizeof(points)/sizeof(XPoint);
    /* draw a small triangle at the top-left corner of the window. */
    /* the triangle is made of a set of consecutive lines, whose   */
    /* end-point pixels are specified in the 'points' array.       */
    XDrawLines(display, win, gc, points, npoints, CoordModeOrigin);
  }
/* draw a rectangle whose top-left corner is at '120,150', its width is */
/* 50 pixels, and height is 60 pixels.                                  */
XDrawRectangle(display, win, gc, 120, 150, 50, 60);
/* draw a filled rectangle of the same size as above, to the left of the  */
/* previous rectangle. note that this rectangle is one pixel smaller than */
/* the previous line, since 'XFillRectangle()' assumes it is filling up   */
/* an already drawn rectangle. This may be used to draw a rectangle using */
/* one color, and later to fill it using another color.                   */
XFillRectangle(display, win, gc, 60, 150, 50, 60);
但愿你跟上了我的进度。我们还将提到更多的一些使用上差不多的函数。例如,XFillArc( )和XDrawArc()带有相同的参数,但是只画出弧的内部(像XFillRectangle()函数所作的和用 XDrawRectangle()函数画出的矩形一样)。还有一个填充多边形内部的XFillPolygon()函数 。它和XDrawLines()基本上有相同的参数。然而,如果数组的最后一个点和第一个点处于不 同的位置,XFillPolygon()函数自动添加一条”virtual“线,连接这两个点。两个函数的 另外一个不同点就是XFillPolygon()带另外一个参数,shape。它用来帮助X服务器优化它的 行为。你能够在手册页上学到这些。对于这些函数还有复数版本,名字为XFillArcs()和XFi llRectangles()。 完成这些绘画的程序的源代码位于文件simple-drawing.c中。
10.X 事件

在Xlib程序中,所有的事情都是被事件驱动的。事件绘图有时是对事件-一个”暴露的“事
件-的反应。如果程序窗口被隐藏的一部分重又暴露了(例如窗口从另外一个窗口后面升上
来了),X服务器将发送一个”暴露的“事件让程序知道它应当重新画处窗口的这个部分。用
户输入(按键,鼠标移动,等)也是作为一套事件被接收的。

1>.使用事件遮罩给事件型别注册

在程序创建了一个窗口(或者几个窗口)之后,它应当告诉X服务器它想让这个窗口接收什么
型别的事件。缺省的,没有事件发送给程序。它可能注册各种鼠标(也被称为”指针“)事件
,键盘事件,暴露事件等等。这是用于优化服务器和客户之间的连接(也就是,为什么要发
送给程序(那可能是运行于地球的另外一边的)它不感兴趣的事件的?)。
在Xlib中,我们使用XSelectInput()函数来注册事件。这个函数接收3个参数 - display结
构,窗口的ID,以及它想要收到的事件型别的遮罩。窗口ID这个参数使得我们能够为不同的
窗口注册接收不同型别的事件。这儿是我们如何给ID为‘win’的窗口注册”暴露”事件的
:
XSelectInput(display, win, ExposureMask);
ExposureMask 是定义在头文件“X.h”中的常量。如果我们想要注册好几种事件型别,我们
用逻辑或进行连接,如下:
XSelectInput(display, win, ExposureMask | ButtonPressMask);
遮注册了“暴露”事件以及鼠标按钮在给定窗口按下的事件。你应当注意到一个遮罩有可能 代表了好几种事件子型别。 注意:一个常见的蹩脚程序员所作的是在它们的程序中添加代码来处理新的事件型别,而忘 记了在调用XSelectInput()中添加这些事件的遮罩。这样的程序员然后坐下来花数个小时调 试它们的程序,奇怪于“为什么我的程序没注意到我释放鼠标??”,最后只是发现它们忘 记了只注册鼠标按下事件,而不是鼠标释放事件。 2>.接收事件-撰写事件循环
在我们为感兴趣的事件型别注册了之后,我们需要进入接收事件和处理它们的循环。有好几
种撰写这样的循环的办法,但基本的循环是这样的:
/* this structure will contain the event's data, once received. */
XEvent an_event;
/* enter an "endless" loop of handling events. */
while (1) {
    XNextEvent(display, &an_event);
    switch (an_event.type)
      case Expose:
        /* handle this event type... */
        .
        .
        break;
      default: /* unknown event type - ignore it. */
        break;
    }
}
XNextEvent()函数取得从X服务器发送来的下一个事件。如果没有事件在等待,它阻塞在那 知道接收到了一个。当它返回了,事件的数据被放置给函数的第二个参数XEvent变量中。之 后,变量的“type”域指定了我们得到的事件的型别。事件的型别是Expose告诉我们窗口的 一部分布需要重画。在我们处理了事件之后,我们回过头来继续等待下一个要处理的。明显 ,我们需要给用户某种终止程序的途径。如我们即将看到的,这通常是通过处理“quit”事 件来完成那个的。 3>.暴露事件
“暴露”事件是程序可能接收的最基本的事件中的一个。它在一下情况中将发送给我们:
    * 覆盖我们一部分窗口的窗口被移开了,暴露我们窗口的部分(或者全部)。
    * 我们的窗口从其他窗口后面升上来了
    * 我们的窗口第一次映射
    * 我们的窗口被取消标识了。
你应当注意背后隐藏的假设 - 我们窗口的内容在被其他窗口遮盖时候丢失了。你可能奇怪
X服务器为什么不保存这些内容。答案是 - 为了节省内存。毕竟,窗口在display上的数量
在给定时间是非常巨大的,而且保存它们的所有内容可能需要很多的内存(例如,大小为400
*400象素的256色位图占据160KB的内存存储。现在想想20个窗口,比这个数字要大得多)。
事实上,在特殊情况下有告诉X服务器保存窗口内容的办法,我们将在后面看到。

当我们得到一个“暴露”事件的时候,我们应当从XEvent结构的"xexpose“成员处取出事件
的数据(在我们的代码例子中用”an_event.xexpose“引用它)。它包含几个有趣的域:
count
    在服务器中的事件队列等待的其他暴露事件的数量。这个可能在我们一次接连得到好几
个的时候有用 - 我们通常将避免重画知道我们得到它们的最后一个(也就是知道count为0
的时候)。
Window window
    暴露事件被发送的窗口的ID(如果我们的程序在几个窗口中注册事件)。
int x, y
    需要重画的窗口区域的从窗口左上角开始的x和y坐标(象素为单位)。
int width, height
    需要重画的窗口区域的宽和高(象素为单位)。
在我们的演示程序中,我们将倾向于忽略提供的区域,而仅仅重画整个屏幕。然而,这是非
常没有效率的,而且我们将尝试在后面演示一些仅仅画出相关屏幕部分的技术。

作为例子,这是我们将如何横跨我们的窗口画一条线,每当我们接收到”暴露“事件的时候
。假设这个’case‘是事件循环switch语句的一部分。

  case Expose:
    /* if we have several other expose events waiting, don't redraw. */
    /* we will do the redrawing when we receive the last of them.    */
    if (an_event.xexpose.count > 0)
        break;
    /* ok, now draw the line... */
    XDrawLine(display, win, gc, 0, 100, 400, 100);
    break;
4>.获得用户输入 传统上用户输入有两个来源 - 鼠标和键盘。存在多种事件型别来通知我们用户的输入 -  键盘上的按键被按下,在键盘上释放按键,鼠标移动于我们的窗口之上,鼠标进入(或者离 开)我们的窗口等等。
鼠标按钮点击和释放事件
我们将要处理的第一个事件型别是在我们窗口中鼠标按钮按下(或者按键放开)事件。为了注
册这样的一个事件,我们将添加一下遮罩中一个(或者更多)来在XSelectInput()函数中指定
事件型别:
ButtonPressMask
    Notify us of any button that was pressed in one of our windows.
ButtonReleaseMask
    Notify us of any button that was released over one of our windows.

The event types to be checked for in our event-loop switch, are any of the follo
wing:

ButtonPress
    A button was pressed over one of our windows.
ButtonRelease
    A button was released over one of our windows.
这些事件型别的事件结构是通过"an_event.xbutton“来访问的,并且包括一下有趣的域:

Window window
    鼠标事件发送给的窗口ID(如果我们的程序在几个窗口中注册了事件)。
int x, y
    在点击时,鼠标指针从窗口左上角为原点的x和y坐标(象素为单位)。
int button
    被点击的鼠标按钮的编号。可能是像Button1, Button2, Button3这样的值。
Time time
    事件发生的时间(毫秒为单位)。可能用于在程序中计算”双击“的情况(例如,如果鼠
标按钮在小于给定时间内被点击两次,就认定这个为双击)。

作为例子,这儿是我们如何每当接收到”鼠标按下“事件时,当按下的是第一个鼠标按钮的
时候在鼠标点击位置画一个黑点的,而是第二个的时候擦除该点(也就是画个白点)。我们假
定存在两个GC,gc_draw设置为前景色为黑,而gc_erase前景色为白。
假定一下'case’是事件循环的swtich语句的一部分。

  case ButtonPress:
    /* store the mouse button coordinates in 'int' variables. */
    /* also store the ID of the window on which the mouse was */
    /* pressed.                                               */
    x = an_event.xbutton.x;
    y = an_event.xbutton.y;
    the_win = an_event.xbutton.window;

    /* check which mouse button was pressed, and act accordingly. */
    switch (an_event.xbutton.button) {
        case Button1:
            /* draw a pixel at the mouse position. */
            XDrawPoint(display, the_win, gc_draw, x, y);
            break;
        case Button2:
            /* erase a pixel at the mouse position. */
            XDrawPoint(display, the_win, gc_erase, x, y);
            break;
        default: /* probably 3rd button - just ignore this event. */
            break;
    }
    break;

鼠标移动事件

与鼠标按下和释放事件类似,我们也可以得到各种鼠标移动事件的通知。这些能够划分为两
类。一类是按钮没有被按下时鼠标指针的移动,而第二类时当一个或者多个按钮被按下的时
候鼠标指针的移动(这有时被称为“鼠标托放操作”,或者仅仅“拖放”)。下面的事件遮罩
可以加到XSelectInput()的调用中以让我们的应用程序得到这些事件的通知。

指针移动遮罩
    当没有鼠标按钮被按下时,由程序控制的窗口中的一个的指针移动的事件
按钮移动遮罩
    当鼠标的一个(或者更多)的鼠标按钮被按下的时候指针移动的事件。
按钮1移动遮罩
    核按钮移动遮罩一样,只不过当第一个鼠标按钮被按下的时候。
鼠标2移动遮罩,鼠标3移动遮罩,鼠标4移动遮罩,鼠标5移动遮罩
    类似的,用于第二个鼠标按钮,或者第三,第四,第五。
在我们的事件循环swtich语句中要检查的事件型别,是以下的任何一个:

移动通知
    在我们需要得到这个消息通知的窗口中移动的鼠标指针。

这些事件型别的事件结构是以”an_event.xbutton“来访问的,并且包含一下有趣的域:

Window window
    鼠标移动事件发送给的窗口的ID(如果我们的应用程序给几个窗口注册了事件)。
int x, y
    事件发生的时候,以窗口的左上角为原点鼠标指针所位于的x和y坐标(象素为单位)。
unsigned int state
    按钮(或者按键)在事件发生时按下的遮罩 - 如果有的话。改域是以下值的位或:
  # Button1Mask
  # Button2Mask
  # Button3Mask
  # Button4Mask
  # Button5Mask
  # ShiftMask
  # LockMask
  # ControlMask
  # Mod1Mask
  # Mod2Mask
  # Mod3Mask
  # Mod4Mask
  # Mod5Mask
    它们的名字是可以自明的,前五个是指被按下的鼠标按钮,而剩下的指的是被按下的“
特殊按键”(Mod1通常是‘ALT’或者‘META’键)。
Time time
    事件发生所处的事件(毫秒为单位)。
作为例子,以下的代码处理一个绘图程序的“绘图模式”,也就是说如果用户在鼠标1键按
下的时候移动了,那么我们在屏幕上“绘图”。注意代码有一个惯性:因为鼠标移动可能产
生许多事件,可能我们不会在每个鼠标移到的点都得到鼠标移动事件。我们的程序应当能够
处理这么一个情况。解决的一个办法可能是记住鼠标托过的上一个点,并在和新的鼠标指针
位置之间画直线。假定下面的‘case’是事件循环的switch语句的一部分。


  case MotionNotify:
    /* store the mouse button coordinates in 'int' variables. */
    /* also store the ID of the window on which the mouse was */
    /* pressed.                                               */
    x = an_event.xmotion.x;
    y = an_event.xmotion.y;
    the_win = an_event.xbutton.window;

    /* if the 1st mouse button was held during this event, draw a pixel */
    /* at the mouse pointer location.                                   */
    if (an_event.xmotion.state & Button1Mask) {
        /* draw a pixel at the mouse position. */
        XDrawPoint(display, the_win, gc_draw, x, y);
    }
    break;

鼠标指针进入和离开事件

另一个应用程序可能感兴趣的事件型别,是鼠标指针进入或者离开程序控制的窗口。一些程
序使用这些事件来向用户展示应用程序现在在焦点状态。为了注册这么一个事件型别,我们
将把下面的一个(或者多个)遮罩添加到我们给XSelectInput()函数指定的事件型别中:

EnterWindowMask
    当鼠标指针进入我们控制的任何窗口时通知我们。
LeaveWindowMask
    当鼠标指针离开我们控制的任何窗口时通知我们。
要在我们的事件循环swtich中检查的事件型别是以下的这些:

EnterNotify
    鼠标指针刚刚进入了我们控制的窗口
LeaveNotify
    鼠标指针刚刚离开了我们控制的窗口

这些事件型别的事件结构是通过“an_event.xcrossing"来访问的,并且包含以下有趣的域
:

Window window
    鼠标事件发送给的窗口的ID(如果我们的程序给几个程序注册了事件)。
Window subwindow
    鼠标进入到我们的窗口(在EnterNotify事件中),或者鼠标指针移出到的子窗口ID(在Le
aveNotify事件中),或者两者均否,如果鼠标从我们的窗口外边移入。
int x, y
    事件产生的时候,鼠标指针以窗口的左上角为原点的x和y坐标(象素为单位)。
int mode
    鼠标指针点击的编号。可能是如Button1, Button2, Button3这样的值。
Time time
    事件发生的时间(毫秒为单位)。可能用于在程序中计算”双击“的情况(例如,如果鼠
标按钮在小于给定时间内被点击两次,就认定这个为双击)。
unsigned int state
    A mask of the buttons (or keys) held down during this event - if any. This f
ield is a bitwise OR of any of the following:
  # Button1Mask
  # Button2Mask
  # Button3Mask
  # Button4Mask
  # Button5Mask
  # ShiftMask
  # LockMask
  # ControlMask
  # Mod1Mask
  # Mod2Mask
  # Mod3Mask
  # Mod4Mask
  # Mod5Mask
    Their names are self explanatory, where the first 5 refer to mouse buttons t
hat are being pressed, while the rest refer to various "special keys" that are b
eing pressed (Mod1 is usually the 'ALT' key or the 'META' key).
Bool focus
    如果窗口拥有键盘焦点,设置为真。否则反之。

键盘焦点
屏幕上有许多窗口,但是仅仅有一个键盘和他附着。X服务器如何知道键盘输入是发送给哪
个窗口的呢?这个是通过键盘焦点来完成的。在给定的时间,屏幕上只有一个窗口能够有键
盘焦点。存在Xlib函数来使得程序给某个窗口设置键盘焦点。用户通常能够使用窗口管理器
来安排键盘焦点(通常是通过点击所需窗口的标题栏)。一旦我们的窗口拥有了键盘焦点,每
个键的按下和放开都将导致事件发送给我们的程序(如果它注册了这些事件类型...)。

键盘按下和释放事件

如果由我们程序控制的窗口当前保有键盘焦点,它能够接收键的按下和释放事件。为了注册
这些事件,下面的遮罩要加到XSelectInput()的调用中去:

KeyPressMask
    Notify our program when a key was pressed while any of its controlled window
s had the keyboard focus.
KeyPressMask
    Notify our program when a key was released while any of its controlled windo
ws had the keyboard focus.

The event types to be checked for in our event-loop switch, are any of the follo
wing:

KeyPress
    A key was just pressed on the keyboard while any of our windows had the keyb
oard focus.
KeyRelease
    A key was just released on the keyboard while any of our windows had the key
board focus.

The event structure for these event types is accessed as "an_event.xkey", and co
ntains the following interesting fields:

Window window
    The ID of the window this button event was sent for (in case our application
 registered for events on several windows).
unsigned int keycode
    The code of the key that was pressed (or released). This is some internal X
code, that should be translated into a key symbol, as will be explained below.
int x, y
    The x and y coordinates (in pixels) from the top-left of the window, of the
mouse pointer, when the event was generated.
Time time
    time (in millisecond) the event took place in. May be used to calculate "dou
ble-click" situations by an application (e.g. if the mouse button was clicked tw
o times in a duration shorter than a given amount, assume this was a double-clic
k).
unsigned int state
    A mask of the buttons (or modifier keys) held down during this event - if an
y. This field is a bitwise OR of any of the following:
  # Button1Mask
  # Button2Mask
  # Button3Mask
  # Button4Mask
  # Button5Mask
  # ShiftMask
  # LockMask
  # ControlMask
  # Mod1Mask
  # Mod2Mask
  # Mod3Mask
  # Mod4Mask
  # Mod5Mask
    Their names are self explanatory, where the first 5 refer to mouse buttons t
hat are being pressed, while the rest refer to various "special keys" that are b
eing pressed (Mod1 is usually the 'ALT' key or the 'META' key).

如果我们提到过的,按键代码于其本身是相当没有意义的,并且是受到了附着于运行X服务
器的机器的键盘设备的影响。为了真正使用这些代码,我们要把他们翻译为按键符号,它们
是标准的。我们能用XKeycodeToKeysym()函数来完成翻译工作。这个函数需要3个参数:只
想display的指针,要翻译的按键代码,和一个索引(我们将用‘0’来作这个参数)。标准Xl
ib案件代码可以在”X11/keysymdef.h“中找到。作为一个使用键按下事件和XKeycodeToKey
sym函数的例子,我们将展示如何处理这种的按键:按下‘1’将导致鼠标当前所在的点涂黑
。按下DEL键将导致点的擦除(使用‘gc_erase’GC)。按下任何字母(a到z,大写或者小写)
将使得他们显示在标准输出上。任何其他键的按下都被忽略。假定下面的‘case’是事件循
环的switch语句的一部分。

  case KeyPress:
    /* store the mouse button coordinates in 'int' variables. */
    /* also store the ID of the window on which the mouse was */
    /* pressed.                                               */
    x = an_event.xkey.x;
    y = an_event.xkey.y;
    the_win = an_event.xkey.window;
    {
        /* translate the key code to a key symbol. */
        KeySym key_symbol = XKeycodeToKeysym(display, an_event.xkey.keycode, 0);
        switch (key_symbol) {
            case XK_1:
            case XK_KP_1: /* '1' key was pressed, either the normal '1', or */
                          /* the '1' on the keypad. draw the current pixel. */
                XDrawPoint(display, the_win, gc_draw, x, y);
                break;
            case XK_Delete: /* DEL key was pressed, erase the current pixel. */
                XDrawPoint(display, the_win, gc_erase, x, y);
                break;
            default:  /* anything else - check if it is a letter key */
                if (key_symbol >= XK_A && key_symbol <= XK_Z) {
                    int ascii_key = key_symbol - XK_A + 'A';
                    printf("Key pressed - '%c'\n", ascii_key);
                }
                if (key_symbol >= XK_a && key_symbol <= XK_z) {
                    int ascii_key = key_symbol - XK_a + 'a';
                    printf("Key pressed - '%c'\n", ascii_key);
                }
                break;
        }
    }
    break;
如你所见,按键符号以某种方法与键盘上的屋里按键对应,因而你应当谨慎以恰当检查到所
有的可能情况(如我们在上面例子中对‘1’所作的那样)。我们假定字母键有连续的符号值
,否则范围检查的技巧与按键符号到ASCII代码之间的翻译将不能正常工作。

X Events - A Complete Example
X Events - 一个完整的例子

作为一个处理事件的例子,我们将展示events.c程序。这个程序创建一个窗口,在其上作一
些绘画,然后进入事件循环。如果它得到一个暴露时间 - 它重画整个窗口。如果它得到一
个左键按下(或者移动)事件,它用白色画在鼠标指针下面的点(也就是说擦除这个点)。应当
注意这些画面的改变是怎么被处理的。仅仅用恰当的颜色画出点是不够的。我们需要注意到
颜色的改变,因而下一个暴露事件我们将能够再用合适的颜色画点。就这个目的,我们使用
一个巨大的(1000乘1000)数组代表窗口的点。初始时,数组中的所有的元素被初始化为‘0
’。当以黑色画点的时候,我们设置相应的数组元素为‘1’。当以白色画点的时候我们设
置相应的数组元素为‘-1’。我们不能仅仅把他们重新设置为‘0’,否则我们原来画在屏
幕上的东西总是会被擦除。最终,当用户按下键盘的任意键,程序退出。

当运行这个程序时,你应注意到移动事件常常漏掉点。 如果鼠标快速从一个点移到另一个
点,我们将不会在某个鼠标指针移过的点处得到移动事件。因而,如果我们故意好好处理这
些间隙,我们也许需要记住上次移动事件发生的位置,然后再那个地点和下一个移动事件的
地点之间画一条线。这就是绘画程序通常干的事情。

 

 

 

 

Posted by 陈着 Nov 16, 2009 09:02:04 AM


用Xlib库进行基本图形编程(3)

5.编译基于Xlib的程序
编译基于Xlib的需要把他们和Xlib库进行链接。这是通过使用如下的编译命令行来完成的:
cc prog.c -o prog -lX11
如果编译器抱怨它找不到X11库,尝试加上‘-L’标志,像这样:
cc prog.c -o prog -L/usr/X11/lib -lX11
或者也许是这样(对于用X11的release 6的系统):
cc prog.c -o prog -L/usr/X11R6/lib -lX11
在SunOs 4系统上,X库被放置于/usr/openwin/lib:
cc prog.c -o prog -L/usr/openwin/lib -lX11
6.打开和关闭连接到X服务器的连接
X程序首先需要打开连接到X服务器的连接。在我们完成这件工作的时候,我们需要指定运行
X服务器的机器的地址,以及display号码。X window系统能够支持全部连接于同一个机器的
好几个display。然而,通常只有一个这样的display,它的display号是‘0’。如果我们想
要连接到本地display(也就是我们客户程序所运行的机器的display),我们可以指定displa
y为’:0‘。要连接到地址为”simey“的机器的第一个display,我们能够使用地址”simey
:0“。这儿是连接是如何被打开的:
#include <X11/Xlib.h>   /* defines common Xlib functions and structs. */
.
.
/* this variable will contain the pointer to the Display structure */
/* returned when opening a connection.                             */
Display* display;

/* open the connection to the display "simey:0". */
display = XOpenDisplay("simey:0");
if (display == NULL) {
    fprintf(stderr, "Cannot connect to X server %s\n", "simey:0");
    exit (-1);
}
注意,对于X程序来说检查系统变量‘DISPLAY’是否被定义了是很常见的,而且如果是的话
,使用它的内容作为XOpenDisplay()函数的参数。
当程序完成了它的使命并且需要关闭连接到X服务器的连接的时候,它如下动作:
XCloseDisplay(display);
这将导致所有由程序创造的窗口(如果还有剩下的话)自动被服务器关闭,而且为了客户的利
益任何留在服务器上的资源-将被释放。注意这将不会导致我们的客户程序终止-我们使用
普通的exit()函数来完成。
7.检查关于Display的基本信息
一旦我们打开了一个连接到X服务器的连接,我们应当检查有关它的一些基本信息:他有什
么样的屏幕,尺寸是多少(宽和高),它支持多少颜色(黑白?灰度?256色?更多?),以及
等等。我们将展示一些作一些这样检查的代码片段,以及在使用中解释每个函数的注释。我
们假定‘display’是一个指向‘Display’的结构的指针,由前面对XOpenDisplay()的调用
返回的。
/* this variable will be used to store the "default" screen of the  */
/* X server. usually an X server has only one screen, so we're only */
/* interested in that screen.                                       */
int screen_num;

/* these variables will store the size of the screen, in pixels.    */
int screen_width;
int screen_height;

/* this variable will be used to store the ID of the root window of our */
/* screen. Each screen always has a root window that covers the whole   */
/* screen, and always exists.                                           */
Window root_window;

/* these variables will be used to store the IDs of the black and white */
/* colors of the given screen. More on this will be explained later.    */
unsigned long white_pixel;
unsigned long black_pixel;

/* check the number of the default screen for our X server. */
screen_num = DefaultScreen(display);

/* find the width of the default screen of our X server, in pixels. */
screen_width = DisplayWidth(display, screen_num);

/* find the height of the default screen of our X server, in pixels. */
screen_height = DisplayHeight(display, screen_num);

/* find the ID of the root window of the screen. */
root_window = RootWindow(display, screen_num);

/* find the value of a white pixel on this screen. */
white_pixel = WhitePixel(display, screen_num);

/* find the value of a black pixel on this screen. */
black_pixel = BlackPixel(display, screen_num);
 
有各种其他的宏来得到关于屏幕的更多信息,你可以从任何Xlib参考书中得到它们。还有和
这些宏完成相同功能的函数(例如XWhitePixel,它和WhitePixel干一样的事情)。

Posted by 陈着 Nov 06, 2009 09:53:52 AM