2009年6月10日星期三

又发现并解决两个U++的BUG

又发现并解决两个U++的BUG,一个是如果用鼠标在LineEdit构件中从一行的行头或行尾开始选择文本,然后再反选时,就会发现行首与行尾的字符无法反选。这个Bug无论在Linux还是Windows下都存在。在分析代码后发现:
void LineEdit::MouseMove(Point p, dword flags) {
if((flags & K_MOUSELEFT) && HasFocus() && HasCapture()) {
int c = GetMousePos(p);
if(c != mpos) //问题出在这,这个条件语句导至光标无法移动到两端的字符前
PlaceCaret(c, true);
}
}

将上面代码中的if语句删除就可以解决问题,在与Mirek Fidler讨论之后将最终代码改为:
void LineEdit::MouseMove(Point p, dword flags) {
if((flags & K_MOUSELEFT) && HasFocus() && HasCapture()) {
int c = GetMousePos(p);
PlaceCaret(c, mpos != c); //使用逻辑表达式mpos!=c来决定光标移动时的刷新方式,避免不必要的刷新
}
}

第二个问题严重一些,在Linux下,如果在LineEdit中使用鼠标拖动选择的文本,有时这些文本会丢失。分析源码后发现:
void LineEdit::LeftDrag(Point p, dword flags)
{
int c = GetMousePos(p);
int l, h;
if(!HasCapture() && GetSelection(l, h) && c >= l && c < h) {
WString sample = GetW(l, min(h - l, 3000));
Size sz = StdSampleSize();
ImageDraw iw(sz);
iw.DrawRect(sz, Black());
iw.Alpha().DrawRect(sz, Black());
DrawTLText(iw.Alpha(), 0, 0, 9999, sample, Courier(10), White());
NextUndo();
if(DoDragAndDrop(ClipFmtsText(), iw) == DND_MOVE) {
RemoveSelection(); //这里出现问题,实际上DoDragAndDrop已经做了相应的移除源文本工作,这里移除的实际上是拖动后的内容,导致这些内容丢失
Action();
}
}
}

更改后的代码:
void LineEdit::LeftDrag(Point p, dword flags)
{
int c = GetMousePos(p);
int l, h;
if(!HasCapture() && GetSelection(l, h) && c >= l && c < h) {
WString sample = GetW(l, min(h - l, 3000));
Size sz = StdSampleSize();
ImageDraw iw(sz);
iw.DrawRect(sz, Black());
iw.Alpha().DrawRect(sz, Black());
DrawTLText(iw.Alpha(), 0, 0, 9999, sample, Courier(10), White());
NextUndo();
if(DoDragAndDrop(ClipFmtsText(), iw) == DND_MOVE) {
Action(); //删除RemoveSelection()
}
Refresh(); //刷新窗口,以正确显示新内容
}
}

编译后工作正常。不过目前Mirek Fidler没有接受这个补丁,他认为应该去修改更底层的X11部分的代码,以更好的解决问题。或许他是对的,不过已经好几天了,Mirek Fidler还没有做出修复,我还是先用自己的吧。

2009年5月27日星期三

让U++的Theide在linux下完美支持中文

blogspot被GFW封了好多天了,有点烦,连自己的博客也访问不了。
昨天在Ubuntu上装了U++,结果发现代码编辑器对中文支持的非常差,无法正常显示中文。仔细看了一下发现是theide不支持中文字体,指望老外是指望不上了,人家不用中文。只能自己动手,阅读了ide的源码,把setup.cpp中的void FontSelectManager::Set函数中的读取字体循环改一下就可以支持中文字体了。
原函数:
void FontSelectManager::Set(DropList& _face, DropList& _height,
Option& _bold, Option& _italic, Option& _naa) {
face = &_face;
face->WhenAction = THISBACK(FaceSelect);
height = &_height;
height->WhenAction = THISBACK(Select);
bold = &_bold;
bold->WhenAction = THISBACK(Select);
italic = &_italic;
italic->WhenAction = THISBACK(Select);
naa = &_naa;
naa->WhenAction = THISBACK(Select);
face->Clear();
for(int i = 0; i < Font::GetFaceCount(); i++)
if(Font::GetFaceInfo(i) & Font::FIXEDPITCH) { //就是要改这一行,原作者为了保持代码编辑器内文本格式的整齐而不读取非固定宽度的字体,这使的支持中文字形的字体无法被IDE支持。
face->Add(i, Font::GetFaceName(i));
LLOG("Face: " << Font::GetFaceName(i));
}
face->SetIndex(0);
height->ClearList();
for(int i = 6; i < 32; i++)
height->Add(i);
FaceSelect();
}

