利用Firefox书签快速搜索Eclipse新闻组

EMF网站上看到的窍门:在Firefox里可以为每个书签指定一个快捷词(keyword),只要在地址栏里输入这个快捷词即可跳转到该书签对应的地址。不仅如此,快捷词后面还可以跟参数,Firefox会将书签地址里的“%s”替换为这个参数。例如,新建一个书签,地址为 http://www.google.com/search?hl=zh-CN&q=%s ,快捷词为“google”,则可以在地址栏里输入google eclipse plugin来搜索Eclipse插件。

Eclipse提供了Web界面的搜索功能,所以我们也可以利用Firefox的这个功能简化在某个新闻组里的搜索(新闻组下拉列表太长了),例如搜索EMF新闻组的Firefox书签地址就应该是这个链接 ,我们不妨将快捷词指定为“emf”,这样一来,遇到EMF的问题只要在Firefox地址栏输入“emf xxx”即可,其他新闻组的定制也类似。

file

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2006/08/10/473734.html

EMF介绍系列(八、模型的验证)

在ecore模型里可以详细的定义各种类型、属性和方法,但对于像“每个类别里至少有两种产品”这样的限制就无能为力了。为此,EMF提供了一套验证框架(Validator Framework)用于解决这个问题,在ecore文件里特定的方法可以被识别为验证方法并生成用于验证的代码。

还是以shop模型为例,假设要求“每个类别里至少有两种产品”,我们需要在shop.ecore里添加一个名为validateProductsCount的验证方法,如图1所示。验证方法的返回类型是要具有两个参数:第一个是EDiagnosticChain类型,第二个是EMap类型,参数的名称没有特别要求,但这两个参数的顺序不能交换。

file
图1 新增的验证方法

接下来,通过shop.genmodel重新生成一遍代码(如果还没有shop.genmodel文件,通过New -> EMF Model创建一个),注意没有必要reload这个genmodel文件。通过比较添加验证方法前后的代码,可以发现EMF在util包里多生成了一个名为ShopValidator.java的文件;同时,在CategoryImpl的validateProductsCount()方法里的代码如下所示:

/**
 * <!-- begin-user-doc -->
 * <!-- end-user-doc -->
 * @generated
 */
public boolean validateProductsCount(DiagnosticChain diagnostics, Map contex) {
    // TODO: implement this method
    // -> specify the condition that violates the invariant
    // -> verify the details of the diagnostic, including severity and message
    // Ensure that you remove @generated or mark it @generated NOT
    if (false) {
        if (diagnostics != null) {
            diagnostics.add
                (new BasicDiagnostic
                    (Diagnostic.ERROR,
                     ShopValidator.DIAGNOSTIC_SOURCE,
                     ShopValidator.CATEGORY__VALIDATE_PRODUCTS_COUNT,
                     EcorePlugin.INSTANCE.getString("_UI_GenericInvariant_diagnostic", new Object[] { "validateProductsCount", EObjectValidator.getObjectLabel(this, contex) }),
                     new Object [] { this }));
        }
        return false;
    }
    return true;
}

有别于普通方法的实现(简单的抛出一个UnsupportedOperationException异常)。由于这段代码是被if(false){...}包围的,所以如果不进行定制,则里面的内容永远不会被执行,被包围代码的功能是将验证错误记录下来以便统一报告。现在,我们要做的就是修改这个条件,来告诉EMF什么时候运行里面的代码,如下所示:

/**
 * <!-- begin-user-doc -->
 * <!-- end-user-doc -->
 * @generated NOT
 */
public boolean validateProductsCount(DiagnosticChain diagnostics, Map contex) {
    // 我们修改了验证条件
    if (getProducts().size() < 2) {
        if (diagnostics != null) {
            diagnostics.add
                (new BasicDiagnostic
                    (Diagnostic.ERROR,
                     ShopValidator.DIAGNOSTIC_SOURCE,
                     ShopValidator.CATEGORY__VALIDATE_PRODUCTS_COUNT,
                     EcorePlugin.INSTANCE.getString("_UI_GenericInvariant_diagnostic", new Object[] { "validateProductsCount", EObjectValidator.getObjectLabel(this, contex) }),
                     new Object [] { this }));
        }
        return false;
    }
    return true;
}

