Discussion about Issue 100

1 view
Skip to first unread message

Wang Kunshan

unread,
Feb 26, 2009, 11:05:44 PM2/26/09
to labyrin...@googlegroups.com
This problem seems harder to address than Issue 135.
I fixed it on my machine with some quick and dirty workarounds. I am
not hurrying to fix this issue in this post.

Labyrinth 0.4 have rudimentary zooming capability and the SVN trunk
version made some improvement, but still buggy.

As for the SVN version, when mouse wheel is scrolled, what actually
happened is as follows:

1. The cursor point (I mean the point where the mouse cursor is
pointing at) moves to the center of the window.
2. The view zooms, with the center of the window as an fixed point(the
point that doesn't move when zooming).

A good zooming expects the "cursor point" to be the "fixed point".
This point should not move while other points move towards this point
when zooming out, and move away from this point when zooming in.

Look at how "zooming" is currently implemented:

in MMapArea.py:

def scroll (self, widget, event):
scale = self.scale_fac # Backup the old scale factor
if event.direction == gtk.gdk.SCROLL_UP:
self.scale_fac*=1.2 # update to new scale factor

coords = self.transform_coords(event.x, event.y) # get the
"user" coordinates of mouse-pointer
geom = self.window.get_geometry() # get the window size ( in
"device" coords )
middle = self.transform_coords(geom[2]/2.0, geom[3]/2.0) #
get window center and transform into "user" coords

self.translation[0] -= coords[0] - middle[0] # move the
"cursor point" to the center of the window
self.translation[1] -= coords[1] - middle[1] # and y coordinate
elif event.direction == gtk.gdk.SCROLL_DOWN:
self.scale_fac/=1.2
self.undo.add_undo (UndoManager.UndoAction (self,
UndoManager.TRANSFORM_CANVAS, \
self.undo_transform_cb,
scale, self.scale_fac,
self.translation,
self.translation))
self.invalidate()

See the problem? After zooming, the "cursor point" is moved to the
center of the window. That is not expected to happen.
And another problem is that zooming-in and zooming-out are also
assymetric. Zooming-out does not move self.translation. This is also
incorrect.
But I will not go for a quick workaround here.
Instead, let's look at the draw() function:

In MMapArea.py:

def draw (self, event, context):
'''Draw the map and all the associated thoughts'''
area = event.area

... # some initializations

alloc = self.get_allocation ()
context.translate(alloc.width/2., alloc.height/2.)
context.scale(self.scale_fac, self.scale_fac)
context.translate(-alloc.width/2., -alloc.height/2.)
context.translate(self.translation[0], self.translation[1]) #
attention here.

... # other drawings follows

There are four transformations: three translate()'s and one scale()'s.
These can be interpreted as four operations done sequentially (in the
reversed order of the code).

I think the problem is in the self.translation. What does
self.translation denotes? According to the above operations,
self.translation is merely an "ACTION" that should be performed as the
first step of coordinates transformation. Does self.translation have
other meanings? I found that the point (-self.translation[0],
-self.translation[1]) lies in the top-left corner of the window when
self.scale_fac==1. Though I have found "where" self.translation is, I
think it is a "weird" point that haunts around on the "user" canvas.

We can use other denotion instead of the currently used self.translation.

Let's introduce another attribute self.central_point_of_view. It
satisfies the following condition:
1. self.central_point_of_view is a point on the "user" coordinates.
2. When translating from "user" coordinates to the "device"
coordinates self.central_point_of_view should always be translated to
the center of the window, and not affected by self.scale_fac and the
size of the window.

The effect of this change will be:
1. When panning the view-area (i.e. hold the middle mouse-button and
move), self.central_point_of_view should be moved in the opposite
direction of self.translation
2. The translation steps in the draw() method should become:

context.translate(alloc.width/2., alloc.height/2.)
context.scale(self.scale_fac, self.scale_fac)
context.translate(-self.central_point_of_view[0],-self.central_point_of_view[1])
# note the negative sign.

3. When scaling (move the mouse-wheel), the code would become:

def scroll (self, widget, event):
scale = self.scale_fac
if event.direction == gtk.gdk.SCROLL_UP:
self.scale_fac*=1.2
elif event.direction == gtk.gdk.SCROLL_DOWN:
self.scale_fac/=1.2

coords = self.transform_coords(event.x, event.y)
# geom = self.window.get_geometry()
# middle = self.transform_coords(geom[2]/2.0, geom[3]/2.0)
# These are not needed. middle is always self.central_point_of_view