修改后的代码:
void FontSelectManager::Set(DropList& _face, DropList& _height,
Option& _bold, Option& _italic, Option& _naa) {
face = &_face;
face->WhenAction = THISBACK(FaceSelect);
height = &_height;
height->WhenAction = THISBACK(Select);
bold = &_bold;
bold->WhenAction = THISBACK(Select);
italic = &_italic;
italic->WhenAction = THISBACK(Select);
naa = &_naa;
naa->WhenAction = THISBACK(Select);
face->Clear();
for(int i = 0; i < Font::GetFaceCount(); i++)
{ //直接将条件语句删除
face->Add(i, Font::GetFaceName(i));
LLOG("Face: " << Font::GetFaceName(i));
}
face->SetIndex(0);
height->ClearList();
for(int i = 6; i < 32; i++)
height->Add(i);
FaceSelect();
}

这样修改后IDE可以支持中文字体了,但在使用中文字体时显示的格式非常难看,字间距变的大小不等,严重影响源码的编写与阅读。这也让我明白了原作者为什么不读取非固定宽度字体的原因。不过这也难不住咱,继续改。在研究源码后,发现问题在于U++中的LineEdit构件的文字描绘部分。源码位于usr/CtrlLib/LineEdit.cpp中。只要改动一下计算字体宽度的函数中就可以了:
原函数:
Size LineEdit::GetFontSize() const {
FontInfo fi = font.Info();
return Size(fi.GetAveWidth(), fi.GetHeight());
}

更改后的函数:
Size LineEdit::GetFontSize() const {
FontInfo fi = font.Info();
//增加下面这行,用来正确处理非固定宽度字体的宽度。
int width = font.GetFaceInfo() & Font::FIXEDPITCH ? fi.GetAveWidth() : fi.GetAveWidth()/2;
return Size(width , fi.GetHeight());
}

这样修改后对中文字体的支持就一切正常了。

修改前的含中文代码效果:


第一步修改后的效果:


最终修改效果:


最终的文字效果已经与固定宽度字体的显示效果完全相同了,呵呵。问题完满解决。

2009年4月16日星期四

U++教程3:多语言支持

使用U++编程可以方便的支持多国语言,U++平台本身就带了许多国家与地区的语言包,其中就包括了中文。U++支持编译时语言支持与运行时语言支持两种方式。我们先来看看编译时语言支持。还是用我们的Hello World程序作例子,看看我们是如何让它支持中文的。首先将我们的程序代码改成下面这样,蓝色部分为新增加的:
#include <CtrlLib/CtrlLib.h>
#define TFILE <helloworld/helloworl.t> //定义编译时语言文件
#include <Core/t.h>                   //多语言支持头文件
using namespace Upp;

class HelloWindow:public TopWindow
{
 private:
 Button hellobtn;
 Button closebtn;
 public:
 void hellofn();
 void closefn();
 HelloWindow();
 typedef HelloWindow CLASSNAME;
};

void HelloWindow::hellofn()
{
 PromptOK(t_("Hello World")); //为需要翻译的文件添加t_()宏
}

void HelloWindow::closefn()
{
 Close();
}

HelloWindow::HelloWindow()
{
 Title(t_("Hello World!")).Sizeable().Zoomable();
 SetRect(0,0,300,150);
 Add(hellobtn.SetLabel(t_("Hello")).LeftPos(50,80).TopPos(50,30));
 Add(closebtn.SetLabel(t_("Close")).RightPos(50,80).TopPos(50,30));
 hellobtn<<=THISBACK(hellofn);
 closebtn<<=THISBACK(closefn);
}

GUI_APP_MAIN
{

    SetLanguage(GetSystemLNG()); //设定程序所用的语言
    HelloWindow().Run(); //为了使代码更紧凑,我们使用了匿名对象。
}

我们在源代码所在的目录下新建helloworl.t文件,此文件就为U++程序所用的编译时语言文件,内容如下:
T_("Hello World")
zhCN("世界你好")

T_("Hello World!")
zhCN("世界你好!")

T_("Hello")
zhCN("你好")

T_("Close")
zhCN("关闭")


编译后我们就得到了一个中文版的HelloWorld了。


2009年4月13日星期一

U++教程2:与U++交谈