因为我们要实现的验证条件很简单,所以代码的改动也很少,还是注意不要忘记修改注释中的@generated标记。现在可以运行我们的shop编辑器了,在一个只包含一种产品(Product)的类别(Category)上按右键,选择弹出菜单里的“Validate”命令,就会得到如图2所示的提示信息。

file
图2 模型未通过验证

通过修改代码的方式表达模型的限制条件是很直观,不过当条件又多又不确定的时候,直接在ecore文件里集中的表达这些条件也许更方便管理,而且Java代码甚至不需要重新生成和编译。Eclipse网站上的这篇文章“Implementing Model Integrity in EMF with EMFT OCL”通过自定义JET模板实现了这个功能,有兴趣的朋友不妨试试。

下载本文工程

补充另一种让EMF生成Validate代码的方法:在ecore模型里需要验证的EClass下建立一个EAnnotation,其source属性为“http://www.eclipse.org/emf/2002/Ecore”;然后在这个EAnnotation下建立key为“constraints”的Details Entry,value属性指定为想要的constraint名字,如果要定义多个constraint,则每个名字之间用空格分隔(格式见EcoreUtil#setConstraints()),如图3所示。这样,EMF就会在util包里生成XXXValidator.java文件,以及相应的验证方法,这些方法的代码和上面第一段嗲吗是类似的,同样需要自己修改if语句里的条件。

file
图3 在ecore模型里添加EAnnotation以生成验证代码

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2006/08/09/472607.html

得到相对Plugin的路径

在Aimd同学的代码里看到的,得到相对一个Plugin路径的方法,赶紧记下来:

FileLocator.toFileURL(Platform.getBundle("plugin.id").getEntry("/images")).getFile();

注意:需要Eclipse 3.2版本

Update(2007/01/19): 这里还有其他一些路径的获得方法:http://www.blogjava.net/hopeshared/archive/2005/12/20/24798.html

Update(2008/11/20): 从IResource得到java.io.File的方法:IResource#getLocation().toFile()

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2006/07/19/454980.html

GEF常见问题6:使用对话框

除了利用Eclipse提供的属性视图以外,GEF应用程序里当然也可以通过弹出对话框修改模型信息。

要实现双击一个节点打开对话框,在NodePart里要增加的代码如下:

public void performRequest(Request req) {
    if(req.getType().equals(RequestConstants.REQ_OPEN)){
        MessageDialog.openInformation(getViewer().getControl().getShell(),"Gef Practice","A Dialog");
    }
}

作为例子,上面这段代码只打开一个显示信息的对话框,你可以替换成自己实现的对话框显示/修改节点信息。

在CreateNodeCommand里增加下面的代码,可以在每次创建一个节点时通过对话框指定节点的名称:

public void execute() {
    InputDialog dlg = new InputDialog(shell, "Gef Practice", "New node's name:", "Node", null);
    if (Window.OK == dlg.open()) {
        this.node.setName(dlg.getValue());
    }
    this.diagram.addNode(this.node);
}

因为打开对话框时需要用到Shell,所以要在CreateNodeCommand里增加一个Shell类型的成员变量,并在DiagramLayoutEditPolicy里创建CreateNodeCommand时把一个shell实例传递给它。

file
创建节点时先弹出对话框

代码下载

点此下载工程,此工程修改自GEF应用实例中的GefPractice,目标文件的扩展名改为.gefpracticedlg。

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2006/07/07/445603.html

GEF常见问题5:自动布局

利用自动布局功能,我们可以把本来不包含图形信息的文件以图形化的方式展示出来,典型的例子比如将一组Java接口反向工程为类图,那么图中每个图元的坐标应该必须都是自动生成的。GEF里提供了DirectedGraphLayout类用来实现自动布局功能,下面介绍一下怎样在程序里使用它。

DirectedGraphLayout提供的visit()方法接受一个org.eclipse.draw2d.graph.DirectedGraph实例,它遍历这个有向图的所有节点和边,并按照它自己的算法计算出每个节点布局后的新位置。所以在使用它布局画布上的图元分为两个步骤:1、构造有向图,2、将布局信息应用到图元。