def move_position(mouse, middle):
""" Since we are moving x-coord and y-coord in the same
way, I create a function """
old_delta = middle - mouse
new_delta = old_delta / 1.2 # suppose we zoom in. Yes, zoom IN
new_middle = mouse+new_delta
return new_middle

self.central_point_of_view[0] =
move_position(coords[0],self.central_point_of_view[0])
self.central_point_of_view[1] =
move_position(coords[1],self.central_point_of_view[1])

How move_position() works?
If we zoom-in while keeping the current "mouse point" fixed, then the
self.central_point_of_view moves towards the "mouse point" in the
"user" coords, because zooming-in means that the same on-screen
distance worths less in the "user" coords.

And there is a side-effect when we use self.central_point_of_view
instead of self.translation. When we resize the window, the result
becomes different. Now the central point keeps fixed while in the
past the top-left point was fixed.

Currently self.translation is referenced all around the code. It
should be taken seriously whether to introduce
self.central_point_of_view and remove self.translation.

Summary:

1. The current implementation of scroll() is obviously incorrect.
2. The self.translation is strange. It is difficult to understand and
difficult to use.
3. Introducing self.central_point_of_view will simplify coordinate
handling, but will result in many code-changes.


Kunshan Wang

Matthias Vogelgesang

unread,
Feb 27, 2009, 4:48:33 AM2/27/09
to labyrin...@googlegroups.com
Hello Wang and Readers,

On Fri, Feb 27, 2009 at 05:05, Wang Kunshan <wks...@gmail.com> wrote:
> Currently self.translation is referenced all around the code.  It
> should be taken seriously whether to introduce
> self.central_point_of_view and remove self.translation.

The code is quite a mess, indeed. Oh, and nice analysis of the zoom
problem. I had a hard time doing it correctly and it never worked they
way I wanted it to do.

> Summary:
>
> 1. The current implementation of scroll() is obviously incorrect.
> 2. The self.translation is strange.  It is difficult to understand and
> difficult to use.
> 3. Introducing self.central_point_of_view will simplify coordinate
> handling, but will result in many code-changes.

The way labyrinth is working with all those nifty details of a canvas
is rather awkward, that's right. In the last week I needed something
similar to represent a directed graph in a python application.
However, the "canvas" (or MMapArea) of Labyrinth is too tied to the
rest of the code, to be useful for any other application.

I checked out other gtk canvas widgets which might be useful for
Labyrinth [1]. The pros and cons of adopting an existing canvas widget
are obvious:

+ well tested
+ we can spend time on Labyrinth's real problems
+ with a proper widget a11y is already done
- might not be that flexible

A must-have for our purpose seems to be Cairo rendering. GnomeCanvas
seems to be a no-go, whereas (py)goocanvas and ccc are already adopted
by some projects. Clutter on the other hand is a bit overkill for our
purposes, I think.

So we have several options:

* GooCanvas: has a11y support, very low-level (zooming and dnd must
be done by us)
* ccc: according to the wiki page it seems ok, but it looks like it
is abandoned
* CrCanvas [2]: this is my all-time favor, it has builtin panning,
zooming and infinite scrolling and is made with performance in mind
(this is where Labyrinth really sucks)
* make our own generic canvas from scratch

And I have a question regarding the vcs. I would like to use git or
some other kind of dvcs, however the git-to-svn-bridge I used sucked a
little bit. Has anyone objections of moving to a distributed vcs?

So I would suggest to release the next version of Labyrinth asap and
then make a hard break.


[1] http://live.gnome.org/ProjectRidley/CanvasOverview
[2] http://geocanvas.sourceforge.net/crcanvas/index.html

--
Matthias Vogelgesang
Public-Key: http://tinyurl.com/2qcydl

Wang Kunshan

unread,
Feb 27, 2009, 8:30:40 AM2/27/09
to labyrin...@googlegroups.com
I add gaphas, the canvas system of gaphor UML modelling program.

The code looks well-commented. And the README shows its structure
which addressed some problems (like the responsibility of geometric
objects, canvas, tools) that I am also considering recently.

But the size of gaphas is almost as big as the entire labyrinth.
Isn't is a bit overkilling?



2009/2/27 Matthias Vogelgesang <matthias.v...@gmail.com>:

Tomeu Vizoso

unread,
Feb 28, 2009, 4:05:09 AM2/28/09
to labyrin...@googlegroups.com
On Fri, Feb 27, 2009 at 14:30, Wang Kunshan <wks...@gmail.com> wrote:
>
> I add gaphas, the canvas system of gaphor UML modelling program.

Gaphas is a good fit in my experience.

Regards,

Tomeu
Reply all
Reply to author
Forward
0 new messages