教程1里我们已经了解了U++开发环境的基本用法,并写了一个小小的HelloWorld。
不过那真的算不上是一个程序,在这一节里,我们将让事情变的更象样一点,让我们的程序有点交互性。
U++程序与其它GUI框架一样,都需要一个主窗口来容纳各种各样的控件用来与用户进行交互。在U++中,这个主窗口是由TopWindow类来负责的,让我们来看看它是如何工作的。把教程1里的程序改成下面这样:


#include <ctrllib/ctrllib.h>
using namespace Upp;

GUI_APP_MAIN
{
TopWindow a;
a.Run();
}

编译运行一下,我们就得到了一个空白窗口:
不过这个窗口没有什么用处,它不包含任何可与用户进行交互的控件。下面我们就来看看如何让这个窗口变的生动起来。我们将在这个窗口中添加两个按钮,一个名为Hello,用来产生教程1里的Hello World对话框,一个名为Close用来退出程序。
为了向窗口添加控件,并让其可与用户交互,我们需要从TopWindow派生一个我们自己的主窗口类。类定义如下:
class HelloWindow:public TopWindow
{
private:
Button hellobtn; //建立两个按钮成员
Button closebtn;
public:
void hellofn(); //定义两个方法,将来我们要把它们与按钮联接
void closefn();
HelloWindow(); //构造函数
};

下面是HelloWindow类中成员函数的实现:
void HelloWindow::hellofn()
{
PromptOK("Hello World"); //显示对话框
}

void HelloWindow::closefn()
{
Close(); //退出程序
}

//下面是HelloWindow类的构造函数,控件的添加主要在这里进行
HelloWindow::HelloWindow()
{
Title("Hello World!").Sizeable().Zoomable(); 
SetRect(0,0,300,150);
Add(hellobtn.SetLabel("Hello").LeftPos(50,80).TopPos(50,30));
Add(closebtn.SetLabel("Close").RightPos(50,80).TopPos(50,30));
hellobtn<<=callback(this, &HelloWindow::hellofn);
closebtn<<=callback(this, &HelloWindow::closefn);
}

各种控件的添加及属性设置均在构造函数里完成,我们来详细看看这些语句的功能。
Title("Hello World!").Sizeable().Zoomable(); 
这一句用来对主程序窗口本身进行一些属性设置,在U++里这些属性的设置是可以串联进行了,因为它们在基类里的定义是返回控件本身的引用。这使得程序语句可以变得十分紧凑。在这一句里,Title用来设置主窗口的标题,Sizeable令窗口可以调整大小,Zoomable令主窗口具有最大最小化按钮。读者可以通过增减这几个属性来看看窗口的变化,以了解这些属性的实际意义。主窗口类还有许多其它的属性,这可以通过阅读U++编程手册来了解,现在我们先了解这些。

SetRect(0,0,300,150);
用来指定窗口的初始大小,要注意的是这里的窗口大小并不包括窗口的标题栏。

Add(hellobtn.SetLabel("Hello").LeftPos(50,80).TopPos(50,30));
向主窗口添加控件就通过Add方法来完成。其参数就是所要添加的控件,在这里是我们的Hello按钮。hellobtn.SetLabel("Hello")用来设置按钮上的文字,其后跟随的LeftPos与TopPos用来指定按钮在主窗口中的位置。LeftPos表示按钮与主窗口左边界的距离,TopPos则表示其与主窗口上边界的距离。这两个属性的第二个参数则是指定了按钮的大小。LeftPos(50,80)表示按钮距离主窗口左边界50个象素,按钮宽度80。TopPos(50,30)表示按钮距主窗口上边界50,按钮高度30。

hellobtn<<=callback(this, &HelloWindow::hellofn);
这一句用来指定按下按钮后要调用的函数。在u++中对事件的响应都是通过回调函数(callback)来完成的,所以在U++中 callback是一个很重要的概念,我们以后会对它进行更详细的介绍。

OK,到这里我们对主窗口的设定就基本完成了,我们只需要生成一个HelloWorld类的实例然后调用Run函数就可以了:
GUI_APP_MAIN
{
HelloWindow myapp;
myapp.Run();
}

编译运行一下试试,呵呵。我们得到了如下的界面:


点击一下按钮试试,然后再缩放一下窗口的大小看看会有什么变化。通过这些操作你会对U++如何管理界面有进一步的了解。
现在再来看一下我们的代码,噢,为按钮指定回调函数的语句显的有点太冗长了。让我们对代码进行一点调整。
首先我们在类定义中添加这条语句:
typedef HelloWindow CLASSNAME;