还是以gefpractice为基础,我们在主工具条上增加了一个自动布局按钮,当用户按下它时自动布局编辑器里的图形,再次按下时恢复以前的布局。为了完成步骤1,我们要在DiagramPart里添加以下两个方法:

/**
 * 将图元(NodePart)转换为节点(Node)到有向图
 * @param graph
 * @param map
 */
public void contributeNodesToGraph(DirectedGraph graph, Map map) {
    for (int i = 0; i < getChildren().size(); i++) {
        NodePart node = (NodePart)getChildren().get(i);
        org.eclipse.draw2d.graph.Node n = new org.eclipse.draw2d.graph.Node(node);
        n.width = node.getFigure().getPreferredSize().width;
        n.height = node.getFigure().getPreferredSize().height;
        n.setPadding(new Insets(10,8,10,12));
        map.put(node, n);
        graph.nodes.add(n);
    }
}

/**
 * 将连接(ConnectionPart)转换为边(Edge)添加到有向图
 * @param graph
 * @param map
 */
public void contributeEdgesToGraph(DirectedGraph graph, Map map) {
    for (int i = 0; i < getChildren().size(); i++) {
        NodePart node = (NodePart)children.get(i);
        List outgoing = node.getSourceConnections();
        for (int j = 0; j < outgoing.size(); j++) {
            ConnectionPart conn = (ConnectionPart)outgoing.get(j);
            Node source = (Node)map.get(conn.getSource());
            Node target = (Node)map.get(conn.getTarget());
            Edge e = new Edge(conn, source, target);
            e.weight = 2;
            graph.edges.add(e);
            map.put(conn, e);
        }
    }
}

要实现步骤2,在DiagramPart里添加下面这个方法:

/**
 * 利用布局后的有向图里节点的位置信息重新定位画布上的图元
 * @param graph
 * @param map
 */
protected void applyGraphResults(DirectedGraph graph, Map map) {
    for (int i = 0; i < getChildren().size(); i++) {
        NodePart node = (NodePart)getChildren().get(i);
        Node n = (Node)map.get(node);
        node.getFigure().setBounds(new Rectangle(n.x, n.y, n.width, n.height));
    }
}

为了以最少的代码说明问题,上面的方法里只是简单的移动了图形,而没有改变模型里Node的属性值,在大多情况下这里使用一个CompoundCommand对模型进行修改更为合适,这样用户不仅可以撤消(Undo)这个自动布局操作,还可以在重新打开文件时看到关闭前的样子。注意,DirectedGraphLayout是不保证每次布局都得到完全相同的结果的。

因为Draw2D里是用LayoutManager管理布局的,而DirectedGraphLayout只是对布局算法的一个包装,所以我们还要创建一个布局类。GraphLayoutManager调用我们在上面已经添加的那几个方法生成有向图(partsToNodes变量维护了编辑器图元到有向图图元的映射),利用DirectedGraphLayout对这个有向图布局,再把结果应用到编辑器里图元。如下所示:

class GraphLayoutManager extends AbstractLayout {

    private DiagramPart diagram;

    GraphLayoutManager(DiagramPart diagram) {
        this.diagram = diagram;
    }

    protected Dimension calculatePreferredSize(IFigure container, int wHint, int hHint) {
        container.validate();
        List children = container.getChildren();
        Rectangle result = new Rectangle().setLocation(container.getClientArea().getLocation());
        for (int i = 0; i < children.size(); i++)
            result.union(((IFigure) children.get(i)).getBounds());
        result.resize(container.getInsets().getWidth(), container.getInsets().getHeight());
        return result.getSize();
    }

    public void layout(IFigure container) {
        DirectedGraph graph = new DirectedGraph();
        Map partsToNodes = new HashMap();
        diagram.contributeNodesToGraph(graph, partsToNodes);
        diagram.contributeEdgesToGraph(graph, partsToNodes);
        new DirectedGraphLayout().visit(graph);
        diagram.applyGraphResults(graph, partsToNodes);
    }

}

当用户按下自动布局按钮时,只要设置DiagramPart对应的图形的布局管理器为GraphLayoutManager就可以实现自动布局了,布局的结果如图所示。

file
自动布局的结果

最后有几点需要说明:

1、DirectedGraphLayout只能对连通的有向图进行布局,否则会产生异常graph is not fully connected,参考Eclipse.org文章Building a Database Schema Diagram Editor中使用的NodeJoiningDirectedGraphLayout可以解决这个问题;

2、如果要对具有嵌套关系的有向图自动布局,应使用SubGraphCompoundDirectedGraphLayout

3、这个版本的gefpractice在自动布局后,没有对连接线进行处理,所以可能会出现连接线穿过图元的情况,这个问题和上一个问题都可以参考GEF提供的flow例子解决。

Update(2007/4/9):如果diagram是放在ScrollPane里的,则要修改一下applyGraphResults()方法,增加container作为参数以使整个diagram能正确滚动。

protected void applyGraphResults(DirectedGraph graph, Map map, IFigure container) {
    for (Iterator iterator = this.nodeParts.iterator(); iterator.hasNext();) {
        NodePart element = (NodePart) iterator.next();
        Node n = (Node) map.get(element);
        Rectangle containerBounds=container.getBounds();
        Rectangle elementBounds=new Rectangle(n.x, n.y, n.width, n.height);
        element.getFigure().setBounds(elementBounds.translate(containerBounds.getLocation()));
    }
}

代码下载

点此下载工程,此工程修改自GEF应用实例中的GefPractice,目标文件的扩展名改为.gefpracticeal。

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2006/07/02/440896.html

GEF常见问题4:非矩形图元

现在假设要把原来GefPractice例子里的矩形图元节点换成用椭圆形表示,都需要做哪些改动呢?很显然,首先要把原来继承RectangleFigure的NodeFigure类改为继承Ellipse类:

public class NodeFigure extends Ellipse {
    ...
}

这样修改后可以看到编辑器中的图元已经变成椭圆形了。但如果用户点选一个图元,表示选中的边框(选择框)仍然是矩形的,如图1所示:

file
图1 椭圆形的节点和矩形选择框

如果觉得矩形的选择框不太协调,可以通过覆盖DiagramLayoutEditPolicy的createChildEditPolicy()方法修改。缺省情况下这个方法返回一个ResizableEditPolicy,我们要定义自己的子类(EllipseResizableEditPolicy)来替代它作为返回值。

EllipseResizableEditPolicy里需要覆盖ResizableEditPolicy的两个方法。第一个是createSelectionHandles()方法,它决定“控制柄”(ResizeHandle)和“选择框”(MoveHandle)的相关情况,我们的实现如下:

protected List createSelectionHandles() {
    List list = new ArrayList();
    //添加选择框
    //ResizableHandleKit.addMoveHandle((GraphicalEditPart) getHost(), list);
    list.add(new MoveHandle((GraphicalEditPart) getHost()) {
        protected void initialize() {
            super.initialize();
            setBorder(new LineBorder(1) {
                public void paint(IFigure figure, Graphics graphics, Insets insets) {
                    tempRect.setBounds(getPaintRectangle(figure, insets));
                    if (getWidth() % 2 == 1) {
                        tempRect.width--;
                        tempRect.height--;
                    }
                    tempRect.shrink(getWidth() / 2, getWidth() / 2);
                    graphics.setLineWidth(getWidth());
                    if (getColor() != null)
                        graphics.setForegroundColor(getColor());
                    //用椭圆形替代矩形
                    //graphics.drawRectangle(tempRect);
                    graphics.drawOval(tempRect);
                }
            });
        }
    });

    //添加控制柄
    ResizableHandleKit.addHandle((GraphicalEditPart) getHost(), list, PositionConstants.EAST);
    ResizableHandleKit.addHandle((GraphicalEditPart) getHost(), list, PositionConstants.SOUTH);
    ResizableHandleKit.addHandle((GraphicalEditPart) getHost(), list, PositionConstants.WEST);
    ResizableHandleKit.addHandle((GraphicalEditPart) getHost(), list, PositionConstants.NORTH);
    return list;
}

第二个是createDragSourceFeedbackFigure()方法,它决定用户拖动图形时,随鼠标移动的半透明图形(即“鬼影”)的形状和颜色,因此我们覆盖这个方法以显示椭圆形的鬼影。

protected IFigure createDragSourceFeedbackFigure() {
    //用椭圆替代矩形
    //RectangleFigure r = new RectangleFigure();
    Ellipse r = new Ellipse();
    FigureUtilities.makeGhostShape(r);
    r.setLineStyle(Graphics.LINE_DOT);
    r.setForegroundColor(ColorConstants.white);
    r.setBounds(getInitialFeedbackBounds());
    addFeedback(r);
    return r;
}

经过以上这些修改,可以看到选择框和鬼影都是椭圆的了,如图2所示。

file
图2 与节点形状相同的选择框和鬼影

代码下载

点此下载工程,此工程修改自GEF应用实例中的GefPractice,目标文件的扩展名改为.gefpracticeel。

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2006/06/26/436455.html

GEF常见问题3:自身连接

在类图里能看到一些对象具有对自己的引用,通常这些引用用于表达树状结构,即父子节点都是同一类对象。用GEF绘制这样的连接线一般是通过转折点(Bendpoint)实现的,如果你的GEF应用程序里还不能使用Bendpoint,请按照上一篇介绍的步骤添加对Bendpoint的支持。

原先我们的GefPractice应用程序是不允许一条连接线的起点和终点都是同一个图形的,因为这样会导致连接线缩成一个点隐藏在图形下方,用户并不知道它的存在。当时我们在CreateConnectionCommand类的canExecute()方法里进行了如下判断:

public boolean canExecute() {
    if (source.equals(target))
        return false;
    ...
}

因此现在首先要把这两句删除。然后在execute()方法里对自身连接的这种情况稍做处理,处理的方法是给这条连接线在适当位置增加三个Bendpoint,你也可以根据想要的连接线形状修改Bendpoint的数目和位置。

public void execute() {
    connection = new Connection(source, target);
    if (source == target) {
        //The start and end points of our connection are both at the center of the rectangle,
        //so the two relative dimensions are equal.
        ConnectionBendpoint cbp = new ConnectionBendpoint();
        cbp.setRelativeDimensions(new Dimension(0, -60), new Dimension(0, -60));
        connection.addBendpoint(0, cbp);
        ConnectionBendpoint cbp2 = new ConnectionBendpoint();
        cbp2.setRelativeDimensions(new Dimension(100, -60), new Dimension(100, -60));
        connection.addBendpoint(1, cbp2);
        ConnectionBendpoint cbp3 = new ConnectionBendpoint();
        cbp3.setRelativeDimensions(new Dimension(100, 0), new Dimension(100, 0));
        connection.addBendpoint(2, cbp3);
    }
}

现在用户只要选择连接工具,然后在一个节点上连续点两下就可以创建自身连接了,如下图所示。

file
图:自身连接

代码下载

点此下载工程,此工程修改自GEF常见问题2中的GefPractice-bp,目标文件扩展名为.gefpracticesc。

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2006/06/22/432553.html

GEF常见问题2:具有转折点的连接线

从直线连接转换到可以任意增减转折点的折线连接,因为模型里要增加新的元素,所以模型、editpart和图形部分都要有所修改,显得稍微有些烦琐,但其实很多代码是通用的。这个过程主要分为以下几个部分:

1、在模型里增加转折点对应的类(这些转折点在GEF里称作Bendpoint),在类里要具有两个Dimension类型用来记录Bendpoint相对连接线起止点的位置。在连接类里要维护一个Bendpoint列表,并提供访问方法,由于篇幅关系这里只列出连接类中的这几个方法。

public void addBendpoint(int index, ConnectionBendpoint point) {
    getBendpoints().add(index, point);
    firePropertyChange(PROP_BENDPOINT, null, null);
}

/**
 * zhanghao: 为了在更新两个dimension后能发送事件,在MoveBendpointCommand要在用这个方法设置新坐标,
 * 而不是直接用BendPoint里的方法。
 */
public void setBendpointRelativeDimensions(int index, Dimension d1, Dimension d2){
    ConnectionBendpoint cbp=(ConnectionBendpoint)getBendpoints().get(index);
    cbp.setRelativeDimensions(d1,d2);
    firePropertyChange(PROP_BENDPOINT, null, null);
}

public void removeBendpoint(int index) {
    getBendpoints().remove(index);
    firePropertyChange(PROP_BENDPOINT, null, null);
}

2、在原来的连接方式里,由于连接线本身不需要刷新,所以现在要确保这个editpart实现了PropertyChangeListener接口,并像其他editpart一样覆盖了activate()deactivate()这两个方法,以便接收Bendpoint发生改变的事件。

public void activate() {
    super.activate();
    ((Connection)getModel()).addPropertyChangeListener(this);
}

public void deactivate() {
    super.deactivate();
    ((Connection)getModel()).removePropertyChangeListener(this);
}

public void propertyChange(PropertyChangeEvent event) {
    String property = event.getPropertyName();
    if(Connection.PROP_BENDPOINT.equals(property)){
        refreshBendpoints();
    }
}

为模型连接类对应的editpart里增加一个继承自BendpointEditPolicy的子类ConnectionBendPointEditPolicy,这个类的内容后面会说到。

protected void createEditPolicies() {
    ...
    installEditPolicy(EditPolicy.CONNECTION_BENDPOINTS_ROLE, new ConnectionBendPointEditPolicy());

直线连接的情况下,连接的刷新不需要我们负责,但增加了Bendpoint以后,必须在Bendpoint发生改变时刷新连接线的显示。所以在上面这个editpart的refreshVisuals()方法里需要增加一些代码,以便把模型里的Bendpoint转换为图形上的relativeBendpoint。

protected void refreshVisuals() {
    Connection conn = (Connection) getModel();
    List modelConstraint = conn.getBendpoints();
    List figureConstraint = new ArrayList();
    for (int i = 0; i < modelConstraint.size(); i++) {
        ConnectionBendpoint cbp = (ConnectionBendpoint) modelConstraint
                .get(i);
        RelativeBendpoint rbp = new RelativeBendpoint(getConnectionFigure());
        rbp.setRelativeDimensions(cbp.getFirstRelativeDimension(), cbp
                .getSecondRelativeDimension());
        rbp.setWeight((i + 1) / ((float) modelConstraint.size() + 1));
        figureConstraint.add(rbp);
    }
    getConnectionFigure().setRoutingConstraint(figureConstraint);
}

3、创建CreateBendpointCommand、MoveBendpointCommand和DeleteBendpointCommand这三个类,可以像Logic例子那样创建一个基类BendPointCommand让它们来继承。作为例子,BendpointCommand的内容如下。

public class BendpointCommand extends Command {

    protected int index;
    protected Connection connection;
    protected Dimension d1, d2;

    public void setConnection(Connection connection) {
        this.connection = connection;
    }

    public void redo() {
        execute();
    }

    public void setRelativeDimensions(Dimension dim1, Dimension dim2) {
        d1 = dim1;
        d2 = dim2;
    }

    public void setIndex(int i) {
        index = i;
    }
}

4、在ConnectionBendPointEditPolicy里实现BendpointEditPolicy定义的创建、移动和删除Bendpoint的三个方法。

public class ConnectionBendPointEditPolicy extends BendpointEditPolicy {

    protected Command getCreateBendpointCommand(BendpointRequest request) {
        CreateBendpointCommand cmd = new CreateBendpointCommand();
        Point p = request.getLocation();
        Connection conn = getConnection();

        conn.translateToRelative(p);

        Point ref1 = getConnection().getSourceAnchor().getReferencePoint();
        Point ref2 = getConnection().getTargetAnchor().getReferencePoint();

        conn.translateToRelative(ref1);
        conn.translateToRelative(ref2);

        cmd.setRelativeDimensions(p.getDifference(ref1), p.getDifference(ref2));
        cmd.setConnection((com.example.model.Connection) request.getSource()
                .getModel());
        cmd.setIndex(request.getIndex());
        return cmd;
    }

    protected Command getDeleteBendpointCommand(BendpointRequest request) {
        BendpointCommand cmd = new DeleteBendpointCommand();
        Point p = request.getLocation();
        cmd.setConnection((com.example.model.Connection) request.getSource().getModel());
        cmd.setIndex(request.getIndex());
        return cmd;
    }

    protected Command getMoveBendpointCommand(BendpointRequest request) {
        MoveBendpointCommand cmd = new MoveBendpointCommand();
        Point p = request.getLocation();
        Connection conn = getConnection();

        conn.translateToRelative(p);

        Point ref1 = getConnection().getSourceAnchor().getReferencePoint();
        Point ref2 = getConnection().getTargetAnchor().getReferencePoint();

        conn.translateToRelative(ref1);
        conn.translateToRelative(ref2);

        cmd.setRelativeDimensions(p.getDifference(ref1), p.getDifference(ref2));
        cmd.setConnection((com.example.model.Connection) request.getSource()
                .getModel());
        cmd.setIndex(request.getIndex());
        return cmd;
    }
}

修改完成后的编辑器如下图所示。

file
图:编辑器中的转折连接线

代码下载

点此下载工程,此工程修改自GEF应用实例中的GefPractice,目标文件的扩展名改为.gefpracticebp。

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2006/06/22/432227.html

GEF常见问题1:为图形编辑器设置背景图片

GEF的RootEditPart对应的Figure是一个由多个Layer组成的LayeredPane,每个Layer负责包含不同类型的图形元素,如节点、连接、网格线等等。所以要让图形编辑器显示一个图片作为背景,可以在它们其中一个层里绘制这个图片,也可以添加一个层专门放置背景图片。我推荐使用后者,以下代码是在前面的GefPractice项目基础上做了简单修改得到的:

static Image BG_IMAGE=new Image(null,"c:\\bg.jpg");

protected void configureGraphicalViewer() {
    super.configureGraphicalViewer();
    getGraphicalViewer().setRootEditPart(new ScalableFreeformRootEditPart() {

        //覆盖ScalableFreeformRootEditPart的createlayers方法以便增加自己的层
        protected void createLayers(LayeredPane layeredPane) {
            Layer layer = new FreeformLayer() {
                protected void paintFigure(Graphics graphics) {
                    super.paintFigure(graphics);
                    //在层上绘制图片,也可以绘制其他图形作为背景,GEF的网格线就是一例
                    graphics.drawImage(BG_IMAGE,0,0);
                }
            };
            layeredPane.add(layer);
            super.createLayers(layeredPane);
        }
    });
    getGraphicalViewer().setEditPartFactory(new PartFactory());
}

这样得到的背景图片只显示编辑器可见区域的部分,也就是会随滚动条滚动,见下图。

file
具有背景图片的图形编辑器

代码下载

工程下载(默认背景图片名为c:\bg.jpg可根据实际情况修改)

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2006/06/21/431774.html

Palm OS Developer Suite的安装和使用

终于买了一个掌上电脑,我又开始关注Palm开发了。Palm上虽然可以安装Java虚拟机,但据我所知绝大多数用户受里的Palm并没有安装,让他们为了用你的一个小软件而装虚拟机不太现实,因此只能用C/C++了。大学里学的C语言一直没有实际应用过,所以经过这么多年也和没学差不多,最近通过看wj给我的书,对C语言又有了重新的认识。不过C语言是面向过程的,我在Java里积累的经验似乎帮不上什么忙,边走边看吧。我在“Palm开发”这个分类里记录学习过程,这是第一篇,先介绍一下PODS的使用方法。

如果不熟悉Palm,可以先通过这篇文章了解一下Palm公司的历史和产品。没有掌上电脑也可以开发Palm应用,你可以在模拟器上运行和调试,但我相信把自己编写的应用程序拿在手里的感觉一定不错。

Palm OS Developer Suite(PODS)是PalmSource提供的基于Eclipse的Palm应用程序开发工具,它包含了编译工具、调试工具、模拟器、资源编辑工具和Palm SDK等等,可以在这个地址下载。虽然很多人都使用CodeWarrier开发Palm应用程序,但PODS毕竟是PalmSource官方推出的开发工具,从最初的1.0到现在的1.2版本看来,支持得也不错,加上它是基于我们熟悉的Eclipse平台,更重要的一点它是免费的,所以我还是选择了PODS。当然,用什么工具只能在一定程度上影响开发速度,熟练以后工具间的差别就不那么明显了。

我目前收集到的Palm开发资料主要有这几部分:首先是PalmSource网站上提供的不少pdf文档,对入门者比较有用的是“Palm OS Companion”和“Palm OS Reference”这两份,还有一个“UI Guideline”在设计窗体之前可以看看;另一个是PalmOS的开发者新闻组(news.palmos.com),比较活跃,到现在已经有超过10万个post了;最后就是纸版的图书,我找到的比较新的中文书籍是《PalmOS编程宝典(第二版)》,网上可以找到该书第一版的英文电子版,决定购买以前可以先看一下。

PODS的安装很简单:运行你下载的安装文件就可以了(PODS没有Linux的版本,它带的模拟器也是运行在Windows里的,所以我暂时还是要用回Windows了),可能需要你的机器里事先装有JRE。安装以后在“开始”菜单里会出现PODS组,其中有两个模拟器(Simulator,注意和Emulator的区别),开发OS5及以下应用程序用Garnet Simulator,开发OS6应用程序用Cobalt Simulator,所以我们一般用前者,它的样子见图1。

file
图1 Garnet模拟器

如果你用过Eclipse,对PODS的界面也不会感到陌生,基本上PODS就是在Eclipse里加了个透视图(Perspective)和一些向导,PODS 1.2版本带的Eclipse是3.0.1版本,显得有点过时了(也许可以把PODS那部分拿出来放在新版本Eclipse和CDT里,不知道能不能兼容),图2是PODS的运行界面。

file
图2 PODS的运行界面

环境看得差不多了,现在试着创建第一个Palm应用程序吧。在新建向导里创建“Managed Make 68K C/C++ Project”,如图3。这种应用程序可以运行在各个版本的Palm上,具有最好的兼容性。

file
图3 创建Palm应用程序

按下一步按钮,给项目起个名字“hello”,见图4。

file
图4 给项目起名

按下一步按钮,这里要指定你的应用程序的一些属性,对普通应用程序来说,大部分可以不动,只有“Creator ID”这一项必须改为其他值。按正规的流程,我们首先要在palmos网站上注册自己唯一的ID,然后把这个ID填在这里,目的是避免应用程序间的冲突。因为现在只是试验,随便改成“HELL”就可以了,见图5。

file
图5 修改Creator ID

按下一步按钮,在这一步里PODS提供了一些代码模板,这样可以不用从零开始写每个程序。我们选择“Sample Application”这一项,见图6,这样在向导结束后我们会得到一个很简单的应用程序。

file
图6 选择一个模板

现在直接按Finish按钮结束向导,这时要稍等一会儿,PODS在生成必要的代码和编译它们。简单来看一下生成的代码,主要的程序文件是src目录下的AppMain.c文件,注意它的入口方法不是main()而是PilotMain(),这个文件里的内容以后的帖子里会说明;在rsc目录下生成了名为AppResources.xrd的文件,这是一个资源文件,如果你在PODS里双击它,会打开Palm OS资源编辑器,见图7,在资源编辑器里你可以编辑窗体、定义菜单、定义图标、定义字符串等等。

file
图7 Palm OS资源编辑器

怎样在模拟器里运行这个应用程序呢?首先确认你已经启动了Garnet模拟器;然后在PODS里选择菜单“Run->Run...”,这将弹出一个对话框。在对话框左边选中“Palm OS Application”,然后按下面的New按钮,在“Palm OS Application”下面会出现一个新的节点(运行项);选中这个节点,在对话框右半部分把它的名称改为和项目名一样的“hello”,并确认“Files to install”框里只勾选了hello项目;点击Target属性页,在Device下拉列表里选择“Palm OS Garnet Simulator 5.4”,这个设置很重要,不要忽略。

现在运行项已经配置好了,见图8,按下对话框右下方的Run按钮即可运行程序。注意,以上这个过程对一个项目只需要配置一次就够了,再需要运行可以在PODS的工具栏里直接按Run下拉按钮。

file
图8 为hello项目配置运行项

我们第一个应用程序在模拟器上运行的界面如图9所示。

file
图9 第一个应用程序

你可能已经注意到了,在项目的Debug目录下已经生成了hello.prc文件,这个文件可以直接在Palm设备上运行(方法和安装其他软件一样,通过同步,或者复制到扩展卡上,等等),图10是我们的hello项目在真正的Palm上运行的样子。

file
图10 运行在Palm T|X上的hello项目

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2006/06/09/421821.html