然后在构造函数中把指定回调函数的语句改成这样:
hellobtn<<=THISBACK(hellofn);
closebtn<<=THISBACK(closefn);

这样看上去就要简洁多了,THISBACK是u++定义的一个宏,它会自动扩展为:callback(this, &HelloWindow::hellofn)这种形式。

我们最终的代码如下:
#include <ctrllib/ctrllib.h>

using namespace Upp;

class HelloWindow:public TopWindow
{
private:
Button hellobtn;
Button closebtn;
public:
void hellofn();
void closefn();
HelloWindow();
typedef HelloWindow CLASSNAME;
};

void HelloWindow::hellofn()
{
PromptOK("Hello World");
}

void HelloWindow::closefn()
{
Close();
}

HelloWindow::HelloWindow()
{
Title("Hello World!").Sizeable().Zoomable();
SetRect(0,0,300,150);
Add(hellobtn.SetLabel("Hello").LeftPos(50,80).TopPos(50,30));
Add(closebtn.SetLabel("Close").RightPos(50,80).TopPos(50,30));
hellobtn<<=THISBACK(hellofn);
closebtn<<=THISBACK(closefn);
}

GUI_APP_MAIN
{
HelloWindow().Run(); //为了使代码更紧凑,我们使用了匿名对象。
}

OK,这篇教程就到这里了,赶快动手编译试试吧。
嗯?我听见有人说了,既然是中文教程,为啥程序界面上全是英文?呵呵,那是我为下一篇教程准备的,下一篇我们将讨论一下u++程序的国际化支持。

2009年4月11日星期六

U++教程1:第一印象

U++是一个非常完整的C++开发平台,包含了基本算法库,图形库,GUI库,数据库接口,富文本,脚本语言,图标编辑器等一系列开发工具。它有自己的集成开发环境名为theIDE,与U++框架无缝集成。theIDE是一款非常好用的IDE,具有相当完善的代码提示,自动完成,自动格式化等功能。仅这一点就远远好过其它开源的C++Framework。但它的问题在于操作方式与一般的IDE区别很大,有它自己的一套操作方式,初用会觉得有些不适应,这或许是它无法流行起来的原因。不过一旦你熟悉它就会觉得非常方便,这是我自己用下来的切身体会。好了,废话不多说,开始正题。


启动theIDE后,会出现如下对话框:
上图是theIDE的工程管理窗口,也是程序启动后的第一个界面。与一般的开发平台不同,U++的工程管理方式是基于Assembly的,你在对话框的Assembly栏里右击鼠标可得到一个右键菜单用来建立新的Assembly或是编辑、删除现有的Assembly。我为我们的教程新建了一个Assembly:

从上图中可以看出我为我们的教程Assembly取名为mytutorial。

随后我们在这个Assembly里建立我们的第一个项目,在U++中称为Package。我们先在Assembly栏里选中我们刚刚建立的mytutorial,由于我们还没有建立任何Package所以在右边的Package栏里是空白的。我们点击右下脚的New package 按钮就可以建立一个新Package了:



与所有入门教程一样,我们也来写一个Hello World! 在Package name里输入Package名称helloworld。在Template栏里选择Basic CtrlLib application。这时在右边会显示自动生成的代码模板。然后我们点击Create就会进入代码编辑界面。


我们可以看到IDE已经为我们自动生成了一些代码,我们将其改成如下代码:
#include <CtrlLib/CtrlLib.h> //U++ GUI库头文件

using namespace Upp; //使用U++名称空间

GUI_APP_MAIN //程序入口,这是一个macro,根据不同的平台
{ //会替换为相应的程序入口,比如Windows下的winmain。
PromptOK("Hello World");//这是我们添加的代码,用来显示一个提示对话框。
}

写完后按下快捷键Ctrl+F5,将会编译并运行程序。U++与一般的开发平台不同,它提供给你的库完全是源代码形式的,没有预编译的二进制文件,在你第一次编译程序时它会编译相应的库文件,所以第一次的编译时间比较长,在我的老P3 933的机器上这大概要用掉3分钟左右。不过第一次编译后再编译其它Package就不会再次编译库文件,编译速度就比较快了。
我们这第一个HelloWorld程序的运行结果如下:


呵呵,很小的一个程序,但它确实运行了。我们已经了解了U++基本的工程管理方式,知道了如何建立一个程序,一个良好的开始。在以后的章节中我们将继续去了解U++。

特立独行U++

呵呵,这个U++是一个开源的跨平台C++ RAD平台,作者从98年就开始开发,到今年持续了十一年了。U++一直都是一个非常非常小众的开发平台,几乎没什么人用,与当今火红的几个开源C++ Framewrok比如wxwidgets, QT等相比,它实在是被人冷落的可以。换做旁人早就歇菜了,在SourceForge上搜一下,能搜出一堆半路夭折的C++Framework。而U++却仍在不断的开发中,可算是特立独行,仅这一点就足以让我对作者肃然起敬了。
U++的作者是捷克人Mirek Fidler和Tomas Rylek,Mirek现在非常活跃,平台的开发及网站的维护主要是他在做。U++的用户也主要集中在东欧地区,象俄罗斯,罗马尼亚,波兰等且用户群很小。不过U++倒是提供了非常完整的国际化支持,它的语言包里囊括了很多国家及地区的语言,包括中文(简体,繁体都有)。不过中文语言包似乎很老,内容不是很完整。为此我提交了一个根据最新U++做的语言包。
我最初接触u++是在2003年,不过当时我正与人合作一个基于wxWidgets(当时还叫wxWindows)的项目,兴趣主要集中在wxWidgets上,对U++没有过多的了解。直到前几天我才又打开了U++的主页,看了几天觉得还是非常有意思的。是一个相当完整的开发平台,作者涉猎之广实在是让人佩服。钦佩之余决定为推广u++做点事,就象当初我对wxWidgets所做的那样。当初我是翻译了wxWidgets的一个教程,如今我不想仅做翻译了,因为老实说U++的文档做的不是特别好,这大概也是影响其普及程度的一个重要原因。我决定自己写一些U++的小教程,以让更多的人得以窥其一斑。
教程我会在这个BLog上慢慢的贴出,有兴趣的可随时关注我的Blog。
U++网站:http://www.ultimatepp.org

2009年3月24日星期二

机械师小工具1.0正式版发布

又一个用D语言实现的小工具,这回与本人的职业可是息息相关了,呵呵。

这个小工具实现了一些日常机械加工过程中常用到的计算工作,比如利用数控机床进行空间尺寸测量,角度测量,简单的工装设计,刀具的切削用量等。平时工作中用起来还挺方便的,呵呵,还没见到网上有与之具有相同功能的软件,拿出来与所有同行共享吧。

简单的使用说明:

快扔掉厚厚的切削手册吧!机械师小工具将帮您完成切削手册的工作,让您的工作变得轻松惬意。

机械师小工具使用D编程语言与DFL框架库开发,界面美观,体积小巧,计算准确,程序运行稳定。软件操作简单,最大限度减少用户输入。 软件取消了所有标准按钮,使用新的目标框触发方式执行用户指令。动态隐藏式实时帮助窗口随时指导用户正确使用软件的各项功能。 软件界面里,当鼠标移到一个输入框时如果鼠标指针变成手形,这表示它是一个目标框。通过点击目标框就可以执行相应的计算功能。有些目标框具有黑色边框(在XP标准主题下),这表示它是一个纯输出文本框,用户不能向其输入数据。

软件的基本操作方式为:

一、根据动态帮助里的提示,在对应输入框内输入数据。

二、输入完成后,可直接按Enter键,目标框中就会显示出计算结果。

三、也可在输入完成后,点击相应目标框得到计算结果。

四、如果用户输入错误数据,软件会给出相应提示,提醒用户。

五、如果用户没有输入足够的数据项,软件将不执行计算操作。

六、在具有多种选项的模块里,用户可以根据实际情况通过点击组合框进行选择。相应的选项有时会与相应的输入框相关联,具有互锁功能,以防止用户输入错误数据。当某个输入框为灰色时,则表示此输入框在当前模式下不可用。

七,在某些模块中,比如功丝计算模块,如果你输入的是国标当中的标准值,则在相应的数据框中会给出相对应的标准参数供你选择。反之如果没有相应数值显示,则表示你给的数值是非标的。

八、本程序中默认长度值输入单位为毫米(mm),其余则按界面上注明的输入,请用户注意。 软件从设计上力求很高的容错性,可自动判断用户输入的正确性,并作出修正,但用户仍应保证输入的准确,以避免得到错误数据造成生产上的损失。

软件截图: