Revert "Merge pull request #14 from danyeaw/master"
This reverts commit 81ebdf8641e962aadf3c14262c453b154ba78290, reversing changes made to c9e9f81b62f6c710dd3a361e0219d148ff08a477.
This commit is contained in:
parent
63b7110945
commit
b4f7950f7a
2
.mailmap
2
.mailmap
@ -1,2 +0,0 @@
|
||||
Dan Yeaw <dan@yeaw.me> danyeaw <danyeaw@gmail.com>
|
||||
Artur Wroblewski <wrobell@pld-linux.org> wrobell <wrobell@pld-linux.org>
|
@ -1,27 +0,0 @@
|
||||
[project]
|
||||
name: Gaphas
|
||||
vcs: Git
|
||||
|
||||
[files]
|
||||
authors: yes
|
||||
files: yes
|
||||
ignored: LICENSE.txt | README.rst | .update-copyright.conf | .git* | *.md | *.txt | MANIFEST.in | NEWS | gaphor.desktop | pylintc | pygtk.vext | tox.ini
|
||||
pyfile: ../update-copyright-0.6.2/update_copyright/license.py
|
||||
|
||||
[copyright]
|
||||
short: {project} comes with ABSOLUTELY NO WARRANTY and is licensed under the GNU General Public License.
|
||||
long: This file is part of {project}.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Library General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Library General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Library General Public
|
||||
License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
6
AUTHORS
6
AUTHORS
@ -1,6 +0,0 @@
|
||||
Gaphas was written by:
|
||||
Adrian Boguszewski <adrbogus1@student.pg.gda.pl>
|
||||
Antony N. Pavlov <antony@niisi.msk.ru>
|
||||
Arjan Molenaar <gaphor@gmail.com>
|
||||
Artur Wroblewski <wrobell@pld-linux.org>
|
||||
Dan Yeaw <dan@yeaw.me>
|
37
README.rst
37
README.rst
@ -45,8 +45,8 @@ in the item true (e.g. make sure a box maintains it's rectangular shape).
|
||||
View (from view.py) is used to visualize a canvas. On a View, a Tool
|
||||
(from tool.py) can be applied, which will handle user input (button presses,
|
||||
key presses, etc.). Painters (from painter.py) are used to do the actual
|
||||
drawing. Keep the painters modular could allow you to draw to other media than
|
||||
the screen, such as a printer or PDF document.
|
||||
drawing. This way it should be easy do draw to other media than the screen,
|
||||
such as a printer or PDF document.
|
||||
|
||||
Updating item state
|
||||
-------------------
|
||||
@ -54,13 +54,13 @@ If an items needs updating, it sets out an update request on the Canvas
|
||||
(Canvas.request_update()). The canvas performs an update by calling:
|
||||
|
||||
1. Item.pre_update(context) for each item marked for update
|
||||
2. Updating Canvas-to-Item (c2i) matrices, for fast transformation of coordinates
|
||||
2. Updating Canvas-to-Item matrices, for fast transformation of coordinates
|
||||
from the canvas' to the items' coordinate system.
|
||||
The c2i matrix is stored on the Item as Item._matrix_c2i.
|
||||
3. Solve constraints.
|
||||
4. Normalize items by setting the coordinates of the first handle to (0, 0).
|
||||
5. Updating Canvas-to-Item matrices for items that have been changed by
|
||||
normalization.
|
||||
normalization, just to be on the save side.
|
||||
6. Item.post_update(context) for each item marked for update, including items
|
||||
that have been marked during constraint solving.
|
||||
|
||||
@ -74,15 +74,16 @@ The context contains:
|
||||
|
||||
NOTE: updating is done from the canvas, items should not update sub-items.
|
||||
|
||||
After an update, the item should be ready to be drawn.
|
||||
After an update, the Item should be ready to be drawn.
|
||||
|
||||
Constraint solving
|
||||
------------------
|
||||
A word about the constraint solver since it is one of the big features of this
|
||||
library. The Solver is able to solve constraints. Constraints can be applied to
|
||||
items (variables owned by the item actually). For example, element items use
|
||||
constraints to maintain their recangular shape. Constraints can be created
|
||||
*between* items (for example a line that connects to a box).
|
||||
A word about the constraint solver seems in place. It is one of the big
|
||||
features of this library after all. The Solver is able to solve constraints.
|
||||
Constraints can be applied to items (Variables owned by the item actually).
|
||||
Element items, for example, uses constraints to maintain their recangular
|
||||
shape. Constraints can be created *between* items (for example a line that
|
||||
connects to a box).
|
||||
|
||||
Constraints that apply to one item are pretty straight forward, as all variables
|
||||
live in the same coordinate system (of the item). The variables (in most cases
|
||||
@ -90,19 +91,19 @@ a Handle's x and y coordinate) can simply be put in a constraint.
|
||||
|
||||
When two items are connected to each other and constraints are created, a
|
||||
problem shows up: variables live in separate coordinate systems. To overcome
|
||||
this problem a projection (from solver.py) has been defined. With a projection
|
||||
this problem a Projection (from solver.py) has been defined. With a Projection
|
||||
instance, a variable can be "projected" on another coordinate system. In this
|
||||
case, where two items are connecting to each other, the canvas' coordinate
|
||||
case, where two items are connecting to each other, the Canvas' coordinate
|
||||
system is used.
|
||||
|
||||
|
||||
Drawing
|
||||
-------
|
||||
Drawing is done by the View. All items marked for redraw (i.e. the items
|
||||
Drawing is done by the View. All items marked for redraw (e.i. the items
|
||||
that had been updated) will be drawn in the order in which they reside in the
|
||||
canvas (first root item, then it's children; second root item, etc.)
|
||||
Canvas (first root item, then it's children; second root item, etc.)
|
||||
|
||||
The view context passed to the item's draw() method has the following properties:
|
||||
The view context passed to the Items draw() method has the following properties:
|
||||
|
||||
:view: the view we're drawing to
|
||||
:cairo: the CairoContext to draw to
|
||||
@ -116,7 +117,7 @@ The view context passed to the item's draw() method has the following properties
|
||||
:draw_all: True if everything drawable on the item should be drawn (e.g. when
|
||||
calculating the bounding boxes).
|
||||
|
||||
The view automatically calculates the bounding box for the item, based on the
|
||||
The View automatically calculates the bounding box for the item, based on the
|
||||
items drawn in the draw(context) function (this is only done when really
|
||||
necessary, e.g. after an update of the item). The bounding box is in viewport
|
||||
coordinates.
|
||||
@ -182,7 +183,7 @@ is called at the moment the item it's connected to is removed from the canvas.
|
||||
Undo
|
||||
====
|
||||
|
||||
Gaphas has a simple built-in system for registering changes in its classes and
|
||||
Gaphas has a simple build-in system for registering changes in it's classes and
|
||||
notifying the application. This code resides in state.py.
|
||||
|
||||
There is also a "reverter" framework in place. This "framework" is notified
|
||||
@ -195,6 +196,8 @@ See state.txt and undo.txt for details and usage examples.
|
||||
Guidelines
|
||||
==========
|
||||
|
||||
Documentation should be in UK English.
|
||||
|
||||
Following the `Python coding guidelines`_ indentation should be 4 spaces
|
||||
(no tabs), function and method names should be ``lowercase_with_underscore()``.
|
||||
We're using two white lines as separator between methods, as it makes method
|
||||
|
219
demo.py
219
demo.py
@ -1,24 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2006-2017 Arjan Molenaar <gaphor@gmail.com>
|
||||
# Artur Wroblewski <wrobell@pld-linux.org>
|
||||
# Dan Yeaw <dan@yeaw.me>
|
||||
#
|
||||
# This file is part of Gaphas.
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Library General Public License as published by the Free
|
||||
# Software Foundation; either version 2 of the License, or (at your option) any
|
||||
# later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Library General Public License
|
||||
# along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
A simple demo app.
|
||||
|
||||
@ -34,40 +14,39 @@ It sports a small canvas and some trivial operations:
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import print_function
|
||||
__version__ = "$Revision$"
|
||||
# $HeadURL$
|
||||
|
||||
try:
|
||||
import gi
|
||||
import pygtk
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
gi.require_version('Gtk', '3.0')
|
||||
pygtk.require('2.0')
|
||||
|
||||
import math
|
||||
from gi.repository import Gtk
|
||||
import gtk
|
||||
import cairo
|
||||
from gaphas import Canvas, GtkView, View
|
||||
from gaphas.examples import Box, PortoBox, Text, FatLine, Circle
|
||||
from gaphas.item import Line
|
||||
from gaphas.item import Line, NW, SE
|
||||
from gaphas.tool import PlacementTool, HandleTool
|
||||
from gaphas.segment import Segment
|
||||
import gaphas.guide
|
||||
from gaphas.painter import PainterChain, ItemPainter, HandlePainter, FocusedItemPainter, ToolPainter, BoundingBoxPainter
|
||||
from gaphas import state
|
||||
from gaphas.util import text_extents, text_underline
|
||||
from gaphas.freehand import FreeHandPainter
|
||||
|
||||
__version__ = "$Revision$"
|
||||
# $HeadURL$
|
||||
|
||||
# painter.DEBUG_DRAW_BOUNDING_BOX = True
|
||||
from gaphas import painter
|
||||
#painter.DEBUG_DRAW_BOUNDING_BOX = True
|
||||
|
||||
# Ensure data gets picked well:
|
||||
import gaphas.picklers
|
||||
|
||||
# Global undo list
|
||||
undo_list = []
|
||||
|
||||
|
||||
def undo_handler(event):
|
||||
global undo_list
|
||||
undo_list.append(event)
|
||||
@ -77,12 +56,10 @@ def factory(view, cls):
|
||||
"""
|
||||
Simple canvas item factory.
|
||||
"""
|
||||
|
||||
def wrapper():
|
||||
item = cls()
|
||||
view.canvas.add(item)
|
||||
return item
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
@ -90,11 +67,9 @@ class MyBox(Box):
|
||||
"""Box with an example connection protocol.
|
||||
"""
|
||||
|
||||
|
||||
class MyLine(Line):
|
||||
"""Line with experimental connection protocol.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super(MyLine, self).__init__()
|
||||
self.fuzziness = 2
|
||||
@ -114,11 +89,13 @@ class MyLine(Line):
|
||||
cr.stroke()
|
||||
|
||||
|
||||
|
||||
|
||||
class MyText(Text):
|
||||
"""
|
||||
Text with experimental connection protocol.
|
||||
"""
|
||||
|
||||
|
||||
def draw(self, context):
|
||||
Text.draw(self, context)
|
||||
cr = context.cairo
|
||||
@ -129,10 +106,11 @@ class MyText(Text):
|
||||
|
||||
|
||||
class UnderlineText(Text):
|
||||
|
||||
def draw(self, context):
|
||||
cr = context.cairo
|
||||
text_underline(cr, 0, 0, "Some text(y)")
|
||||
|
||||
|
||||
|
||||
def create_window(canvas, title, zoom=1.0):
|
||||
view = GtkView()
|
||||
@ -142,32 +120,32 @@ def create_window(canvas, title, zoom=1.0):
|
||||
append(FocusedItemPainter()). \
|
||||
append(ToolPainter())
|
||||
view.bounding_box_painter = FreeHandPainter(BoundingBoxPainter())
|
||||
w = Gtk.Window()
|
||||
w = gtk.Window()
|
||||
w.set_title(title)
|
||||
h = Gtk.HBox()
|
||||
h = gtk.HBox()
|
||||
w.add(h)
|
||||
|
||||
# VBox contains buttons that can be used to manipulate the canvas:
|
||||
v = Gtk.VBox()
|
||||
v = gtk.VBox()
|
||||
v.set_property('border-width', 3)
|
||||
v.set_property('spacing', 2)
|
||||
f = Gtk.Frame()
|
||||
f = gtk.Frame()
|
||||
f.set_property('border-width', 1)
|
||||
f.add(v)
|
||||
h.pack_start(f, False, True, 0)
|
||||
h.pack_start(f, expand=False)
|
||||
|
||||
v.add(Gtk.Label(label='Item placement:'))
|
||||
|
||||
b = Gtk.Button('Add box')
|
||||
v.add(gtk.Label('Item placement:'))
|
||||
|
||||
b = gtk.Button('Add box')
|
||||
|
||||
def on_clicked(button, view):
|
||||
# view.window.set_cursor(Gdk.Cursor.new(Gdk.CursorType.CROSSHAIR))
|
||||
#view.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.CROSSHAIR))
|
||||
view.tool.grab(PlacementTool(view, factory(view, MyBox), HandleTool(), 2))
|
||||
|
||||
b.connect('clicked', on_clicked, view)
|
||||
v.add(b)
|
||||
|
||||
b = Gtk.Button('Add line')
|
||||
b = gtk.Button('Add line')
|
||||
|
||||
def on_clicked(button):
|
||||
view.tool.grab(PlacementTool(view, factory(view, MyLine), HandleTool(), 1))
|
||||
@ -175,9 +153,9 @@ def create_window(canvas, title, zoom=1.0):
|
||||
b.connect('clicked', on_clicked)
|
||||
v.add(b)
|
||||
|
||||
v.add(Gtk.Label(label='Zooming:'))
|
||||
|
||||
b = Gtk.Button('Zoom in')
|
||||
v.add(gtk.Label('Zooming:'))
|
||||
|
||||
b = gtk.Button('Zoom in')
|
||||
|
||||
def on_clicked(button):
|
||||
view.zoom(1.2)
|
||||
@ -185,17 +163,17 @@ def create_window(canvas, title, zoom=1.0):
|
||||
b.connect('clicked', on_clicked)
|
||||
v.add(b)
|
||||
|
||||
b = Gtk.Button('Zoom out')
|
||||
b = gtk.Button('Zoom out')
|
||||
|
||||
def on_clicked(button):
|
||||
view.zoom(1 / 1.2)
|
||||
view.zoom(1/1.2)
|
||||
|
||||
b.connect('clicked', on_clicked)
|
||||
v.add(b)
|
||||
|
||||
v.add(Gtk.Label(label='Misc:'))
|
||||
v.add(gtk.Label('Misc:'))
|
||||
|
||||
b = Gtk.Button('Split line')
|
||||
b = gtk.Button('Split line')
|
||||
|
||||
def on_clicked(button):
|
||||
if isinstance(view.focused_item, Line):
|
||||
@ -206,55 +184,55 @@ def create_window(canvas, title, zoom=1.0):
|
||||
b.connect('clicked', on_clicked)
|
||||
v.add(b)
|
||||
|
||||
b = Gtk.Button('Delete focused')
|
||||
b = gtk.Button('Delete focused')
|
||||
|
||||
def on_clicked(button):
|
||||
if view.focused_item:
|
||||
canvas.remove(view.focused_item)
|
||||
# print 'items:', canvas.get_all_items()
|
||||
#print 'items:', canvas.get_all_items()
|
||||
|
||||
b.connect('clicked', on_clicked)
|
||||
v.add(b)
|
||||
|
||||
v.add(Gtk.Label(label='State:'))
|
||||
b = Gtk.ToggleButton('Record')
|
||||
v.add(gtk.Label('State:'))
|
||||
b = gtk.ToggleButton('Record')
|
||||
|
||||
def on_toggled(button):
|
||||
global undo_list
|
||||
if button.get_active():
|
||||
print('start recording')
|
||||
print 'start recording'
|
||||
del undo_list[:]
|
||||
state.subscribers.add(undo_handler)
|
||||
else:
|
||||
print('stop recording')
|
||||
print 'stop recording'
|
||||
state.subscribers.remove(undo_handler)
|
||||
|
||||
b.connect('toggled', on_toggled)
|
||||
v.add(b)
|
||||
|
||||
b = Gtk.Button('Play back')
|
||||
|
||||
b = gtk.Button('Play back')
|
||||
|
||||
def on_clicked(self):
|
||||
global undo_list
|
||||
apply_me = list(undo_list)
|
||||
del undo_list[:]
|
||||
print('Actions on the undo stack:', len(apply_me))
|
||||
print 'Actions on the undo stack:', len(apply_me)
|
||||
apply_me.reverse()
|
||||
saveapply = state.saveapply
|
||||
for event in apply_me:
|
||||
print('Undo: invoking', event)
|
||||
print 'Undo: invoking', event
|
||||
saveapply(*event)
|
||||
print('New undo stack size:', len(undo_list))
|
||||
print 'New undo stack size:', len(undo_list)
|
||||
# Visualize each event:
|
||||
# while Gtk.events_pending():
|
||||
# Gtk.main_iteration()
|
||||
#while gtk.events_pending():
|
||||
# gtk.main_iteration()
|
||||
|
||||
b.connect('clicked', on_clicked)
|
||||
v.add(b)
|
||||
|
||||
v.add(Gtk.Label(label='Export:'))
|
||||
v.add(gtk.Label('Export:'))
|
||||
|
||||
b = Gtk.Button('Write demo.png')
|
||||
b = gtk.Button('Write demo.png')
|
||||
|
||||
def on_clicked(button):
|
||||
svgview = View(view.canvas)
|
||||
@ -267,7 +245,7 @@ def create_window(canvas, title, zoom=1.0):
|
||||
svgview.update_bounding_box(tmpcr)
|
||||
tmpcr.show_page()
|
||||
tmpsurface.flush()
|
||||
|
||||
|
||||
w, h = svgview.bounding_box.width, svgview.bounding_box.height
|
||||
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, int(w), int(h))
|
||||
cr = cairo.Context(surface)
|
||||
@ -282,7 +260,7 @@ def create_window(canvas, title, zoom=1.0):
|
||||
b.connect('clicked', on_clicked)
|
||||
v.add(b)
|
||||
|
||||
b = Gtk.Button('Write demo.svg')
|
||||
b = gtk.Button('Write demo.svg')
|
||||
|
||||
def on_clicked(button):
|
||||
svgview = View(view.canvas)
|
||||
@ -295,7 +273,7 @@ def create_window(canvas, title, zoom=1.0):
|
||||
svgview.update_bounding_box(tmpcr)
|
||||
tmpcr.show_page()
|
||||
tmpsurface.flush()
|
||||
|
||||
|
||||
w, h = svgview.bounding_box.width, svgview.bounding_box.height
|
||||
surface = cairo.SVGSurface('demo.svg', w, h)
|
||||
cr = cairo.Context(surface)
|
||||
@ -308,7 +286,8 @@ def create_window(canvas, title, zoom=1.0):
|
||||
b.connect('clicked', on_clicked)
|
||||
v.add(b)
|
||||
|
||||
b = Gtk.Button('Dump QTree')
|
||||
|
||||
b = gtk.Button('Dump QTree')
|
||||
|
||||
def on_clicked(button, li):
|
||||
view._qtree.dump()
|
||||
@ -316,12 +295,13 @@ def create_window(canvas, title, zoom=1.0):
|
||||
b.connect('clicked', on_clicked, [0])
|
||||
v.add(b)
|
||||
|
||||
b = Gtk.Button('Pickle (save)')
|
||||
|
||||
b = gtk.Button('Pickle (save)')
|
||||
|
||||
def on_clicked(button, li):
|
||||
f = open('demo.pickled', 'w')
|
||||
try:
|
||||
import six.moves.cPickle as pickle
|
||||
import cPickle as pickle
|
||||
pickle.dump(view.canvas, f)
|
||||
finally:
|
||||
f.close()
|
||||
@ -329,12 +309,13 @@ def create_window(canvas, title, zoom=1.0):
|
||||
b.connect('clicked', on_clicked, [0])
|
||||
v.add(b)
|
||||
|
||||
b = Gtk.Button('Unpickle (load)')
|
||||
|
||||
b = gtk.Button('Unpickle (load)')
|
||||
|
||||
def on_clicked(button, li):
|
||||
f = open('demo.pickled', 'r')
|
||||
try:
|
||||
import six.moves.cPickle as pickle
|
||||
import cPickle as pickle
|
||||
canvas = pickle.load(f)
|
||||
canvas.update_now()
|
||||
finally:
|
||||
@ -344,23 +325,25 @@ def create_window(canvas, title, zoom=1.0):
|
||||
b.connect('clicked', on_clicked, [0])
|
||||
v.add(b)
|
||||
|
||||
b = Gtk.Button('Unpickle (in place)')
|
||||
|
||||
b = gtk.Button('Unpickle (in place)')
|
||||
|
||||
def on_clicked(button, li):
|
||||
f = open('demo.pickled', 'r')
|
||||
try:
|
||||
import six.moves.cPickle as pickle
|
||||
import cPickle as pickle
|
||||
canvas = pickle.load(f)
|
||||
finally:
|
||||
f.close()
|
||||
# [i.request_update() for i in canvas.get_all_items()]
|
||||
#[i.request_update() for i in canvas.get_all_items()]
|
||||
canvas.update_now()
|
||||
view.canvas = canvas
|
||||
|
||||
b.connect('clicked', on_clicked, [0])
|
||||
v.add(b)
|
||||
|
||||
b = Gtk.Button('Reattach (in place)')
|
||||
|
||||
b = gtk.Button('Reattach (in place)')
|
||||
|
||||
def on_clicked(button, li):
|
||||
view.canvas = None
|
||||
@ -369,42 +352,42 @@ def create_window(canvas, title, zoom=1.0):
|
||||
b.connect('clicked', on_clicked, [0])
|
||||
v.add(b)
|
||||
|
||||
|
||||
# Add the actual View:
|
||||
|
||||
view.canvas = canvas
|
||||
view.zoom(zoom)
|
||||
view.set_size_request(150, 120)
|
||||
s = Gtk.ScrolledWindow()
|
||||
s.set_policy(Gtk.PolicyType.ALWAYS, Gtk.PolicyType.ALWAYS)
|
||||
s = gtk.ScrolledWindow()
|
||||
s.add(view)
|
||||
h.add(s)
|
||||
|
||||
w.show_all()
|
||||
|
||||
w.connect('destroy', Gtk.main_quit)
|
||||
|
||||
w.connect('destroy', gtk.main_quit)
|
||||
|
||||
def handle_changed(view, item, what):
|
||||
print(what, 'changed: ', item)
|
||||
print what, 'changed: ', item
|
||||
|
||||
view.connect('focus-changed', handle_changed, 'focus')
|
||||
view.connect('hover-changed', handle_changed, 'hover')
|
||||
view.connect('selection-changed', handle_changed, 'selection')
|
||||
|
||||
|
||||
|
||||
def create_canvas(c=None):
|
||||
if not c:
|
||||
c = Canvas()
|
||||
b = MyBox()
|
||||
b=MyBox()
|
||||
b.min_width = 20
|
||||
b.min_height = 30
|
||||
print('box', b)
|
||||
b.matrix = (1.0, 0.0, 0.0, 1, 20, 20)
|
||||
print 'box', b
|
||||
b.matrix=(1.0, 0.0, 0.0, 1, 20,20)
|
||||
b.width = b.height = 40
|
||||
c.add(b)
|
||||
|
||||
bb = Box()
|
||||
print('box', bb)
|
||||
bb.matrix = (1.0, 0.0, 0.0, 1, 10, 10)
|
||||
bb=Box()
|
||||
print 'box', bb
|
||||
bb.matrix=(1.0, 0.0, 0.0, 1, 10,10)
|
||||
c.add(bb, parent=b)
|
||||
|
||||
fl = FatLine()
|
||||
@ -420,14 +403,14 @@ def create_canvas(c=None):
|
||||
|
||||
# AJM: extra boxes:
|
||||
bb = Box()
|
||||
print('rotated box', bb)
|
||||
bb.matrix.rotate(math.pi / 1.567)
|
||||
print 'rotated box', bb
|
||||
bb.matrix.rotate(math.pi/1.567)
|
||||
c.add(bb, parent=b)
|
||||
# for i in xrange(10):
|
||||
# bb=Box()
|
||||
# print 'box', bb
|
||||
# bb.matrix.rotate(math.pi/4.0 * i / 10.0)
|
||||
# c.add(bb, parent=b)
|
||||
# for i in xrange(10):
|
||||
# bb=Box()
|
||||
# print 'box', bb
|
||||
# bb.matrix.rotate(math.pi/4.0 * i / 10.0)
|
||||
# c.add(bb, parent=b)
|
||||
|
||||
b = PortoBox(60, 60)
|
||||
b.min_width = 40
|
||||
@ -436,11 +419,11 @@ def create_canvas(c=None):
|
||||
c.add(b)
|
||||
|
||||
t = UnderlineText()
|
||||
t.matrix.translate(70, 30)
|
||||
t.matrix.translate(70,30)
|
||||
c.add(t)
|
||||
|
||||
t = MyText('Single line')
|
||||
t.matrix.translate(70, 70)
|
||||
t.matrix.translate(70,70)
|
||||
c.add(t)
|
||||
|
||||
l = MyLine()
|
||||
@ -454,54 +437,52 @@ def create_canvas(c=None):
|
||||
off_y = 0
|
||||
for align_x in (-1, 0, 1):
|
||||
for align_y in (-1, 0, 1):
|
||||
t = MyText('Aligned text %d/%d' % (align_x, align_y),
|
||||
align_x=align_x, align_y=align_y)
|
||||
t=MyText('Aligned text %d/%d' % (align_x, align_y),
|
||||
align_x=align_x, align_y=align_y)
|
||||
t.matrix.translate(120, 200 + off_y)
|
||||
off_y += 30
|
||||
c.add(t)
|
||||
|
||||
t = MyText('Multiple\nlines', multiline=True)
|
||||
t.matrix.translate(70, 100)
|
||||
t=MyText('Multiple\nlines', multiline = True)
|
||||
t.matrix.translate(70,100)
|
||||
c.add(t)
|
||||
|
||||
return c
|
||||
|
||||
|
||||
def main():
|
||||
#
|
||||
# State handling (a.k.a. undo handlers)
|
||||
#
|
||||
##
|
||||
## State handling (a.k.a. undo handlers)
|
||||
##
|
||||
|
||||
# First, activate the revert handler:
|
||||
state.observers.add(state.revert_handler)
|
||||
|
||||
def print_handler(event):
|
||||
print('event:', event)
|
||||
print 'event:', event
|
||||
|
||||
c = Canvas()
|
||||
c=Canvas()
|
||||
|
||||
create_window(c, 'View created before')
|
||||
|
||||
create_canvas(c)
|
||||
|
||||
# state.subscribers.add(print_handler)
|
||||
#state.subscribers.add(print_handler)
|
||||
|
||||
#
|
||||
# Start the main application
|
||||
#
|
||||
##
|
||||
## Start the main application
|
||||
##
|
||||
|
||||
create_window(c, 'View created after')
|
||||
|
||||
Gtk.main()
|
||||
gtk.main()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
|
||||
if '-p' in sys.argv:
|
||||
print('Profiling...')
|
||||
print 'Profiling...'
|
||||
import hotshot, hotshot.stats
|
||||
|
||||
prof = hotshot.Profile('demo-gaphas.prof')
|
||||
prof.runcall(main)
|
||||
prof.close()
|
||||
|
@ -1,6 +1,5 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
from __future__ import absolute_import
|
||||
from demo import *
|
||||
|
||||
|
||||
@ -11,7 +10,7 @@ if __name__ == '__main__':
|
||||
cProfile.run('main()', 'demo-gaphas.prof')
|
||||
p = pstats.Stats('demo-gaphas.prof')
|
||||
p.strip_dirs().sort_stats('time').print_stats(40)
|
||||
except ImportError as ex:
|
||||
except ImportError, ex:
|
||||
import hotshot, hotshot.stats
|
||||
import gc
|
||||
prof = hotshot.Profile('demo-gaphas.prof')
|
||||
|
43
ez_setup.py
43
ez_setup.py
@ -1,23 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2006-2017 Arjan Molenaar <gaphor@gmail.com>
|
||||
# Dan Yeaw <dan@yeaw.me>
|
||||
#
|
||||
# This file is part of Gaphas.
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Library General Public License as published by the Free
|
||||
# Software Foundation; either version 2 of the License, or (at your option) any
|
||||
# later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Library General Public License
|
||||
# along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#!python
|
||||
"""Bootstrap setuptools installation
|
||||
|
||||
If you want to use setuptools in your package's setup.py, just include this
|
||||
@ -32,8 +13,6 @@ the appropriate options to ``use_setuptools()``.
|
||||
|
||||
This file can also be run as a script to install or upgrade setuptools.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from __future__ import print_function
|
||||
import sys
|
||||
DEFAULT_VERSION = "0.6c11"
|
||||
DEFAULT_URL = "http://pypi.python.org/packages/%s/s/setuptools/" % sys.version[:3]
|
||||
@ -91,10 +70,10 @@ def _validate_md5(egg_name, data):
|
||||
if egg_name in md5_data:
|
||||
digest = md5(data).hexdigest()
|
||||
if digest != md5_data[egg_name]:
|
||||
print((
|
||||
print >>sys.stderr, (
|
||||
"md5 validation of %s failed! (Possible download problem?)"
|
||||
% egg_name
|
||||
), file=sys.stderr)
|
||||
)
|
||||
sys.exit(2)
|
||||
return data
|
||||
|
||||
@ -124,14 +103,14 @@ def use_setuptools(
|
||||
return do_download()
|
||||
try:
|
||||
pkg_resources.require("setuptools>="+version); return
|
||||
except pkg_resources.VersionConflict as e:
|
||||
except pkg_resources.VersionConflict, e:
|
||||
if was_imported:
|
||||
print((
|
||||
print >>sys.stderr, (
|
||||
"The required version of setuptools (>=%s) is not available, and\n"
|
||||
"can't be installed while this script is running. Please install\n"
|
||||
" a more recent version first, using 'easy_install -U setuptools'."
|
||||
"\n\n(Currently using %r)"
|
||||
) % (version, e.args[0]), file=sys.stderr)
|
||||
) % (version, e.args[0])
|
||||
sys.exit(2)
|
||||
else:
|
||||
del pkg_resources, sys.modules['pkg_resources'] # reload ok
|
||||
@ -237,10 +216,10 @@ def main(argv, version=DEFAULT_VERSION):
|
||||
os.unlink(egg)
|
||||
else:
|
||||
if setuptools.__version__ == '0.0.1':
|
||||
print((
|
||||
print >>sys.stderr, (
|
||||
"You have an obsolete version of setuptools installed. Please\n"
|
||||
"remove it from your system entirely before rerunning this script."
|
||||
), file=sys.stderr)
|
||||
)
|
||||
sys.exit(2)
|
||||
|
||||
req = "setuptools>="+version
|
||||
@ -259,8 +238,8 @@ def main(argv, version=DEFAULT_VERSION):
|
||||
from setuptools.command.easy_install import main
|
||||
main(argv)
|
||||
else:
|
||||
print("Setuptools version",version,"or greater has been installed.")
|
||||
print('(Run "ez_setup.py -U setuptools" to reinstall or upgrade.)')
|
||||
print "Setuptools version",version,"or greater has been installed."
|
||||
print '(Run "ez_setup.py -U setuptools" to reinstall or upgrade.)'
|
||||
|
||||
def update_md5(filenames):
|
||||
"""Update our built-in md5 registry"""
|
||||
@ -283,7 +262,7 @@ def update_md5(filenames):
|
||||
|
||||
match = re.search("\nmd5_data = {\n([^}]+)}", src)
|
||||
if not match:
|
||||
print("Internal error!", file=sys.stderr)
|
||||
print >>sys.stderr, "Internal error!"
|
||||
sys.exit(2)
|
||||
|
||||
src = src[:match.start(1)] + repl + src[match.end(1):]
|
||||
|
@ -1,23 +1,3 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2006-2017 Arjan Molenaar <gaphor@gmail.com>
|
||||
# Dan Yeaw <dan@yeaw.me>
|
||||
#
|
||||
# This file is part of Gaphas.
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Library General Public License as published by the Free
|
||||
# Software Foundation; either version 2 of the License, or (at your option) any
|
||||
# later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Library General Public License
|
||||
# along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
Gaphas
|
||||
======
|
||||
@ -39,15 +19,13 @@ used in Gaphas instead of the multiplier (``*``). In both the ``Canvas`` and
|
||||
is used.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from gaphas.canvas import Canvas
|
||||
from gaphas.connector import Handle
|
||||
from gaphas.item import Item, Line, Element
|
||||
from gaphas.view import View, GtkView
|
||||
|
||||
__version__ = "$Revision$"
|
||||
# $HeadURL$
|
||||
|
||||
|
||||
from canvas import Canvas
|
||||
from connector import Handle
|
||||
from item import Item, Line, Element
|
||||
from view import View, GtkView
|
||||
|
||||
# vi:sw=4:et:ai
|
||||
|
@ -1,23 +1,3 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2009-2017 Arjan Molenaar <gaphor@gmail.com>
|
||||
# Dan Yeaw <dan@yeaw.me>
|
||||
#
|
||||
# This file is part of Gaphas.
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Library General Public License as published by the Free
|
||||
# Software Foundation; either version 2 of the License, or (at your option) any
|
||||
# later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Library General Public License
|
||||
# along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
Defines aspects for Items. Aspects form intermediate items between tools
|
||||
and items.
|
||||
@ -29,11 +9,9 @@ The simplegeneric module is dispatching opnly based on the first argument.
|
||||
For Gaphas that's enough.
|
||||
"""
|
||||
|
||||
from gi.repository import Gdk
|
||||
|
||||
import gtk.gdk
|
||||
from simplegeneric import generic
|
||||
|
||||
from gaphas.item import Element
|
||||
from gaphas.item import Item, Element
|
||||
|
||||
|
||||
class ItemFinder(object):
|
||||
@ -155,10 +133,10 @@ HandleSelection = generic(ItemHandleSelection)
|
||||
@HandleSelection.when_type(Element)
|
||||
class ElementHandleSelection(ItemHandleSelection):
|
||||
CURSORS = (
|
||||
Gdk.Cursor(Gdk.CursorType.TOP_LEFT_CORNER),
|
||||
Gdk.Cursor(Gdk.CursorType.TOP_RIGHT_CORNER),
|
||||
Gdk.Cursor(Gdk.CursorType.BOTTOM_RIGHT_CORNER),
|
||||
Gdk.Cursor(Gdk.CursorType.BOTTOM_LEFT_CORNER))
|
||||
gtk.gdk.Cursor(gtk.gdk.TOP_LEFT_CORNER),
|
||||
gtk.gdk.Cursor(gtk.gdk.TOP_RIGHT_CORNER),
|
||||
gtk.gdk.Cursor(gtk.gdk.BOTTOM_RIGHT_CORNER),
|
||||
gtk.gdk.Cursor(gtk.gdk.BOTTOM_LEFT_CORNER) )
|
||||
|
||||
def select(self):
|
||||
index = self.item.handles().index(self.handle)
|
||||
@ -167,10 +145,11 @@ class ElementHandleSelection(ItemHandleSelection):
|
||||
|
||||
def unselect(self):
|
||||
from view import DEFAULT_CURSOR
|
||||
cursor = Gdk.Cursor(DEFAULT_CURSOR)
|
||||
cursor = gtk.gdk.Cursor(DEFAULT_CURSOR)
|
||||
self.view.window.set_cursor(cursor)
|
||||
|
||||
|
||||
|
||||
class ItemHandleInMotion(object):
|
||||
"""
|
||||
Move a handle (role is applied to the handle)
|
||||
@ -228,7 +207,7 @@ class ItemHandleInMotion(object):
|
||||
return None
|
||||
|
||||
connectable, port, glue_pos = \
|
||||
view.get_port_at_point(pos, distance=distance, exclude=(item,))
|
||||
view.get_port_at_point(pos, distance=distance, exclude=(item,))
|
||||
|
||||
# check if item and found item can be connected on closest port
|
||||
if port is not None:
|
||||
@ -250,7 +229,8 @@ HandleInMotion = generic(ItemHandleInMotion)
|
||||
|
||||
|
||||
class ItemConnector(object):
|
||||
GLUE_DISTANCE = 10 # Glue distance in view points
|
||||
|
||||
GLUE_DISTANCE = 10 # Glue distance in view points
|
||||
|
||||
def __init__(self, item, handle):
|
||||
self.item = item
|
||||
@ -292,6 +272,7 @@ class ItemConnector(object):
|
||||
|
||||
self.connect_handle(sink)
|
||||
|
||||
|
||||
def connect_handle(self, sink, callback=None):
|
||||
"""
|
||||
Create constraint between handle of a line and port of connectable
|
||||
@ -310,7 +291,8 @@ class ItemConnector(object):
|
||||
constraint = sink.port.constraint(canvas, item, handle, sink.item)
|
||||
|
||||
canvas.connect_item(item, handle, sink.item, sink.port,
|
||||
constraint, callback=callback)
|
||||
constraint, callback=callback)
|
||||
|
||||
|
||||
def disconnect(self):
|
||||
"""
|
||||
@ -351,9 +333,9 @@ class ItemConnectionSink(object):
|
||||
ConnectionSink = generic(ItemConnectionSink)
|
||||
|
||||
|
||||
#
|
||||
# Painter aspects
|
||||
#
|
||||
##
|
||||
## Painter aspects
|
||||
##
|
||||
|
||||
class ItemPaintFocused(object):
|
||||
"""
|
||||
|
120
gaphas/canvas.py
120
gaphas/canvas.py
@ -1,25 +1,3 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2006-2017 Antony N. Pavlov <antony@niisi.msk.ru>
|
||||
# Arjan Molenaar <gaphor@gmail.com>
|
||||
# Artur Wroblewski <wrobell@pld-linux.org>
|
||||
# Dan Yeaw <dan@yeaw.me>
|
||||
#
|
||||
# This file is part of Gaphas.
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Library General Public License as published by the Free
|
||||
# Software Foundation; either version 2 of the License, or (at your option) any
|
||||
# later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Library General Public License
|
||||
# along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
A Canvas owns a set of Items and acts as a container for both the items
|
||||
and a constraint solver.
|
||||
@ -49,20 +27,19 @@ To get connecting items (i.e. all lines connected to a class)::
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
__version__ = "$Revision$"
|
||||
# $HeadURL$
|
||||
|
||||
import logging
|
||||
from collections import namedtuple
|
||||
import logging
|
||||
|
||||
from cairo import Matrix
|
||||
from six.moves import map
|
||||
from six.moves import range
|
||||
|
||||
from gaphas import tree
|
||||
from gaphas import solver
|
||||
from gaphas import table
|
||||
from gaphas import tree
|
||||
from gaphas.decorators import nonrecursive, async, PRIORITY_HIGH_IDLE
|
||||
from .state import observed, reversible_method, reversible_pair
|
||||
from state import observed, reversible_method, reversible_pair
|
||||
|
||||
|
||||
#
|
||||
# Information about two connected items
|
||||
@ -75,7 +52,7 @@ from .state import observed, reversible_method, reversible_pair
|
||||
# - callback: optional disconnection callback
|
||||
#
|
||||
Connection = namedtuple('Connection',
|
||||
'item handle connected port constraint callback')
|
||||
'item handle connected port constraint callback')
|
||||
|
||||
|
||||
class ConnectionError(Exception):
|
||||
@ -103,7 +80,7 @@ class Context(object):
|
||||
self.__dict__.update(**kwargs)
|
||||
|
||||
def __setattr__(self, key, value):
|
||||
raise AttributeError('context is not writable')
|
||||
raise AttributeError, 'context is not writable'
|
||||
|
||||
|
||||
class Canvas(object):
|
||||
@ -114,7 +91,7 @@ class Canvas(object):
|
||||
def __init__(self):
|
||||
self._tree = tree.Tree()
|
||||
self._solver = solver.Solver()
|
||||
self._connections = table.Table(Connection, list(range(4)))
|
||||
self._connections = table.Table(Connection, range(4))
|
||||
self._dirty_items = set()
|
||||
self._dirty_matrix_items = set()
|
||||
self._dirty_index = False
|
||||
@ -123,6 +100,7 @@ class Canvas(object):
|
||||
|
||||
solver = property(lambda s: s._solver)
|
||||
|
||||
|
||||
@observed
|
||||
def add(self, item, parent=None, index=None):
|
||||
"""
|
||||
@ -147,6 +125,7 @@ class Canvas(object):
|
||||
|
||||
self.request_update(item)
|
||||
|
||||
|
||||
@observed
|
||||
def _remove(self, item):
|
||||
"""
|
||||
@ -159,6 +138,7 @@ class Canvas(object):
|
||||
self._dirty_items.discard(item)
|
||||
self._dirty_matrix_items.discard(item)
|
||||
|
||||
|
||||
def remove(self, item):
|
||||
"""
|
||||
Remove item from the canvas.
|
||||
@ -179,7 +159,8 @@ class Canvas(object):
|
||||
|
||||
reversible_pair(add, _remove,
|
||||
bind1={'parent': lambda self, item: self.get_parent(item),
|
||||
'index': lambda self, item: self._tree.get_siblings(item).index(item)})
|
||||
'index': lambda self, item: self._tree.get_siblings(item).index(item) })
|
||||
|
||||
|
||||
@observed
|
||||
def reparent(self, item, parent, index=None):
|
||||
@ -192,7 +173,8 @@ class Canvas(object):
|
||||
|
||||
reversible_method(reparent, reverse=reparent,
|
||||
bind={'parent': lambda self, item: self.get_parent(item),
|
||||
'index': lambda self, item: self._tree.get_siblings(item).index(item)})
|
||||
'index': lambda self, item: self._tree.get_siblings(item).index(item) })
|
||||
|
||||
|
||||
def get_all_items(self):
|
||||
"""
|
||||
@ -209,6 +191,7 @@ class Canvas(object):
|
||||
"""
|
||||
return self._tree.nodes
|
||||
|
||||
|
||||
def get_root_items(self):
|
||||
"""
|
||||
Return the root items of the canvas.
|
||||
@ -226,6 +209,7 @@ class Canvas(object):
|
||||
"""
|
||||
return self._tree.get_children(None)
|
||||
|
||||
|
||||
def get_parent(self, item):
|
||||
"""
|
||||
See `tree.Tree.get_parent()`.
|
||||
@ -242,6 +226,7 @@ class Canvas(object):
|
||||
"""
|
||||
return self._tree.get_parent(item)
|
||||
|
||||
|
||||
def get_ancestors(self, item):
|
||||
"""
|
||||
See `tree.Tree.get_ancestors()`.
|
||||
@ -263,6 +248,7 @@ class Canvas(object):
|
||||
"""
|
||||
return self._tree.get_ancestors(item)
|
||||
|
||||
|
||||
def get_children(self, item):
|
||||
"""
|
||||
See `tree.Tree.get_children()`.
|
||||
@ -284,6 +270,7 @@ class Canvas(object):
|
||||
"""
|
||||
return self._tree.get_children(item)
|
||||
|
||||
|
||||
def get_all_children(self, item):
|
||||
"""
|
||||
See `tree.Tree.get_all_children()`.
|
||||
@ -305,6 +292,7 @@ class Canvas(object):
|
||||
"""
|
||||
return self._tree.get_all_children(item)
|
||||
|
||||
|
||||
@observed
|
||||
def connect_item(self, item, handle, connected, port, constraint=None, callback=None):
|
||||
"""
|
||||
@ -340,6 +328,7 @@ class Canvas(object):
|
||||
if constraint:
|
||||
self._solver.add_constraint(constraint)
|
||||
|
||||
|
||||
def disconnect_item(self, item, handle=None):
|
||||
"""
|
||||
Disconnect the connections of an item. If handle is not None, only the
|
||||
@ -349,6 +338,7 @@ class Canvas(object):
|
||||
for cinfo in list(self._connections.query(item=item, handle=handle)):
|
||||
self._disconnect_item(*cinfo)
|
||||
|
||||
|
||||
@observed
|
||||
def _disconnect_item(self, item, handle, connected, port, constraint, callback):
|
||||
"""
|
||||
@ -365,6 +355,7 @@ class Canvas(object):
|
||||
|
||||
reversible_pair(connect_item, _disconnect_item)
|
||||
|
||||
|
||||
def remove_connections_to_item(self, item):
|
||||
"""
|
||||
Remove all connections (handles connected to and constraints)
|
||||
@ -380,6 +371,7 @@ class Canvas(object):
|
||||
for cinfo in list(self._connections.query(connected=item)):
|
||||
disconnect_item(*cinfo)
|
||||
|
||||
|
||||
@observed
|
||||
def reconnect_item(self, item, handle, constraint=None):
|
||||
"""
|
||||
@ -428,7 +420,7 @@ class Canvas(object):
|
||||
# checks:
|
||||
cinfo = self.get_connection(handle)
|
||||
if not cinfo:
|
||||
raise ValueError('No data available for item "%s" and handle "%s"' % (item, handle))
|
||||
raise ValueError, 'No data available for item "%s" and handle "%s"' % (item, handle)
|
||||
|
||||
if cinfo.constraint:
|
||||
self._solver.remove_constraint(cinfo.constraint)
|
||||
@ -439,7 +431,8 @@ class Canvas(object):
|
||||
self._solver.add_constraint(constraint)
|
||||
|
||||
reversible_method(reconnect_item, reverse=reconnect_item,
|
||||
bind={'constraint': lambda self, item, handle: self.get_connection(handle).constraint})
|
||||
bind={'constraint': lambda self, item, handle: self.get_connection(handle).constraint })
|
||||
|
||||
|
||||
def get_connection(self, handle):
|
||||
"""
|
||||
@ -460,10 +453,11 @@ class Canvas(object):
|
||||
>>> c.get_connection(ii.handles()[0]) # doctest: +ELLIPSIS
|
||||
"""
|
||||
try:
|
||||
return next(self._connections.query(handle=handle))
|
||||
except StopIteration as ex:
|
||||
return self._connections.query(handle=handle).next()
|
||||
except StopIteration, ex:
|
||||
return None
|
||||
|
||||
|
||||
def get_connections(self, item=None, handle=None, connected=None, port=None):
|
||||
"""
|
||||
Return an iterator of connection information.
|
||||
@ -497,9 +491,10 @@ class Canvas(object):
|
||||
[Connection(item=<gaphas.item.Line object at 0x...]
|
||||
"""
|
||||
return self._connections.query(item=item,
|
||||
handle=handle,
|
||||
connected=connected,
|
||||
port=port)
|
||||
handle=handle,
|
||||
connected=connected,
|
||||
port=port)
|
||||
|
||||
|
||||
def sort(self, items, reverse=False):
|
||||
"""
|
||||
@ -523,6 +518,7 @@ class Canvas(object):
|
||||
"""
|
||||
return self._tree.sort(items, index_key='_canvas_index', reverse=reverse)
|
||||
|
||||
|
||||
def get_matrix_i2c(self, item, calculate=False):
|
||||
"""
|
||||
Get the Item to Canvas matrix for ``item``.
|
||||
@ -539,6 +535,7 @@ class Canvas(object):
|
||||
self.update_matrix(item)
|
||||
return item._matrix_i2c
|
||||
|
||||
|
||||
def get_matrix_c2i(self, item, calculate=False):
|
||||
"""
|
||||
Get the Canvas to Item matrix for ``item``.
|
||||
@ -557,10 +554,11 @@ class Canvas(object):
|
||||
# Fall back to old behaviour
|
||||
return i2c * c2i
|
||||
|
||||
|
||||
@observed
|
||||
def request_update(self, item, update=True, matrix=True):
|
||||
"""
|
||||
Set an update request for the item.
|
||||
Set an update request for the item.
|
||||
|
||||
>>> c = Canvas()
|
||||
>>> from gaphas import item
|
||||
@ -583,12 +581,14 @@ class Canvas(object):
|
||||
|
||||
reversible_method(request_update, reverse=request_update)
|
||||
|
||||
|
||||
def request_matrix_update(self, item):
|
||||
"""
|
||||
Schedule only the matrix to be updated.
|
||||
"""
|
||||
self.request_update(item, update=False, matrix=True)
|
||||
|
||||
|
||||
def require_update(self):
|
||||
"""
|
||||
Returns ``True`` or ``False`` depending on if an update is needed.
|
||||
@ -607,6 +607,7 @@ class Canvas(object):
|
||||
"""
|
||||
return bool(self._dirty_items)
|
||||
|
||||
|
||||
@async(single=True, priority=PRIORITY_HIGH_IDLE)
|
||||
def update(self):
|
||||
"""
|
||||
@ -615,17 +616,20 @@ class Canvas(object):
|
||||
"""
|
||||
self.update_now()
|
||||
|
||||
|
||||
def _pre_update_items(self, items, cr):
|
||||
context_map = dict()
|
||||
c = Context(cairo=cr)
|
||||
for item in items:
|
||||
item.pre_update(c)
|
||||
|
||||
|
||||
def _post_update_items(self, items, cr):
|
||||
c = Context(cairo=cr)
|
||||
for item in items:
|
||||
item.post_update(c)
|
||||
|
||||
|
||||
def _extend_dirty_items(self, dirty_items):
|
||||
# item's can be marked dirty due to external constraints solving
|
||||
if self._dirty_items:
|
||||
@ -671,8 +675,7 @@ class Canvas(object):
|
||||
self.update_constraints(dirty_matrix_items)
|
||||
|
||||
# no matrix can change during constraint solving
|
||||
assert not self._dirty_matrix_items, 'No matrices may have been marked dirty (%s)' % (
|
||||
self._dirty_matrix_items,)
|
||||
assert not self._dirty_matrix_items, 'No matrices may have been marked dirty (%s)' % (self._dirty_matrix_items,)
|
||||
|
||||
# item's can be marked dirty due to external constraints solving
|
||||
extend_dirty_items(dirty_items)
|
||||
@ -696,14 +699,15 @@ class Canvas(object):
|
||||
|
||||
self._post_update_items(dirty_items, cr)
|
||||
|
||||
except Exception as e:
|
||||
except Exception, e:
|
||||
logging.error('Error while updating canvas', exc_info=e)
|
||||
|
||||
assert len(self._dirty_items) == 0 and len(self._dirty_matrix_items) == 0, \
|
||||
'dirty: %s; matrix: %s' % (self._dirty_items, self._dirty_matrix_items)
|
||||
'dirty: %s; matrix: %s' % (self._dirty_items, self._dirty_matrix_items)
|
||||
|
||||
self._update_views(dirty_items, dirty_matrix_items)
|
||||
|
||||
|
||||
def update_matrices(self, items):
|
||||
"""
|
||||
Recalculate matrices of the items. Items' children matrices are
|
||||
@ -727,6 +731,7 @@ class Canvas(object):
|
||||
|
||||
return changed
|
||||
|
||||
|
||||
def update_matrix(self, item, parent=None):
|
||||
"""
|
||||
Update matrices of an item.
|
||||
@ -750,6 +755,7 @@ class Canvas(object):
|
||||
item._matrix_c2i = Matrix(*item._matrix_i2c)
|
||||
item._matrix_c2i.invert()
|
||||
|
||||
|
||||
def update_constraints(self, items):
|
||||
"""
|
||||
Update constraints. Also variables may be marked as dirty before the
|
||||
@ -765,6 +771,7 @@ class Canvas(object):
|
||||
# solve all constraints
|
||||
self._solver.solve()
|
||||
|
||||
|
||||
def _normalize(self, items):
|
||||
"""
|
||||
Update handle positions of items, so the first handle is always
|
||||
@ -804,6 +811,7 @@ class Canvas(object):
|
||||
|
||||
return dirty_matrix_items
|
||||
|
||||
|
||||
def update_index(self):
|
||||
"""
|
||||
Provide each item in the canvas with an index attribute. This makes
|
||||
@ -811,6 +819,7 @@ class Canvas(object):
|
||||
"""
|
||||
self._tree.index_nodes('_canvas_index')
|
||||
|
||||
|
||||
def register_view(self, view):
|
||||
"""
|
||||
Register a view on this canvas. This method is called when setting
|
||||
@ -818,6 +827,7 @@ class Canvas(object):
|
||||
"""
|
||||
self._registered_views.add(view)
|
||||
|
||||
|
||||
def unregister_view(self, view):
|
||||
"""
|
||||
Unregister a view on this canvas. This method is called when setting
|
||||
@ -825,6 +835,7 @@ class Canvas(object):
|
||||
"""
|
||||
self._registered_views.discard(view)
|
||||
|
||||
|
||||
def _update_views(self, dirty_items=(), dirty_matrix_items=(), removed_items=()):
|
||||
"""
|
||||
Send an update notification to all registered views.
|
||||
@ -832,6 +843,7 @@ class Canvas(object):
|
||||
for v in self._registered_views:
|
||||
v.request_update(dirty_items, dirty_matrix_items, removed_items)
|
||||
|
||||
|
||||
def _obtain_cairo_context(self):
|
||||
"""
|
||||
Try to obtain a Cairo context.
|
||||
@ -855,6 +867,7 @@ class Canvas(object):
|
||||
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 0, 0)
|
||||
return cairo.Context(surface)
|
||||
|
||||
|
||||
def __getstate__(self):
|
||||
"""
|
||||
Persist canvas. Dirty item sets and views are not saved.
|
||||
@ -867,6 +880,7 @@ class Canvas(object):
|
||||
pass
|
||||
return d
|
||||
|
||||
|
||||
def __setstate__(self, state):
|
||||
"""
|
||||
Load persisted state.
|
||||
@ -878,7 +892,8 @@ class Canvas(object):
|
||||
self._dirty_matrix_items = set(self._tree.nodes)
|
||||
self._dirty_index = True
|
||||
self._registered_views = set()
|
||||
# self.update()
|
||||
#self.update()
|
||||
|
||||
|
||||
def project(self, item, *points):
|
||||
"""
|
||||
@ -888,7 +903,6 @@ class Canvas(object):
|
||||
returned. If there are more than one points, then tuple of
|
||||
projected points is returned.
|
||||
"""
|
||||
|
||||
def reg(cp):
|
||||
item._canvas_projections.add(cp)
|
||||
return cp
|
||||
@ -907,7 +921,7 @@ class VariableProjection(solver.Projection):
|
||||
|
||||
The value has been set in the "other" coordinate system. A callback is
|
||||
executed when the value changes.
|
||||
|
||||
|
||||
It's a simple Variable-like class, following the Projection protocol:
|
||||
|
||||
>>> def notify_me(val):
|
||||
@ -989,9 +1003,9 @@ class CanvasProjection(object):
|
||||
self._px, self._py = item.canvas.get_matrix_i2c(item).transform_point(x, y)
|
||||
return self._px, self._py
|
||||
|
||||
pos = property(lambda self: list(map(VariableProjection,
|
||||
self._point, self._get_value(),
|
||||
(self._on_change_x, self._on_change_y))))
|
||||
pos = property(lambda self: map(VariableProjection,
|
||||
self._point, self._get_value(),
|
||||
(self._on_change_x, self._on_change_y)))
|
||||
|
||||
def __getitem__(self, key):
|
||||
# Note: we can not use bound methods as callbacks, since that will
|
||||
@ -1007,7 +1021,7 @@ __test__ = {
|
||||
'Canvas.add': Canvas.add,
|
||||
'Canvas.remove': Canvas.remove,
|
||||
'Canvas.request_update': Canvas.request_update,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# vim:sw=4:et:ai
|
||||
|
@ -1,34 +1,11 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2008-2017 Arjan Molenaar <gaphor@gmail.com>
|
||||
# Artur Wroblewski <wrobell@pld-linux.org>
|
||||
# Dan Yeaw <dan@yeaw.me>
|
||||
#
|
||||
# This file is part of Gaphas.
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Library General Public License as published by the Free
|
||||
# Software Foundation; either version 2 of the License, or (at your option) any
|
||||
# later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Library General Public License
|
||||
# along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
Basic connectors such as Ports and Handles.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
__version__ = "$Revision: 2341 $"
|
||||
# $HeadURL: https://svn.devjavu.com/gaphor/gaphas/trunk/gaphas/item.py $
|
||||
|
||||
from gaphas.solver import solvable, NORMAL
|
||||
from gaphas.solver import solvable, WEAK, NORMAL, STRONG, VERY_STRONG
|
||||
from gaphas.state import observed, reversible_property
|
||||
from gaphas.geometry import distance_line_point, distance_point_point
|
||||
from gaphas.constraint import LineConstraint, PositionConstraint
|
||||
@ -86,7 +63,6 @@ class Position(object):
|
||||
|
||||
def __str__(self):
|
||||
return '<%s object on (%g, %g)>' % (self.__class__.__name__, float(self.x), float(self.y))
|
||||
|
||||
__repr__ = __str__
|
||||
|
||||
def __getitem__(self, index):
|
||||
@ -122,6 +98,7 @@ class Handle(object):
|
||||
self._movable = movable
|
||||
self._visible = True
|
||||
|
||||
|
||||
def _set_pos(self, pos):
|
||||
"""
|
||||
Shortcut for ``handle.pos.pos = pos``
|
||||
@ -157,27 +134,30 @@ class Handle(object):
|
||||
|
||||
y = property(deprecated(_get_y), deprecated(_set_y))
|
||||
|
||||
|
||||
@observed
|
||||
def _set_connectable(self, connectable):
|
||||
self._connectable = connectable
|
||||
|
||||
connectable = reversible_property(lambda s: s._connectable, _set_connectable)
|
||||
|
||||
|
||||
@observed
|
||||
def _set_movable(self, movable):
|
||||
self._movable = movable
|
||||
|
||||
movable = reversible_property(lambda s: s._movable, _set_movable)
|
||||
|
||||
|
||||
@observed
|
||||
def _set_visible(self, visible):
|
||||
self._visible = visible
|
||||
|
||||
visible = reversible_property(lambda s: s._visible, _set_visible)
|
||||
|
||||
|
||||
def __str__(self):
|
||||
return '<%s object on (%g, %g)>' % (self.__class__.__name__, float(self._pos.x), float(self._pos.y))
|
||||
|
||||
__repr__ = __str__
|
||||
|
||||
|
||||
@ -191,18 +171,21 @@ class Port(object):
|
||||
|
||||
self._connectable = True
|
||||
|
||||
|
||||
@observed
|
||||
def _set_connectable(self, connectable):
|
||||
self._connectable = connectable
|
||||
|
||||
connectable = reversible_property(lambda s: s._connectable, _set_connectable)
|
||||
|
||||
|
||||
def glue(self, pos):
|
||||
"""
|
||||
Get glue point on the port and distance to the port.
|
||||
"""
|
||||
raise NotImplemented('Glue method not implemented')
|
||||
|
||||
|
||||
def constraint(self, canvas, item, handle, glue_item):
|
||||
"""
|
||||
Create connection constraint between item's handle and glue item.
|
||||
@ -221,6 +204,7 @@ class LinePort(Port):
|
||||
self.start = start
|
||||
self.end = end
|
||||
|
||||
|
||||
def glue(self, pos):
|
||||
"""
|
||||
Get glue point on the port and distance to the port.
|
||||
@ -235,6 +219,7 @@ class LinePort(Port):
|
||||
d, pl = distance_line_point(self.start, self.end, pos)
|
||||
return pl, d
|
||||
|
||||
|
||||
def constraint(self, canvas, item, handle, glue_item):
|
||||
"""
|
||||
Create connection line constraint between item's handle and the
|
||||
@ -254,6 +239,7 @@ class PointPort(Port):
|
||||
super(PointPort, self).__init__()
|
||||
self.point = point
|
||||
|
||||
|
||||
def glue(self, pos):
|
||||
"""
|
||||
Get glue point on the port and distance to the port.
|
||||
@ -266,6 +252,7 @@ class PointPort(Port):
|
||||
d = distance_point_point(self.point, pos)
|
||||
return self.point, d
|
||||
|
||||
|
||||
def constraint(self, canvas, item, handle, glue_item):
|
||||
"""
|
||||
Return connection position constraint between item's handle and the
|
||||
@ -274,6 +261,7 @@ class PointPort(Port):
|
||||
origin = canvas.project(glue_item, self.point)
|
||||
point = canvas.project(item, handle.pos)
|
||||
c = PositionConstraint(origin, point)
|
||||
return c # PositionConstraint(origin, point)
|
||||
return c #PositionConstraint(origin, point)
|
||||
|
||||
|
||||
# vim: sw=4:et:ai
|
||||
|
@ -1,24 +1,3 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2006-2017 Arjan Molenaar <gaphor@gmail.com>
|
||||
# Artur Wroblewski <wrobell@pld-linux.org>
|
||||
# Dan Yeaw <dan@yeaw.me>
|
||||
#
|
||||
# This file is part of Gaphas.
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Library General Public License as published by the Free
|
||||
# Software Foundation; either version 2 of the License, or (at your option) any
|
||||
# later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Library General Public License
|
||||
# along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
This module contains several flavors of constraint classes.
|
||||
Each has a method `Constraint.solve_for(name)` and a method
|
||||
@ -49,12 +28,11 @@ implement `Constraint.solve_for(Variable)` method to update a variable with
|
||||
appropriate value.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import operator
|
||||
import math
|
||||
from gaphas.solver import Projection
|
||||
from solver import Projection
|
||||
|
||||
|
||||
__version__ = "$Revision$"
|
||||
# $HeadURL$
|
||||
@ -63,7 +41,6 @@ __version__ = "$Revision$"
|
||||
# is simple abs(x - y) > EPSILON enough for canvas needs?
|
||||
EPSILON = 1e-6
|
||||
|
||||
|
||||
def _update(variable, value):
|
||||
if abs(variable.value - value) > EPSILON:
|
||||
variable.value = value
|
||||
@ -96,6 +73,7 @@ class Constraint(object):
|
||||
# Used by the Solver for efficiency
|
||||
self._solver_has_projections = False
|
||||
|
||||
|
||||
def create_weakest_list(self):
|
||||
"""
|
||||
Create list of weakest variables.
|
||||
@ -104,6 +82,7 @@ class Constraint(object):
|
||||
strength = min(v.strength for v in self._variables)
|
||||
self._weakest = [v for v in self._variables if v.strength == strength]
|
||||
|
||||
|
||||
def variables(self):
|
||||
"""
|
||||
Return an iterator which iterates over the variables that are
|
||||
@ -111,6 +90,7 @@ class Constraint(object):
|
||||
"""
|
||||
return self._variables
|
||||
|
||||
|
||||
def weakest(self):
|
||||
"""
|
||||
Return the weakest variable. The weakest variable should be always
|
||||
@ -118,6 +98,7 @@ class Constraint(object):
|
||||
"""
|
||||
return self._weakest[0]
|
||||
|
||||
|
||||
def mark_dirty(self, v):
|
||||
"""
|
||||
Mark variable v dirty and if possible move it to the end of
|
||||
@ -158,6 +139,7 @@ class Constraint(object):
|
||||
raise NotImplemented
|
||||
|
||||
|
||||
|
||||
class EqualsConstraint(Constraint):
|
||||
"""
|
||||
Constraint, which ensures that two arguments ``a`` and ``b`` are equal:
|
||||
@ -183,12 +165,16 @@ class EqualsConstraint(Constraint):
|
||||
self.b = b
|
||||
self.delta = delta
|
||||
|
||||
|
||||
def solve_for(self, var):
|
||||
assert var in (self.a, self.b, self.delta)
|
||||
|
||||
_update(*((var is self.a) and (self.a, self.b.value - self.delta) or (var is self.b) and
|
||||
(self.b, self.a.value + self.delta) or
|
||||
(self.delta, self.b.value - self.a.value)))
|
||||
_update(*((var is self.a) and \
|
||||
(self.a, self.b.value - self.delta) or \
|
||||
(var is self.b) and \
|
||||
(self.b, self.a.value + self.delta) or \
|
||||
(self.delta, self.b.value - self.a.value)))
|
||||
|
||||
|
||||
|
||||
class CenterConstraint(Constraint):
|
||||
@ -219,6 +205,7 @@ class CenterConstraint(Constraint):
|
||||
self.b = b
|
||||
self.center = center
|
||||
|
||||
|
||||
def solve_for(self, var):
|
||||
assert var in (self.a, self.b, self.center)
|
||||
|
||||
@ -226,6 +213,7 @@ class CenterConstraint(Constraint):
|
||||
_update(self.center, v)
|
||||
|
||||
|
||||
|
||||
class LessThanConstraint(Constraint):
|
||||
"""
|
||||
Ensure ``smaller`` is less than ``bigger``. The variable that is passed
|
||||
@ -258,6 +246,7 @@ class LessThanConstraint(Constraint):
|
||||
self.bigger = bigger
|
||||
self.delta = delta
|
||||
|
||||
|
||||
def solve_for(self, var):
|
||||
if self.smaller.value > self.bigger.value - self.delta:
|
||||
if var is self.smaller:
|
||||
@ -268,10 +257,11 @@ class LessThanConstraint(Constraint):
|
||||
self.delta.value = self.bigger.value - self.smaller.value
|
||||
|
||||
|
||||
# Constants for the EquationConstraint
|
||||
ITERLIMIT = 1000 # iteration limit
|
||||
|
||||
|
||||
# Constants for the EquationConstraint
|
||||
ITERLIMIT = 1000 # iteration limit
|
||||
|
||||
class EquationConstraint(Constraint):
|
||||
"""
|
||||
Equation solver using attributes and introspection.
|
||||
@ -295,21 +285,23 @@ class EquationConstraint(Constraint):
|
||||
"""
|
||||
|
||||
def __init__(self, f, **args):
|
||||
super(EquationConstraint, self).__init__(*list(args.values()))
|
||||
super(EquationConstraint, self).__init__(*args.values())
|
||||
self._f = f
|
||||
self._args = {}
|
||||
# see important note on order of operations in __setattr__ below.
|
||||
for arg in f.__code__.co_varnames[0:f.__code__.co_argcount]:
|
||||
for arg in f.func_code.co_varnames[0:f.func_code.co_argcount]:
|
||||
self._args[arg] = None
|
||||
self._set(**args)
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
argstring = ', '.join(['%s=%s' % (arg, str(value)) for (arg, value) in
|
||||
self._args.items()])
|
||||
self._args.items()])
|
||||
if argstring:
|
||||
return 'EquationConstraint(%s, %s)' % (self._f.__code__.co_name, argstring)
|
||||
return 'EquationConstraint(%s, %s)' % (self._f.func_code.co_name, argstring)
|
||||
else:
|
||||
return 'EquationConstraint(%s)' % self._f.__code__.co_name
|
||||
return 'EquationConstraint(%s)' % self._f.func_code.co_name
|
||||
|
||||
|
||||
def __getattr__(self, name):
|
||||
"""
|
||||
@ -318,6 +310,7 @@ class EquationConstraint(Constraint):
|
||||
self._args[name]
|
||||
return self.solve_for(name)
|
||||
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
"""
|
||||
Sets function argument values.
|
||||
@ -326,16 +319,17 @@ class EquationConstraint(Constraint):
|
||||
# be added to self.__dict__. This is a good thing as it throws
|
||||
# an exception if you try to assign to an arg which is inappropriate
|
||||
# for the function in the solver.
|
||||
if '_args' in self.__dict__:
|
||||
if self.__dict__.has_key('_args'):
|
||||
if name in self._args:
|
||||
self._args[name] = value
|
||||
elif name in self.__dict__:
|
||||
self.__dict__[name] = value
|
||||
else:
|
||||
raise KeyError(name)
|
||||
raise KeyError, name
|
||||
else:
|
||||
object.__setattr__(self, name, value)
|
||||
|
||||
|
||||
def _set(self, **args):
|
||||
"""
|
||||
Sets values of function arguments.
|
||||
@ -344,6 +338,7 @@ class EquationConstraint(Constraint):
|
||||
self._args[arg] # raise exception if arg not in _args
|
||||
setattr(self, arg, args[arg])
|
||||
|
||||
|
||||
def solve_for(self, var):
|
||||
"""
|
||||
Solve this constraint for the variable named 'arg' in the
|
||||
@ -357,12 +352,13 @@ class EquationConstraint(Constraint):
|
||||
if var.value != v:
|
||||
var.value = v
|
||||
|
||||
|
||||
def _solve_for(self, arg, args):
|
||||
"""
|
||||
Newton's method solver
|
||||
"""
|
||||
# args = self._args
|
||||
close_runs = 10 # after getting close, do more passes
|
||||
#args = self._args
|
||||
close_runs = 10 # after getting close, do more passes
|
||||
if args[arg]:
|
||||
x0 = args[arg]
|
||||
else:
|
||||
@ -370,38 +366,36 @@ class EquationConstraint(Constraint):
|
||||
if x0 == 0:
|
||||
x1 = 1
|
||||
else:
|
||||
x1 = x0 * 1.1
|
||||
|
||||
x1 = x0*1.1
|
||||
def f(x):
|
||||
"""function to solve"""
|
||||
args[arg] = x
|
||||
return self._f(**args)
|
||||
|
||||
fx0 = f(x0)
|
||||
n = 0
|
||||
while 1: # Newton's method loop here
|
||||
while 1: # Newton's method loop here
|
||||
fx1 = f(x1)
|
||||
if fx1 == 0 or x1 == x0: # managed to nail it exactly
|
||||
break
|
||||
if abs(fx1 - fx0) < EPSILON: # very close
|
||||
if abs(fx1-fx0) < EPSILON: # very close
|
||||
close_flag = True
|
||||
if close_runs == 0: # been close several times
|
||||
if close_runs == 0: # been close several times
|
||||
break
|
||||
else:
|
||||
close_runs -= 1 # try some more
|
||||
close_runs -= 1 # try some more
|
||||
else:
|
||||
close_flag = False
|
||||
if n > ITERLIMIT:
|
||||
print("Failed to converge; exceeded iteration limit")
|
||||
print "Failed to converge; exceeded iteration limit"
|
||||
break
|
||||
slope = (fx1 - fx0) / (x1 - x0)
|
||||
if slope == 0:
|
||||
if close_flag: # we're close but have zero slope, finish
|
||||
break
|
||||
else:
|
||||
print('Zero slope and not close enough to solution')
|
||||
print 'Zero slope and not close enough to solution'
|
||||
break
|
||||
x2 = x0 - fx0 / slope # New 'x1'
|
||||
x2 = x0 - fx0 / slope # New 'x1'
|
||||
fx0 = fx1
|
||||
x0 = x1
|
||||
x1 = x2
|
||||
@ -409,6 +403,7 @@ class EquationConstraint(Constraint):
|
||||
return x1
|
||||
|
||||
|
||||
|
||||
class BalanceConstraint(Constraint):
|
||||
"""
|
||||
Ensure that a variable ``v`` is between values specified by ``band``
|
||||
@ -447,6 +442,7 @@ class BalanceConstraint(Constraint):
|
||||
if self.balance is None:
|
||||
self.update_balance()
|
||||
|
||||
|
||||
def update_balance(self):
|
||||
b1, b2 = self.band
|
||||
w = b2 - b1
|
||||
@ -455,6 +451,7 @@ class BalanceConstraint(Constraint):
|
||||
else:
|
||||
self.balance = 0
|
||||
|
||||
|
||||
def solve_for(self, var):
|
||||
b1, b2 = self.band
|
||||
w = b2.value - b1.value
|
||||
@ -462,6 +459,7 @@ class BalanceConstraint(Constraint):
|
||||
_update(var, value)
|
||||
|
||||
|
||||
|
||||
class LineConstraint(Constraint):
|
||||
"""
|
||||
Ensure a point is kept on a line.
|
||||
@ -478,6 +476,7 @@ class LineConstraint(Constraint):
|
||||
self._point = point
|
||||
self.update_ratio()
|
||||
|
||||
|
||||
def update_ratio(self):
|
||||
"""
|
||||
>>> from gaphas.solver import Variable
|
||||
@ -508,13 +507,15 @@ class LineConstraint(Constraint):
|
||||
except ZeroDivisionError:
|
||||
self.ratio_y = 0.0
|
||||
|
||||
|
||||
def solve_for(self, var=None):
|
||||
self._solve()
|
||||
|
||||
|
||||
def _solve(self):
|
||||
"""
|
||||
Solve the equation for the connected_handle.
|
||||
|
||||
|
||||
>>> from gaphas.solver import Variable
|
||||
>>> line = (Variable(0), Variable(0)), (Variable(30), Variable(20))
|
||||
>>> point = (Variable(15), Variable(4))
|
||||
@ -540,6 +541,7 @@ class LineConstraint(Constraint):
|
||||
_update(py, y)
|
||||
|
||||
|
||||
|
||||
class PositionConstraint(Constraint):
|
||||
"""
|
||||
Ensure that point is always in origin position.
|
||||
@ -551,11 +553,12 @@ class PositionConstraint(Constraint):
|
||||
|
||||
def __init__(self, origin, point):
|
||||
super(PositionConstraint, self).__init__(origin[0], origin[1],
|
||||
point[0], point[1])
|
||||
point[0], point[1])
|
||||
|
||||
self._origin = origin
|
||||
self._point = point
|
||||
|
||||
|
||||
def solve_for(self, var=None):
|
||||
"""
|
||||
Ensure that point's coordinates are the same as coordinates of the
|
||||
@ -566,6 +569,7 @@ class PositionConstraint(Constraint):
|
||||
_update(self._point[1], y)
|
||||
|
||||
|
||||
|
||||
class LineAlignConstraint(Constraint):
|
||||
"""
|
||||
Ensure a point is kept on a line in position specified by align and padding
|
||||
@ -601,6 +605,7 @@ class LineAlignConstraint(Constraint):
|
||||
self._align = align
|
||||
self._delta = delta
|
||||
|
||||
|
||||
def solve_for(self, var=None):
|
||||
sx, sy = self._line[0]
|
||||
ex, ey = self._line[1]
|
||||
@ -613,4 +618,5 @@ class LineAlignConstraint(Constraint):
|
||||
_update(px, x)
|
||||
_update(py, y)
|
||||
|
||||
|
||||
# vim:sw=4:et:ai
|
||||
|
@ -1,38 +1,16 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2006-2017 Adrian Boguszewski <adrbogus1@student.pg.gda.pl>
|
||||
# Arjan Molenaar <gaphor@gmail.com>
|
||||
# Artur Wroblewski <wrobell@pld-linux.org>
|
||||
# Dan Yeaw <dan@yeaw.me>
|
||||
#
|
||||
# This file is part of Gaphas.
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Library General Public License as published by the Free
|
||||
# Software Foundation; either version 2 of the License, or (at your option) any
|
||||
# later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Library General Public License
|
||||
# along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
Custom decorators.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import print_function
|
||||
__version__ = "$Revision$"
|
||||
# $HeadURL$
|
||||
|
||||
import threading
|
||||
|
||||
from gi.repository import GObject
|
||||
from gi.repository.GLib import PRIORITY_HIGH, PRIORITY_HIGH_IDLE, PRIORITY_DEFAULT, \
|
||||
import gobject
|
||||
from gobject import PRIORITY_HIGH, PRIORITY_HIGH_IDLE, PRIORITY_DEFAULT, \
|
||||
PRIORITY_DEFAULT_IDLE, PRIORITY_LOW
|
||||
|
||||
|
||||
DEBUG_ASYNC = False
|
||||
|
||||
|
||||
@ -56,28 +34,28 @@ class async(object):
|
||||
'Hi'
|
||||
|
||||
Simple method:
|
||||
|
||||
|
||||
>>> class A(object):
|
||||
... @async(single=False, priority=GObject.PRIORITY_HIGH)
|
||||
... @async(single=False, priority=gobject.PRIORITY_HIGH)
|
||||
... def a(self):
|
||||
... print 'idle-a', GObject.main_depth()
|
||||
|
||||
... print 'idle-a', gobject.main_depth()
|
||||
|
||||
Methods can also set single mode to True (the method is only scheduled one).
|
||||
|
||||
>>> class B(object):
|
||||
... @async(single=True)
|
||||
... def b(self):
|
||||
... print 'idle-b', GObject.main_depth()
|
||||
... print 'idle-b', gobject.main_depth()
|
||||
|
||||
Also a timeout property can be provided:
|
||||
|
||||
>>> class C(object):
|
||||
... @async(timeout=50)
|
||||
... def c1(self):
|
||||
... print 'idle-c1', GObject.main_depth()
|
||||
... print 'idle-c1', gobject.main_depth()
|
||||
... @async(single=True, timeout=60)
|
||||
... def c2(self):
|
||||
... print 'idle-c2', GObject.main_depth()
|
||||
... print 'idle-c2', gobject.main_depth()
|
||||
|
||||
This is a helper function used to test classes A and B from within the GTK+
|
||||
main loop:
|
||||
@ -98,11 +76,11 @@ class async(object):
|
||||
... a.a()
|
||||
... b.b()
|
||||
... print 'after'
|
||||
... GObject.timeout_add(100, Gtk.main_quit)
|
||||
>>> GObject.timeout_add(1, delayed) > 0 # timeout id may vary
|
||||
... gobject.timeout_add(100, gtk.main_quit)
|
||||
>>> gobject.timeout_add(1, delayed) > 0 # timeout id may vary
|
||||
True
|
||||
>>> from gi.repository import Gtk
|
||||
>>> Gtk.main()
|
||||
>>> import gtk
|
||||
>>> gtk.main()
|
||||
before
|
||||
after
|
||||
idle-a 1
|
||||
@ -117,7 +95,7 @@ class async(object):
|
||||
executed once.
|
||||
"""
|
||||
|
||||
def __init__(self, single=False, timeout=0, priority=PRIORITY_DEFAULT):
|
||||
def __init__(self, single=False, timeout=0, priority=gobject.PRIORITY_DEFAULT):
|
||||
self.single = single
|
||||
self.timeout = timeout
|
||||
self.priority = priority
|
||||
@ -125,9 +103,9 @@ class async(object):
|
||||
def source(self, func):
|
||||
timeout = self.timeout
|
||||
if timeout > 0:
|
||||
s = GObject.Timeout(timeout)
|
||||
s = gobject.Timeout(timeout)
|
||||
else:
|
||||
s = GObject.Idle()
|
||||
s = gobject.Idle()
|
||||
s.set_callback(func)
|
||||
s.priority = self.priority
|
||||
return s
|
||||
@ -139,12 +117,11 @@ class async(object):
|
||||
def wrapper(*args, **kwargs):
|
||||
global getattr, setattr, delattr
|
||||
# execute directly if we're not in the main loop.
|
||||
if GObject.main_depth() == 0:
|
||||
if gobject.main_depth() == 0:
|
||||
return func(*args, **kwargs)
|
||||
elif not self.single:
|
||||
def async_wrapper(*x):
|
||||
if DEBUG_ASYNC:
|
||||
print('async:', func, args, kwargs)
|
||||
def async_wrapper():
|
||||
if DEBUG_ASYNC: print 'async:', func, args, kwargs
|
||||
func(*args, **kwargs)
|
||||
source(async_wrapper).attach()
|
||||
else:
|
||||
@ -153,10 +130,9 @@ class async(object):
|
||||
try:
|
||||
if getattr(holder, async_id):
|
||||
return
|
||||
except AttributeError as e:
|
||||
def async_wrapper(*x):
|
||||
if DEBUG_ASYNC:
|
||||
print('async:', func, args, kwargs)
|
||||
except AttributeError, e:
|
||||
def async_wrapper():
|
||||
if DEBUG_ASYNC: print 'async:', func, args, kwargs
|
||||
try:
|
||||
func(*args, **kwargs)
|
||||
finally:
|
||||
@ -182,7 +158,6 @@ def nonrecursive(func):
|
||||
1
|
||||
"""
|
||||
m = threading.Lock()
|
||||
|
||||
def wrapper(*args, **kwargs):
|
||||
"""
|
||||
Decorate function with a mutex that prohibits recursice execution.
|
||||
|
@ -1,39 +1,16 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2006-2017 Arjan Molenaar <gaphor@gmail.com>
|
||||
# Artur Wroblewski <wrobell@pld-linux.org>
|
||||
# Dan Yeaw <dan@yeaw.me>
|
||||
#
|
||||
# This file is part of Gaphas.
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Library General Public License as published by the Free
|
||||
# Software Foundation; either version 2 of the License, or (at your option) any
|
||||
# later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Library General Public License
|
||||
# along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
Simple example items.
|
||||
These items are used in various tests.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
__version__ = "$Revision$"
|
||||
# $HeadURL$
|
||||
|
||||
from gaphas.item import Element, Item, NW, NE, SW, SE
|
||||
from gaphas.item import Element, Item, NW, NE,SW, SE
|
||||
from gaphas.connector import Handle, PointPort, LinePort, Position
|
||||
from gaphas.solver import WEAK
|
||||
from .util import text_align, text_multiline, path_ellipse
|
||||
|
||||
from gaphas.solver import solvable, WEAK
|
||||
import tool
|
||||
from util import text_align, text_multiline, path_ellipse
|
||||
|
||||
class Box(Element):
|
||||
""" A Box has 4 handles (for a start):
|
||||
@ -49,18 +26,18 @@ class Box(Element):
|
||||
nw = self._handles[NW].pos
|
||||
c.rectangle(nw.x, nw.y, self.width, self.height)
|
||||
if context.hovered:
|
||||
c.set_source_rgba(.8, .8, 1, .8)
|
||||
c.set_source_rgba(.8,.8,1, .8)
|
||||
else:
|
||||
c.set_source_rgba(1, 1, 1, .8)
|
||||
c.set_source_rgba(1,1,1, .8)
|
||||
c.fill_preserve()
|
||||
c.set_source_rgb(0, 0, 0.8)
|
||||
c.set_source_rgb(0,0,0.8)
|
||||
c.stroke()
|
||||
|
||||
|
||||
class PortoBox(Box):
|
||||
"""
|
||||
Box item with few falvours of port(o)s.
|
||||
|
||||
Box item with few flavours of port(o)s.
|
||||
|
||||
Default box ports are disabled. Three, non-default connectable ports
|
||||
are created (represented by ``x`` on the picture).
|
||||
|
||||
@ -76,7 +53,6 @@ class PortoBox(Box):
|
||||
SW +--------+ SE
|
||||
x
|
||||
"""
|
||||
|
||||
def __init__(self, width=10, height=10):
|
||||
super(PortoBox, self).__init__(width, height)
|
||||
|
||||
@ -112,6 +88,7 @@ class PortoBox(Box):
|
||||
self._lport = LinePort(nw.pos, se.pos)
|
||||
self._ports.append(self._lport)
|
||||
|
||||
|
||||
def draw(self, context):
|
||||
super(PortoBox, self).draw(context)
|
||||
c = context.cairo
|
||||
@ -123,12 +100,12 @@ class PortoBox(Box):
|
||||
|
||||
# draw movable port
|
||||
x, y = self._hm.pos
|
||||
c.rectangle(x - 20, y - 5, 20, 10)
|
||||
c.rectangle(x - 1, y - 1, 2, 2)
|
||||
c.rectangle(x - 20 , y - 5, 20, 10)
|
||||
c.rectangle(x - 1 , y - 1, 2, 2)
|
||||
|
||||
# draw static port
|
||||
x, y = self._sport.point
|
||||
c.rectangle(x - 2, y - 2, 4, 4)
|
||||
c.rectangle(x - 2 , y - 2, 4, 4)
|
||||
|
||||
c.fill_preserve()
|
||||
|
||||
@ -138,10 +115,12 @@ class PortoBox(Box):
|
||||
c.move_to(x1, y1)
|
||||
c.line_to(x2, y2)
|
||||
|
||||
c.set_source_rgb(0, 0, 0.8)
|
||||
c.set_source_rgb(0,0,0.8)
|
||||
c.stroke()
|
||||
|
||||
|
||||
|
||||
|
||||
class Text(Item):
|
||||
"""
|
||||
Simple item showing some text on the canvas.
|
||||
@ -156,7 +135,7 @@ class Text(Item):
|
||||
self.align_y = align_y
|
||||
|
||||
def draw(self, context):
|
||||
# print 'Text.draw', self
|
||||
#print 'Text.draw', self
|
||||
cr = context.cairo
|
||||
if self.multiline:
|
||||
text_multiline(cr, 0, 0, self.text)
|
||||
@ -175,7 +154,6 @@ class FatLine(Item):
|
||||
|
||||
todo: rectangle port instead of line port would be nicer
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super(FatLine, self).__init__()
|
||||
self._handles.extend((Handle(), Handle()))
|
||||
@ -187,16 +165,20 @@ class FatLine(Item):
|
||||
self.constraint(vertical=(h1.pos, h2.pos))
|
||||
self.constraint(above=(h1.pos, h2.pos), delta=20)
|
||||
|
||||
|
||||
def _set_height(self, height):
|
||||
h1, h2 = self._handles
|
||||
h2.pos.y = height
|
||||
|
||||
|
||||
def _get_height(self):
|
||||
h1, h2 = self._handles
|
||||
return h2.pos.y
|
||||
|
||||
|
||||
height = property(_get_height, _set_height)
|
||||
|
||||
|
||||
def draw(self, context):
|
||||
cr = context.cairo
|
||||
cr.set_line_width(10)
|
||||
@ -206,16 +188,19 @@ class FatLine(Item):
|
||||
cr.stroke()
|
||||
|
||||
|
||||
|
||||
class Circle(Item):
|
||||
def __init__(self):
|
||||
super(Circle, self).__init__()
|
||||
self._handles.extend((Handle(), Handle()))
|
||||
|
||||
|
||||
def _set_radius(self, r):
|
||||
h1, h2 = self._handles
|
||||
h2.pos.x = r
|
||||
h2.pos.y = r
|
||||
|
||||
|
||||
def _get_radius(self):
|
||||
h1, h2 = self._handles
|
||||
p1, p2 = h1.pos, h2.pos
|
||||
@ -223,14 +208,18 @@ class Circle(Item):
|
||||
|
||||
radius = property(_get_radius, _set_radius)
|
||||
|
||||
|
||||
def setup_canvas(self):
|
||||
super(Circle, self).setup_canvas()
|
||||
h1, h2 = self._handles
|
||||
h1.movable = False
|
||||
|
||||
|
||||
def draw(self, context):
|
||||
cr = context.cairo
|
||||
path_ellipse(cr, 0, 0, 2 * self.radius, 2 * self.radius)
|
||||
cr.stroke()
|
||||
|
||||
|
||||
|
||||
# vim: sw=4:et:ai
|
||||
|
@ -1,12 +1,10 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Cairo context using Steve Hanov's freehand drawing code.
|
||||
|
||||
# Crazyline. By Steve Hanov, 2008
|
||||
# Released to the public domain.
|
||||
|
||||
# The idea is to draw a curve, setting two control points at random
|
||||
# The idea is to draw a curve, setting two control points at random
|
||||
# close to each side of the line. The longer the line, the sloppier
|
||||
# it's drawn.
|
||||
|
||||
@ -14,15 +12,13 @@ See: http://stevehanov.ca/blog/index.php?id=33 and
|
||||
http://stevehanov.ca/blog/index.php?id=93
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from math import sqrt
|
||||
from random import Random
|
||||
|
||||
from gaphas.painter import Context
|
||||
from painter import Context
|
||||
|
||||
|
||||
class FreeHandCairoContext(object):
|
||||
|
||||
KAPPA = 0.5522847498
|
||||
|
||||
def __init__(self, cr, sloppiness=0.5):
|
||||
@ -37,7 +33,7 @@ class FreeHandCairoContext(object):
|
||||
* Drunk: 2.0
|
||||
"""
|
||||
self.cr = cr
|
||||
self.sloppiness = sloppiness # In range 0.0 .. 2.0
|
||||
self.sloppiness = sloppiness # In range 0.0 .. 2.0
|
||||
|
||||
def __getattr__(self, key):
|
||||
return getattr(self.cr, key)
|
||||
@ -48,22 +44,21 @@ class FreeHandCairoContext(object):
|
||||
from_x, from_y = cr.get_current_point()
|
||||
|
||||
# calculate the length of the line.
|
||||
length = sqrt((x - from_x) * (x - from_x) + (y - from_y) * (y - from_y))
|
||||
length = sqrt( (x-from_x)*(x-from_x) + (y-from_y)*(y-from_y))
|
||||
|
||||
# This offset determines how sloppy the line is drawn. It depends on
|
||||
# the length, but maxes out at 20.
|
||||
offset = length / 10 * sloppiness
|
||||
if offset > 20:
|
||||
offset = 20
|
||||
offset = length/10 * sloppiness
|
||||
if offset > 20: offset = 20
|
||||
|
||||
dev_x, dev_y = cr.user_to_device(x, y)
|
||||
rand = Random((from_x, from_y, dev_x, dev_y, length, offset)).random
|
||||
|
||||
# Overshoot the destination a little, as one might if drawing with a pen.
|
||||
to_x = x + sloppiness * rand() * offset / 4
|
||||
to_y = y + sloppiness * rand() * offset / 4
|
||||
to_x = x + sloppiness * rand() * offset/4
|
||||
to_y = y + sloppiness * rand() * offset/4
|
||||
|
||||
# t1 and t2 are coordinates of a line shifted under or to the right of
|
||||
# t1 and t2 are coordinates of a line shifted under or to the right of
|
||||
# our original.
|
||||
t1_x = from_x + offset
|
||||
t1_y = from_y + offset
|
||||
@ -72,10 +67,10 @@ class FreeHandCairoContext(object):
|
||||
|
||||
# create a control point at random along our shifted line.
|
||||
r = rand()
|
||||
control1_x = t1_x + r * (t2_x - t1_x)
|
||||
control1_y = t1_y + r * (t2_y - t1_y)
|
||||
control1_x = t1_x + r * (t2_x-t1_x)
|
||||
control1_y = t1_y + r * (t2_y-t1_y)
|
||||
|
||||
# now make t1 and t2 the coordinates of our line shifted above
|
||||
# now make t1 and t2 the coordinates of our line shifted above
|
||||
# and to the left of the original.
|
||||
|
||||
t1_x = from_x - offset
|
||||
@ -85,8 +80,8 @@ class FreeHandCairoContext(object):
|
||||
|
||||
# create a second control point at random along the shifted line.
|
||||
r = rand()
|
||||
control2_x = t1_x + r * (t2_x - t1_x)
|
||||
control2_y = t1_y + r * (t2_y - t1_y)
|
||||
control2_x = t1_x + r * (t2_x-t1_x)
|
||||
control2_y = t1_y + r * (t2_y-t1_y)
|
||||
|
||||
# draw the line!
|
||||
cr.curve_to(control1_x, control1_y, control2_x, control2_y, to_x, to_y)
|
||||
@ -104,30 +99,31 @@ class FreeHandCairoContext(object):
|
||||
rand = Random((from_x, from_y, dev_x, dev_y, x1, y1, x2, y2, x3, y3)).random
|
||||
|
||||
r = rand()
|
||||
c1_x = from_x + r * (x1 - from_x)
|
||||
c1_y = from_y + r * (y1 - from_y)
|
||||
c1_x = from_x + r * (x1-from_x)
|
||||
c1_y = from_y + r * (y1-from_y)
|
||||
|
||||
r = rand()
|
||||
c2_x = x3 + r * (x2 - x3)
|
||||
c2_y = y3 + r * (y2 - y3)
|
||||
c2_x = x3 + r * (x2-x3)
|
||||
c2_y = y3 + r * (y2-y3)
|
||||
|
||||
cr.curve_to(c1_x, c1_y, c2_x, c2_y, x3, y3)
|
||||
|
||||
def rel_curve_to(self, dx1, dy1, dx2, dy2, dx3, dy3):
|
||||
cr = self.cr
|
||||
from_x, from_y = cr.get_current_point()
|
||||
self.curve_to(from_x + dx1, from_y + dy1, from_x + dx2, from_y + dy2, from_x + dx3, from_y + dy3)
|
||||
self.curve_to(from_x+dx1, from_y+dy1, from_x+dx2, from_y+dy2, from_x+dx3, from_y+dy3)
|
||||
|
||||
|
||||
def corner_to(self, cx, cy, x, y):
|
||||
cr = self.cr
|
||||
from_x, from_y = cr.get_current_point()
|
||||
|
||||
# calculate radius of the circle.
|
||||
radius1 = Math.sqrt((cx - from_x) * (cx - from_x) +
|
||||
(cy - from_y) * (cy - from_y))
|
||||
radius1 = Math.sqrt( (cx-from_x)*(cx-from_x) +
|
||||
(cy-from_y)*(cy-from_y));
|
||||
|
||||
radius2 = Math.sqrt((cx - x) * (cx - x) +
|
||||
(cy - y) * (cy - y))
|
||||
radius2 = Math.sqrt( (cx-x)*(cx-x) +
|
||||
(cy-y)*(cy-y));
|
||||
|
||||
dev_x, dev_y = cr.user_to_device(x, y)
|
||||
rand = Random((cx, cy, dev_x, dev_y, radius1, radius2)).random
|
||||
@ -156,6 +152,7 @@ class FreeHandCairoContext(object):
|
||||
|
||||
|
||||
class FreeHandPainter(object):
|
||||
|
||||
def __init__(self, subpainter, sloppiness=1.0, view=None):
|
||||
self.subpainter = subpainter
|
||||
self.view = view
|
||||
@ -166,8 +163,8 @@ class FreeHandPainter(object):
|
||||
self.subpainter.set_view(view)
|
||||
|
||||
def paint(self, context):
|
||||
subcontext = Context(cairo=FreeHandCairoContext(context.cairo, self.sloppiness), items=context.items,
|
||||
area=context.area)
|
||||
subcontext = Context(cairo=FreeHandCairoContext(context.cairo, self.sloppiness), items=context.items, area=context.area)
|
||||
self.subpainter.paint(subcontext)
|
||||
|
||||
|
||||
# vi:sw=4:et:ai
|
||||
|
@ -1,24 +1,3 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2006-2017 Arjan Molenaar <gaphor@gmail.com>
|
||||
# Artur Wroblewski <wrobell@pld-linux.org>
|
||||
# Dan Yeaw <dan@yeaw.me>
|
||||
#
|
||||
# This file is part of Gaphas.
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Library General Public License as published by the Free
|
||||
# Software Foundation; either version 2 of the License, or (at your option) any
|
||||
# later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Library General Public License
|
||||
# along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
Geometry functions.
|
||||
|
||||
@ -29,13 +8,11 @@ A point is represented as a tuple `(x, y)`.
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from math import sqrt
|
||||
|
||||
__version__ = "$Revision$"
|
||||
# $HeadURL$
|
||||
|
||||
from math import sqrt
|
||||
|
||||
|
||||
class Rectangle(object):
|
||||
"""
|
||||
@ -84,8 +61,7 @@ class Rectangle(object):
|
||||
"""
|
||||
"""
|
||||
width = x1 - self.x
|
||||
if width < 0:
|
||||
width = 0
|
||||
if width < 0: width = 0
|
||||
self.width = width
|
||||
|
||||
x1 = property(lambda s: s.x + s.width, _set_x1)
|
||||
@ -94,8 +70,7 @@ class Rectangle(object):
|
||||
"""
|
||||
"""
|
||||
height = y1 - self.y
|
||||
if height < 0:
|
||||
height = 0
|
||||
if height < 0: height = 0
|
||||
self.height = height
|
||||
|
||||
y1 = property(lambda s: s.y + s.height, _set_y1)
|
||||
@ -147,10 +122,10 @@ class Rectangle(object):
|
||||
|
||||
def __eq__(self, other):
|
||||
return (type(self) is type(other)) \
|
||||
and self.x == other.x \
|
||||
and self.y == other.y \
|
||||
and self.width == other.width \
|
||||
and self.height == self.height
|
||||
and self.x == other.x \
|
||||
and self.y == other.y \
|
||||
and self.width == other.width \
|
||||
and self.height == self.height
|
||||
|
||||
def __add__(self, obj):
|
||||
"""
|
||||
@ -182,7 +157,7 @@ class Rectangle(object):
|
||||
try:
|
||||
x, y, width, height = obj
|
||||
except ValueError:
|
||||
raise TypeError("Can only add Rectangle or tuple (x, y, width, height), not %s." % repr(obj))
|
||||
raise TypeError, "Can only add Rectangle or tuple (x, y, width, height), not %s." % repr(obj)
|
||||
x1, y1 = x + width, y + height
|
||||
if self:
|
||||
ox1, oy1 = self.x + self.width, self.y + self.height
|
||||
@ -222,7 +197,7 @@ class Rectangle(object):
|
||||
try:
|
||||
x, y, width, height = obj
|
||||
except ValueError:
|
||||
raise TypeError("Can only substract Rectangle or tuple (x, y, width, height), not %s." % repr(obj))
|
||||
raise TypeError, "Can only substract Rectangle or tuple (x, y, width, height), not %s." % repr(obj)
|
||||
x1, y1 = x + width, y + height
|
||||
|
||||
if self:
|
||||
@ -272,10 +247,9 @@ class Rectangle(object):
|
||||
x, y = obj
|
||||
x1, y1 = obj
|
||||
except ValueError:
|
||||
raise TypeError(
|
||||
"Should compare to Rectangle, tuple (x, y, width, height) or point (x, y), not %s." % repr(obj)
|
||||
)
|
||||
return x >= self.x and x1 <= self.x1 and y >= self.y and y1 <= self.y1
|
||||
raise TypeError, "Should compare to Rectangle, tuple (x, y, width, height) or point (x, y), not %s." % repr(obj)
|
||||
return x >= self.x and x1 <= self.x1 and \
|
||||
y >= self.y and y1 <= self.y1
|
||||
|
||||
|
||||
def distance_point_point(point1, point2=(0., 0.)):
|
||||
@ -287,7 +261,7 @@ def distance_point_point(point1, point2=(0., 0.)):
|
||||
"""
|
||||
dx = point1[0] - point2[0]
|
||||
dy = point1[1] - point2[1]
|
||||
return sqrt(dx * dx + dy * dy)
|
||||
return sqrt(dx*dx + dy*dy)
|
||||
|
||||
|
||||
def distance_point_point_fast(point1, point2=(0., 0.)):
|
||||
@ -337,7 +311,7 @@ def point_on_rectangle(rect, point, border=False):
|
||||
Return the point on which ``point`` can be projecten on the rectangle.
|
||||
``border = True`` will make sure the point is bound to the border of
|
||||
the reactangle. Otherwise, if the point is in the rectangle, it's okay.
|
||||
|
||||
|
||||
>>> point_on_rectangle(Rectangle(0, 0, 10, 10), (11, -1))
|
||||
(10, 0)
|
||||
>>> point_on_rectangle((0, 0, 10, 10), (5, 12))
|
||||
@ -384,7 +358,7 @@ def point_on_rectangle(rect, point, border=False):
|
||||
if x_inside and y_inside:
|
||||
# Find point on side closest to the point
|
||||
if min(abs(rx - px), abs(rx + rw - px)) > \
|
||||
min(abs(ry - py), abs(ry + rh - py)):
|
||||
min(abs(ry - py), abs(ry + rh - py)):
|
||||
if py < ry + rh / 2.:
|
||||
py = ry
|
||||
else:
|
||||
@ -401,7 +375,7 @@ def point_on_rectangle(rect, point, border=False):
|
||||
def distance_line_point(line_start, line_end, point):
|
||||
"""
|
||||
Calculate the distance of a ``point`` from a line. The line is marked
|
||||
by begin and end point ``line_start`` and ``line_end``.
|
||||
by begin and end point ``line_start`` and ``line_end``.
|
||||
|
||||
A tuple is returned containing the distance and point on the line.
|
||||
|
||||
@ -442,9 +416,8 @@ def distance_line_point(line_start, line_end, point):
|
||||
# Projection is on the line. multiply the line_end with the projlen
|
||||
# factor to obtain the point on the line.
|
||||
proj = line_end[0] * projlen, line_end[1] * projlen
|
||||
return distance_point_point((proj[0] - point[0], proj[1] - point[1])), (
|
||||
line_start[0] + proj[0], line_start[1] + proj[1]
|
||||
)
|
||||
return distance_point_point((proj[0] - point[0], proj[1] - point[1])),\
|
||||
(line_start[0] + proj[0], line_start[1] + proj[1])
|
||||
|
||||
|
||||
def intersect_line_line(line1_start, line1_end, line2_start, line2_end):
|
||||
@ -514,9 +487,9 @@ def intersect_line_line(line1_start, line1_end, line2_start, line2_end):
|
||||
x3, y3 = line2_start
|
||||
x4, y4 = line2_end
|
||||
|
||||
# long a1, a2, b1, b2, c1, c2; /* Coefficients of line eqns. */
|
||||
# long r1, r2, r3, r4; /* 'Sign' values */
|
||||
# long denom, offset, num; /* Intermediate values */
|
||||
#long a1, a2, b1, b2, c1, c2; /* Coefficients of line eqns. */
|
||||
#long r1, r2, r3, r4; /* 'Sign' values */
|
||||
#long denom, offset, num; /* Intermediate values */
|
||||
|
||||
# Compute a1, b1, c1, where line joining points 1 and 2
|
||||
# is "a1 x + b1 y + c1 = 0".
|
||||
@ -534,7 +507,7 @@ def intersect_line_line(line1_start, line1_end, line2_start, line2_end):
|
||||
# same side of line 1, the line segments do not intersect.
|
||||
|
||||
if r3 and r4 and (r3 * r4) >= 0:
|
||||
return None # ( DONT_INTERSECT )
|
||||
return None # ( DONT_INTERSECT )
|
||||
|
||||
# Compute a2, b2, c2
|
||||
|
||||
@ -551,14 +524,14 @@ def intersect_line_line(line1_start, line1_end, line2_start, line2_end):
|
||||
# on same side of second line segment, the line segments do
|
||||
# not intersect.
|
||||
|
||||
if r1 and r2 and (r1 * r2) >= 0: # SAME_SIGNS( r1, r2 ))
|
||||
return None # ( DONT_INTERSECT )
|
||||
if r1 and r2 and (r1 * r2) >= 0: #SAME_SIGNS( r1, r2 ))
|
||||
return None # ( DONT_INTERSECT )
|
||||
|
||||
# Line segments intersect: compute intersection point.
|
||||
# Line segments intersect: compute intersection point.
|
||||
|
||||
denom = a1 * b2 - a2 * b1
|
||||
if not denom:
|
||||
return None # ( COLLINEAR )
|
||||
return None # ( COLLINEAR )
|
||||
offset = abs(denom) / 2
|
||||
|
||||
# The denom/2 is to get rounding instead of truncating. It
|
||||
@ -566,10 +539,10 @@ def intersect_line_line(line1_start, line1_end, line2_start, line2_end):
|
||||
# sign of the numerator.
|
||||
|
||||
num = b1 * c2 - b2 * c1
|
||||
x = ((num < 0) and (num - offset) or (num + offset)) / denom
|
||||
x = ( (num < 0) and (num - offset) or (num + offset) ) / denom
|
||||
|
||||
num = a2 * c1 - a1 * c2
|
||||
y = ((num < 0) and (num - offset) or (num + offset)) / denom
|
||||
y = ( (num < 0) and (num - offset) or (num + offset) ) / denom
|
||||
|
||||
return x, y
|
||||
|
||||
@ -609,10 +582,11 @@ def rectangle_clip(recta, rectb):
|
||||
bx, by, bw, bh = rectb
|
||||
x = max(ax, bx)
|
||||
y = max(ay, by)
|
||||
w = min(ax + aw, bx + bw) - x
|
||||
h = min(ay + ah, by + bh) - y
|
||||
w = min(ax +aw, bx + bw) - x
|
||||
h = min(ay +ah, by + bh) - y
|
||||
if w < 0 or h < 0:
|
||||
return None
|
||||
return (x, y, w, h)
|
||||
|
||||
|
||||
# vim:sw=4:et:ai
|
||||
|
@ -1,37 +1,12 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2010-2017 Arjan Molenaar <gaphor@gmail.com>
|
||||
# Dan Yeaw <dan@yeaw.me>
|
||||
#
|
||||
# This file is part of Gaphas.
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Library General Public License as published by the Free
|
||||
# Software Foundation; either version 2 of the License, or (at your option) any
|
||||
# later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Library General Public License
|
||||
# along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
Module implements guides when moving items and handles around.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from functools import reduce
|
||||
|
||||
from simplegeneric import generic
|
||||
from six.moves import map
|
||||
|
||||
from gaphas.aspect import InMotion, HandleInMotion, PaintFocused
|
||||
from gaphas.aspect import ItemInMotion, ItemHandleInMotion, ItemPaintFocused
|
||||
from gaphas.item import Item, Element, Line
|
||||
from gaphas.connector import Handle
|
||||
from gaphas.item import Item, Element, Line, SE
|
||||
|
||||
|
||||
class ItemGuide(object):
|
||||
@ -60,13 +35,14 @@ Guide = generic(ItemGuide)
|
||||
|
||||
@Guide.when_type(Element)
|
||||
class ElementGuide(ItemGuide):
|
||||
|
||||
def horizontal(self):
|
||||
y = self.item.height
|
||||
return 0, y / 2, y
|
||||
return (0, y/2, y)
|
||||
|
||||
def vertical(self):
|
||||
x = self.item.width
|
||||
return 0, x / 2, x
|
||||
return (0, x/2, x)
|
||||
|
||||
|
||||
@Guide.when_type(Line)
|
||||
@ -101,6 +77,7 @@ class LineGuide(ItemGuide):
|
||||
|
||||
|
||||
class Guides(object):
|
||||
|
||||
def __init__(self, v, h):
|
||||
self.v = v
|
||||
self.h = h
|
||||
@ -126,9 +103,9 @@ class GuideMixin(object):
|
||||
margin = self.MARGIN
|
||||
items = []
|
||||
for x in item_vedges:
|
||||
items.append(view.get_items_in_rectangle((x - margin, 0, margin * 2, height)))
|
||||
items.append(view.get_items_in_rectangle((x - margin, 0, margin*2, height)))
|
||||
try:
|
||||
guides = list(map(Guide, reduce(set.union, list(map(set, items))) - excluded_items))
|
||||
guides = map(Guide, reduce(set.union, map(set, items)) - excluded_items)
|
||||
except TypeError:
|
||||
guides = []
|
||||
|
||||
@ -139,6 +116,7 @@ class GuideMixin(object):
|
||||
dx, edges_x = self.find_closest(item_vedges, vedges)
|
||||
return dx, edges_x
|
||||
|
||||
|
||||
def find_horizontal_guides(self, item_hedges, pdy, width, excluded_items):
|
||||
view = self.view
|
||||
item = self.item
|
||||
@ -146,9 +124,9 @@ class GuideMixin(object):
|
||||
margin = self.MARGIN
|
||||
items = []
|
||||
for y in item_hedges:
|
||||
items.append(view.get_items_in_rectangle((0, y - margin, width, margin * 2)))
|
||||
items.append(view.get_items_in_rectangle((0, y - margin, width, margin*2)))
|
||||
try:
|
||||
guides = list(map(Guide, reduce(set.union, list(map(set, items))) - excluded_items))
|
||||
guides = map(Guide, reduce(set.union, map(set, items)) - excluded_items)
|
||||
except TypeError:
|
||||
guides = []
|
||||
|
||||
@ -161,6 +139,7 @@ class GuideMixin(object):
|
||||
dy, edges_y = self.find_closest(item_hedges, hedges)
|
||||
return dy, edges_y
|
||||
|
||||
|
||||
def get_excluded_items(self):
|
||||
"""
|
||||
Get a set of items excluded from guide calculation.
|
||||
@ -173,13 +152,15 @@ class GuideMixin(object):
|
||||
excluded_items.update(view.selected_items)
|
||||
return excluded_items
|
||||
|
||||
|
||||
def get_view_dimensions(self):
|
||||
try:
|
||||
allocation = self.view.get_allocation()
|
||||
except AttributeError as e:
|
||||
allocation = self.view.allocation
|
||||
except AttributeError, e:
|
||||
return 0, 0
|
||||
return allocation.width, allocation.height
|
||||
|
||||
|
||||
def queue_draw_guides(self):
|
||||
view = self.view
|
||||
try:
|
||||
@ -190,9 +171,10 @@ class GuideMixin(object):
|
||||
w, h = self.get_view_dimensions()
|
||||
|
||||
for x in guides.vertical():
|
||||
view.queue_draw_area(x - 1, 0, x + 2, h)
|
||||
view.queue_draw_area(x-1, 0, x+2, h)
|
||||
for y in guides.horizontal():
|
||||
view.queue_draw_area(0, y - 1, w, y + 2)
|
||||
view.queue_draw_area(0, y-1, w, y+2)
|
||||
|
||||
|
||||
def find_closest(self, item_edges, edges):
|
||||
delta = 0
|
||||
@ -252,6 +234,7 @@ class GuidedItemInMotion(GuideMixin, ItemInMotion):
|
||||
|
||||
return sink
|
||||
|
||||
|
||||
def stop_move(self):
|
||||
self.queue_draw_guides()
|
||||
try:
|
||||
@ -263,6 +246,7 @@ class GuidedItemInMotion(GuideMixin, ItemInMotion):
|
||||
|
||||
@HandleInMotion.when_type(Item)
|
||||
class GuidedItemHandleInMotion(GuideMixin, ItemHandleInMotion):
|
||||
|
||||
def move(self, pos):
|
||||
|
||||
sink = super(GuidedItemHandleInMotion, self).move(pos)
|
||||
@ -275,20 +259,29 @@ class GuidedItemHandleInMotion(GuideMixin, ItemHandleInMotion):
|
||||
v2i = view.get_matrix_v2i(item)
|
||||
|
||||
excluded_items = self.get_excluded_items()
|
||||
|
||||
w, h = self.get_view_dimensions()
|
||||
|
||||
dx, edges_x = self.find_vertical_guides((x,), 0, h, excluded_items)
|
||||
|
||||
dy, edges_y = self.find_horizontal_guides((y,), 0, w, excluded_items)
|
||||
|
||||
newpos = x + dx, y + dy
|
||||
|
||||
x, y = v2i.transform_point(*newpos)
|
||||
|
||||
self.handle.pos = (x, y)
|
||||
#super(GuidedItemHandleInMotion, self).move(newpos)
|
||||
|
||||
self.queue_draw_guides()
|
||||
|
||||
view.guides = Guides(edges_x, edges_y)
|
||||
|
||||
self.queue_draw_guides()
|
||||
|
||||
item.request_update()
|
||||
|
||||
|
||||
def stop_move(self):
|
||||
self.queue_draw_guides()
|
||||
try:
|
||||
@ -300,6 +293,7 @@ class GuidedItemHandleInMotion(GuideMixin, ItemHandleInMotion):
|
||||
|
||||
@PaintFocused.when_type(Item)
|
||||
class GuidePainter(ItemPaintFocused):
|
||||
|
||||
def paint(self, context):
|
||||
try:
|
||||
guides = self.view.guides
|
||||
@ -308,7 +302,7 @@ class GuidePainter(ItemPaintFocused):
|
||||
|
||||
cr = context.cairo
|
||||
view = self.view
|
||||
allocation = view.get_allocation()
|
||||
allocation = view.allocation
|
||||
w, h = allocation.width, allocation.height
|
||||
|
||||
cr.save()
|
||||
|
181
gaphas/item.py
181
gaphas/item.py
@ -1,51 +1,24 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2006-2017 Arjan Molenaar <gaphor@gmail.com>
|
||||
# Artur Wroblewski <wrobell@pld-linux.org>
|
||||
# Dan Yeaw <dan@yeaw.me>
|
||||
#
|
||||
# This file is part of Gaphas.
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Library General Public License as published by the Free
|
||||
# Software Foundation; either version 2 of the License, or (at your option) any
|
||||
# later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Library General Public License
|
||||
# along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
Basic items.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
__version__ = "$Revision$"
|
||||
# $HeadURL$
|
||||
|
||||
from functools import reduce
|
||||
from math import atan2
|
||||
from weakref import WeakKeyDictionary
|
||||
|
||||
from six.moves import map
|
||||
from six.moves import range
|
||||
from six.moves import zip
|
||||
|
||||
try:
|
||||
# python 3.0 (better be prepared)
|
||||
from weakref import WeakSet
|
||||
except ImportError:
|
||||
from gaphas.weakset import WeakSet
|
||||
|
||||
from gaphas.matrix import Matrix
|
||||
from gaphas.geometry import distance_line_point, distance_rectangle_point
|
||||
from gaphas.connector import Handle, LinePort
|
||||
from gaphas.solver import solvable, WEAK, VERY_STRONG, REQUIRED
|
||||
from gaphas.constraint import EqualsConstraint, LessThanConstraint, LineConstraint, LineAlignConstraint
|
||||
from gaphas.state import observed, reversible_method, reversible_pair, reversible_property
|
||||
from weakset import WeakSet
|
||||
|
||||
from matrix import Matrix
|
||||
from geometry import distance_line_point, distance_rectangle_point
|
||||
from connector import Handle, LinePort
|
||||
from solver import solvable, WEAK, NORMAL, STRONG, VERY_STRONG, REQUIRED
|
||||
from constraint import EqualsConstraint, LessThanConstraint, LineConstraint, LineAlignConstraint
|
||||
from state import observed, reversible_method, reversible_pair, reversible_property
|
||||
|
||||
class Item(object):
|
||||
"""
|
||||
@ -101,10 +74,10 @@ class Item(object):
|
||||
self.setup_canvas()
|
||||
|
||||
canvas = reversible_property(lambda s: s._canvas, _set_canvas,
|
||||
doc="Canvas owning this item")
|
||||
doc="Canvas owning this item")
|
||||
|
||||
constraints = property(lambda s: s._constraints,
|
||||
doc="Item constraints")
|
||||
doc="Item constraints")
|
||||
|
||||
def setup_canvas(self):
|
||||
"""
|
||||
@ -115,6 +88,7 @@ class Item(object):
|
||||
for c in self._constraints:
|
||||
add(c)
|
||||
|
||||
|
||||
def teardown_canvas(self):
|
||||
"""
|
||||
Called when the canvas is unset for the item.
|
||||
@ -126,6 +100,7 @@ class Item(object):
|
||||
for c in self._constraints:
|
||||
remove(c)
|
||||
|
||||
|
||||
@observed
|
||||
def _set_matrix(self, matrix):
|
||||
"""
|
||||
@ -137,10 +112,12 @@ class Item(object):
|
||||
|
||||
matrix = reversible_property(lambda s: s._matrix, _set_matrix)
|
||||
|
||||
|
||||
def request_update(self, update=True, matrix=True):
|
||||
if self._canvas:
|
||||
self._canvas.request_update(self, update=update, matrix=matrix)
|
||||
|
||||
|
||||
def pre_update(self, context):
|
||||
"""
|
||||
Perform any changes before item update here, for example:
|
||||
@ -154,6 +131,7 @@ class Item(object):
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
def post_update(self, context):
|
||||
"""
|
||||
Method called after item update.
|
||||
@ -168,11 +146,12 @@ class Item(object):
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
def normalize(self):
|
||||
"""
|
||||
Update handle positions of the item, so the first handle is always
|
||||
located at (0, 0).
|
||||
|
||||
|
||||
Note that, since this method basically does some housekeeping during
|
||||
the update phase, there's no need to keep track of the changes.
|
||||
|
||||
@ -187,7 +166,7 @@ class Item(object):
|
||||
updated = False
|
||||
handles = self._handles
|
||||
if handles:
|
||||
x, y = list(map(float, handles[0].pos))
|
||||
x, y = map(float, handles[0].pos)
|
||||
if x:
|
||||
self.matrix.translate(x, 0)
|
||||
updated = True
|
||||
@ -200,6 +179,7 @@ class Item(object):
|
||||
h.pos.y -= y
|
||||
return updated
|
||||
|
||||
|
||||
def draw(self, context):
|
||||
"""
|
||||
Render the item to a canvas view.
|
||||
@ -212,18 +192,21 @@ class Item(object):
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
def handles(self):
|
||||
"""
|
||||
Return a list of handles owned by the item.
|
||||
"""
|
||||
return self._handles
|
||||
|
||||
|
||||
def ports(self):
|
||||
"""
|
||||
Return list of ports.
|
||||
"""
|
||||
return self._ports
|
||||
|
||||
|
||||
def point(self, pos):
|
||||
"""
|
||||
Get the distance from a point (``x``, ``y``) to the item.
|
||||
@ -231,14 +214,15 @@ class Item(object):
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
def constraint(self,
|
||||
horizontal=None,
|
||||
vertical=None,
|
||||
left_of=None,
|
||||
above=None,
|
||||
line=None,
|
||||
delta=0.0,
|
||||
align=None):
|
||||
horizontal=None,
|
||||
vertical=None,
|
||||
left_of=None,
|
||||
above=None,
|
||||
line=None,
|
||||
delta=0.0,
|
||||
align=None):
|
||||
"""
|
||||
Utility (factory) method to create item's internal constraint between
|
||||
two positions or between a position and a line.
|
||||
@ -264,7 +248,7 @@ class Item(object):
|
||||
line=(p, l)
|
||||
Keep position ``p`` on line ``l``.
|
||||
"""
|
||||
cc = None # created constraint
|
||||
cc = None # created constraint
|
||||
if horizontal:
|
||||
p1, p2 = horizontal
|
||||
cc = EqualsConstraint(p1[1], p2[1], delta)
|
||||
@ -289,6 +273,7 @@ class Item(object):
|
||||
self._constraints.append(cc)
|
||||
return cc
|
||||
|
||||
|
||||
def __getstate__(self):
|
||||
"""
|
||||
Persist all, but calculated values (``_matrix_?2?``).
|
||||
@ -302,6 +287,7 @@ class Item(object):
|
||||
d['_canvas_projections'] = tuple(self._canvas_projections)
|
||||
return d
|
||||
|
||||
|
||||
def __setstate__(self, state):
|
||||
"""
|
||||
Set state. No ``__init__()`` is called.
|
||||
@ -314,11 +300,10 @@ class Item(object):
|
||||
self._canvas_projections = WeakSet(state['_canvas_projections'])
|
||||
|
||||
|
||||
[NW,
|
||||
NE,
|
||||
SE,
|
||||
SW] = range(4)
|
||||
|
||||
[ NW,
|
||||
NE,
|
||||
SE,
|
||||
SW ] = xrange(4)
|
||||
|
||||
class Element(Item):
|
||||
"""
|
||||
@ -334,7 +319,7 @@ class Element(Item):
|
||||
|
||||
def __init__(self, width=10, height=10):
|
||||
super(Element, self).__init__()
|
||||
self._handles = [h(strength=VERY_STRONG) for h in [Handle] * 4]
|
||||
self._handles = [ h(strength=VERY_STRONG) for h in [Handle]*4 ]
|
||||
|
||||
handles = self._handles
|
||||
h_nw = handles[NW]
|
||||
@ -367,7 +352,8 @@ class Element(Item):
|
||||
self.height = height
|
||||
|
||||
# TODO: constraints that calculate width and height based on handle pos
|
||||
# self.constraints.append(EqualsConstraint(p1[1], p2[1], delta))
|
||||
#self.constraints.append(EqualsConstraint(p1[1], p2[1], delta))
|
||||
|
||||
|
||||
def setup_canvas(self):
|
||||
super(Element, self).setup_canvas()
|
||||
@ -390,6 +376,7 @@ class Element(Item):
|
||||
h = self._handles
|
||||
h[SE].pos.x = h[NW].pos.x + width
|
||||
|
||||
|
||||
def _get_width(self):
|
||||
"""
|
||||
Width of the box, calculated as the distance from the left and
|
||||
@ -426,33 +413,6 @@ class Element(Item):
|
||||
|
||||
height = property(_get_height, _set_height)
|
||||
|
||||
# @observed
|
||||
# def _set_min_width(self, min_width):
|
||||
# """
|
||||
# Set minimal width.
|
||||
# """
|
||||
# if min_width < 0:
|
||||
# raise ValueError, 'Minimal width cannot be less than 0'
|
||||
#
|
||||
# self._c_min_w.delta = min_width
|
||||
# if self.canvas:
|
||||
# self.canvas.solver.request_resolve_constraint(self._c_min_w)
|
||||
|
||||
# min_width = reversible_property(lambda s: s._c_min_w.delta, _set_min_width)
|
||||
|
||||
# @observed
|
||||
# def _set_min_height(self, min_height):
|
||||
# """
|
||||
# Set minimal height.
|
||||
# """
|
||||
# if min_height < 0:
|
||||
# raise ValueError, 'Minimal height cannot be less than 0'
|
||||
#
|
||||
# self._c_min_h.delta = min_height
|
||||
# if self.canvas:
|
||||
# self.canvas.solver.request_resolve_constraint(self._c_min_h)
|
||||
#
|
||||
# min_height = reversible_property(lambda s: s._c_min_h.delta, _set_min_height)
|
||||
|
||||
def normalize(self):
|
||||
"""
|
||||
@ -461,7 +421,7 @@ class Element(Item):
|
||||
updated = False
|
||||
handles = self._handles
|
||||
handles = (handles[NW], handles[SE])
|
||||
x, y = list(map(float, handles[0].pos))
|
||||
x, y = map(float, handles[0].pos)
|
||||
if x:
|
||||
self.matrix.translate(x, 0)
|
||||
updated = True
|
||||
@ -484,7 +444,7 @@ class Element(Item):
|
||||
"""
|
||||
h = self._handles
|
||||
pnw, pse = h[NW].pos, h[SE].pos
|
||||
return distance_rectangle_point(list(map(float, (pnw.x, pnw.y, pse.x, pse.y))), pos)
|
||||
return distance_rectangle_point(map(float, (pnw.x, pnw.y, pse.x, pse.y)), pos)
|
||||
|
||||
|
||||
class Line(Item):
|
||||
@ -537,7 +497,7 @@ class Line(Item):
|
||||
observed, so the undo system will update the contents properly
|
||||
"""
|
||||
if not self.canvas:
|
||||
self._orthogonal_constraints = orthogonal and [None] or []
|
||||
self._orthogonal_constraints = orthogonal and [ None ] or []
|
||||
return
|
||||
|
||||
for c in self._orthogonal_constraints:
|
||||
@ -548,16 +508,16 @@ class Line(Item):
|
||||
return
|
||||
|
||||
h = self._handles
|
||||
# if len(h) < 3:
|
||||
#if len(h) < 3:
|
||||
# self.split_segment(0)
|
||||
eq = EqualsConstraint # lambda a, b: a - b
|
||||
eq = EqualsConstraint #lambda a, b: a - b
|
||||
add = self.canvas.solver.add_constraint
|
||||
cons = []
|
||||
rest = self._horizontal and 1 or 0
|
||||
for pos, (h0, h1) in enumerate(zip(h, h[1:])):
|
||||
p0 = h0.pos
|
||||
p1 = h1.pos
|
||||
if pos % 2 == rest: # odd
|
||||
if pos % 2 == rest: # odd
|
||||
cons.append(add(eq(a=p0.x, b=p1.x)))
|
||||
else:
|
||||
cons.append(add(eq(a=p0.y, b=p1.y)))
|
||||
@ -583,7 +543,7 @@ class Line(Item):
|
||||
False
|
||||
"""
|
||||
if orthogonal and len(self.handles()) < 3:
|
||||
raise ValueError("Can't set orthogonal line with less than 3 handles")
|
||||
raise ValueError, "Can't set orthogonal line with less than 3 handles"
|
||||
self._update_orthogonal_constraints(orthogonal)
|
||||
|
||||
orthogonal = reversible_property(lambda s: bool(s._orthogonal_constraints), _set_orthogonal)
|
||||
@ -593,7 +553,7 @@ class Line(Item):
|
||||
self._horizontal = horizontal
|
||||
|
||||
reversible_method(_inner_set_horizontal, _inner_set_horizontal,
|
||||
{'horizontal': lambda horizontal: not horizontal})
|
||||
{'horizontal': lambda horizontal: not horizontal })
|
||||
|
||||
def _set_horizontal(self, horizontal):
|
||||
"""
|
||||
@ -632,8 +592,8 @@ class Line(Item):
|
||||
def _reversible_remove_handle(self, handle):
|
||||
self._handles.remove(handle)
|
||||
|
||||
reversible_pair(_reversible_insert_handle, _reversible_remove_handle,
|
||||
bind1={'index': lambda self, handle: self._handles.index(handle)})
|
||||
reversible_pair(_reversible_insert_handle, _reversible_remove_handle, \
|
||||
bind1={'index': lambda self, handle: self._handles.index(handle)})
|
||||
|
||||
@observed
|
||||
def _reversible_insert_port(self, index, port):
|
||||
@ -643,15 +603,18 @@ class Line(Item):
|
||||
def _reversible_remove_port(self, port):
|
||||
self._ports.remove(port)
|
||||
|
||||
reversible_pair(_reversible_insert_port, _reversible_remove_port,
|
||||
bind1={'index': lambda self, port: self._ports.index(port)})
|
||||
reversible_pair(_reversible_insert_port, _reversible_remove_port, \
|
||||
bind1={'index': lambda self, port: self._ports.index(port)})
|
||||
|
||||
|
||||
def _create_handle(self, pos, strength=WEAK):
|
||||
return Handle(pos, strength=strength)
|
||||
|
||||
|
||||
def _create_port(self, p1, p2):
|
||||
return LinePort(p1, p2)
|
||||
|
||||
|
||||
def _update_ports(self):
|
||||
"""
|
||||
Update line ports. This destroys all previously created ports and
|
||||
@ -663,6 +626,7 @@ class Line(Item):
|
||||
for h1, h2 in zip(handles[:-1], handles[1:]):
|
||||
self._ports.append(self._create_port(h1.pos, h2.pos))
|
||||
|
||||
|
||||
def opposite(self, handle):
|
||||
"""
|
||||
Given the handle of one end of the line, return the other end.
|
||||
@ -689,7 +653,7 @@ class Line(Item):
|
||||
def closest_segment(self, pos):
|
||||
"""
|
||||
Obtain a tuple (distance, point_on_line, segment).
|
||||
Distance is the distance from point to the closest line segment
|
||||
Distance is the distance from point to the closest line segment
|
||||
Point_on_line is the reflection of the point on the line.
|
||||
Segment is the line segment closest to (x, y)
|
||||
|
||||
@ -698,12 +662,12 @@ class Line(Item):
|
||||
(0.7071067811865476, (4.5, 4.5), 0)
|
||||
"""
|
||||
h = self._handles
|
||||
hpos = list(map(getattr, h, ['pos'] * len(h)))
|
||||
hpos = map(getattr, h, ['pos'] * len(h))
|
||||
|
||||
# create a list of (distance, point_on_line) tuples:
|
||||
distances = list(map(distance_line_point, hpos[:-1], hpos[1:], [pos] * (len(hpos) - 1)))
|
||||
distances, pols = list(zip(*distances))
|
||||
return reduce(min, list(zip(distances, pols, list(range(len(distances))))))
|
||||
distances = map(distance_line_point, hpos[:-1], hpos[1:], [pos] * (len(hpos) - 1))
|
||||
distances, pols = zip(*distances)
|
||||
return reduce(min, zip(distances, pols, range(len(distances))))
|
||||
|
||||
def point(self, pos):
|
||||
"""
|
||||
@ -732,12 +696,12 @@ class Line(Item):
|
||||
"""
|
||||
context.cairo.line_to(0, 0)
|
||||
|
||||
|
||||
def draw(self, context):
|
||||
"""
|
||||
Draw the line itself.
|
||||
See Item.draw(context).
|
||||
"""
|
||||
|
||||
def draw_line_end(pos, angle, draw):
|
||||
cr = context.cairo
|
||||
cr.save()
|
||||
@ -756,18 +720,19 @@ class Line(Item):
|
||||
draw_line_end(self._handles[-1].pos, self._tail_angle, self.draw_tail)
|
||||
cr.stroke()
|
||||
|
||||
# debug code to draw line ports
|
||||
# cr.set_line_width(1)
|
||||
# cr.set_source_rgb(1.0, 0.0, 0.0)
|
||||
# for p in self.ports():
|
||||
# cr.move_to(*p.start)
|
||||
# cr.line_to(*p.end)
|
||||
# cr.stroke()
|
||||
### debug code to draw line ports
|
||||
### cr.set_line_width(1)
|
||||
### cr.set_source_rgb(1.0, 0.0, 0.0)
|
||||
### for p in self.ports():
|
||||
### cr.move_to(*p.start)
|
||||
### cr.line_to(*p.end)
|
||||
### cr.stroke()
|
||||
|
||||
|
||||
|
||||
__test__ = {
|
||||
'Line._set_orthogonal': Line._set_orthogonal,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# vim: sw=4:et:ai
|
||||
|
@ -1,23 +1,3 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2007-2017 Arjan Molenaar <gaphor@gmail.com>
|
||||
# Dan Yeaw <dan@yeaw.me>
|
||||
#
|
||||
# This file is part of Gaphas.
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Library General Public License as published by the Free
|
||||
# Software Foundation; either version 2 of the License, or (at your option) any
|
||||
# later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Library General Public License
|
||||
# along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
Some Gaphor specific updates to the canvas. This is done by setting the
|
||||
correct properties on gaphas' modules.
|
||||
@ -28,15 +8,12 @@ Small utility class wrapping cairo.Matrix. The `Matrix` class
|
||||
adds state preservation capabilities.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import cairo
|
||||
|
||||
from gaphas.state import observed, reversible_method
|
||||
|
||||
__version__ = "$Revision$"
|
||||
# $HeadURL$
|
||||
|
||||
import cairo
|
||||
from state import observed, reversible_method
|
||||
|
||||
|
||||
class Matrix(object):
|
||||
"""
|
||||
@ -77,11 +54,11 @@ class Matrix(object):
|
||||
|
||||
reversible_method(invert, invert)
|
||||
reversible_method(rotate, rotate,
|
||||
{'radians': lambda radians: -radians})
|
||||
{ 'radians': lambda radians: -radians })
|
||||
reversible_method(scale, scale,
|
||||
{'sx': lambda sx: 1 / sx, 'sy': lambda sy: 1 / sy})
|
||||
{ 'sx': lambda sx: 1/sx, 'sy': lambda sy: 1/sy })
|
||||
reversible_method(translate, translate,
|
||||
{'tx': lambda tx: -tx, 'ty': lambda ty: -ty})
|
||||
{ 'tx': lambda tx: -tx, 'ty': lambda ty: -ty })
|
||||
|
||||
def transform_distance(self, dx, dy):
|
||||
self._matrix.transform_distance(dx, dy)
|
||||
|
@ -1,25 +1,3 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2006-2017 Antony N. Pavlov <antony@niisi.msk.ru>
|
||||
# Arjan Molenaar <gaphor@gmail.com>
|
||||
# Artur Wroblewski <wrobell@pld-linux.org>
|
||||
# Dan Yeaw <dan@yeaw.me>
|
||||
#
|
||||
# This file is part of Gaphas.
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Library General Public License as published by the Free
|
||||
# Software Foundation; either version 2 of the License, or (at your option) any
|
||||
# later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Library General Public License
|
||||
# along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
The painter module provides different painters for parts of the canvas.
|
||||
|
||||
@ -29,24 +7,23 @@ Each painter takes care of a layer in the canvas (such as grid, items
|
||||
and handles).
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from cairo import ANTIALIAS_NONE, LINE_JOIN_ROUND
|
||||
|
||||
from gaphas.aspect import PaintFocused
|
||||
from gaphas.canvas import Context
|
||||
from gaphas.geometry import Rectangle
|
||||
|
||||
__version__ = "$Revision$"
|
||||
# $HeadURL$
|
||||
|
||||
from cairo import Matrix, ANTIALIAS_NONE, LINE_JOIN_ROUND
|
||||
|
||||
from gaphas.canvas import Context
|
||||
from gaphas.geometry import Rectangle
|
||||
from gaphas.item import Line
|
||||
from gaphas.aspect import PaintFocused
|
||||
|
||||
|
||||
DEBUG_DRAW_BOUNDING_BOX = False
|
||||
|
||||
# The tolerance for Cairo. Bigger values increase speed and reduce accuracy
|
||||
# (default: 0.1)
|
||||
TOLERANCE = 0.8
|
||||
|
||||
|
||||
class Painter(object):
|
||||
"""
|
||||
Painter interface.
|
||||
@ -116,13 +93,16 @@ class DrawContext(Context):
|
||||
|
||||
|
||||
class ItemPainter(Painter):
|
||||
|
||||
draw_all = False
|
||||
|
||||
def _draw_item(self, item, cairo, area=None):
|
||||
view = self.view
|
||||
cairo.save()
|
||||
try:
|
||||
cairo.set_matrix(view.matrix)
|
||||
cairo.transform(view.canvas.get_matrix_i2c(item))
|
||||
|
||||
item.draw(DrawContext(painter=self,
|
||||
cairo=cairo,
|
||||
_area=area,
|
||||
@ -150,7 +130,7 @@ class ItemPainter(Painter):
|
||||
try:
|
||||
b = view.get_item_bounding_box(item)
|
||||
except KeyError:
|
||||
pass # No bounding box right now..
|
||||
pass # No bounding box right now..
|
||||
else:
|
||||
cairo.save()
|
||||
cairo.identity_matrix()
|
||||
@ -164,11 +144,7 @@ class ItemPainter(Painter):
|
||||
cairo = context.cairo
|
||||
cairo.set_tolerance(TOLERANCE)
|
||||
cairo.set_line_join(LINE_JOIN_ROUND)
|
||||
|
||||
cairo.save()
|
||||
cairo.transform(self.view.matrix)
|
||||
self._draw_items(context.items, cairo, context.area)
|
||||
cairo.restore()
|
||||
|
||||
|
||||
class CairoBoundingBoxContext(object):
|
||||
@ -180,7 +156,7 @@ class CairoBoundingBoxContext(object):
|
||||
|
||||
def __init__(self, cairo):
|
||||
self._cairo = cairo
|
||||
self._bounds = None # a Rectangle object
|
||||
self._bounds = None # a Rectangle object
|
||||
|
||||
def __getattr__(self, key):
|
||||
return getattr(self._cairo, key)
|
||||
@ -211,9 +187,9 @@ class CairoBoundingBoxContext(object):
|
||||
cr.restore()
|
||||
if b and line_width:
|
||||
# Do this after the restore(), so we can get the proper width.
|
||||
lw = cr.get_line_width() / 2
|
||||
lw = cr.get_line_width()/2
|
||||
d = cr.user_to_device_distance(lw, lw)
|
||||
b.expand(d[0] + d[1])
|
||||
b.expand(d[0]+d[1])
|
||||
self._update_bounds(b)
|
||||
return b
|
||||
|
||||
@ -259,8 +235,8 @@ class CairoBoundingBoxContext(object):
|
||||
if not b:
|
||||
x, y = cr.get_current_point()
|
||||
e = cr.text_extents(utf8)
|
||||
x0, y0 = cr.user_to_device(x + e[0], y + e[1])
|
||||
x1, y1 = cr.user_to_device(x + e[0] + e[2], y + e[1] + e[3])
|
||||
x0, y0 = cr.user_to_device(x+e[0], y+e[1])
|
||||
x1, y1 = cr.user_to_device(x+e[0]+e[2], y+e[1]+e[3])
|
||||
b = Rectangle(x0, y0, x1=x1, y1=y1)
|
||||
self._update_bounds(b)
|
||||
cr.show_text(utf8)
|
||||
@ -289,6 +265,7 @@ class BoundingBoxPainter(ItemPainter):
|
||||
bounds.expand(1)
|
||||
view.set_item_bounding_box(item, bounds)
|
||||
|
||||
|
||||
def _draw_items(self, items, cairo, area=None):
|
||||
"""
|
||||
Draw the items.
|
||||
@ -308,7 +285,7 @@ class HandlePainter(Painter):
|
||||
def _draw_handles(self, item, cairo, opacity=None, inner=False):
|
||||
"""
|
||||
Draw handles for an item.
|
||||
The handles are drawn in non-antialiased mode for clearity.
|
||||
The handles are drawn in non-antialiased mode for clarity.
|
||||
"""
|
||||
view = self.view
|
||||
cairo.save()
|
||||
@ -317,7 +294,6 @@ class HandlePainter(Painter):
|
||||
opacity = (item is view.focused_item) and .7 or .4
|
||||
|
||||
cairo.set_line_width(1)
|
||||
cairo.set_antialias(ANTIALIAS_NONE)
|
||||
|
||||
get_connection = view.canvas.get_connection
|
||||
for h in item.handles():
|
||||
@ -334,8 +310,8 @@ class HandlePainter(Painter):
|
||||
else:
|
||||
r, g, b = 0, 0, 1
|
||||
|
||||
# cairo.identity_matrix()
|
||||
cairo.save()
|
||||
cairo.identity_matrix()
|
||||
cairo.set_antialias(ANTIALIAS_NONE)
|
||||
cairo.translate(*i2v.transform_point(*h.pos))
|
||||
cairo.rectangle(-4, -4, 8, 8)
|
||||
if inner:
|
||||
@ -347,9 +323,8 @@ class HandlePainter(Painter):
|
||||
cairo.line_to(2, 3)
|
||||
cairo.move_to(2, -2)
|
||||
cairo.line_to(-2, 3)
|
||||
cairo.set_source_rgba(r / 4., g / 4., b / 4., opacity * 1.3)
|
||||
cairo.set_source_rgba(r/4., g/4., b/4., opacity*1.3)
|
||||
cairo.stroke()
|
||||
cairo.restore()
|
||||
cairo.restore()
|
||||
|
||||
def paint(self, context):
|
||||
@ -379,10 +354,10 @@ class ToolPainter(Painter):
|
||||
cairo = context.cairo
|
||||
if view.tool:
|
||||
cairo.save()
|
||||
cairo.identity_matrix()
|
||||
view.tool.draw(context)
|
||||
cairo.restore()
|
||||
|
||||
|
||||
class FocusedItemPainter(Painter):
|
||||
"""
|
||||
This painter allows for drawing on top off all other layers for the
|
||||
@ -406,4 +381,5 @@ def DefaultPainter(view=None):
|
||||
append(FocusedItemPainter()). \
|
||||
append(ToolPainter())
|
||||
|
||||
|
||||
# vim: sw=4:et:ai
|
||||
|
@ -1,58 +1,35 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2008-2017 Arjan Molenaar <gaphor@gmail.com>
|
||||
# Dan Yeaw <dan@yeaw.me>
|
||||
#
|
||||
# This file is part of Gaphas.
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Library General Public License as published by the Free
|
||||
# Software Foundation; either version 2 of the License, or (at your option) any
|
||||
# later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Library General Public License
|
||||
# along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
Some extra picklers needed to gracefully dump and load a canvas.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import types
|
||||
|
||||
import cairo
|
||||
import copyreg
|
||||
|
||||
|
||||
def construct_instancemethod(funcname, self, clazz):
|
||||
func = getattr(clazz, funcname)
|
||||
return types.MethodType(func, self, clazz)
|
||||
|
||||
|
||||
def reduce_instancemethod(im):
|
||||
return construct_instancemethod, (im.__func__.__name__, im.__self__, im.__self__.__class__)
|
||||
|
||||
|
||||
copyreg.pickle(types.MethodType, reduce_instancemethod, construct_instancemethod)
|
||||
|
||||
# Allow cairo.Matrix to be pickled:
|
||||
|
||||
|
||||
def construct_cairo_matrix(*args):
|
||||
return cairo.Matrix(*args)
|
||||
|
||||
|
||||
def reduce_cairo_matrix(m):
|
||||
return construct_cairo_matrix, tuple(m)
|
||||
|
||||
|
||||
copyreg.pickle(cairo.Matrix, reduce_cairo_matrix, construct_cairo_matrix)
|
||||
|
||||
|
||||
# vim:sw=4:et:ai
|
||||
"""
|
||||
Some extra picklers needed to gracefully dump and load a canvas.
|
||||
"""
|
||||
|
||||
import copy_reg
|
||||
|
||||
|
||||
# Allow instancemethod to be pickled:
|
||||
|
||||
import new
|
||||
|
||||
def construct_instancemethod(funcname, self, clazz):
|
||||
func = getattr(clazz, funcname)
|
||||
return new.instancemethod(func, self, clazz)
|
||||
|
||||
def reduce_instancemethod(im):
|
||||
return construct_instancemethod, (im.im_func.__name__, im.im_self, im.im_class)
|
||||
|
||||
copy_reg.pickle(new.instancemethod, reduce_instancemethod, construct_instancemethod)
|
||||
|
||||
|
||||
# Allow cairo.Matrix to be pickled:
|
||||
|
||||
import cairo
|
||||
|
||||
def construct_cairo_matrix(*args):
|
||||
return cairo.Matrix(*args)
|
||||
|
||||
def reduce_cairo_matrix(m):
|
||||
return construct_cairo_matrix, tuple(m)
|
||||
|
||||
copy_reg.pickle(cairo.Matrix, reduce_cairo_matrix, construct_cairo_matrix)
|
||||
|
||||
|
||||
# vim:sw=4:et:ai
|
||||
|
@ -1,24 +1,3 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2007-2017 Arjan Molenaar <gaphor@gmail.com>
|
||||
# Artur Wroblewski <wrobell@pld-linux.org>
|
||||
# Dan Yeaw <dan@yeaw.me>
|
||||
#
|
||||
# This file is part of Gaphas.
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Library General Public License as published by the Free
|
||||
# Software Foundation; either version 2 of the License, or (at your option) any
|
||||
# later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Library General Public License
|
||||
# along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
Quadtree
|
||||
========
|
||||
@ -39,20 +18,12 @@ common features:
|
||||
(From Wikipedia, the free encyclopedia)
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import print_function
|
||||
|
||||
import operator
|
||||
|
||||
import six
|
||||
from six.moves import map
|
||||
from six.moves import zip
|
||||
|
||||
from gaphas.geometry import rectangle_contains, rectangle_intersects, rectangle_clip
|
||||
|
||||
__version__ = "$Revision$"
|
||||
# $HeadURL$
|
||||
|
||||
import operator
|
||||
from geometry import rectangle_contains, rectangle_intersects, rectangle_clip
|
||||
|
||||
|
||||
class Quadtree(object):
|
||||
"""
|
||||
@ -114,10 +85,10 @@ class Quadtree(object):
|
||||
def __init__(self, bounds=(0, 0, 0, 0), capacity=10):
|
||||
"""
|
||||
Create a new Quadtree instance.
|
||||
|
||||
|
||||
Bounds is the boundries of the quadtree. this is fixed and do not
|
||||
change depending on the contents.
|
||||
|
||||
|
||||
Capacity defines the number of elements in one tree bucket (default: 10)
|
||||
"""
|
||||
self._capacity = capacity
|
||||
@ -126,8 +97,10 @@ class Quadtree(object):
|
||||
# Easy lookup item->(bounds, data, clipped bounds) mapping
|
||||
self._ids = dict()
|
||||
|
||||
|
||||
bounds = property(lambda s: s._bucket.bounds)
|
||||
|
||||
|
||||
def resize(self, bounds):
|
||||
"""
|
||||
Resize the tree.
|
||||
@ -136,6 +109,7 @@ class Quadtree(object):
|
||||
self._bucket = QuadtreeBucket(bounds, self._capacity)
|
||||
self.rebuild()
|
||||
|
||||
|
||||
def get_soft_bounds(self):
|
||||
"""
|
||||
Calculate the size of all items in the tree. This size may be beyond
|
||||
@ -156,18 +130,19 @@ class Quadtree(object):
|
||||
>>> qtree.bounds
|
||||
(0, 0, 0, 0)
|
||||
"""
|
||||
x_y_w_h = list(zip(*list(map(operator.getitem, six.itervalues(self._ids), [0] * len(self._ids)))))
|
||||
x_y_w_h = zip(*map(operator.getitem, self._ids.itervalues(), [0] * len(self._ids)))
|
||||
if not x_y_w_h:
|
||||
return 0, 0, 0, 0
|
||||
x0 = min(x_y_w_h[0])
|
||||
y0 = min(x_y_w_h[1])
|
||||
add = operator.add
|
||||
x1 = max(list(map(add, x_y_w_h[0], x_y_w_h[2])))
|
||||
y1 = max(list(map(add, x_y_w_h[1], x_y_w_h[3])))
|
||||
return x0, y0, x1 - x0, y1 - y0
|
||||
x1 = max(map(add, x_y_w_h[0], x_y_w_h[2]))
|
||||
y1 = max(map(add, x_y_w_h[1], x_y_w_h[3]))
|
||||
return (x0, y0, x1 - x0, y1 - y0)
|
||||
|
||||
soft_bounds = property(get_soft_bounds)
|
||||
|
||||
|
||||
def add(self, item, bounds, data=None):
|
||||
"""
|
||||
Add an item to the tree.
|
||||
@ -198,6 +173,7 @@ class Quadtree(object):
|
||||
self._bucket.find_bucket(clipped_bounds).add(item, clipped_bounds)
|
||||
self._ids[item] = (bounds, data, clipped_bounds)
|
||||
|
||||
|
||||
def remove(self, item):
|
||||
"""
|
||||
Remove an item from the tree.
|
||||
@ -207,6 +183,7 @@ class Quadtree(object):
|
||||
if clipped_bounds:
|
||||
self._bucket.find_bucket(clipped_bounds).remove(item)
|
||||
|
||||
|
||||
def clear(self):
|
||||
"""
|
||||
Remove all items from the tree.
|
||||
@ -214,6 +191,7 @@ class Quadtree(object):
|
||||
self._bucket.clear()
|
||||
self._ids.clear()
|
||||
|
||||
|
||||
def rebuild(self):
|
||||
"""
|
||||
Rebuild the tree structure.
|
||||
@ -221,24 +199,27 @@ class Quadtree(object):
|
||||
# Clean bucket and items:
|
||||
self._bucket.clear()
|
||||
|
||||
for item, (bounds, data, _) in six.iteritems(dict(self._ids)):
|
||||
for item, (bounds, data, _) in dict(self._ids).iteritems():
|
||||
clipped_bounds = rectangle_clip(bounds, self._bucket.bounds)
|
||||
if clipped_bounds:
|
||||
self._bucket.find_bucket(clipped_bounds).add(item, clipped_bounds)
|
||||
self._ids[item] = (bounds, data, clipped_bounds)
|
||||
|
||||
|
||||
def get_bounds(self, item):
|
||||
"""
|
||||
Return the bounding box for the given item.
|
||||
"""
|
||||
return self._ids[item][0]
|
||||
|
||||
|
||||
def get_data(self, item):
|
||||
"""
|
||||
Return the data for the given item, None if no data was provided.
|
||||
"""
|
||||
return self._ids[item][1]
|
||||
|
||||
|
||||
def get_clipped_bounds(self, item):
|
||||
"""
|
||||
Return the bounding box for the given item. The bounding box is clipped
|
||||
@ -247,6 +228,7 @@ class Quadtree(object):
|
||||
"""
|
||||
return self._ids[item][2]
|
||||
|
||||
|
||||
def find_inside(self, rect):
|
||||
"""
|
||||
Find all items in the given rectangle (x, y, with, height).
|
||||
@ -254,6 +236,7 @@ class Quadtree(object):
|
||||
"""
|
||||
return set(self._bucket.find(rect, method=rectangle_contains))
|
||||
|
||||
|
||||
def find_intersect(self, rect):
|
||||
"""
|
||||
Find all items that intersect with the given rectangle
|
||||
@ -262,18 +245,21 @@ class Quadtree(object):
|
||||
"""
|
||||
return set(self._bucket.find(rect, method=rectangle_intersects))
|
||||
|
||||
|
||||
def __len__(self):
|
||||
"""
|
||||
Return number of items in tree.
|
||||
"""
|
||||
return len(self._ids)
|
||||
|
||||
|
||||
def __contains__(self, item):
|
||||
"""
|
||||
Check if an item is in tree.
|
||||
"""
|
||||
return item in self._ids
|
||||
|
||||
|
||||
def dump(self):
|
||||
"""
|
||||
Print structure to stdout.
|
||||
@ -296,6 +282,7 @@ class QuadtreeBucket(object):
|
||||
self.items = {}
|
||||
self._buckets = []
|
||||
|
||||
|
||||
def add(self, item, bounds):
|
||||
"""
|
||||
Add an item to the quadtree.
|
||||
@ -313,7 +300,7 @@ class QuadtreeBucket(object):
|
||||
QuadtreeBucket((x, cy, rw, rh), self.capacity),
|
||||
QuadtreeBucket((cx, cy, rw, rh), self.capacity)]
|
||||
# Add items to subnodes
|
||||
items = list(self.items.items())
|
||||
items = self.items.items()
|
||||
self.items.clear()
|
||||
for i, b in items:
|
||||
self.find_bucket(b).add(i, b)
|
||||
@ -321,6 +308,7 @@ class QuadtreeBucket(object):
|
||||
else:
|
||||
self.items[item] = bounds
|
||||
|
||||
|
||||
def remove(self, item):
|
||||
"""
|
||||
Remove an item from the quadtree bucket.
|
||||
@ -328,6 +316,7 @@ class QuadtreeBucket(object):
|
||||
"""
|
||||
del self.items[item]
|
||||
|
||||
|
||||
def update(self, item, new_bounds):
|
||||
"""
|
||||
Update the position of an item within the current bucket.
|
||||
@ -338,6 +327,7 @@ class QuadtreeBucket(object):
|
||||
self.remove(item)
|
||||
self.find_bucket(new_bounds).add(item, new_bounds)
|
||||
|
||||
|
||||
def find_bucket(self, bounds):
|
||||
"""
|
||||
Find the bucket that holds a bounding box.
|
||||
@ -362,6 +352,7 @@ class QuadtreeBucket(object):
|
||||
return self._buckets[index].find_bucket(bounds)
|
||||
return self
|
||||
|
||||
|
||||
def find(self, rect, method):
|
||||
"""
|
||||
Find all items in the given rectangle (x, y, with, height).
|
||||
@ -370,13 +361,14 @@ class QuadtreeBucket(object):
|
||||
Returns an iterator.
|
||||
"""
|
||||
if rectangle_intersects(rect, self.bounds):
|
||||
for item, bounds in six.iteritems(self.items):
|
||||
for item, bounds in self.items.iteritems():
|
||||
if method(bounds, rect):
|
||||
yield item
|
||||
for bucket in self._buckets:
|
||||
for item in bucket.find(rect, method=method):
|
||||
yield item
|
||||
|
||||
|
||||
def clear(self):
|
||||
"""
|
||||
Clear the bucket, including sub-buckets.
|
||||
@ -384,12 +376,14 @@ class QuadtreeBucket(object):
|
||||
del self._buckets[:]
|
||||
self.items.clear()
|
||||
|
||||
|
||||
def dump(self, indent=''):
|
||||
print(indent, self, self.bounds)
|
||||
indent += ' '
|
||||
for item, bounds in sorted(six.iteritems(self.items)):
|
||||
print(indent, item, bounds)
|
||||
for bucket in self._buckets:
|
||||
bucket.dump(indent)
|
||||
print indent, self, self.bounds
|
||||
indent += ' '
|
||||
for item, bounds in sorted(self.items.iteritems()):
|
||||
print indent, item, bounds
|
||||
for bucket in self._buckets:
|
||||
bucket.dump(indent)
|
||||
|
||||
|
||||
# vim:sw=4:et:ai
|
||||
|
@ -1,48 +1,26 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2010-2017 Arjan Molenaar <gaphor@gmail.com>
|
||||
# Dan Yeaw <dan@yeaw.me>
|
||||
#
|
||||
# This file is part of Gaphas.
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Library General Public License as published by the Free
|
||||
# Software Foundation; either version 2 of the License, or (at your option) any
|
||||
# later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Library General Public License
|
||||
# along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
Allow for easily adding segments to lines.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from cairo import Matrix, ANTIALIAS_NONE
|
||||
from simplegeneric import generic
|
||||
from six.moves import zip
|
||||
|
||||
from gaphas.aspect import ConnectionSink
|
||||
from gaphas.aspect import HandleFinder, HandleSelection, PaintFocused
|
||||
from gaphas.aspect import ItemHandleFinder, ItemHandleSelection, ItemPaintFocused
|
||||
from gaphas.geometry import distance_point_point_fast, distance_line_point
|
||||
from gaphas.item import Line
|
||||
from gaphas.aspect import HandleFinder, HandleSelection, PaintFocused
|
||||
from gaphas.aspect import ConnectionSink
|
||||
from gaphas.aspect import ItemHandleFinder, ItemHandleSelection, ItemPaintFocused
|
||||
|
||||
|
||||
@generic
|
||||
class Segment(object):
|
||||
|
||||
def __init__(self, item, view):
|
||||
raise TypeError
|
||||
|
||||
|
||||
@Segment.when_type(Line)
|
||||
class LineSegment(object):
|
||||
|
||||
def __init__(self, item, view):
|
||||
self.item = item
|
||||
self.view = view
|
||||
@ -54,7 +32,7 @@ class LineSegment(object):
|
||||
for h1, h2 in zip(handles, handles[1:]):
|
||||
xp = (h1.pos.x + h2.pos.x) / 2
|
||||
yp = (h1.pos.y + h2.pos.y) / 2
|
||||
if distance_point_point_fast((x, y), (xp, yp)) <= 4:
|
||||
if distance_point_point_fast((x,y), (xp, yp)) <= 4:
|
||||
segment = handles.index(h1)
|
||||
handles, ports = self.split_segment(segment)
|
||||
return handles and handles[0]
|
||||
@ -72,7 +50,7 @@ class LineSegment(object):
|
||||
segment
|
||||
Segment number to split (starting from zero).
|
||||
count
|
||||
Amount of new segments to be created (minimum 2).
|
||||
Amount of new segments to be created (minimum 2).
|
||||
"""
|
||||
item = self.item
|
||||
if segment < 0 or segment >= len(item.ports()):
|
||||
@ -108,6 +86,7 @@ class LineSegment(object):
|
||||
ports = item.ports()[segment:segment + count - 1]
|
||||
return handles, ports
|
||||
|
||||
|
||||
def merge_segment(self, segment, count=2):
|
||||
"""
|
||||
Merge two (or more) item segments.
|
||||
@ -119,7 +98,7 @@ class LineSegment(object):
|
||||
segment
|
||||
Segment number to start merging from (starting from zero).
|
||||
count
|
||||
Amount of segments to be merged (minimum 2).
|
||||
Amount of segments to be merged (minimum 2).
|
||||
"""
|
||||
item = self.item
|
||||
if len(item.ports()) < 2:
|
||||
@ -151,6 +130,7 @@ class LineSegment(object):
|
||||
|
||||
return deleted_handles, deleted_ports
|
||||
|
||||
|
||||
def _recreate_constraints(self):
|
||||
"""
|
||||
Create connection constraints between connecting lines and an item.
|
||||
@ -160,10 +140,9 @@ class LineSegment(object):
|
||||
Connected item.
|
||||
"""
|
||||
connected = self.item
|
||||
|
||||
def find_port(line, handle, item):
|
||||
# port = None
|
||||
# max_dist = sys.maxint
|
||||
#port = None
|
||||
#max_dist = sys.maxint
|
||||
canvas = item.canvas
|
||||
|
||||
ix, iy = canvas.get_matrix_i2i(line, item).transform_point(*handle.pos)
|
||||
@ -233,7 +212,7 @@ class SegmentHandleSelection(ItemHandleSelection):
|
||||
|
||||
# cannot merge starting from last segment
|
||||
if segment == len(item.ports()) - 1:
|
||||
segment = - 1
|
||||
segment =- 1
|
||||
assert segment >= 0 and segment < len(item.ports()) - 1
|
||||
|
||||
before = handles[handle_index - 1]
|
||||
@ -248,10 +227,11 @@ class SegmentHandleSelection(ItemHandleSelection):
|
||||
item.request_update()
|
||||
|
||||
|
||||
|
||||
@PaintFocused.when_type(Line)
|
||||
class LineSegmentPainter(ItemPaintFocused):
|
||||
"""
|
||||
This painter draws pseudo-hanldes on gaphas.item.Line objects. Each
|
||||
This painter draws pseudo-handles on gaphas.item.Line objects. Each
|
||||
line can be split by dragging those points, which will result in
|
||||
a new handle.
|
||||
|
||||
@ -283,4 +263,6 @@ class LineSegmentPainter(ItemPaintFocused):
|
||||
cr.stroke()
|
||||
cr.restore()
|
||||
|
||||
|
||||
|
||||
# vim:sw=4:et:ai
|
||||
|
@ -1,24 +1,3 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2006-2017 Arjan Molenaar <gaphor@gmail.com>
|
||||
# Artur Wroblewski <wrobell@pld-linux.org>
|
||||
# Dan Yeaw <dan@yeaw.me>
|
||||
#
|
||||
# This file is part of Gaphas.
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Library General Public License as published by the Free
|
||||
# Software Foundation; either version 2 of the License, or (at your option) any
|
||||
# later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Library General Public License
|
||||
# along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
Constraint solver allows to define constraint between two or more different
|
||||
variables and keep this constraint always true when one or more of the
|
||||
@ -52,14 +31,14 @@ constraint is being asked to solve itself (`constraint.Constraint.solve_for()`
|
||||
method) changing appropriate variables to make the constraint valid again.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
|
||||
from gaphas.state import observed, reversible_pair, reversible_property
|
||||
|
||||
__version__ = "$Revision$"
|
||||
# $HeadURL$
|
||||
|
||||
from operator import isCallable
|
||||
from state import observed, reversible_pair, reversible_property
|
||||
|
||||
# epsilon for float comparison
|
||||
# is simple abs(x - y) > EPSILON enough for canvas needs?
|
||||
EPSILON = 1e-6
|
||||
@ -76,9 +55,9 @@ REQUIRED = 100
|
||||
class Variable(object):
|
||||
"""
|
||||
Representation of a variable in the constraint solver.
|
||||
Each Variable has a @value and a @strength. In a constraint the
|
||||
Each Variable has a @value and a @strength. Ina constraint the
|
||||
weakest variables are changed.
|
||||
|
||||
|
||||
You can even do some calculating with it. The Variable always represents
|
||||
a float variable.
|
||||
"""
|
||||
@ -91,9 +70,6 @@ class Variable(object):
|
||||
self._solver = None
|
||||
self._constraints = set()
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self._value, self._strength))
|
||||
|
||||
@observed
|
||||
def _set_strength(self, strength):
|
||||
self._strength = strength
|
||||
@ -106,7 +82,7 @@ class Variable(object):
|
||||
"""
|
||||
Mark the variable dirty in both the constraint solver and attached
|
||||
constraints.
|
||||
|
||||
|
||||
Variables are marked dirty also during constraints solving to
|
||||
solve all dependent constraints, i.e. two equals constraints
|
||||
between 3 variables.
|
||||
@ -121,7 +97,7 @@ class Variable(object):
|
||||
def set_value(self, value):
|
||||
oldval = self._value
|
||||
if abs(oldval - value) > EPSILON:
|
||||
# print id(self), oldval, value
|
||||
#print id(self), oldval, value
|
||||
self._value = float(value)
|
||||
self.dirty()
|
||||
|
||||
@ -129,7 +105,6 @@ class Variable(object):
|
||||
|
||||
def __str__(self):
|
||||
return 'Variable(%g, %d)' % (self._value, self._strength)
|
||||
|
||||
__repr__ = __str__
|
||||
|
||||
def __float__(self):
|
||||
@ -386,7 +361,6 @@ class Projection(object):
|
||||
|
||||
def __str__(self):
|
||||
return '%s(%s)' % (self.__class__.__name__, self.variable())
|
||||
|
||||
__repr__ = __str__
|
||||
|
||||
|
||||
@ -404,6 +378,7 @@ class Solver(object):
|
||||
|
||||
constraints = property(lambda s: s._constraints)
|
||||
|
||||
|
||||
def request_resolve(self, variable, projections_only=False):
|
||||
"""
|
||||
Mark a variable as "dirty". This means it it solved the next time
|
||||
@ -449,8 +424,8 @@ class Solver(object):
|
||||
c.mark_dirty(variable)
|
||||
self._marked_cons.append(c)
|
||||
if self._marked_cons.count(c) > 100:
|
||||
raise JuggleError('Variable juggling detected, constraint %s resolved %d times out of %d' % (
|
||||
c, self._marked_cons.count(c), len(self._marked_cons)))
|
||||
raise JuggleError, 'Variable juggling detected, constraint %s resolved %d times out of %d' % (c, self._marked_cons.count(c), len(self._marked_cons))
|
||||
|
||||
|
||||
@observed
|
||||
def add_constraint(self, constraint):
|
||||
@ -485,7 +460,7 @@ class Solver(object):
|
||||
constraint._solver_has_projections = True
|
||||
v._constraints.add(constraint)
|
||||
v._solver = self
|
||||
# print 'added constraint', constraint
|
||||
#print 'added constraint', constraint
|
||||
return constraint
|
||||
|
||||
@observed
|
||||
@ -520,12 +495,14 @@ class Solver(object):
|
||||
|
||||
reversible_pair(add_constraint, remove_constraint)
|
||||
|
||||
|
||||
def request_resolve_constraint(self, c):
|
||||
"""
|
||||
Request resolving a constraint.
|
||||
"""
|
||||
self._marked_cons.append(c)
|
||||
|
||||
|
||||
def constraints_with_variable(self, *variables):
|
||||
"""
|
||||
Return an iterator of constraints that work with variable.
|
||||
@ -590,12 +567,13 @@ class Solver(object):
|
||||
else:
|
||||
found = False
|
||||
if not found:
|
||||
break # quit for loop, variable not in constraint
|
||||
break # quit for loop, variable not in constraint
|
||||
else:
|
||||
# All iteration have completed succesfully,
|
||||
# so all variables are in the constraint
|
||||
yield c
|
||||
|
||||
|
||||
def solve(self):
|
||||
"""
|
||||
Example:
|
||||
@ -661,9 +639,9 @@ class solvable(object):
|
||||
>>> a._v_x
|
||||
Variable(12, 20)
|
||||
>>> a.x = 3
|
||||
>>> a.x
|
||||
>>> a.x
|
||||
Variable(3, 20)
|
||||
>>> a.y
|
||||
>>> a.y
|
||||
Variable(0, 30)
|
||||
"""
|
||||
|
||||
@ -702,7 +680,7 @@ class JuggleError(AssertionError):
|
||||
__test__ = {
|
||||
'Solver.add_constraint': Solver.add_constraint,
|
||||
'Solver.remove_constraint': Solver.remove_constraint,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# vim:sw=4:et:ai
|
||||
|
@ -1,24 +1,3 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2007-2017 Arjan Molenaar <gaphor@gmail.com>
|
||||
# Artur Wroblewski <wrobell@pld-linux.org>
|
||||
# Dan Yeaw <dan@yeaw.me>
|
||||
#
|
||||
# This file is part of Gaphas.
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Library General Public License as published by the Free
|
||||
# Software Foundation; either version 2 of the License, or (at your option) any
|
||||
# later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Library General Public License
|
||||
# along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
This module is the central point where Gaphas' classes report their state
|
||||
changes.
|
||||
@ -39,18 +18,12 @@ For this to work the revert_handler has to be added to the observers set::
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import inspect
|
||||
import types, inspect
|
||||
import threading
|
||||
import types
|
||||
|
||||
import six
|
||||
from decorator import decorator
|
||||
from six.moves import zip
|
||||
|
||||
# This string is added to each docstring in order to denote is's observed
|
||||
# OBSERVED_DOCSTRING = \
|
||||
#OBSERVED_DOCSTRING = \
|
||||
# '\n\n This method is @observed. See gaphas.state for extra info.\n'
|
||||
|
||||
# Tell @observed to dispatch invokation messages by default
|
||||
@ -59,19 +32,19 @@ from six.moves import zip
|
||||
DISPATCH_BY_DEFAULT = True
|
||||
|
||||
# Add/remove methods from this subscribers list.
|
||||
# Subscribers should have signature method(event) where event is a
|
||||
# Subscribers should have signature method(event) where event is a
|
||||
# Event has the form: (func, keywords)
|
||||
# Since most events originate from methods, it's save to call
|
||||
# saveapply(func, keywords) for those functions
|
||||
subscribers = set()
|
||||
|
||||
|
||||
# Subscribe to low-level change events:
|
||||
observers = set()
|
||||
|
||||
# Perform locking (should be per thread?).
|
||||
mutex = threading.Lock()
|
||||
|
||||
|
||||
def observed(func):
|
||||
"""
|
||||
Simple observer, dispatches events to functions registered in the observers
|
||||
@ -84,7 +57,6 @@ def observed(func):
|
||||
Also note that the events are dispatched *before* the function is invoked.
|
||||
This is an important feature, esp. for the reverter code.
|
||||
"""
|
||||
|
||||
def wrapper(func, *args, **kwargs):
|
||||
o = func.__observer__
|
||||
acquired = mutex.acquire(False)
|
||||
@ -95,7 +67,6 @@ def observed(func):
|
||||
finally:
|
||||
if acquired:
|
||||
mutex.release()
|
||||
|
||||
dec = decorator(wrapper)(func)
|
||||
|
||||
func.__observer__ = dec
|
||||
@ -124,8 +95,7 @@ def dispatch(event, queue):
|
||||
>>> observers.remove(handler)
|
||||
>>> callme()
|
||||
"""
|
||||
for s in queue:
|
||||
s(event)
|
||||
for s in queue: s(event)
|
||||
|
||||
|
||||
_reverse = dict()
|
||||
@ -163,10 +133,10 @@ def reversible_pair(func1, func2, bind1={}, bind2={}):
|
||||
def reversible_property(fget=None, fset=None, fdel=None, doc=None, bind={}):
|
||||
"""
|
||||
Replacement for the property descriptor. In addition to creating a
|
||||
property instance, the property is registered as reversible and
|
||||
property instance, the property is registered as reversible and
|
||||
reverse events can be send out when changes occur.
|
||||
|
||||
Cave eat: we can't handle both fset and fdel in the proper way. Therefore
|
||||
Caveat: we can't handle both fset and fdel in the proper way. Therefore
|
||||
fdel should somehow invoke fset. (persinally, I hardly use fdel)
|
||||
|
||||
See revert_handler() for doctesting.
|
||||
@ -182,8 +152,8 @@ def reversible_property(fget=None, fset=None, fdel=None, doc=None, bind={}):
|
||||
|
||||
argself, argvalue = argnames
|
||||
func = getfunction(fset)
|
||||
b = {argvalue: eval("lambda %(self)s: fget(%(self)s)" % {'self': argself},
|
||||
{'fget': fget})}
|
||||
b = { argvalue: eval("lambda %(self)s: fget(%(self)s)" % {'self': argself },
|
||||
{'fget': fget}) }
|
||||
b.update(bind)
|
||||
_reverse[func] = (func, spec, b)
|
||||
|
||||
@ -198,7 +168,7 @@ def revert_handler(event):
|
||||
First thing to do is to actually enable the revert_handler:
|
||||
|
||||
>>> observers.add(revert_handler)
|
||||
|
||||
|
||||
First let's define our simple list:
|
||||
|
||||
>>> class SList(object):
|
||||
@ -255,21 +225,18 @@ def revert_handler(event):
|
||||
return
|
||||
|
||||
kw = dict(kwargs)
|
||||
kw.update(dict(list(zip(spec[0], args))))
|
||||
for arg, binding in six.iteritems(bind):
|
||||
kw.update(dict(zip(spec[0], args)))
|
||||
for arg, binding in bind.iteritems():
|
||||
kw[arg] = saveapply(binding, kw)
|
||||
argnames = list(revspec[0])
|
||||
if spec[1]:
|
||||
argnames.append(revspec[1])
|
||||
if spec[2]:
|
||||
argnames.append(revspec[2])
|
||||
if spec[1]: argnames.append(revspec[1])
|
||||
if spec[2]: argnames.append(revspec[2])
|
||||
kwargs = {}
|
||||
for arg in argnames:
|
||||
kwargs[arg] = kw.get(arg)
|
||||
|
||||
dispatch((reverse, kwargs), queue=subscribers)
|
||||
|
||||
|
||||
def saveapply(func, kw):
|
||||
"""
|
||||
Do apply a set of keywords to a method or function.
|
||||
@ -278,10 +245,8 @@ def saveapply(func, kw):
|
||||
"""
|
||||
spec = inspect.getargspec(func)
|
||||
argnames = list(spec[0])
|
||||
if spec[1]:
|
||||
argnames.append(spec[1])
|
||||
if spec[2]:
|
||||
argnames.append(spec[2])
|
||||
if spec[1]: argnames.append(spec[1])
|
||||
if spec[2]: argnames.append(spec[2])
|
||||
kwargs = {}
|
||||
for arg in argnames:
|
||||
kwargs[arg] = kw.get(arg)
|
||||
@ -292,8 +257,9 @@ def getfunction(func):
|
||||
"""
|
||||
Return the function associated with a class method.
|
||||
"""
|
||||
if isinstance(func, types.MethodType):
|
||||
return func.__func__
|
||||
if isinstance(func, types.UnboundMethodType):
|
||||
return func.im_func
|
||||
return func
|
||||
|
||||
|
||||
# vim:sw=4:et:ai
|
||||
|
@ -1,36 +1,8 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2009-2017 Arjan Molenaar <gaphor@gmail.com>
|
||||
# Artur Wroblewski <wrobell@pld-linux.org>
|
||||
# Dan Yeaw <dan@yeaw.me>
|
||||
#
|
||||
# This file is part of Gaphas.
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Library General Public License as published by the Free
|
||||
# Software Foundation; either version 2 of the License, or (at your option) any
|
||||
# later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Library General Public License
|
||||
# along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
Table is a storage class that can be used to store information, like one
|
||||
would in a database table, with indexes on the desired "columns."
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from functools import reduce
|
||||
|
||||
from six.moves import zip
|
||||
|
||||
|
||||
class Table(object):
|
||||
"""
|
||||
A Table structure with indexing. Optimized for lookups.
|
||||
@ -55,8 +27,10 @@ class Table(object):
|
||||
index[n] = dict()
|
||||
self._index = index
|
||||
|
||||
|
||||
columns = property(lambda s: s._type)
|
||||
|
||||
|
||||
def insert(self, *values):
|
||||
"""
|
||||
Add a set of values to the store.
|
||||
@ -76,9 +50,7 @@ class Table(object):
|
||||
ValueError: Number of arguments doesn't match the number of columns (2 != 3)
|
||||
"""
|
||||
if len(values) != len(self._type._fields):
|
||||
raise ValueError("Number of arguments doesn't match the number of columns (%d != %d)" % (
|
||||
len(values), len(self._type._fields))
|
||||
)
|
||||
raise ValueError, "Number of arguments doesn't match the number of columns (%d != %d)" % (len(values), len(self._type._fields))
|
||||
# Add value to index entries
|
||||
index = self._index
|
||||
data = self._type._make(values)
|
||||
@ -89,6 +61,7 @@ class Table(object):
|
||||
else:
|
||||
index[n][v] = set([data])
|
||||
|
||||
|
||||
def delete(self, *_row, **kv):
|
||||
"""
|
||||
Remove value from the table. Either a complete set may be given or
|
||||
@ -130,10 +103,10 @@ class Table(object):
|
||||
"""
|
||||
fields = self._type._fields
|
||||
if _row and kv:
|
||||
raise ValueError("Should either provide a row or a query statement, not both")
|
||||
raise ValueError, "Should either provide a row or a query statement, not both"
|
||||
if _row:
|
||||
assert len(_row) == len(fields)
|
||||
kv = dict(list(zip(self._indexes, _row)))
|
||||
kv = dict(zip(self._indexes, _row))
|
||||
|
||||
rows = list(self.query(**kv))
|
||||
|
||||
@ -146,6 +119,7 @@ class Table(object):
|
||||
if len(index[n][v]) == 0:
|
||||
del index[n][v]
|
||||
|
||||
|
||||
def query(self, **kv):
|
||||
"""
|
||||
Get rows (tuples) for each key defined. An iterator is returned.
|
||||
@ -195,8 +169,9 @@ class Table(object):
|
||||
rows = (index[n][v] for n, v in items)
|
||||
try:
|
||||
r = iter(reduce(set.intersection, rows))
|
||||
except TypeError as ex:
|
||||
except TypeError, ex:
|
||||
pass
|
||||
return r
|
||||
|
||||
|
||||
# vi:sw=4:et:ai
|
||||
|
@ -1,18 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2007 Artur Wroblewski <wrobell@pld-linux.org>
|
||||
#
|
||||
# This file is part of Gaphas.
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Library General Public License as published by the Free
|
||||
# Software Foundation; either version 2 of the License, or (at your option) any
|
||||
# later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Library General Public License
|
||||
# along with this library; if not, see <http://www.gnu.org/licenses/>.
|
@ -1,37 +1,14 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2009-2017 Arjan Molenaar <gaphor@gmail.com>
|
||||
# Dan Yeaw <dan@yeaw.me>
|
||||
#
|
||||
# This file is part of Gaphas.
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Library General Public License as published by the Free
|
||||
# Software Foundation; either version 2 of the License, or (at your option) any
|
||||
# later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Library General Public License
|
||||
# along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
Generic gaphas item tests.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import unittest
|
||||
|
||||
from gaphas.item import Item
|
||||
from gaphas.aspect import *
|
||||
from gaphas.canvas import Canvas
|
||||
from gaphas.item import Item
|
||||
from gaphas.view import View
|
||||
|
||||
|
||||
class AspectTestCase(unittest.TestCase):
|
||||
"""
|
||||
Test aspects for items.
|
||||
@ -41,6 +18,7 @@ class AspectTestCase(unittest.TestCase):
|
||||
self.canvas = Canvas()
|
||||
self.view = View(self.canvas)
|
||||
|
||||
|
||||
def test_selection_select(self):
|
||||
"""
|
||||
Test the Selection role methods
|
||||
@ -57,6 +35,7 @@ class AspectTestCase(unittest.TestCase):
|
||||
assert item not in view.selected_items
|
||||
assert None is view.focused_item
|
||||
|
||||
|
||||
def test_selection_move(self):
|
||||
"""
|
||||
Test the Selection role methods
|
||||
@ -70,4 +49,5 @@ class AspectTestCase(unittest.TestCase):
|
||||
inmotion.move((12, 26))
|
||||
self.assertEquals((1, 0, 0, 1, 12, 26), tuple(item.matrix))
|
||||
|
||||
|
||||
# vim:sw=4:et:ai
|
||||
|
@ -1,34 +1,10 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2007-2017 Arjan Molenaar <gaphor@gmail.com>
|
||||
# Artur Wroblewski <wrobell@pld-linux.org>
|
||||
# Dan Yeaw <dan@yeaw.me>
|
||||
#
|
||||
# This file is part of Gaphas.
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Library General Public License as published by the Free
|
||||
# Software Foundation; either version 2 of the License, or (at your option) any
|
||||
# later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Library General Public License
|
||||
# along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import unittest
|
||||
|
||||
import cairo
|
||||
|
||||
from gaphas.canvas import Canvas, ConnectionError
|
||||
from gaphas.examples import Box
|
||||
from gaphas.item import Line
|
||||
|
||||
from gaphas.item import Line, Handle
|
||||
from gaphas.constraint import BalanceConstraint, EqualsConstraint
|
||||
import cairo
|
||||
|
||||
class MatricesTestCase(unittest.TestCase):
|
||||
def test_update_matrices(self):
|
||||
@ -55,7 +31,6 @@ class MatricesTestCase(unittest.TestCase):
|
||||
c.add(b2, b1)
|
||||
c.reparent(b2, None)
|
||||
|
||||
|
||||
# fixme: what about multiple constraints for a handle?
|
||||
# what about 1d projection?
|
||||
|
||||
@ -64,6 +39,7 @@ def count(i):
|
||||
|
||||
|
||||
class CanvasTestCase(unittest.TestCase):
|
||||
|
||||
def test_connect_item(self):
|
||||
b1 = Box()
|
||||
b2 = Box()
|
||||
@ -81,12 +57,12 @@ class CanvasTestCase(unittest.TestCase):
|
||||
assert count(c.get_connections(handle=l.handles()[0])) == 1
|
||||
|
||||
# Same item, different port
|
||||
# c.connect_item(l, l.handles()[0], b1, b1.ports()[-1])
|
||||
# assert count(c.get_connections(handle=l.handles()[0])) == 1
|
||||
#c.connect_item(l, l.handles()[0], b1, b1.ports()[-1])
|
||||
#assert count(c.get_connections(handle=l.handles()[0])) == 1
|
||||
|
||||
# Different item
|
||||
# c.connect_item(l, l.handles()[0], b2, b2.ports()[0])
|
||||
# assert count(c.get_connections(handle=l.handles()[0])) == 1
|
||||
#c.connect_item(l, l.handles()[0], b2, b2.ports()[0])
|
||||
#assert count(c.get_connections(handle=l.handles()[0])) == 1
|
||||
|
||||
def test_disconnect_item_with_callback(self):
|
||||
b1 = Box()
|
||||
@ -98,7 +74,6 @@ class CanvasTestCase(unittest.TestCase):
|
||||
c.add(l)
|
||||
|
||||
events = []
|
||||
|
||||
def callback():
|
||||
events.append('called')
|
||||
|
||||
@ -109,6 +84,7 @@ class CanvasTestCase(unittest.TestCase):
|
||||
assert count(c.get_connections(handle=l.handles()[0])) == 0
|
||||
assert events == ['called']
|
||||
|
||||
|
||||
def test_disconnect_item_with_constraint(self):
|
||||
b1 = Box()
|
||||
b2 = Box()
|
||||
@ -131,6 +107,7 @@ class CanvasTestCase(unittest.TestCase):
|
||||
|
||||
assert len(c.solver.constraints) == 4
|
||||
|
||||
|
||||
def test_disconnect_item_by_deleting_element(self):
|
||||
b1 = Box()
|
||||
b2 = Box()
|
||||
@ -141,7 +118,6 @@ class CanvasTestCase(unittest.TestCase):
|
||||
c.add(l)
|
||||
|
||||
events = []
|
||||
|
||||
def callback():
|
||||
events.append('called')
|
||||
|
||||
@ -153,6 +129,7 @@ class CanvasTestCase(unittest.TestCase):
|
||||
assert count(c.get_connections(handle=l.handles()[0])) == 0
|
||||
assert events == ['called']
|
||||
|
||||
|
||||
def test_disconnect_item_with_constraint_by_deleting_element(self):
|
||||
b1 = Box()
|
||||
b2 = Box()
|
||||
@ -178,6 +155,7 @@ class CanvasTestCase(unittest.TestCase):
|
||||
|
||||
|
||||
class ConstraintProjectionTestCase(unittest.TestCase):
|
||||
|
||||
def test_line_projection(self):
|
||||
"""Test projection with line's handle on element's side"""
|
||||
line = Line()
|
||||
@ -199,31 +177,31 @@ class ConstraintProjectionTestCase(unittest.TestCase):
|
||||
# move line's second handle on box side
|
||||
h2.x, h2.y = 5, -20
|
||||
|
||||
|
||||
# bc = BalanceConstraint(band=(h_sw.x, h_se.x), v=h2.x, balance=0.25)
|
||||
# canvas.projector(bc, x={h_sw.x: box, h_se.x: box, h2.x: line})
|
||||
# canvas._solver.add_constraint(bc)
|
||||
#
|
||||
# eq = EqualsConstraint(a=h_se.y, b=h2.y)
|
||||
# canvas.projector(eq, y={h_se.y: box, h2.y: line})
|
||||
# canvas._solver.add_constraint(eq)
|
||||
#
|
||||
# box.request_update()
|
||||
# line.request_update()
|
||||
#
|
||||
# canvas.update()
|
||||
#
|
||||
# box.width = 60
|
||||
# box.height = 30
|
||||
#
|
||||
# canvas.update()
|
||||
#
|
||||
# # expect h2.x to be moved due to balance constraint
|
||||
# self.assertEquals(10, h2.x)
|
||||
# self.assertEquals(-10, h2.y)
|
||||
### bc = BalanceConstraint(band=(h_sw.x, h_se.x), v=h2.x, balance=0.25)
|
||||
### canvas.projector(bc, x={h_sw.x: box, h_se.x: box, h2.x: line})
|
||||
### canvas._solver.add_constraint(bc)
|
||||
###
|
||||
### eq = EqualsConstraint(a=h_se.y, b=h2.y)
|
||||
### canvas.projector(eq, y={h_se.y: box, h2.y: line})
|
||||
### canvas._solver.add_constraint(eq)
|
||||
###
|
||||
### box.request_update()
|
||||
### line.request_update()
|
||||
###
|
||||
### canvas.update()
|
||||
###
|
||||
### box.width = 60
|
||||
### box.height = 30
|
||||
###
|
||||
### canvas.update()
|
||||
###
|
||||
### # expect h2.x to be moved due to balance constraint
|
||||
### self.assertEquals(10, h2.x)
|
||||
### self.assertEquals(-10, h2.y)
|
||||
|
||||
|
||||
class CanvasConstraintTestCase(unittest.TestCase):
|
||||
|
||||
def test_remove_connected_item(self):
|
||||
"""Test adding canvas constraint"""
|
||||
canvas = Canvas()
|
||||
@ -233,6 +211,7 @@ class CanvasConstraintTestCase(unittest.TestCase):
|
||||
l1 = Line()
|
||||
canvas.add(l1)
|
||||
|
||||
|
||||
b1 = Box()
|
||||
canvas.add(b1)
|
||||
|
||||
@ -257,6 +236,7 @@ class CanvasConstraintTestCase(unittest.TestCase):
|
||||
|
||||
assert canvas.get_connection(l1.handles()[1])
|
||||
|
||||
|
||||
self.assertEquals(number_cons2 + 2, len(canvas.solver.constraints))
|
||||
|
||||
canvas.remove(b1)
|
||||
@ -264,112 +244,112 @@ class CanvasConstraintTestCase(unittest.TestCase):
|
||||
# Expecting a class + line connected at one end only
|
||||
self.assertEquals(number_cons1 + 1, len(canvas.solver.constraints))
|
||||
|
||||
# def test_adding_constraint(self):
|
||||
# """Test adding canvas constraint"""
|
||||
# canvas = Canvas()
|
||||
#
|
||||
# l1 = Line()
|
||||
# canvas.add(l1)
|
||||
#
|
||||
# h1, h2 = l1.handles()
|
||||
# h = Handle()
|
||||
#
|
||||
# eq1 = EqualsConstraint(h1.x, h.x)
|
||||
# canvas.add_canvas_constraint(l1, h1, eq1)
|
||||
# self.assertTrue(l1 in cons)
|
||||
# self.assertTrue(h1 in cons[l1])
|
||||
# self.assertTrue(eq1 in cons[l1][h1])
|
||||
#
|
||||
# l2 = Line()
|
||||
# canvas.add(l2)
|
||||
#
|
||||
# h1, h2 = l2.handles()
|
||||
# h = Handle()
|
||||
#
|
||||
# eq2 = EqualsConstraint(h1.x, h.x)
|
||||
# canvas.add_canvas_constraint(l2, h1, eq2)
|
||||
# self.assertTrue(l2 in cons)
|
||||
# self.assertTrue(h1 in cons[l2])
|
||||
# self.assertTrue(eq2 in cons[l2][h1])
|
||||
#
|
||||
#
|
||||
# def test_adding_constraint_ex(self):
|
||||
# """Test adding canvas constraint for non-existing item"""
|
||||
# canvas = Canvas()
|
||||
# l1 = Line()
|
||||
# h1, h2 = l1.handles()
|
||||
# h = Handle()
|
||||
#
|
||||
# eq = EqualsConstraint(h1.x, h.x)
|
||||
# self.assertRaises(ValueError, canvas.add_canvas_constraint, l1, h1, eq)
|
||||
#
|
||||
#
|
||||
# def test_removing_constraint(self):
|
||||
# """Test removing canvas constraint"""
|
||||
# canvas = Canvas()
|
||||
# cons = canvas._canvas_constraints
|
||||
#
|
||||
# l1 = Line()
|
||||
# canvas.add(l1)
|
||||
#
|
||||
# h1, h2 = l1.handles()
|
||||
# h = Handle()
|
||||
#
|
||||
# eq1 = EqualsConstraint(h1.x, h.x)
|
||||
# canvas.add_canvas_constraint(l1, h1, eq1)
|
||||
#
|
||||
# # test preconditions
|
||||
# assert l1 in cons
|
||||
# assert h1 in cons[l1]
|
||||
# assert eq1 in cons[l1][h1]
|
||||
#
|
||||
# canvas.remove_canvas_constraint(l1, h1, eq1)
|
||||
# self.assertTrue(l1 in cons)
|
||||
# self.assertTrue(h1 in cons[l1])
|
||||
# self.assertFalse(eq1 in cons[l1][h1])
|
||||
#
|
||||
# eq1 = EqualsConstraint(h1.x, h.x)
|
||||
# eq2 = EqualsConstraint(h1.y, h.y)
|
||||
# canvas.add_canvas_constraint(l1, h1, eq1)
|
||||
# canvas.add_canvas_constraint(l1, h1, eq2)
|
||||
#
|
||||
# # test preconditions
|
||||
# assert l1 in cons
|
||||
# assert h1 in cons[l1]
|
||||
# assert eq1 in cons[l1][h1]
|
||||
# assert eq2 in cons[l1][h1]
|
||||
#
|
||||
# canvas.remove_canvas_constraint(l1, h1)
|
||||
#
|
||||
# self.assertTrue(l1 in cons)
|
||||
# self.assertTrue(h1 in cons[l1])
|
||||
# self.assertFalse(eq1 in cons[l1][h1])
|
||||
# self.assertFalse(eq2 in cons[l1][h1])
|
||||
#
|
||||
#
|
||||
# def test_fetching_constraints(self):
|
||||
# """Test fetching canvas constraints"""
|
||||
# canvas = Canvas()
|
||||
# cons = canvas._canvas_constraints
|
||||
#
|
||||
# l1 = Line()
|
||||
# canvas.add(l1)
|
||||
#
|
||||
# h1, h2 = l1.handles()
|
||||
# h = Handle()
|
||||
#
|
||||
# eq1 = EqualsConstraint(h1.x, h.x)
|
||||
# eq2 = EqualsConstraint(h1.y, h.y)
|
||||
# canvas.add_canvas_constraint(l1, h1, eq1)
|
||||
# canvas.add_canvas_constraint(l1, h1, eq2)
|
||||
#
|
||||
# # test preconditions
|
||||
# assert l1 in cons
|
||||
# assert h1 in cons[l1]
|
||||
# assert eq1 in cons[l1][h1]
|
||||
# assert eq2 in cons[l1][h1]
|
||||
#
|
||||
# self.assertTrue(eq1 in canvas.canvas_constraints(l1))
|
||||
# self.assertTrue(eq2 in canvas.canvas_constraints(l1))
|
||||
### def test_adding_constraint(self):
|
||||
### """Test adding canvas constraint"""
|
||||
### canvas = Canvas()
|
||||
###
|
||||
### l1 = Line()
|
||||
### canvas.add(l1)
|
||||
###
|
||||
### h1, h2 = l1.handles()
|
||||
### h = Handle()
|
||||
###
|
||||
### eq1 = EqualsConstraint(h1.x, h.x)
|
||||
### canvas.add_canvas_constraint(l1, h1, eq1)
|
||||
### self.assertTrue(l1 in cons)
|
||||
### self.assertTrue(h1 in cons[l1])
|
||||
### self.assertTrue(eq1 in cons[l1][h1])
|
||||
###
|
||||
### l2 = Line()
|
||||
### canvas.add(l2)
|
||||
###
|
||||
### h1, h2 = l2.handles()
|
||||
### h = Handle()
|
||||
###
|
||||
### eq2 = EqualsConstraint(h1.x, h.x)
|
||||
### canvas.add_canvas_constraint(l2, h1, eq2)
|
||||
### self.assertTrue(l2 in cons)
|
||||
### self.assertTrue(h1 in cons[l2])
|
||||
### self.assertTrue(eq2 in cons[l2][h1])
|
||||
###
|
||||
###
|
||||
### def test_adding_constraint_ex(self):
|
||||
### """Test adding canvas constraint for non-existing item"""
|
||||
### canvas = Canvas()
|
||||
### l1 = Line()
|
||||
### h1, h2 = l1.handles()
|
||||
### h = Handle()
|
||||
###
|
||||
### eq = EqualsConstraint(h1.x, h.x)
|
||||
### self.assertRaises(ValueError, canvas.add_canvas_constraint, l1, h1, eq)
|
||||
###
|
||||
###
|
||||
### def test_removing_constraint(self):
|
||||
### """Test removing canvas constraint"""
|
||||
### canvas = Canvas()
|
||||
### cons = canvas._canvas_constraints
|
||||
###
|
||||
### l1 = Line()
|
||||
### canvas.add(l1)
|
||||
###
|
||||
### h1, h2 = l1.handles()
|
||||
### h = Handle()
|
||||
###
|
||||
### eq1 = EqualsConstraint(h1.x, h.x)
|
||||
### canvas.add_canvas_constraint(l1, h1, eq1)
|
||||
###
|
||||
### # test preconditions
|
||||
### assert l1 in cons
|
||||
### assert h1 in cons[l1]
|
||||
### assert eq1 in cons[l1][h1]
|
||||
###
|
||||
### canvas.remove_canvas_constraint(l1, h1, eq1)
|
||||
### self.assertTrue(l1 in cons)
|
||||
### self.assertTrue(h1 in cons[l1])
|
||||
### self.assertFalse(eq1 in cons[l1][h1])
|
||||
###
|
||||
### eq1 = EqualsConstraint(h1.x, h.x)
|
||||
### eq2 = EqualsConstraint(h1.y, h.y)
|
||||
### canvas.add_canvas_constraint(l1, h1, eq1)
|
||||
### canvas.add_canvas_constraint(l1, h1, eq2)
|
||||
###
|
||||
### # test preconditions
|
||||
### assert l1 in cons
|
||||
### assert h1 in cons[l1]
|
||||
### assert eq1 in cons[l1][h1]
|
||||
### assert eq2 in cons[l1][h1]
|
||||
###
|
||||
### canvas.remove_canvas_constraint(l1, h1)
|
||||
###
|
||||
### self.assertTrue(l1 in cons)
|
||||
### self.assertTrue(h1 in cons[l1])
|
||||
### self.assertFalse(eq1 in cons[l1][h1])
|
||||
### self.assertFalse(eq2 in cons[l1][h1])
|
||||
###
|
||||
###
|
||||
### def test_fetching_constraints(self):
|
||||
### """Test fetching canvas constraints"""
|
||||
### canvas = Canvas()
|
||||
### cons = canvas._canvas_constraints
|
||||
###
|
||||
### l1 = Line()
|
||||
### canvas.add(l1)
|
||||
###
|
||||
### h1, h2 = l1.handles()
|
||||
### h = Handle()
|
||||
###
|
||||
### eq1 = EqualsConstraint(h1.x, h.x)
|
||||
### eq2 = EqualsConstraint(h1.y, h.y)
|
||||
### canvas.add_canvas_constraint(l1, h1, eq1)
|
||||
### canvas.add_canvas_constraint(l1, h1, eq2)
|
||||
###
|
||||
### # test preconditions
|
||||
### assert l1 in cons
|
||||
### assert h1 in cons[l1]
|
||||
### assert eq1 in cons[l1][h1]
|
||||
### assert eq2 in cons[l1][h1]
|
||||
###
|
||||
### self.assertTrue(eq1 in canvas.canvas_constraints(l1))
|
||||
### self.assertTrue(eq2 in canvas.canvas_constraints(l1))
|
||||
|
||||
# vim:sw=4:et:ai
|
||||
|
@ -1,32 +1,10 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2009-2017 Arjan Molenaar <gaphor@gmail.com>
|
||||
# Dan Yeaw <dan@yeaw.me>
|
||||
#
|
||||
# This file is part of Gaphas.
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Library General Public License as published by the Free
|
||||
# Software Foundation; either version 2 of the License, or (at your option) any
|
||||
# later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Library General Public License
|
||||
# along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import unittest
|
||||
|
||||
from gaphas.connector import Position, Handle
|
||||
from gaphas.solver import Variable
|
||||
|
||||
|
||||
class PositionTestCase(unittest.TestCase):
|
||||
|
||||
def test_position(self):
|
||||
pos = Position((0, 0))
|
||||
self.assertEquals(0, pos.x)
|
||||
@ -38,7 +16,7 @@ class PositionTestCase(unittest.TestCase):
|
||||
self.assertEquals(2, pos.y)
|
||||
|
||||
def test_set_xy(self):
|
||||
pos = Position((1, 2))
|
||||
pos = Position((1,2))
|
||||
x = Variable()
|
||||
y = Variable()
|
||||
assert x is not pos.x
|
||||
@ -49,8 +27,8 @@ class PositionTestCase(unittest.TestCase):
|
||||
assert x is pos.x
|
||||
assert y is pos.y
|
||||
|
||||
|
||||
class HandleTestCase(unittest.TestCase):
|
||||
|
||||
def test_handle_x_y(self):
|
||||
h = Handle()
|
||||
self.assertEquals(0.0, h.x)
|
||||
|
@ -1,30 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2008-2017 Arjan Molenaar <gaphor@gmail.com>
|
||||
# Dan Yeaw <dan@yeaw.me>
|
||||
#
|
||||
# This file is part of Gaphas.
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Library General Public License as published by the Free
|
||||
# Software Foundation; either version 2 of the License, or (at your option) any
|
||||
# later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Library General Public License
|
||||
# along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import unittest
|
||||
|
||||
from gaphas.constraint import PositionConstraint, LineAlignConstraint
|
||||
from gaphas.solver import Variable
|
||||
|
||||
from gaphas.constraint import PositionConstraint, LineAlignConstraint
|
||||
|
||||
class PositionTestCase(unittest.TestCase):
|
||||
def test_pos_constraint(self):
|
||||
@ -53,11 +30,11 @@ class PositionTestCase(unittest.TestCase):
|
||||
self.assertEquals(14, y2)
|
||||
|
||||
|
||||
|
||||
class LineAlignConstraintTestCase(unittest.TestCase):
|
||||
"""
|
||||
Line align constraint test case.
|
||||
"""
|
||||
|
||||
def test_delta(self):
|
||||
"""Test line align delta
|
||||
"""
|
||||
@ -69,11 +46,12 @@ class LineAlignConstraintTestCase(unittest.TestCase):
|
||||
self.assertAlmostEqual(12.77, point[1].value, 2)
|
||||
|
||||
line[1][0].value = 40
|
||||
line[1][1].value = 30
|
||||
line[1][1].value = 30
|
||||
lc.solve_for()
|
||||
self.assertAlmostEqual(24.00, point[0].value, 2)
|
||||
self.assertAlmostEqual(18.00, point[1].value, 2)
|
||||
|
||||
|
||||
def test_delta_below_zero(self):
|
||||
"""Test line align with delta below zero
|
||||
"""
|
||||
@ -85,7 +63,7 @@ class LineAlignConstraintTestCase(unittest.TestCase):
|
||||
self.assertAlmostEqual(7.23, point[1].value, 2)
|
||||
|
||||
line[1][0].value = 40
|
||||
line[1][1].value = 30
|
||||
line[1][1].value = 30
|
||||
lc.solve_for()
|
||||
self.assertAlmostEqual(16.0, point[0].value, 2)
|
||||
self.assertAlmostEqual(12.00, point[1].value, 2)
|
||||
|
@ -1,37 +1,14 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2007-2017 Arjan Molenaar <gaphor@gmail.com>
|
||||
# Artur Wroblewski <wrobell@pld-linux.org>
|
||||
# Dan Yeaw <dan@yeaw.me>
|
||||
#
|
||||
# This file is part of Gaphas.
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Library General Public License as published by the Free
|
||||
# Software Foundation; either version 2 of the License, or (at your option) any
|
||||
# later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Library General Public License
|
||||
# along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import unittest
|
||||
from os import getenv
|
||||
|
||||
from six.moves import range
|
||||
import unittest
|
||||
|
||||
from gaphas.canvas import Canvas
|
||||
from gaphas.examples import Box
|
||||
|
||||
from gaphas.item import NW, NE, SE, SW
|
||||
|
||||
|
||||
class ElementTestCase(unittest.TestCase):
|
||||
|
||||
def test_creation_with_size(self):
|
||||
"""
|
||||
Test if initial size holds when added to a canvas.
|
||||
@ -51,6 +28,7 @@ class ElementTestCase(unittest.TestCase):
|
||||
assert box.handles()[SE].pos.x == 150, box.handles()[SE].pos.x
|
||||
assert box.handles()[SE].pos.y == 153, box.handles()[SE].pos.y
|
||||
|
||||
|
||||
def test_resize_se(self):
|
||||
"""
|
||||
Test resizing of element by dragging it SE handle.
|
||||
@ -68,8 +46,7 @@ class ElementTestCase(unittest.TestCase):
|
||||
assert h_se is handles[SE]
|
||||
|
||||
# to see how many solver was called:
|
||||
# GAPHAS_TEST_COUNT=3 nosetests -s --with-prof --profile-restrict=gaphas
|
||||
# gaphas/tests/test_element.py | grep -e '\<solve\>' -e dirty
|
||||
# GAPHAS_TEST_COUNT=3 nosetests -s --with-prof --profile-restrict=gaphas gaphas/tests/test_element.py | grep -e '\<solve\>' -e dirty
|
||||
|
||||
count = getenv('GAPHAS_TEST_COUNT')
|
||||
if count:
|
||||
@ -78,17 +55,18 @@ class ElementTestCase(unittest.TestCase):
|
||||
count = 1
|
||||
|
||||
for i in range(count):
|
||||
h_se.pos.x += 100 # h.se.{x,y} = 10, now
|
||||
h_se.pos.x += 100 # h.se.{x,y} = 10, now
|
||||
h_se.pos.y += 100
|
||||
box.request_update()
|
||||
canvas.update()
|
||||
|
||||
self.assertEquals(110 * count, h_se.pos.x) # h_se changed above, should remain the same
|
||||
self.assertEquals(110 * count, h_se.pos.x) # h_se changed above, should remain the same
|
||||
self.assertEquals(110 * count, float(h_se.pos.y))
|
||||
|
||||
self.assertEquals(110 * count, float(h_ne.pos.x))
|
||||
self.assertEquals(110 * count, float(h_sw.pos.y))
|
||||
|
||||
|
||||
def test_minimal_se(self):
|
||||
"""
|
||||
Test resizing of element by dragging it SE handle.
|
||||
@ -105,14 +83,14 @@ class ElementTestCase(unittest.TestCase):
|
||||
assert h_sw is handles[SW]
|
||||
assert h_se is handles[SE]
|
||||
|
||||
h_se.pos.x -= 20 # h.se.{x,y} == -10
|
||||
h_se.pos.x -= 20 # h.se.{x,y} == -10
|
||||
h_se.pos.y -= 20
|
||||
assert h_se.pos.x == h_se.pos.y == -10
|
||||
|
||||
box.request_update()
|
||||
canvas.update()
|
||||
|
||||
self.assertEquals(10, h_se.pos.x) # h_se changed above, should be 10
|
||||
self.assertEquals(10, h_se.pos.x) # h_se changed above, should be 10
|
||||
self.assertEquals(10, h_se.pos.y)
|
||||
|
||||
self.assertEquals(10, h_ne.pos.x)
|
||||
|
@ -1,43 +1,20 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2010-2017 Arjan Molenaar <gaphor@gmail.com>
|
||||
# Dan Yeaw <dan@yeaw.me>
|
||||
#
|
||||
# This file is part of Gaphas.
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Library General Public License as published by the Free
|
||||
# Software Foundation; either version 2 of the License, or (at your option) any
|
||||
# later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Library General Public License
|
||||
# along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import print_function
|
||||
|
||||
import unittest
|
||||
|
||||
from gaphas.freehand import FreeHandCairoContext
|
||||
import cairo
|
||||
|
||||
from gaphas.freehand import FreeHandCairoContext
|
||||
|
||||
|
||||
class PseudoFile(object):
|
||||
|
||||
def __init__(self):
|
||||
self.data = ''
|
||||
|
||||
def write(self, data):
|
||||
print(data)
|
||||
print data
|
||||
self.data = self.data + data
|
||||
|
||||
|
||||
class FreeHandCairoContextTest(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
pass
|
||||
|
||||
@ -64,7 +41,7 @@ class FreeHandCairoContextTest(unittest.TestCase):
|
||||
|
||||
|
||||
DRAWING_LINES_OUTPUT = """<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100pt" height="100pt"viewBox="0 0 100 100" version="1.1">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100pt" height="100pt" viewBox="0 0 100 100" version="1.1">
|
||||
<g id="surface0">
|
||||
<path style="fill:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 20 20 C 23.324219 50.054688 17.195312 33.457031 20.722656 80.585938 C 38.78125 83.566406 20.984375 77.625 80.652344 80.652344 C 83.65625 70.992188 77.578125 60.988281 80.507812 20.019531 "/>
|
||||
</g>
|
||||
|
@ -1,42 +1,18 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2010-2017 Arjan Molenaar <gaphor@gmail.com>
|
||||
# Dan Yeaw <dan@yeaw.me>
|
||||
#
|
||||
# This file is part of Gaphas.
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Library General Public License as published by the Free
|
||||
# Software Foundation; either version 2 of the License, or (at your option) any
|
||||
# later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Library General Public License
|
||||
# along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import print_function
|
||||
|
||||
import unittest
|
||||
|
||||
from gi.repository import Gtk
|
||||
from six.moves import range
|
||||
|
||||
from gaphas.canvas import Canvas
|
||||
import gtk
|
||||
from gaphas.guide import *
|
||||
from gaphas.item import Element, Line
|
||||
from gaphas.canvas import Canvas
|
||||
from gaphas.view import GtkView
|
||||
from gaphas.item import Element, Line
|
||||
|
||||
|
||||
class GuideTestCase(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.canvas = Canvas()
|
||||
self.view = GtkView(self.canvas)
|
||||
self.window = Gtk.Window()
|
||||
self.window = gtk.Window()
|
||||
self.window.add(self.view)
|
||||
self.window.show_all()
|
||||
|
||||
@ -109,6 +85,7 @@ class GuideTestCase(unittest.TestCase):
|
||||
self.assertEquals(0.0, guides[0])
|
||||
self.assertEquals(20.0, guides[1])
|
||||
|
||||
|
||||
def test_guide_item_in_motion(self):
|
||||
e1 = Element()
|
||||
e2 = Element()
|
||||
@ -135,13 +112,13 @@ class GuideTestCase(unittest.TestCase):
|
||||
|
||||
# Moved back to guided lines:
|
||||
for d in range(0, 3):
|
||||
print('move to', d)
|
||||
print 'move to', d
|
||||
guider.move((d, d))
|
||||
self.assertEquals(0, e3.matrix[4])
|
||||
self.assertEquals(0, e3.matrix[5])
|
||||
|
||||
for d in range(3, 5):
|
||||
print('move to', d)
|
||||
print 'move to', d
|
||||
guider.move((d, d))
|
||||
self.assertEquals(5, e3.matrix[4])
|
||||
self.assertEquals(5, e3.matrix[5])
|
||||
@ -150,6 +127,7 @@ class GuideTestCase(unittest.TestCase):
|
||||
self.assertEquals(20, e3.matrix[4])
|
||||
self.assertEquals(20, e3.matrix[5])
|
||||
|
||||
|
||||
def test_guide_item_in_motion_2(self):
|
||||
e1 = Element()
|
||||
e2 = Element()
|
||||
@ -176,13 +154,13 @@ class GuideTestCase(unittest.TestCase):
|
||||
|
||||
# Moved back to guided lines:
|
||||
for y in range(4, 6):
|
||||
print('move to', y)
|
||||
print 'move to', y
|
||||
guider.move((3, y))
|
||||
self.assertEquals(0, e3.matrix[4])
|
||||
self.assertEquals(0, e3.matrix[5])
|
||||
|
||||
for y in range(6, 9):
|
||||
print('move to', y)
|
||||
print 'move to', y
|
||||
guider.move((3, y))
|
||||
self.assertEquals(0, e3.matrix[4])
|
||||
self.assertEquals(5, e3.matrix[5])
|
||||
@ -192,4 +170,5 @@ class GuideTestCase(unittest.TestCase):
|
||||
self.assertEquals(17, e3.matrix[4])
|
||||
self.assertEquals(20, e3.matrix[5])
|
||||
|
||||
|
||||
# vim:sw=4:et:ai
|
||||
|
@ -1,44 +1,19 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2008-2017 Arjan Molenaar <gaphor@gmail.com>
|
||||
# Artur Wroblewski <wrobell@pld-linux.org>
|
||||
# Dan Yeaw <dan@yeaw.me>
|
||||
#
|
||||
# This file is part of Gaphas.
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Library General Public License as published by the Free
|
||||
# Software Foundation; either version 2 of the License, or (at your option) any
|
||||
# later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Library General Public License
|
||||
# along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
Generic gaphas item tests.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import unittest
|
||||
|
||||
from gaphas.constraint import LineConstraint, \
|
||||
EqualsConstraint, LessThanConstraint
|
||||
from gaphas.item import Item
|
||||
from gaphas.constraint import LineAlignConstraint, LineConstraint, \
|
||||
EqualsConstraint, LessThanConstraint
|
||||
from gaphas.solver import Variable
|
||||
|
||||
|
||||
class ItemConstraintTestCase(unittest.TestCase):
|
||||
"""
|
||||
Item constraint creation tests. The test check functionality of
|
||||
`Item.constraint` method, not constraints themselves.
|
||||
"""
|
||||
|
||||
def test_line_constraint(self):
|
||||
"""
|
||||
Test line creation constraint.
|
||||
@ -54,6 +29,7 @@ class ItemConstraintTestCase(unittest.TestCase):
|
||||
self.assertEquals((1, 2), c._point)
|
||||
self.assertEquals(((3, 4), (5, 6)), c._line)
|
||||
|
||||
|
||||
def test_horizontal_constraint(self):
|
||||
"""
|
||||
Test horizontal constraint creation.
|
||||
@ -70,6 +46,7 @@ class ItemConstraintTestCase(unittest.TestCase):
|
||||
self.assertEquals(2, c.a)
|
||||
self.assertEquals(4, c.b)
|
||||
|
||||
|
||||
def test_vertical_constraint(self):
|
||||
"""
|
||||
Test vertical constraint creation.
|
||||
@ -86,6 +63,7 @@ class ItemConstraintTestCase(unittest.TestCase):
|
||||
self.assertEquals(1, c.a)
|
||||
self.assertEquals(3, c.b)
|
||||
|
||||
|
||||
def test_left_of_constraint(self):
|
||||
"""
|
||||
Test "less than" constraint (horizontal) creation.
|
||||
@ -101,6 +79,7 @@ class ItemConstraintTestCase(unittest.TestCase):
|
||||
self.assertEquals(1, c.smaller)
|
||||
self.assertEquals(3, c.bigger)
|
||||
|
||||
|
||||
def test_above_constraint(self):
|
||||
"""
|
||||
Test "less than" constraint (vertical) creation.
|
||||
|
@ -1,130 +1,111 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2008-2017 Arjan Molenaar <gaphor@gmail.com>
|
||||
# Dan Yeaw <dan@yeaw.me>
|
||||
#
|
||||
# This file is part of Gaphas.
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Library General Public License as published by the Free
|
||||
# Software Foundation; either version 2 of the License, or (at your option) any
|
||||
# later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Library General Public License
|
||||
# along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import unittest
|
||||
|
||||
from gaphas import state
|
||||
from gaphas.canvas import Canvas
|
||||
from gaphas.item import Line
|
||||
from gaphas.segment import Segment
|
||||
|
||||
undo_list = []
|
||||
redo_list = []
|
||||
|
||||
|
||||
def undo_handler(event):
|
||||
undo_list.append(event)
|
||||
|
||||
|
||||
def undo():
|
||||
apply_me = list(undo_list)
|
||||
del undo_list[:]
|
||||
apply_me.reverse()
|
||||
for e in apply_me:
|
||||
state.saveapply(*e)
|
||||
redo_list[:] = undo_list[:]
|
||||
del undo_list[:]
|
||||
|
||||
|
||||
class TestCaseBase(unittest.TestCase):
|
||||
"""
|
||||
Abstract test case class with undo support.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
state.observers.add(state.revert_handler)
|
||||
state.subscribers.add(undo_handler)
|
||||
|
||||
def tearDown(self):
|
||||
state.observers.remove(state.revert_handler)
|
||||
state.subscribers.remove(undo_handler)
|
||||
|
||||
|
||||
class LineTestCase(TestCaseBase):
|
||||
"""
|
||||
Basic line item tests.
|
||||
"""
|
||||
|
||||
def test_initial_ports(self):
|
||||
"""Test initial ports amount
|
||||
"""
|
||||
line = Line()
|
||||
self.assertEquals(1, len(line.ports()))
|
||||
|
||||
def test_orthogonal_horizontal_undo(self):
|
||||
"""Test orthogonal line constraints bug (#107)
|
||||
"""
|
||||
canvas = Canvas()
|
||||
line = Line()
|
||||
canvas.add(line)
|
||||
assert not line.horizontal
|
||||
assert len(canvas.solver._constraints) == 0
|
||||
|
||||
segment = Segment(line, None)
|
||||
segment.split_segment(0)
|
||||
|
||||
line.orthogonal = True
|
||||
|
||||
self.assertEquals(2, len(canvas.solver._constraints))
|
||||
after_ortho = set(canvas.solver._constraints)
|
||||
|
||||
del undo_list[:]
|
||||
line.horizontal = True
|
||||
|
||||
self.assertEquals(2, len(canvas.solver._constraints))
|
||||
|
||||
undo()
|
||||
|
||||
self.assertFalse(line.horizontal)
|
||||
self.assertEquals(2, len(canvas.solver._constraints))
|
||||
|
||||
line.horizontal = True
|
||||
|
||||
self.assertTrue(line.horizontal)
|
||||
self.assertEquals(2, len(canvas.solver._constraints))
|
||||
|
||||
def test_orthogonal_line_undo(self):
|
||||
"""Test orthogonal line undo
|
||||
"""
|
||||
canvas = Canvas()
|
||||
line = Line()
|
||||
canvas.add(line)
|
||||
|
||||
segment = Segment(line, None)
|
||||
segment.split_segment(0)
|
||||
|
||||
# start with no orthogonal constraints
|
||||
assert len(canvas.solver._constraints) == 0
|
||||
|
||||
line.orthogonal = True
|
||||
|
||||
# check orthogonal constraints
|
||||
self.assertEquals(2, len(canvas.solver._constraints))
|
||||
self.assertEquals(3, len(line.handles()))
|
||||
|
||||
undo()
|
||||
|
||||
self.assertFalse(line.orthogonal)
|
||||
self.assertEquals(0, len(canvas.solver._constraints))
|
||||
self.assertEquals(2, len(line.handles()))
|
||||
|
||||
# vim:sw=4:et
|
||||
|
||||
import unittest
|
||||
from gaphas.item import Line
|
||||
from gaphas.canvas import Canvas
|
||||
from gaphas import state
|
||||
from gaphas.segment import Segment
|
||||
|
||||
|
||||
undo_list = []
|
||||
redo_list = []
|
||||
|
||||
def undo_handler(event):
|
||||
undo_list.append(event)
|
||||
|
||||
def undo():
|
||||
apply_me = list(undo_list)
|
||||
del undo_list[:]
|
||||
apply_me.reverse()
|
||||
for e in apply_me:
|
||||
state.saveapply(*e)
|
||||
redo_list[:] = undo_list[:]
|
||||
del undo_list[:]
|
||||
|
||||
|
||||
|
||||
class TestCaseBase(unittest.TestCase):
|
||||
"""
|
||||
Abstract test case class with undo support.
|
||||
"""
|
||||
def setUp(self):
|
||||
state.observers.add(state.revert_handler)
|
||||
state.subscribers.add(undo_handler)
|
||||
|
||||
def tearDown(self):
|
||||
state.observers.remove(state.revert_handler)
|
||||
state.subscribers.remove(undo_handler)
|
||||
|
||||
|
||||
|
||||
class LineTestCase(TestCaseBase):
|
||||
"""
|
||||
Basic line item tests.
|
||||
"""
|
||||
|
||||
def test_initial_ports(self):
|
||||
"""Test initial ports amount
|
||||
"""
|
||||
line = Line()
|
||||
self.assertEquals(1, len(line.ports()))
|
||||
|
||||
|
||||
def test_orthogonal_horizontal_undo(self):
|
||||
"""Test orthogonal line constraints bug (#107)
|
||||
"""
|
||||
canvas = Canvas()
|
||||
line = Line()
|
||||
canvas.add(line)
|
||||
assert not line.horizontal
|
||||
assert len(canvas.solver._constraints) == 0
|
||||
|
||||
segment = Segment(line, None)
|
||||
segment.split_segment(0)
|
||||
|
||||
line.orthogonal = True
|
||||
|
||||
self.assertEquals(2, len(canvas.solver._constraints))
|
||||
after_ortho = set(canvas.solver._constraints)
|
||||
|
||||
del undo_list[:]
|
||||
line.horizontal = True
|
||||
|
||||
self.assertEquals(2, len(canvas.solver._constraints))
|
||||
|
||||
undo()
|
||||
|
||||
self.assertFalse(line.horizontal)
|
||||
self.assertEquals(2, len(canvas.solver._constraints))
|
||||
|
||||
line.horizontal = True
|
||||
|
||||
self.assertTrue(line.horizontal)
|
||||
self.assertEquals(2, len(canvas.solver._constraints))
|
||||
|
||||
|
||||
def test_orthogonal_line_undo(self):
|
||||
"""Test orthogonal line undo
|
||||
"""
|
||||
canvas = Canvas()
|
||||
line = Line()
|
||||
canvas.add(line)
|
||||
|
||||
segment = Segment(line, None)
|
||||
segment.split_segment(0)
|
||||
|
||||
# start with no orthogonal constraints
|
||||
assert len(canvas.solver._constraints) == 0
|
||||
|
||||
line.orthogonal = True
|
||||
|
||||
# check orthogonal constraints
|
||||
self.assertEquals(2, len(canvas.solver._constraints))
|
||||
self.assertEquals(3, len(line.handles()))
|
||||
|
||||
undo()
|
||||
|
||||
self.assertFalse(line.orthogonal)
|
||||
self.assertEquals(0, len(canvas.solver._constraints))
|
||||
self.assertEquals(2, len(line.handles()))
|
||||
|
||||
|
||||
# vim:sw=4:et
|
||||
|
@ -1,46 +1,23 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2008-2017 Arjan Molenaar <gaphor@gmail.com>
|
||||
# Dan Yeaw <dan@yeaw.me>
|
||||
#
|
||||
# This file is part of Gaphas.
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Library General Public License as published by the Free
|
||||
# Software Foundation; either version 2 of the License, or (at your option) any
|
||||
# later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Library General Public License
|
||||
# along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import print_function
|
||||
|
||||
import pickle
|
||||
import unittest
|
||||
|
||||
import pickle
|
||||
from gaphas.canvas import Canvas
|
||||
from gaphas.examples import Box
|
||||
from gaphas.item import Element, Line
|
||||
from gaphas.item import Item, Element, Line
|
||||
from gaphas.view import View, GtkView
|
||||
|
||||
|
||||
# Ensure extra pickle reducers/reconstructors are loaded:
|
||||
import gaphas.picklers
|
||||
|
||||
|
||||
class MyPickler(pickle.Pickler):
|
||||
|
||||
def save(self, obj):
|
||||
print('saving obj', obj, type(obj))
|
||||
print 'saving obj', obj, type(obj)
|
||||
try:
|
||||
return pickle.Pickler.save(self, obj)
|
||||
except pickle.PicklingError as e:
|
||||
print('Error while pickling', obj, self.dispatch.get(type(obj)))
|
||||
except pickle.PicklingError, e:
|
||||
print 'Error while pickling', obj, self.dispatch.get(type(obj))
|
||||
raise e
|
||||
|
||||
|
||||
@ -49,11 +26,9 @@ class my_disconnect(object):
|
||||
Disconnect object should be located at top-level, so the pickle code
|
||||
can find it.
|
||||
"""
|
||||
|
||||
def __call__(self):
|
||||
pass
|
||||
|
||||
|
||||
def create_canvas():
|
||||
canvas = Canvas()
|
||||
box = Box()
|
||||
@ -63,6 +38,7 @@ def create_canvas():
|
||||
box2 = Box()
|
||||
canvas.add(box2, parent=box)
|
||||
|
||||
|
||||
line = Line()
|
||||
line.handles()[0].visible = False
|
||||
line.handles()[0].connected_to = box
|
||||
@ -77,6 +53,7 @@ def create_canvas():
|
||||
|
||||
|
||||
class PickleTestCase(unittest.TestCase):
|
||||
|
||||
def test_pickle_element(self):
|
||||
item = Element()
|
||||
|
||||
@ -86,6 +63,7 @@ class PickleTestCase(unittest.TestCase):
|
||||
assert i2
|
||||
assert len(i2.handles()) == 4
|
||||
|
||||
|
||||
def test_pickle_line(self):
|
||||
item = Line()
|
||||
|
||||
@ -95,6 +73,7 @@ class PickleTestCase(unittest.TestCase):
|
||||
assert i2
|
||||
assert len(i2.handles()) == 2
|
||||
|
||||
|
||||
def test_pickle(self):
|
||||
canvas = create_canvas()
|
||||
|
||||
@ -105,6 +84,7 @@ class PickleTestCase(unittest.TestCase):
|
||||
assert type(canvas._tree.nodes[1]) is Box
|
||||
assert type(canvas._tree.nodes[2]) is Line
|
||||
|
||||
|
||||
def test_pickle_connect(self):
|
||||
"""
|
||||
Persist a connection.
|
||||
@ -115,6 +95,7 @@ class PickleTestCase(unittest.TestCase):
|
||||
box2 = Box()
|
||||
canvas.add(box2, parent=box)
|
||||
|
||||
|
||||
line = Line()
|
||||
line.handles()[0].visible = False
|
||||
line.handles()[0].connected_to = box
|
||||
@ -142,6 +123,7 @@ class PickleTestCase(unittest.TestCase):
|
||||
assert callable(h.disconnect)
|
||||
assert h.disconnect() is None, h.disconnect()
|
||||
|
||||
|
||||
def test_pickle_with_view(self):
|
||||
canvas = create_canvas()
|
||||
|
||||
@ -159,6 +141,7 @@ class PickleTestCase(unittest.TestCase):
|
||||
surface.flush()
|
||||
surface.finish()
|
||||
|
||||
|
||||
def test_pickle_with_gtk_view(self):
|
||||
canvas = create_canvas()
|
||||
|
||||
@ -166,8 +149,8 @@ class PickleTestCase(unittest.TestCase):
|
||||
|
||||
c2 = pickle.loads(pickled)
|
||||
|
||||
from gi.repository import Gtk
|
||||
win = Gtk.Window()
|
||||
import gtk
|
||||
win = gtk.Window()
|
||||
view = GtkView(canvas=c2)
|
||||
win.add(view)
|
||||
|
||||
@ -185,24 +168,24 @@ class PickleTestCase(unittest.TestCase):
|
||||
|
||||
view = GtkView(canvas=canvas)
|
||||
|
||||
# from gaphas.tool import ConnectHandleTool
|
||||
# handle_tool = ConnectHandleTool()
|
||||
# handle_tool.connect(view, line, line.handles()[0], (40, 0))
|
||||
# assert line.handles()[0].connected_to is box, line.handles()[0].connected_to
|
||||
# assert line.handles()[0].connection_data
|
||||
# assert line.handles()[0].disconnect
|
||||
# assert isinstance(line.handles()[0].disconnect, object), line.handles()[0].disconnect
|
||||
# from gaphas.tool import ConnectHandleTool
|
||||
# handle_tool = ConnectHandleTool()
|
||||
# handle_tool.connect(view, line, line.handles()[0], (40, 0))
|
||||
# assert line.handles()[0].connected_to is box, line.handles()[0].connected_to
|
||||
# assert line.handles()[0].connection_data
|
||||
# assert line.handles()[0].disconnect
|
||||
# assert isinstance(line.handles()[0].disconnect, object), line.handles()[0].disconnect
|
||||
|
||||
import io
|
||||
f = io.BytesIO()
|
||||
import StringIO
|
||||
f = StringIO.StringIO()
|
||||
pickler = MyPickler(f)
|
||||
pickler.dump(canvas)
|
||||
pickled = f.getvalue()
|
||||
|
||||
c2 = pickle.loads(pickled)
|
||||
|
||||
from gi.repository import Gtk
|
||||
win = Gtk.Window()
|
||||
import gtk
|
||||
win = gtk.Window()
|
||||
view = GtkView(canvas=c2)
|
||||
win.add(view)
|
||||
view.show()
|
||||
@ -219,8 +202,8 @@ class PickleTestCase(unittest.TestCase):
|
||||
|
||||
c2 = pickle.loads(pickled)
|
||||
|
||||
from gi.repository import Gtk
|
||||
win = Gtk.Window()
|
||||
import gtk
|
||||
win = gtk.Window()
|
||||
view = GtkView(canvas=c2)
|
||||
win.add(view)
|
||||
|
||||
@ -229,7 +212,6 @@ class PickleTestCase(unittest.TestCase):
|
||||
|
||||
view.update()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
|
@ -1,34 +1,9 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2007-2017 Arjan Molenaar <gaphor@gmail.com>
|
||||
# Dan Yeaw <dan@yeaw.me>
|
||||
#
|
||||
# This file is part of Gaphas.
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Library General Public License as published by the Free
|
||||
# Software Foundation; either version 2 of the License, or (at your option) any
|
||||
# later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Library General Public License
|
||||
# along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import unittest
|
||||
|
||||
import six
|
||||
from six.moves import range
|
||||
|
||||
from gaphas.quadtree import Quadtree
|
||||
|
||||
|
||||
class QuadtreeTestCase(unittest.TestCase):
|
||||
|
||||
def test_lookups(self):
|
||||
qtree = Quadtree((0, 0, 100, 100))
|
||||
for i in range(100, 10):
|
||||
@ -37,8 +12,8 @@ class QuadtreeTestCase(unittest.TestCase):
|
||||
|
||||
for i in range(100, 10):
|
||||
for j in range(100, 10):
|
||||
assert qtree.find_intersect((i + 1, j + 1, 1, 1)) == ['%dx%d' % (i, j)], \
|
||||
qtree.find_intersect((i + 1, j + 1, 1, 1))
|
||||
assert qtree.find_intersect((i+1, j+1, 1, 1)) == ['%dx%d' % (i, j)], \
|
||||
qtree.find_intersect((i+1, j+1, 1, 1))
|
||||
|
||||
def test_with_rectangles(self):
|
||||
from gaphas.geometry import Rectangle
|
||||
@ -51,8 +26,9 @@ class QuadtreeTestCase(unittest.TestCase):
|
||||
|
||||
for i in range(100, 10):
|
||||
for j in range(100, 10):
|
||||
assert qtree.find_intersect((i + 1, j + 1, 1, 1)) == ['%dx%d' % (i, j)], \
|
||||
qtree.find_intersect((i + 1, j + 1, 1, 1))
|
||||
assert qtree.find_intersect((i+1, j+1, 1, 1)) == ['%dx%d' % (i, j)], \
|
||||
qtree.find_intersect((i+1, j+1, 1, 1))
|
||||
|
||||
|
||||
def test_moving_items(self):
|
||||
qtree = Quadtree((0, 0, 100, 100), capacity=10)
|
||||
@ -72,7 +48,7 @@ class QuadtreeTestCase(unittest.TestCase):
|
||||
assert len(qtree._bucket.items) == 0, qtree._bucket.items
|
||||
for i in range(4):
|
||||
assert len(qtree._bucket._buckets[i].items) == 9
|
||||
for item, bounds in six.iteritems(qtree._bucket._buckets[i].items):
|
||||
for item, bounds in qtree._bucket._buckets[i].items.iteritems():
|
||||
assert qtree._bucket.find_bucket(bounds) is qtree._bucket._buckets[i]
|
||||
for j in range(4):
|
||||
assert len(qtree._bucket._buckets[i]._buckets[j].items) == 4
|
||||
@ -82,19 +58,20 @@ class QuadtreeTestCase(unittest.TestCase):
|
||||
qtree.add('0x0', (20, 20, 10, 10))
|
||||
assert len(qtree._bucket.items) == 0
|
||||
assert len(qtree._bucket._buckets[0]._buckets[0].items) == 3, \
|
||||
qtree._bucket._buckets[0]._buckets[0].items
|
||||
qtree._bucket._buckets[0]._buckets[0].items
|
||||
assert len(qtree._bucket._buckets[0].items) == 10, \
|
||||
qtree._bucket._buckets[0].items
|
||||
qtree._bucket._buckets[0].items
|
||||
|
||||
# Now move item '0x0' to the second quadrant (70, 20)
|
||||
qtree.add('0x0', (70, 20, 10, 10))
|
||||
assert len(qtree._bucket.items) == 0
|
||||
assert len(qtree._bucket._buckets[0]._buckets[0].items) == 3, \
|
||||
qtree._bucket._buckets[0]._buckets[0].items
|
||||
qtree._bucket._buckets[0]._buckets[0].items
|
||||
assert len(qtree._bucket._buckets[0].items) == 9, \
|
||||
qtree._bucket._buckets[0].items
|
||||
qtree._bucket._buckets[0].items
|
||||
assert len(qtree._bucket._buckets[1].items) == 10, \
|
||||
qtree._bucket._buckets[1].items
|
||||
qtree._bucket._buckets[1].items
|
||||
|
||||
|
||||
def test_get_data(self):
|
||||
"""
|
||||
@ -103,11 +80,11 @@ class QuadtreeTestCase(unittest.TestCase):
|
||||
qtree = Quadtree((0, 0, 100, 100))
|
||||
for i in range(0, 100, 10):
|
||||
for j in range(0, 100, 10):
|
||||
qtree.add("%dx%d" % (i, j), (i, j, 10, 10), i + j)
|
||||
qtree.add("%dx%d" % (i, j), (i, j, 10, 10), i+j)
|
||||
|
||||
for i in range(0, 100, 10):
|
||||
for j in range(0, 100, 10):
|
||||
assert i + j == qtree.get_data("%dx%d" % (i, j))
|
||||
assert i+j == qtree.get_data("%dx%d" % (i, j))
|
||||
|
||||
def test_clipped_bounds(self):
|
||||
qtree = Quadtree((0, 0, 100, 100), capacity=10)
|
||||
|
@ -1,38 +1,14 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2009-2017 Arjan Molenaar <gaphor@gmail.com>
|
||||
# Dan Yeaw <dan@yeaw.me>
|
||||
#
|
||||
# This file is part of Gaphas.
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Library General Public License as published by the Free
|
||||
# Software Foundation; either version 2 of the License, or (at your option) any
|
||||
# later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Library General Public License
|
||||
# along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
Generic gaphas item tests.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import print_function
|
||||
|
||||
import unittest
|
||||
|
||||
from gaphas import state
|
||||
from gaphas.canvas import Canvas
|
||||
from gaphas.item import Item
|
||||
from gaphas.item import Item, Line
|
||||
from gaphas.segment import *
|
||||
from gaphas.tests.test_tool import simple_canvas
|
||||
from gaphas.canvas import Canvas
|
||||
from gaphas.view import View
|
||||
from gaphas import state
|
||||
|
||||
|
||||
class SegmentTestCase(unittest.TestCase):
|
||||
@ -51,9 +27,9 @@ class SegmentTestCase(unittest.TestCase):
|
||||
item = Item()
|
||||
try:
|
||||
s = Segment(item, self.view)
|
||||
print(item, 'segment aspect:', s)
|
||||
except TypeError as e:
|
||||
print('TypeError', e)
|
||||
print item, 'segment aspect:', s
|
||||
except TypeError, e:
|
||||
print 'TypeError', e
|
||||
else:
|
||||
assert False, 'Should not be reached'
|
||||
|
||||
@ -69,6 +45,9 @@ class SegmentTestCase(unittest.TestCase):
|
||||
self.assertEquals(3, len(line.handles()))
|
||||
|
||||
|
||||
from gaphas.tests.test_tool import simple_canvas
|
||||
|
||||
|
||||
undo_list = []
|
||||
redo_list = []
|
||||
|
||||
@ -91,7 +70,6 @@ class TestCaseBase(unittest.TestCase):
|
||||
"""
|
||||
Abstract test case class with undo support.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
state.observers.add(state.revert_handler)
|
||||
state.subscribers.add(undo_handler)
|
||||
@ -102,11 +80,12 @@ class TestCaseBase(unittest.TestCase):
|
||||
state.subscribers.remove(undo_handler)
|
||||
|
||||
|
||||
|
||||
|
||||
class LineSplitTestCase(TestCaseBase):
|
||||
"""
|
||||
Tests for line splitting.
|
||||
"""
|
||||
|
||||
def test_split_single(self):
|
||||
"""Test single line splitting
|
||||
"""
|
||||
@ -142,6 +121,7 @@ class LineSplitTestCase(TestCaseBase):
|
||||
self.assertEquals(h2.pos, p2.start)
|
||||
self.assertEquals(h3.pos, p2.end)
|
||||
|
||||
|
||||
def test_split_multiple(self):
|
||||
"""Test multiple line splitting
|
||||
"""
|
||||
@ -186,6 +166,7 @@ class LineSplitTestCase(TestCaseBase):
|
||||
self.assertEquals(h4.pos, p4.start)
|
||||
self.assertEquals(h5.pos, p4.end)
|
||||
|
||||
|
||||
def test_ports_after_split(self):
|
||||
"""Test ports removal after split
|
||||
"""
|
||||
@ -207,6 +188,7 @@ class LineSplitTestCase(TestCaseBase):
|
||||
self.assertFalse(old_ports[0] in self.line.ports())
|
||||
self.assertEquals(old_ports[1], self.line.ports()[2])
|
||||
|
||||
|
||||
def test_constraints_after_split(self):
|
||||
"""Test if constraints are recreated after line split
|
||||
"""
|
||||
@ -228,6 +210,7 @@ class LineSplitTestCase(TestCaseBase):
|
||||
self.assertEquals(h1.pos, cinfo.constraint._line[0]._point)
|
||||
self.assertEquals(h2.pos, cinfo.constraint._line[1]._point)
|
||||
|
||||
|
||||
def test_split_undo(self):
|
||||
"""Test line splitting undo
|
||||
"""
|
||||
@ -248,6 +231,7 @@ class LineSplitTestCase(TestCaseBase):
|
||||
self.assertEquals(2, len(self.line.handles()))
|
||||
self.assertEquals(1, len(self.line.ports()))
|
||||
|
||||
|
||||
def test_orthogonal_line_split(self):
|
||||
"""Test orthogonal line splitting
|
||||
"""
|
||||
@ -271,6 +255,7 @@ class LineSplitTestCase(TestCaseBase):
|
||||
self.assertEquals(4, len(self.line.handles()))
|
||||
self.assertEquals(3, len(self.line.ports()))
|
||||
|
||||
|
||||
def test_params_errors(self):
|
||||
"""Test parameter error exceptions
|
||||
"""
|
||||
@ -290,6 +275,7 @@ class LineSplitTestCase(TestCaseBase):
|
||||
self.assertRaises(ValueError, segment.split_segment, 0, 1)
|
||||
|
||||
|
||||
|
||||
class LineMergeTestCase(TestCaseBase):
|
||||
"""
|
||||
Tests for line merging.
|
||||
@ -330,6 +316,7 @@ class LineMergeTestCase(TestCaseBase):
|
||||
self.assertEquals((0, 0), port.start.pos)
|
||||
self.assertEquals((20, 0), port.end.pos)
|
||||
|
||||
|
||||
def test_constraints_after_merge(self):
|
||||
"""Test if constraints are recreated after line merge
|
||||
"""
|
||||
@ -339,11 +326,11 @@ class LineMergeTestCase(TestCaseBase):
|
||||
self.canvas.add(line2)
|
||||
head = line2.handles()[0]
|
||||
|
||||
# conn = Connector(line2, head)
|
||||
# sink = conn.glue((25, 25))
|
||||
# assert sink is not None
|
||||
#conn = Connector(line2, head)
|
||||
#sink = conn.glue((25, 25))
|
||||
#assert sink is not None
|
||||
|
||||
# conn.connect(sink)
|
||||
#conn.connect(sink)
|
||||
|
||||
self.tool.connect(line2, head, (25, 25))
|
||||
cinfo = self.canvas.get_connection(head)
|
||||
@ -364,6 +351,7 @@ class LineMergeTestCase(TestCaseBase):
|
||||
self.assertEquals(cinfo.constraint._line[1]._point, h2.pos)
|
||||
self.assertFalse(c1 == cinfo.constraint)
|
||||
|
||||
|
||||
def test_merge_multiple(self):
|
||||
"""Test multiple line merge
|
||||
"""
|
||||
@ -375,7 +363,7 @@ class LineMergeTestCase(TestCaseBase):
|
||||
assert len(self.line.handles()) == 4
|
||||
assert len(self.line.ports()) == 3
|
||||
|
||||
print(self.line.handles())
|
||||
print self.line.handles()
|
||||
handles, ports = segment.merge_segment(0, count=3)
|
||||
self.assertEquals(2, len(handles))
|
||||
self.assertEquals(3, len(ports))
|
||||
@ -390,6 +378,7 @@ class LineMergeTestCase(TestCaseBase):
|
||||
self.assertEquals((0, 0), port.start.pos)
|
||||
self.assertEquals((20, 16), port.end.pos)
|
||||
|
||||
|
||||
def test_merge_undo(self):
|
||||
"""Test line merging undo
|
||||
"""
|
||||
@ -415,6 +404,7 @@ class LineMergeTestCase(TestCaseBase):
|
||||
self.assertEquals(3, len(self.line.handles()))
|
||||
self.assertEquals(2, len(self.line.ports()))
|
||||
|
||||
|
||||
def test_orthogonal_line_merge(self):
|
||||
"""Test orthogonal line merging
|
||||
"""
|
||||
@ -439,6 +429,7 @@ class LineMergeTestCase(TestCaseBase):
|
||||
self.assertEquals(3, len(self.line.handles()))
|
||||
self.assertEquals(2, len(self.line.ports()))
|
||||
|
||||
|
||||
def test_params_errors(self):
|
||||
"""Test parameter error exceptions
|
||||
"""
|
||||
@ -488,14 +479,17 @@ class LineMergeTestCase(TestCaseBase):
|
||||
|
||||
|
||||
class SegmentHandlesTest(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.canvas = Canvas()
|
||||
self.line = Line()
|
||||
self.canvas.add(self.line)
|
||||
self.view = View(self.canvas)
|
||||
|
||||
|
||||
def testHandleFinder(self):
|
||||
finder = HandleFinder(self.line, self.view)
|
||||
assert type(finder) is SegmentHandleFinder, type(finder)
|
||||
|
||||
|
||||
# vim:sw=4:et:ai
|
||||
|
@ -1,37 +1,14 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2007-2017 Arjan Molenaar <gaphor@gmail.com>
|
||||
# Artur Wroblewski <wrobell@pld-linux.org>
|
||||
# Dan Yeaw <dan@yeaw.me>
|
||||
#
|
||||
# This file is part of Gaphas.
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Library General Public License as published by the Free
|
||||
# Software Foundation; either version 2 of the License, or (at your option) any
|
||||
# later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Library General Public License
|
||||
# along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
Unit tests for Gaphas' solver.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import print_function
|
||||
|
||||
import unittest
|
||||
from timeit import Timer
|
||||
|
||||
from gaphas.solver import Solver, Variable
|
||||
from gaphas.constraint import EquationConstraint, EqualsConstraint, \
|
||||
LessThanConstraint
|
||||
from gaphas.solver import Solver, Variable
|
||||
|
||||
|
||||
SETUP = """
|
||||
from gaphas.solver import Solver, Variable
|
||||
@ -46,12 +23,10 @@ solver.add_constraint(c_eq)
|
||||
REPEAT = 30
|
||||
NUMBER = 1000
|
||||
|
||||
|
||||
class WeakestVariableTestCase(unittest.TestCase):
|
||||
"""
|
||||
Test weakest variable calculation.
|
||||
"""
|
||||
|
||||
def test_weakest_list(self):
|
||||
"""Test weakest list"""
|
||||
solver = Solver()
|
||||
@ -66,6 +41,7 @@ class WeakestVariableTestCase(unittest.TestCase):
|
||||
self.assertTrue(b in c_eq._weakest)
|
||||
self.assertTrue(c in c_eq._weakest)
|
||||
|
||||
|
||||
def test_weakest_list_order(self):
|
||||
"""Test weakest list order"""
|
||||
solver = Solver()
|
||||
@ -79,7 +55,7 @@ class WeakestVariableTestCase(unittest.TestCase):
|
||||
weakest = [el for el in c_eq._weakest]
|
||||
a.value = 4
|
||||
|
||||
self.assertEqual(c_eq._weakest, weakest) # does not change if non-weak variable changed
|
||||
self.assertEqual(c_eq._weakest, weakest) # does not change if non-weak variable changed
|
||||
|
||||
b.value = 5
|
||||
self.assertEqual(c_eq.weakest(), c)
|
||||
@ -95,6 +71,7 @@ class WeakestVariableTestCase(unittest.TestCase):
|
||||
b.value = 6
|
||||
self.assertEqual(c_eq.weakest(), c)
|
||||
|
||||
|
||||
def test_strength_change(self):
|
||||
"""Test strength change"""
|
||||
solver = Solver()
|
||||
@ -109,11 +86,11 @@ class WeakestVariableTestCase(unittest.TestCase):
|
||||
self.assertEqual(c_eq._weakest, [b])
|
||||
|
||||
|
||||
|
||||
class SizeTestCase(unittest.TestCase):
|
||||
"""
|
||||
Test size related constraints, i.e. minimal size.
|
||||
"""
|
||||
|
||||
def test_min_size(self):
|
||||
"""Test minimal size constraint"""
|
||||
solver = Solver()
|
||||
@ -159,11 +136,11 @@ class SizeTestCase(unittest.TestCase):
|
||||
self.assertEquals(10, v3)
|
||||
|
||||
|
||||
|
||||
class SolverSpeedTestCase(unittest.TestCase):
|
||||
"""
|
||||
Solver speed tests.
|
||||
"""
|
||||
|
||||
def _test_speed_run_weakest(self):
|
||||
"""
|
||||
Speed test for weakest variable.
|
||||
@ -175,7 +152,7 @@ c_eq.weakest()""").repeat(repeat=REPEAT, number=NUMBER)
|
||||
|
||||
# Print the average of the best 10 runs:
|
||||
results.sort()
|
||||
print('[Avg: %gms]' % ((sum(results[:10]) / 10) * 1000))
|
||||
print '[Avg: %gms]' % ((sum(results[:10]) / 10) * 1000)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
@ -1,54 +1,24 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2009-2017 Artur Wroblewski <wrobell@pld-linux.org>
|
||||
# Dan Yeaw <dan@yeaw.me>
|
||||
#
|
||||
# This file is part of Gaphas.
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Library General Public License as published by the Free
|
||||
# Software Foundation; either version 2 of the License, or (at your option) any
|
||||
# later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Library General Public License
|
||||
# along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import unittest
|
||||
|
||||
from gaphas.state import reversible_pair, observed, _reverse
|
||||
|
||||
|
||||
class SList(object):
|
||||
def __init__(self):
|
||||
self.l = list()
|
||||
|
||||
def add(self, node, before=None):
|
||||
if before:
|
||||
self.l.insert(self.l.index(before), node)
|
||||
else:
|
||||
self.l.append(node)
|
||||
|
||||
if before: self.l.insert(self.l.index(before), node)
|
||||
else: self.l.append(node)
|
||||
add = observed(add)
|
||||
|
||||
@observed
|
||||
def remove(self, node):
|
||||
self.l.remove(self.l.index(node))
|
||||
|
||||
|
||||
class StateTestCase(unittest.TestCase):
|
||||
def test_adding_pair(self):
|
||||
"""Test adding reversible pair
|
||||
"""
|
||||
reversible_pair(SList.add, SList.remove,
|
||||
bind1={'before': lambda self, node: self.l[self.l.index(node) + 1]}
|
||||
)
|
||||
reversible_pair(SList.add, SList.remove, \
|
||||
bind1={'before': lambda self, node: self.l[self.l.index(node)+1] })
|
||||
|
||||
self.assertTrue(SList.add in _reverse)
|
||||
self.assertTrue(SList.remove in _reverse)
|
||||
self.assertTrue(SList.add.im_func in _reverse)
|
||||
self.assertTrue(SList.remove.im_func in _reverse)
|
||||
|
@ -1,39 +1,20 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2008-2017 Arjan Molenaar <gaphor@gmail.com>
|
||||
# Artur Wroblewski <wrobell@pld-linux.org>
|
||||
# Dan Yeaw <dan@yeaw.me>
|
||||
#
|
||||
# This file is part of Gaphas.
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Library General Public License as published by the Free
|
||||
# Software Foundation; either version 2 of the License, or (at your option) any
|
||||
# later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Library General Public License
|
||||
# along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
Test all the tools provided by gaphas.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import unittest
|
||||
|
||||
from gaphas.canvas import Canvas
|
||||
from gaphas.canvas import Context
|
||||
from gaphas.constraint import LineConstraint
|
||||
from gaphas.examples import Box
|
||||
from gaphas.item import Line
|
||||
from gaphas.tool import ConnectHandleTool
|
||||
from gaphas.view import GtkView
|
||||
from gaphas.canvas import Canvas
|
||||
from gaphas.examples import Box
|
||||
from gaphas.item import Item, Element, Line
|
||||
from gaphas.view import View, GtkView
|
||||
from gaphas.constraint import LineConstraint
|
||||
from gaphas.canvas import Context
|
||||
from gaphas import state
|
||||
|
||||
from gaphas.aspect import Connector, ConnectionSink
|
||||
|
||||
|
||||
Event = Context
|
||||
|
||||
@ -68,8 +49,8 @@ def simple_canvas(self):
|
||||
self.canvas.update_now()
|
||||
self.view = GtkView()
|
||||
self.view.canvas = self.canvas
|
||||
from gi.repository import Gtk
|
||||
win = Gtk.Window()
|
||||
import gtk
|
||||
win = gtk.Window()
|
||||
win.add(self.view)
|
||||
self.view.show()
|
||||
self.view.update()
|
||||
@ -78,6 +59,7 @@ def simple_canvas(self):
|
||||
self.tool = ConnectHandleTool(self.view)
|
||||
|
||||
|
||||
|
||||
class ConnectHandleToolGlueTestCase(unittest.TestCase):
|
||||
"""
|
||||
Test handle connection tool glue method.
|
||||
@ -86,6 +68,7 @@ class ConnectHandleToolGlueTestCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
simple_canvas(self)
|
||||
|
||||
|
||||
def test_item_and_port_glue(self):
|
||||
"""Test glue operation to an item and its ports"""
|
||||
|
||||
@ -111,47 +94,49 @@ class ConnectHandleToolGlueTestCase(unittest.TestCase):
|
||||
self.assertEquals(sink.item, self.box1)
|
||||
self.assertEquals(ports[3], sink.port)
|
||||
|
||||
|
||||
def test_failed_glue(self):
|
||||
"""Test glue from too far distance"""
|
||||
sink = self.tool.glue(self.line, self.head, (90, 50))
|
||||
self.assertTrue(sink is None)
|
||||
|
||||
# def test_glue_call_can_glue_once(self):
|
||||
# """Test if glue method calls can glue once only
|
||||
#
|
||||
# Box has 4 ports. Every port is examined once per
|
||||
# ConnectHandleTool.glue method call. The purpose of this test is to
|
||||
# assure that ConnectHandleTool.can_glue is called once (for the
|
||||
# found port), it cannot be called four times (once for every port).
|
||||
# """
|
||||
#
|
||||
# # count ConnectHandleTool.can_glue calls
|
||||
# class Tool(ConnectHandleTool):
|
||||
# def __init__(self, *args):
|
||||
# super(Tool, self).__init__(*args)
|
||||
# self._calls = 0
|
||||
#
|
||||
# def can_glue(self, *args):
|
||||
# self._calls += 1
|
||||
# return True
|
||||
#
|
||||
# tool = Tool(self.view)
|
||||
# item, port = tool.glue(self.line, self.head, (120, 50))
|
||||
# assert item and port
|
||||
# self.assertEquals(1, tool._calls)
|
||||
|
||||
# def test_glue_call_can_glue_once(self):
|
||||
# """Test if glue method calls can glue once only
|
||||
#
|
||||
# Box has 4 ports. Every port is examined once per
|
||||
# ConnectHandleTool.glue method call. The purpose of this test is to
|
||||
# assure that ConnectHandleTool.can_glue is called once (for the
|
||||
# found port), it cannot be called four times (once for every port).
|
||||
# """
|
||||
#
|
||||
# # count ConnectHandleTool.can_glue calls
|
||||
# class Tool(ConnectHandleTool):
|
||||
# def __init__(self, *args):
|
||||
# super(Tool, self).__init__(*args)
|
||||
# self._calls = 0
|
||||
#
|
||||
# def can_glue(self, *args):
|
||||
# self._calls += 1
|
||||
# return True
|
||||
#
|
||||
# tool = Tool(self.view)
|
||||
# item, port = tool.glue(self.line, self.head, (120, 50))
|
||||
# assert item and port
|
||||
# self.assertEquals(1, tool._calls)
|
||||
|
||||
|
||||
# def test_glue_cannot_glue(self):
|
||||
# """Test if glue method respects ConnectHandleTool.can_glue method"""
|
||||
#
|
||||
# class Tool(ConnectHandleTool):
|
||||
# def can_glue(self, *args):
|
||||
# return False
|
||||
#
|
||||
# tool = Tool(self.view)
|
||||
# item, port = tool.glue(self.line, self.head, (120, 50))
|
||||
# self.assertTrue(item is None, item)
|
||||
# self.assertTrue(port is None, port)
|
||||
# def test_glue_cannot_glue(self):
|
||||
# """Test if glue method respects ConnectHandleTool.can_glue method"""
|
||||
#
|
||||
# class Tool(ConnectHandleTool):
|
||||
# def can_glue(self, *args):
|
||||
# return False
|
||||
#
|
||||
# tool = Tool(self.view)
|
||||
# item, port = tool.glue(self.line, self.head, (120, 50))
|
||||
# self.assertTrue(item is None, item)
|
||||
# self.assertTrue(port is None, port)
|
||||
|
||||
|
||||
def test_glue_no_port_no_can_glue(self):
|
||||
@ -172,16 +157,20 @@ class ConnectHandleToolGlueTestCase(unittest.TestCase):
|
||||
self.assertEquals(0, tool._calls)
|
||||
|
||||
|
||||
|
||||
class ConnectHandleToolConnectTestCase(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
simple_canvas(self)
|
||||
|
||||
|
||||
def _get_line(self):
|
||||
line = Line()
|
||||
head = line.handles()[0]
|
||||
self.canvas.add(line)
|
||||
return line, head
|
||||
|
||||
|
||||
def test_connect(self):
|
||||
"""Test connection to an item"""
|
||||
line, head = self._get_line()
|
||||
@ -190,7 +179,7 @@ class ConnectHandleToolConnectTestCase(unittest.TestCase):
|
||||
self.assertTrue(cinfo is not None)
|
||||
self.assertEquals(self.box1, cinfo.connected)
|
||||
self.assertTrue(cinfo.port is self.box1.ports()[0],
|
||||
'port %s' % cinfo.port)
|
||||
'port %s' % cinfo.port)
|
||||
self.assertTrue(isinstance(cinfo.constraint, LineConstraint))
|
||||
# No default callback defined:
|
||||
self.assertTrue(cinfo.callback is None)
|
||||
@ -201,6 +190,7 @@ class ConnectHandleToolConnectTestCase(unittest.TestCase):
|
||||
self.assertTrue(cinfo is not cinfo2, cinfo2)
|
||||
self.assertTrue(cinfo2 is None, cinfo2)
|
||||
|
||||
|
||||
def test_reconnect_another(self):
|
||||
"""Test reconnection to another item"""
|
||||
line, head = self._get_line()
|
||||
@ -227,6 +217,7 @@ class ConnectHandleToolConnectTestCase(unittest.TestCase):
|
||||
self.assertNotEqual(item, cinfo.connected)
|
||||
self.assertNotEqual(constraint, cinfo.constraint)
|
||||
|
||||
|
||||
def test_reconnect_same(self):
|
||||
"""Test reconnection to same item"""
|
||||
line, head = self._get_line()
|
||||
@ -249,6 +240,7 @@ class ConnectHandleToolConnectTestCase(unittest.TestCase):
|
||||
self.assertEqual(self.box1.ports()[0], cinfo.port)
|
||||
self.assertNotEqual(constraint, cinfo.constraint)
|
||||
|
||||
|
||||
def xtest_find_port(self):
|
||||
"""Test finding a port
|
||||
"""
|
||||
@ -271,4 +263,5 @@ class ConnectHandleToolConnectTestCase(unittest.TestCase):
|
||||
port = self.tool.find_port(line, head, self.box1)
|
||||
self.assertEquals(p4, port)
|
||||
|
||||
|
||||
# vim: sw=4:et:ai
|
||||
|
@ -1,31 +1,10 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2007-2017 Arjan Molenaar <gaphor@gmail.com>
|
||||
# Dan Yeaw <dan@yeaw.me>
|
||||
#
|
||||
# This file is part of Gaphas.
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Library General Public License as published by the Free
|
||||
# Software Foundation; either version 2 of the License, or (at your option) any
|
||||
# later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Library General Public License
|
||||
# along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import unittest
|
||||
|
||||
from gaphas.tree import Tree
|
||||
|
||||
|
||||
class TreeTestCase(unittest.TestCase):
|
||||
|
||||
def test_add(self):
|
||||
"""
|
||||
Test creating node trees.
|
||||
@ -95,6 +74,7 @@ class TreeTestCase(unittest.TestCase):
|
||||
assert tree.nodes == [n1, n3, n5, n4, n2], tree.nodes
|
||||
assert tree.get_children(n3) == [n5, n4], tree.get_children(n3)
|
||||
|
||||
|
||||
def test_remove(self):
|
||||
"""
|
||||
Test removal of nodes.
|
||||
@ -115,7 +95,7 @@ class TreeTestCase(unittest.TestCase):
|
||||
assert tree._nodes == [n1, n3, n4, n5, n2]
|
||||
|
||||
all_ch = list(tree.get_all_children(n1))
|
||||
assert all_ch == [n3, n4, n5], all_ch
|
||||
assert all_ch == [ n3, n4, n5 ], all_ch
|
||||
|
||||
tree.remove(n4)
|
||||
assert tree._nodes == [n1, n3, n2]
|
||||
@ -141,18 +121,18 @@ class TreeTestCase(unittest.TestCase):
|
||||
try:
|
||||
tree.get_next_sibling(n3)
|
||||
except IndexError:
|
||||
pass # okay
|
||||
pass # okay
|
||||
else:
|
||||
raise AssertionError('Index should be out of range, not %s' % tree.get_next_sibling(n3))
|
||||
raise AssertionError, 'Index should be out of range, not %s' % tree.get_next_sibling(n3)
|
||||
|
||||
assert tree.get_previous_sibling(n3) is n2
|
||||
assert tree.get_previous_sibling(n2) is n1
|
||||
try:
|
||||
tree.get_previous_sibling(n1)
|
||||
except IndexError:
|
||||
pass # okay
|
||||
pass # okay
|
||||
else:
|
||||
raise AssertionError('Index should be out of range, not %s' % tree.get_previous_sibling(n1))
|
||||
raise AssertionError, 'Index should be out of range, not %s' % tree.get_previous_sibling(n1)
|
||||
|
||||
def test_reparent(self):
|
||||
tree = Tree()
|
||||
@ -179,4 +159,5 @@ class TreeTestCase(unittest.TestCase):
|
||||
tree.reparent(n4, parent=None, index=0)
|
||||
assert tree.nodes == [n4, n5, n1, n2, n3], tree.nodes
|
||||
|
||||
|
||||
# vi:sw=4:et:ai
|
||||
|
@ -1,32 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2010-2017 Arjan Molenaar <gaphor@gmail.com>
|
||||
# Dan Yeaw <dan@yeaw.me>
|
||||
#
|
||||
# This file is part of Gaphas.
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Library General Public License as published by the Free
|
||||
# Software Foundation; either version 2 of the License, or (at your option) any
|
||||
# later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Library General Public License
|
||||
# along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import unittest
|
||||
|
||||
from gaphas import state
|
||||
from gaphas.aspect import Connector, ConnectionSink
|
||||
from gaphas.canvas import Canvas
|
||||
from gaphas.examples import Box
|
||||
from gaphas.item import Line
|
||||
|
||||
state.observers.clear()
|
||||
state.subscribers.clear()
|
||||
@ -34,11 +8,9 @@ state.subscribers.clear()
|
||||
undo_list = []
|
||||
redo_list = []
|
||||
|
||||
|
||||
def undo_handler(event):
|
||||
undo_list.append(event)
|
||||
|
||||
|
||||
def undo():
|
||||
apply_me = list(undo_list)
|
||||
del undo_list[:]
|
||||
@ -49,7 +21,14 @@ def undo():
|
||||
del undo_list[:]
|
||||
|
||||
|
||||
from gaphas.canvas import Canvas
|
||||
from gaphas.examples import Box
|
||||
from gaphas.item import Line
|
||||
from gaphas.aspect import Connector, ConnectionSink
|
||||
|
||||
|
||||
class UndoTestCase(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
state.observers.add(state.revert_handler)
|
||||
state.subscribers.add(undo_handler)
|
||||
@ -112,8 +91,7 @@ class UndoTestCase(unittest.TestCase):
|
||||
cinfo = canvas.get_connection(line.handles()[-1])
|
||||
self.assertEquals(b2, cinfo.connected)
|
||||
|
||||
|
||||
# self.assertEquals(list(canvas.solver.constraints_with_variable(line.handles()[-1].pos.x)))
|
||||
# self.assertEquals(list(canvas.solver.constraints_with_variable(line.handles()[-1].pos.x)))
|
||||
# self.assertTrue(list(canvas.solver.constraints_with_variable(line.handles()[-1].pos.y)))
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
@ -1,42 +1,19 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2007-2017 Arjan Molenaar <gaphor@gmail.com>
|
||||
# Dan Yeaw <dan@yeaw.me>
|
||||
#
|
||||
# This file is part of Gaphas.
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Library General Public License as published by the Free
|
||||
# Software Foundation; either version 2 of the License, or (at your option) any
|
||||
# later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Library General Public License
|
||||
# along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
Test cases for the View class.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import print_function
|
||||
|
||||
import math
|
||||
import unittest
|
||||
|
||||
from gi.repository import Gtk
|
||||
|
||||
from gaphas.canvas import Canvas
|
||||
from gaphas.examples import Box
|
||||
from gaphas.item import Line
|
||||
import gtk
|
||||
import math
|
||||
from gaphas.view import View, GtkView
|
||||
from gaphas.canvas import Canvas, Context
|
||||
from gaphas.item import Line
|
||||
from gaphas.examples import Box
|
||||
from gaphas.tool import HoverTool
|
||||
|
||||
|
||||
class ViewTestCase(unittest.TestCase):
|
||||
|
||||
def test_bounding_box_calculations(self):
|
||||
"""
|
||||
A view created before and after the canvas is populated should contain
|
||||
@ -44,41 +21,37 @@ class ViewTestCase(unittest.TestCase):
|
||||
"""
|
||||
canvas = Canvas()
|
||||
|
||||
window1 = Gtk.Window(Gtk.WindowType.TOPLEVEL)
|
||||
window1 = gtk.Window(gtk.WINDOW_TOPLEVEL)
|
||||
view1 = GtkView(canvas=canvas)
|
||||
window1.add(view1)
|
||||
view1.realize()
|
||||
window1.show_all()
|
||||
|
||||
box = Box()
|
||||
box.matrix = (1.0, 0.0, 0.0, 1, 10, 10)
|
||||
box.matrix = (1.0, 0.0, 0.0, 1, 10,10)
|
||||
canvas.add(box)
|
||||
|
||||
line = Line()
|
||||
line.fyzzyness = 1
|
||||
line.handles()[1].pos = (30, 30)
|
||||
# line.split_segment(0, 3)
|
||||
#line.split_segment(0, 3)
|
||||
line.matrix.translate(30, 60)
|
||||
canvas.add(line)
|
||||
|
||||
window2 = Gtk.Window(Gtk.WindowType.TOPLEVEL)
|
||||
window2 = gtk.Window(gtk.WINDOW_TOPLEVEL)
|
||||
view2 = GtkView(canvas=canvas)
|
||||
window2.add(view2)
|
||||
window2.show_all()
|
||||
|
||||
# Process pending (expose) events, which cause the canvas to be drawn.
|
||||
while Gtk.events_pending():
|
||||
Gtk.main_iteration()
|
||||
while gtk.events_pending():
|
||||
gtk.main_iteration()
|
||||
|
||||
try:
|
||||
assert view2.get_item_bounding_box(box)
|
||||
assert view1.get_item_bounding_box(box)
|
||||
assert view1.get_item_bounding_box(box) == view2.get_item_bounding_box(box), '%s != %s' % (
|
||||
view1.get_item_bounding_box(box), view2.get_item_bounding_box(box)
|
||||
)
|
||||
assert view1.get_item_bounding_box(line) == view2.get_item_bounding_box(line), '%s != %s' % (
|
||||
view1.get_item_bounding_box(line), view2.get_item_bounding_box(line)
|
||||
)
|
||||
assert view1.get_item_bounding_box(box) == view2.get_item_bounding_box(box), '%s != %s' % (view1.get_item_bounding_box(box), view2.get_item_bounding_box(box))
|
||||
assert view1.get_item_bounding_box(line) == view2.get_item_bounding_box(line), '%s != %s' % (view1.get_item_bounding_box(line), view2.get_item_bounding_box(line))
|
||||
finally:
|
||||
window1.destroy()
|
||||
window2.destroy()
|
||||
@ -89,7 +62,7 @@ class ViewTestCase(unittest.TestCase):
|
||||
"""
|
||||
canvas = Canvas()
|
||||
view = GtkView(canvas)
|
||||
window = Gtk.Window(Gtk.WindowType.TOPLEVEL)
|
||||
window = gtk.Window(gtk.WINDOW_TOPLEVEL)
|
||||
window.add(view)
|
||||
window.show_all()
|
||||
|
||||
@ -101,8 +74,8 @@ class ViewTestCase(unittest.TestCase):
|
||||
box.height = 50
|
||||
|
||||
# Process pending (expose) events, which cause the canvas to be drawn.
|
||||
while Gtk.events_pending():
|
||||
Gtk.main_iteration()
|
||||
while gtk.events_pending():
|
||||
gtk.main_iteration()
|
||||
|
||||
assert len(view._qtree._ids) == 1
|
||||
assert not view._qtree._bucket.bounds == (0, 0, 0, 0), view._qtree._bucket.bounds
|
||||
@ -115,7 +88,7 @@ class ViewTestCase(unittest.TestCase):
|
||||
def test_get_handle_at_point(self):
|
||||
canvas = Canvas()
|
||||
view = GtkView(canvas)
|
||||
window = Gtk.Window(Gtk.WindowType.TOPLEVEL)
|
||||
window = gtk.Window(gtk.WINDOW_TOPLEVEL)
|
||||
window.add(view)
|
||||
window.show_all()
|
||||
|
||||
@ -123,7 +96,7 @@ class ViewTestCase(unittest.TestCase):
|
||||
box.min_width = 20
|
||||
box.min_height = 30
|
||||
box.matrix.translate(20, 20)
|
||||
box.matrix.rotate(math.pi / 1.5)
|
||||
box.matrix.rotate(math.pi/1.5)
|
||||
canvas.add(box)
|
||||
|
||||
i, h = view.get_handle_at_point((20, 20))
|
||||
@ -133,7 +106,7 @@ class ViewTestCase(unittest.TestCase):
|
||||
def test_get_handle_at_point_at_pi_div_2(self):
|
||||
canvas = Canvas()
|
||||
view = GtkView(canvas)
|
||||
window = Gtk.Window(Gtk.WindowType.TOPLEVEL)
|
||||
window = gtk.Window(gtk.WINDOW_TOPLEVEL)
|
||||
window.add(view)
|
||||
window.show_all()
|
||||
|
||||
@ -141,7 +114,7 @@ class ViewTestCase(unittest.TestCase):
|
||||
box.min_width = 20
|
||||
box.min_height = 30
|
||||
box.matrix.translate(20, 20)
|
||||
box.matrix.rotate(math.pi / 2)
|
||||
box.matrix.rotate(math.pi/2)
|
||||
canvas.add(box)
|
||||
|
||||
p = canvas.get_matrix_i2c(box).transform_point(0, 20)
|
||||
@ -153,7 +126,7 @@ class ViewTestCase(unittest.TestCase):
|
||||
def test_item_removal(self):
|
||||
canvas = Canvas()
|
||||
view = GtkView(canvas)
|
||||
window = Gtk.Window(Gtk.WindowType.TOPLEVEL)
|
||||
window = gtk.Window(gtk.WINDOW_TOPLEVEL)
|
||||
window.add(view)
|
||||
window.show_all()
|
||||
|
||||
@ -163,8 +136,8 @@ class ViewTestCase(unittest.TestCase):
|
||||
assert not canvas.require_update()
|
||||
|
||||
# Process pending (expose) events, which cause the canvas to be drawn.
|
||||
while Gtk.events_pending():
|
||||
Gtk.main_iteration()
|
||||
while gtk.events_pending():
|
||||
gtk.main_iteration()
|
||||
|
||||
assert len(canvas.get_all_items()) == len(view._qtree)
|
||||
|
||||
@ -188,8 +161,8 @@ class ViewTestCase(unittest.TestCase):
|
||||
canvas.add(box)
|
||||
|
||||
# By default no complex updating/calculations are done:
|
||||
assert view not in box._matrix_i2v
|
||||
assert view not in box._matrix_v2i
|
||||
assert not box._matrix_i2v.has_key(view)
|
||||
assert not box._matrix_v2i.has_key(view)
|
||||
|
||||
# GTK view does register for updates though
|
||||
|
||||
@ -197,28 +170,29 @@ class ViewTestCase(unittest.TestCase):
|
||||
assert len(canvas._registered_views) == 1
|
||||
|
||||
# No entry, since GtkView is not realized and has no window
|
||||
assert view not in box._matrix_i2v
|
||||
assert view not in box._matrix_v2i
|
||||
assert not box._matrix_i2v.has_key(view)
|
||||
assert not box._matrix_v2i.has_key(view)
|
||||
|
||||
window = Gtk.Window(Gtk.WindowType.TOPLEVEL)
|
||||
window = gtk.Window(gtk.WINDOW_TOPLEVEL)
|
||||
window.add(view)
|
||||
window.show_all()
|
||||
|
||||
# Now everything is realized and updated
|
||||
assert view in box._matrix_i2v
|
||||
assert view in box._matrix_v2i
|
||||
assert box._matrix_i2v.has_key(view)
|
||||
assert box._matrix_v2i.has_key(view)
|
||||
|
||||
view.canvas = None
|
||||
assert len(canvas._registered_views) == 0
|
||||
|
||||
assert view not in box._matrix_i2v
|
||||
assert view not in box._matrix_v2i
|
||||
assert not box._matrix_i2v.has_key(view)
|
||||
assert not box._matrix_v2i.has_key(view)
|
||||
|
||||
view.canvas = canvas
|
||||
assert len(canvas._registered_views) == 1
|
||||
|
||||
assert view in box._matrix_i2v
|
||||
assert view in box._matrix_v2i
|
||||
assert box._matrix_i2v.has_key(view)
|
||||
assert box._matrix_v2i.has_key(view)
|
||||
|
||||
|
||||
def test_view_registration_2(self):
|
||||
"""
|
||||
@ -226,7 +200,7 @@ class ViewTestCase(unittest.TestCase):
|
||||
"""
|
||||
canvas = Canvas()
|
||||
view = GtkView(canvas)
|
||||
window = Gtk.Window(Gtk.WindowType.TOPLEVEL)
|
||||
window = gtk.Window(gtk.WINDOW_TOPLEVEL)
|
||||
window.add(view)
|
||||
window.show_all()
|
||||
|
||||
@ -246,15 +220,28 @@ class ViewTestCase(unittest.TestCase):
|
||||
|
||||
assert len(canvas._registered_views) == 0
|
||||
|
||||
assert view not in box._matrix_i2v
|
||||
assert view not in box._matrix_v2i
|
||||
assert not box._matrix_i2v.has_key(view)
|
||||
assert not box._matrix_v2i.has_key(view)
|
||||
|
||||
|
||||
def test_scroll_adjustments_signal(self):
|
||||
def handler(self, hadj, vadj):
|
||||
self.handled = True
|
||||
|
||||
sc = gtk.ScrolledWindow()
|
||||
view = GtkView(Canvas())
|
||||
view.connect('set-scroll-adjustments', handler)
|
||||
sc.add(view)
|
||||
|
||||
assert view.handled
|
||||
|
||||
|
||||
def test_scroll_adjustments(self):
|
||||
sc = Gtk.ScrolledWindow()
|
||||
sc = gtk.ScrolledWindow()
|
||||
view = GtkView(Canvas())
|
||||
sc.add(view)
|
||||
|
||||
print(sc.get_hadjustment(), view.hadjustment)
|
||||
print sc.get_hadjustment(), view.hadjustment
|
||||
assert sc.get_hadjustment() is view.hadjustment
|
||||
assert sc.get_vadjustment() is view.vadjustment
|
||||
|
||||
|
214
gaphas/tool.py
214
gaphas/tool.py
@ -1,25 +1,3 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2006-2017 Adrian Boguszewski <adrbogus1@student.pg.gda.pl>
|
||||
# Arjan Molenaar <gaphor@gmail.com>
|
||||
# Artur Wroblewski <wrobell@pld-linux.org>
|
||||
# Dan Yeaw <dan@yeaw.me>
|
||||
#
|
||||
# This file is part of Gaphas.
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Library General Public License as published by the Free
|
||||
# Software Foundation; either version 2 of the License, or (at your option) any
|
||||
# later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Library General Public License
|
||||
# along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
Tools provide interactive behavior to a `View` by handling specific events
|
||||
sent by view.
|
||||
@ -53,15 +31,21 @@ Tools can handle events in different ways
|
||||
- tool can handle the event (obviously)
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import print_function
|
||||
__version__ = "$Revision$"
|
||||
# $HeadURL$
|
||||
|
||||
from gi.repository import Gtk, Gdk
|
||||
import sys
|
||||
|
||||
from gaphas.aspect import Finder, Selection, InMotion, \
|
||||
HandleFinder, HandleSelection, HandleInMotion, \
|
||||
Connector
|
||||
import cairo
|
||||
import gtk
|
||||
from gaphas.canvas import Context
|
||||
from gaphas.geometry import Rectangle
|
||||
from gaphas.geometry import distance_point_point_fast, distance_line_point
|
||||
from gaphas.item import Line
|
||||
from gaphas.aspect import Finder, Selection, InMotion, \
|
||||
HandleFinder, HandleSelection, HandleInMotion, \
|
||||
Connector
|
||||
|
||||
|
||||
DEBUG_TOOL_CHAIN = False
|
||||
|
||||
@ -70,7 +54,7 @@ Event = Context
|
||||
|
||||
class Tool(object):
|
||||
"""
|
||||
Base class for a tool. This class
|
||||
Base class for a tool. This class
|
||||
A word on click events:
|
||||
|
||||
Mouse (pointer) button click. A button press is normally followed by
|
||||
@ -98,21 +82,21 @@ class Tool(object):
|
||||
|
||||
# Map GDK events to tool methods
|
||||
EVENT_HANDLERS = {
|
||||
Gdk.EventType.BUTTON_PRESS: 'on_button_press',
|
||||
Gdk.EventType.BUTTON_RELEASE: 'on_button_release',
|
||||
Gdk.EventType._2BUTTON_PRESS: 'on_double_click',
|
||||
Gdk.EventType._3BUTTON_PRESS: 'on_triple_click',
|
||||
Gdk.EventType.MOTION_NOTIFY: 'on_motion_notify',
|
||||
Gdk.EventType.KEY_PRESS: 'on_key_press',
|
||||
Gdk.EventType.KEY_RELEASE: 'on_key_release',
|
||||
Gdk.EventType.SCROLL: 'on_scroll',
|
||||
gtk.gdk.BUTTON_PRESS: 'on_button_press',
|
||||
gtk.gdk.BUTTON_RELEASE: 'on_button_release',
|
||||
gtk.gdk._2BUTTON_PRESS: 'on_double_click',
|
||||
gtk.gdk._3BUTTON_PRESS: 'on_triple_click',
|
||||
gtk.gdk.MOTION_NOTIFY: 'on_motion_notify',
|
||||
gtk.gdk.KEY_PRESS: 'on_key_press',
|
||||
gtk.gdk.KEY_RELEASE: 'on_key_release',
|
||||
gtk.gdk.SCROLL: 'on_scroll',
|
||||
# Custom events:
|
||||
GRAB: 'on_grab',
|
||||
UNGRAB: 'on_ungrab',
|
||||
}
|
||||
|
||||
# Those events force the tool to release the grabbed tool.
|
||||
FORCE_UNGRAB_EVENTS = (Gdk.EventType._2BUTTON_PRESS, Gdk.EventType._3BUTTON_PRESS)
|
||||
FORCE_UNGRAB_EVENTS = (gtk.gdk._2BUTTON_PRESS, gtk.gdk._3BUTTON_PRESS)
|
||||
|
||||
def __init__(self, view=None):
|
||||
self.view = view
|
||||
@ -120,24 +104,28 @@ class Tool(object):
|
||||
def set_view(self, view):
|
||||
self.view = view
|
||||
|
||||
|
||||
def _dispatch(self, event):
|
||||
"""
|
||||
Deal with the event. The event is dispatched to a specific handler
|
||||
for the event type.
|
||||
"""
|
||||
handler = self.EVENT_HANDLERS.get(event.type)
|
||||
#print event.type, handler
|
||||
if handler:
|
||||
try:
|
||||
h = getattr(self, handler)
|
||||
except AttributeError:
|
||||
pass # No handler
|
||||
pass # No handler
|
||||
else:
|
||||
return bool(h(event))
|
||||
return False
|
||||
|
||||
|
||||
def handle(self, event):
|
||||
return self._dispatch(event)
|
||||
|
||||
|
||||
def draw(self, context):
|
||||
"""
|
||||
Some tools (such as Rubberband selection) may need to draw something
|
||||
@ -151,6 +139,7 @@ class Tool(object):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class ToolChain(Tool):
|
||||
"""
|
||||
A ToolChain can be used to chain tools together, for example HoverTool,
|
||||
@ -180,8 +169,7 @@ class ToolChain(Tool):
|
||||
|
||||
def grab(self, tool):
|
||||
if not self._grabbed_tool:
|
||||
if DEBUG_TOOL_CHAIN:
|
||||
print('Grab tool', tool)
|
||||
if DEBUG_TOOL_CHAIN: print 'Grab tool', tool
|
||||
# Send grab event
|
||||
event = Event(type=Tool.GRAB)
|
||||
tool.handle(event)
|
||||
@ -189,8 +177,7 @@ class ToolChain(Tool):
|
||||
|
||||
def ungrab(self, tool):
|
||||
if self._grabbed_tool is tool:
|
||||
if DEBUG_TOOL_CHAIN:
|
||||
print('UNgrab tool', self._grabbed_tool)
|
||||
if DEBUG_TOOL_CHAIN: print 'UNgrab tool', self._grabbed_tool
|
||||
# Send ungrab event
|
||||
event = Event(type=Tool.UNGRAB)
|
||||
tool.handle(event)
|
||||
@ -210,7 +197,7 @@ class ToolChain(Tool):
|
||||
or grabbed.
|
||||
|
||||
If a tool is returning True on a button press event, the motion and
|
||||
button release events are also passed to this
|
||||
button release events are also passed to this
|
||||
"""
|
||||
handler = self.EVENT_HANDLERS.get(event.type)
|
||||
|
||||
@ -220,19 +207,19 @@ class ToolChain(Tool):
|
||||
try:
|
||||
return self._grabbed_tool.handle(event)
|
||||
finally:
|
||||
if event.type == Gdk.EventType.BUTTON_RELEASE:
|
||||
if event.type == gtk.gdk.BUTTON_RELEASE:
|
||||
self.ungrab(self._grabbed_tool)
|
||||
else:
|
||||
for tool in self._tools:
|
||||
if DEBUG_TOOL_CHAIN:
|
||||
print('tool', tool)
|
||||
if DEBUG_TOOL_CHAIN: print 'tool', tool
|
||||
rt = tool.handle(event)
|
||||
if rt:
|
||||
if event.type == Gdk.EventType.BUTTON_PRESS:
|
||||
if event.type == gtk.gdk.BUTTON_PRESS:
|
||||
self.view.grab_focus()
|
||||
self.grab(tool)
|
||||
return rt
|
||||
|
||||
|
||||
def draw(self, context):
|
||||
if self._grabbed_tool:
|
||||
self._grabbed_tool.draw(context)
|
||||
@ -264,6 +251,7 @@ class ItemTool(Tool):
|
||||
self._buttons = buttons
|
||||
self._movable_items = set()
|
||||
|
||||
|
||||
def get_item(self):
|
||||
return self.view.hovered_item
|
||||
|
||||
@ -281,23 +269,24 @@ class ItemTool(Tool):
|
||||
if not set(get_ancestors(item)).intersection(selected_items):
|
||||
yield InMotion(item, view)
|
||||
|
||||
|
||||
def on_button_press(self, event):
|
||||
# TODO: make keys configurable
|
||||
### TODO: make keys configurable
|
||||
view = self.view
|
||||
item = self.get_item()
|
||||
|
||||
if event.get_button()[1] not in self._buttons:
|
||||
if event.button not in self._buttons:
|
||||
return False
|
||||
|
||||
# Deselect all items unless CTRL or SHIFT is pressed
|
||||
# or the item is already selected.
|
||||
if not (event.get_state()[1] & (Gdk.ModifierType.CONTROL_MASK | Gdk.ModifierType.SHIFT_MASK)
|
||||
if not (event.state & (gtk.gdk.CONTROL_MASK | gtk.gdk.SHIFT_MASK)
|
||||
or item in view.selected_items):
|
||||
del view.selected_items
|
||||
|
||||
if item:
|
||||
if view.hovered_item in view.selected_items and \
|
||||
event.get_state()[1] & Gdk.ModifierType.CONTROL_MASK:
|
||||
event.state & gtk.gdk.CONTROL_MASK:
|
||||
selection = Selection(item, view)
|
||||
selection.unselect()
|
||||
else:
|
||||
@ -307,7 +296,7 @@ class ItemTool(Tool):
|
||||
return True
|
||||
|
||||
def on_button_release(self, event):
|
||||
if event.get_button()[1] not in self._buttons:
|
||||
if event.button not in self._buttons:
|
||||
return False
|
||||
for inmotion in self._movable_items:
|
||||
inmotion.stop_move()
|
||||
@ -319,7 +308,7 @@ class ItemTool(Tool):
|
||||
Normally do nothing.
|
||||
If a button is pressed move the items around.
|
||||
"""
|
||||
if event.get_state()[1] & Gdk.EventMask.BUTTON_PRESS_MASK:
|
||||
if event.state & gtk.gdk.BUTTON_PRESS_MASK:
|
||||
|
||||
if not self._movable_items:
|
||||
self._movable_items = set(self.movable_items())
|
||||
@ -358,6 +347,7 @@ class HandleTool(Tool):
|
||||
selection = HandleSelection(item, handle, self.view)
|
||||
selection.select()
|
||||
|
||||
|
||||
def ungrab_handle(self):
|
||||
"""
|
||||
Reset grabbed_handle and grabbed_item.
|
||||
@ -370,6 +360,7 @@ class HandleTool(Tool):
|
||||
selection = HandleSelection(item, handle, self.view)
|
||||
selection.unselect()
|
||||
|
||||
|
||||
def on_button_press(self, event):
|
||||
"""
|
||||
Handle button press events. If the (mouse) button is pressed on
|
||||
@ -383,11 +374,11 @@ class HandleTool(Tool):
|
||||
if handle:
|
||||
# Deselect all items unless CTRL or SHIFT is pressed
|
||||
# or the item is already selected.
|
||||
# TODO: duplicate from ItemTool
|
||||
if not (event.get_state()[1] & (Gdk.ModifierType.CONTROL_MASK | Gdk.ModifierType.SHIFT_MASK)
|
||||
### TODO: duplicate from ItemTool
|
||||
if not (event.state & (gtk.gdk.CONTROL_MASK | gtk.gdk.SHIFT_MASK)
|
||||
or view.hovered_item in view.selected_items):
|
||||
del view.selected_items
|
||||
#
|
||||
###/
|
||||
view.hovered_item = item
|
||||
view.focused_item = item
|
||||
|
||||
@ -421,7 +412,7 @@ class HandleTool(Tool):
|
||||
hovered-item.
|
||||
"""
|
||||
view = self.view
|
||||
if self.grabbed_handle and event.get_state()[1] & Gdk.EventMask.BUTTON_PRESS_MASK:
|
||||
if self.grabbed_handle and event.state & gtk.gdk.BUTTON_PRESS_MASK:
|
||||
canvas = view.canvas
|
||||
item = self.grabbed_item
|
||||
handle = self.grabbed_handle
|
||||
@ -436,6 +427,7 @@ class HandleTool(Tool):
|
||||
|
||||
|
||||
class RubberbandTool(Tool):
|
||||
|
||||
def __init__(self, view=None):
|
||||
super(RubberbandTool, self).__init__(view)
|
||||
self.x0, self.y0, self.x1, self.y1 = 0, 0, 0, 0
|
||||
@ -449,11 +441,11 @@ class RubberbandTool(Tool):
|
||||
self.queue_draw(self.view)
|
||||
x0, y0, x1, y1 = self.x0, self.y0, self.x1, self.y1
|
||||
self.view.select_in_rectangle((min(x0, x1), min(y0, y1),
|
||||
abs(x1 - x0), abs(y1 - y0)))
|
||||
abs(x1 - x0), abs(y1 - y0)))
|
||||
return True
|
||||
|
||||
def on_motion_notify(self, event):
|
||||
if event.get_state()[1] & Gdk.EventMask.BUTTON_PRESS_MASK:
|
||||
if event.state & gtk.gdk.BUTTON_PRESS_MASK:
|
||||
view = self.view
|
||||
self.queue_draw(view)
|
||||
self.x1, self.y1 = event.x, event.y
|
||||
@ -468,15 +460,13 @@ class RubberbandTool(Tool):
|
||||
cr = context.cairo
|
||||
x0, y0, x1, y1 = self.x0, self.y0, self.x1, self.y1
|
||||
cr.set_line_width(1.0)
|
||||
cr.set_source_rgba(.5, .5, .7, .5)
|
||||
cr.set_source_rgba(.5, .5, .7, .6)
|
||||
cr.rectangle(min(x0, x1), min(y0, y1), abs(x1 - x0), abs(y1 - y0))
|
||||
cr.fill()
|
||||
|
||||
|
||||
PAN_MASK = Gdk.ModifierType.SHIFT_MASK | Gdk.ModifierType.MOD1_MASK | Gdk.ModifierType.CONTROL_MASK
|
||||
PAN_MASK = gtk.gdk.SHIFT_MASK | gtk.gdk.MOD1_MASK | gtk.gdk.CONTROL_MASK
|
||||
PAN_VALUE = 0
|
||||
|
||||
|
||||
class PanTool(Tool):
|
||||
"""
|
||||
Captures drag events with the middle mouse button and uses them to
|
||||
@ -490,9 +480,9 @@ class PanTool(Tool):
|
||||
self.speed = 10
|
||||
|
||||
def on_button_press(self, event):
|
||||
if not event.get_state()[1] & PAN_MASK == PAN_VALUE:
|
||||
if not event.state & PAN_MASK == PAN_VALUE:
|
||||
return False
|
||||
if event.get_button()[1] == 2:
|
||||
if event.button == 2:
|
||||
self.x0, self.y0 = event.x, event.y
|
||||
return True
|
||||
|
||||
@ -501,12 +491,12 @@ class PanTool(Tool):
|
||||
return True
|
||||
|
||||
def on_motion_notify(self, event):
|
||||
if event.get_state()[1] & Gdk.EventMask.BUTTON2_MOTION_MASK:
|
||||
if event.state & gtk.gdk.BUTTON2_MASK:
|
||||
view = self.view
|
||||
self.x1, self.y1 = event.x, event.y
|
||||
dx = self.x1 - self.x0
|
||||
dy = self.y1 - self.y0
|
||||
view._matrix.translate(dx / view._matrix[0], dy / view._matrix[3])
|
||||
view._matrix.translate(dx/view._matrix[0], dy/view._matrix[3])
|
||||
# Make sure everything's updated
|
||||
view.request_update((), view._canvas.get_all_items())
|
||||
self.x0 = self.x1
|
||||
@ -515,25 +505,25 @@ class PanTool(Tool):
|
||||
|
||||
def on_scroll(self, event):
|
||||
# Ensure no modifiers
|
||||
if not event.get_state()[1] & PAN_MASK == PAN_VALUE:
|
||||
if not event.state & PAN_MASK == PAN_VALUE:
|
||||
return False
|
||||
view = self.view
|
||||
direction = event.scroll.direction
|
||||
if direction == Gdk.ScrollDirection.LEFT:
|
||||
view._matrix.translate(self.speed / view._matrix[0], 0)
|
||||
elif direction == Gdk.ScrollDirection.RIGHT:
|
||||
view._matrix.translate(-self.speed / view._matrix[0], 0)
|
||||
elif direction == Gdk.ScrollDirection.UP:
|
||||
view._matrix.translate(0, self.speed / view._matrix[3])
|
||||
elif direction == Gdk.ScrollDirection.DOWN:
|
||||
view._matrix.translate(0, -self.speed / view._matrix[3])
|
||||
direction = event.direction
|
||||
gdk = gtk.gdk
|
||||
if direction == gdk.SCROLL_LEFT:
|
||||
view._matrix.translate(self.speed/view._matrix[0], 0)
|
||||
elif direction == gdk.SCROLL_RIGHT:
|
||||
view._matrix.translate(-self.speed/view._matrix[0], 0)
|
||||
elif direction == gdk.SCROLL_UP:
|
||||
view._matrix.translate(0, self.speed/view._matrix[3])
|
||||
elif direction == gdk.SCROLL_DOWN:
|
||||
view._matrix.translate(0, -self.speed/view._matrix[3])
|
||||
view.request_update((), view._canvas.get_all_items())
|
||||
return True
|
||||
|
||||
|
||||
ZOOM_MASK = Gdk.ModifierType.CONTROL_MASK | Gdk.ModifierType.SHIFT_MASK | Gdk.ModifierType.MOD1_MASK
|
||||
ZOOM_VALUE = Gdk.ModifierType.CONTROL_MASK
|
||||
|
||||
ZOOM_MASK = gtk.gdk.CONTROL_MASK | gtk.gdk.SHIFT_MASK | gtk.gdk.MOD1_MASK
|
||||
ZOOM_VALUE = gtk.gdk.CONTROL_MASK
|
||||
|
||||
class ZoomTool(Tool):
|
||||
"""
|
||||
@ -549,8 +539,8 @@ class ZoomTool(Tool):
|
||||
self.lastdiff = 0;
|
||||
|
||||
def on_button_press(self, event):
|
||||
if event.get_button()[1] == 2 \
|
||||
and event.get_state()[1] & ZOOM_MASK == ZOOM_VALUE:
|
||||
if event.button == 2 \
|
||||
and event.state & ZOOM_MASK == ZOOM_VALUE:
|
||||
self.x0 = event.x
|
||||
self.y0 = event.y
|
||||
self.lastdiff = 0
|
||||
@ -561,8 +551,8 @@ class ZoomTool(Tool):
|
||||
return True
|
||||
|
||||
def on_motion_notify(self, event):
|
||||
if event.get_state()[1] & ZOOM_MASK == ZOOM_VALUE \
|
||||
and event.get_state()[1] & Gdk.EventMask.BUTTON2_MOTION_MASK:
|
||||
if event.state & ZOOM_MASK == ZOOM_VALUE \
|
||||
and event.state & gtk.gdk.BUTTON2_MASK:
|
||||
view = self.view
|
||||
dy = event.y - self.y0
|
||||
|
||||
@ -573,7 +563,7 @@ class ZoomTool(Tool):
|
||||
|
||||
if abs(dy - self.lastdiff) > 20:
|
||||
if dy - self.lastdiff < 0:
|
||||
factor = 1. / 0.9
|
||||
factor = 1./0.9
|
||||
else:
|
||||
factor = 0.9
|
||||
|
||||
@ -585,18 +575,18 @@ class ZoomTool(Tool):
|
||||
# Make sure everything's updated
|
||||
view.request_update((), view._canvas.get_all_items())
|
||||
|
||||
self.lastdiff = dy
|
||||
self.lastdiff = dy;
|
||||
return True
|
||||
|
||||
def on_scroll(self, event):
|
||||
if event.get_state()[1] & Gdk.ModifierType.CONTROL_MASK:
|
||||
if event.state & gtk.gdk.CONTROL_MASK:
|
||||
view = self.view
|
||||
sx = view._matrix[0]
|
||||
sy = view._matrix[3]
|
||||
ox = (view._matrix[4] - event.scroll.x) / sx
|
||||
oy = (view._matrix[5] - event.scroll.y) / sy
|
||||
ox = (view._matrix[4] - event.x) / sx
|
||||
oy = (view._matrix[5] - event.y) / sy
|
||||
factor = 0.9
|
||||
if event.scroll.direction == Gdk.ScrollDirection.UP:
|
||||
if event.direction == gtk.gdk.SCROLL_UP:
|
||||
factor = 1. / factor
|
||||
view._matrix.translate(-ox, -oy)
|
||||
view._matrix.scale(factor, factor)
|
||||
@ -607,6 +597,7 @@ class ZoomTool(Tool):
|
||||
|
||||
|
||||
class PlacementTool(Tool):
|
||||
|
||||
def __init__(self, view, factory, handle_tool, handle_index):
|
||||
super(PlacementTool, self).__init__(view)
|
||||
self._factory = factory
|
||||
@ -616,10 +607,12 @@ class PlacementTool(Tool):
|
||||
self._new_item = None
|
||||
self.grabbed_handle = None
|
||||
|
||||
#handle_tool = property(lambda s: s._handle_tool, doc="Handle tool")
|
||||
handle_index = property(lambda s: s._handle_index,
|
||||
doc="Index of handle to be used by handle_tool")
|
||||
new_item = property(lambda s: s._new_item, doc="The newly created item")
|
||||
|
||||
|
||||
def on_button_press(self, event):
|
||||
view = self.view
|
||||
canvas = view.canvas
|
||||
@ -637,6 +630,7 @@ class PlacementTool(Tool):
|
||||
self.grabbed_handle = h
|
||||
return True
|
||||
|
||||
|
||||
def _create_item(self, pos, **kw):
|
||||
view = self.view
|
||||
item = self._factory(**kw)
|
||||
@ -644,6 +638,7 @@ class PlacementTool(Tool):
|
||||
item.matrix.translate(x, y)
|
||||
return item
|
||||
|
||||
|
||||
def on_button_release(self, event):
|
||||
if self.grabbed_handle:
|
||||
self.handle_tool.on_button_release(event)
|
||||
@ -668,40 +663,48 @@ class TextEditTool(Tool):
|
||||
def __init__(self, view=None):
|
||||
super(TextEditTool, self).__init__(view)
|
||||
|
||||
|
||||
def on_double_click(self, event):
|
||||
"""
|
||||
Create a popup window with some editable text.
|
||||
"""
|
||||
window = Gtk.Window()
|
||||
window = gtk.Window()
|
||||
window.set_property('decorated', False)
|
||||
window.set_resize_mode(Gtk.ResizeMode.IMMEDIATE)
|
||||
window.set_parent_window(self.view.get_window())
|
||||
buffer = Gtk.TextBuffer()
|
||||
text_view = Gtk.TextView()
|
||||
window.set_resize_mode(gtk.RESIZE_IMMEDIATE)
|
||||
#window.set_modal(True)
|
||||
window.set_parent_window(self.view.window)
|
||||
buffer = gtk.TextBuffer()
|
||||
text_view = gtk.TextView()
|
||||
text_view.set_buffer(buffer)
|
||||
text_view.show()
|
||||
window.add(text_view)
|
||||
allocate = Gdk.Rectangle()
|
||||
allocate.x = int(event.x)
|
||||
allocate.y = int(event.y)
|
||||
allocate.width = 50
|
||||
allocate.height = 50
|
||||
window.size_allocate(allocate)
|
||||
window.size_allocate(gtk.gdk.Rectangle(int(event.x), int(event.y), 50, 50))
|
||||
#window.move(int(event.x), int(event.y))
|
||||
cursor_pos = self.view.get_toplevel().get_screen().get_display().get_pointer()
|
||||
window.move(cursor_pos[1], cursor_pos[2])
|
||||
window.connect('focus-out-event', self._on_focus_out_event, buffer)
|
||||
text_view.connect('key-press-event', self._on_key_press_event, buffer)
|
||||
window.show_all()
|
||||
#text_view.set_size_request(50, 50)
|
||||
window.show()
|
||||
#text_view.grab_focus()
|
||||
#window.set_uposition(event.x, event.y)
|
||||
#window.focus
|
||||
return True
|
||||
|
||||
def _on_key_press_event(self, widget, event, buffer):
|
||||
if event.keyval == Gdk.KEY_Escape:
|
||||
#if event.keyval == gtk.keysyms.Return:
|
||||
#print 'Enter!'
|
||||
#widget.get_toplevel().destroy()
|
||||
if event.keyval == gtk.keysyms.Escape:
|
||||
#print 'Escape!'
|
||||
widget.get_toplevel().destroy()
|
||||
|
||||
def _on_focus_out_event(self, widget, event, buffer):
|
||||
#print 'focus out!', buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter())
|
||||
widget.destroy()
|
||||
|
||||
|
||||
|
||||
class ConnectHandleTool(HandleTool):
|
||||
"""
|
||||
Tool for connecting two items.
|
||||
@ -723,6 +726,7 @@ class ConnectHandleTool(HandleTool):
|
||||
else:
|
||||
return HandleInMotion(item, handle, self.view).glue(vpos)
|
||||
|
||||
|
||||
def connect(self, item, handle, vpos):
|
||||
"""
|
||||
Connect a handle of a item to connectable item.
|
||||
@ -750,6 +754,7 @@ class ConnectHandleTool(HandleTool):
|
||||
if cinfo:
|
||||
connector.disconnect()
|
||||
|
||||
|
||||
def on_button_release(self, event):
|
||||
view = self.view
|
||||
item = self.grabbed_item
|
||||
@ -774,4 +779,5 @@ def DefaultTool(view=None):
|
||||
append(TextEditTool()). \
|
||||
append(RubberbandTool())
|
||||
|
||||
|
||||
# vim: sw=4:et:ai
|
||||
|
@ -1,44 +1,18 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2006-2017 Arjan Molenaar <gaphor@gmail.com>
|
||||
# Artur Wroblewski <wrobell@pld-linux.org>
|
||||
# Dan Yeaw <dan@yeaw.me>
|
||||
#
|
||||
# This file is part of Gaphas.
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Library General Public License as published by the Free
|
||||
# Software Foundation; either version 2 of the License, or (at your option) any
|
||||
# later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Library General Public License
|
||||
# along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
Simple class containing the tree structure for the canvas items.
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from operator import attrgetter
|
||||
|
||||
from six.moves import map
|
||||
from six.moves import range
|
||||
|
||||
__version__ = "$Revision$"
|
||||
# $HeadURL$
|
||||
|
||||
from operator import attrgetter
|
||||
|
||||
|
||||
class Tree(object):
|
||||
"""
|
||||
A Tree structure. Nodes are stores in a depth-first order.
|
||||
|
||||
|
||||
``None`` is the root node.
|
||||
|
||||
@invariant: len(self._children) == len(self._nodes) + 1
|
||||
@ -50,10 +24,10 @@ class Tree(object):
|
||||
self._nodes = []
|
||||
|
||||
# Per entry a list of children is maintained.
|
||||
self._children = {None: []}
|
||||
self._children = { None: [] }
|
||||
|
||||
# For easy and fast lookups, also maintain a child -> parent mapping
|
||||
self._parents = {}
|
||||
self._parents = { }
|
||||
|
||||
nodes = property(lambda s: list(s._nodes))
|
||||
|
||||
@ -207,12 +181,12 @@ class Tree(object):
|
||||
"""
|
||||
nodes = self.nodes
|
||||
lnodes = len(nodes)
|
||||
list(map(setattr, nodes, [index_key] * lnodes, range(lnodes)))
|
||||
map(setattr, nodes, [index_key] * lnodes, xrange(lnodes))
|
||||
|
||||
def sort(self, nodes, index_key, reverse=False):
|
||||
"""
|
||||
Sort a set (or list) of nodes.
|
||||
|
||||
|
||||
>>> class A(object):
|
||||
... def __init__(self, n):
|
||||
... self.n = n
|
||||
@ -246,7 +220,7 @@ class Tree(object):
|
||||
atnode = siblings[index]
|
||||
except (TypeError, IndexError):
|
||||
index = len(siblings)
|
||||
# self._add_to_nodes(node, parent)
|
||||
#self._add_to_nodes(node, parent)
|
||||
if parent:
|
||||
try:
|
||||
next_uncle = self.get_next_sibling(parent)
|
||||
@ -262,6 +236,7 @@ class Tree(object):
|
||||
else:
|
||||
nodes.insert(nodes.index(atnode), node)
|
||||
|
||||
|
||||
def _add(self, node, parent=None, index=None):
|
||||
"""
|
||||
Helper method for both add() and reparent().
|
||||
@ -282,6 +257,7 @@ class Tree(object):
|
||||
if parent:
|
||||
self._parents[node] = parent
|
||||
|
||||
|
||||
def add(self, node, parent=None, index=None):
|
||||
"""
|
||||
Add node to the tree. parent is the parent node, which may
|
||||
@ -292,6 +268,7 @@ class Tree(object):
|
||||
self._add(node, parent, index)
|
||||
self._children[node] = []
|
||||
|
||||
|
||||
def _remove(self, node):
|
||||
# Remove from parent item
|
||||
self.get_siblings(node).remove(node)
|
||||
@ -346,7 +323,7 @@ class Tree(object):
|
||||
['n1', 'n3', 'n2']
|
||||
|
||||
If a node contains children, those are also moved:
|
||||
|
||||
|
||||
>>> tree.add('n4')
|
||||
>>> tree.nodes
|
||||
['n1', 'n3', 'n2', 'n4']
|
||||
@ -374,4 +351,5 @@ class Tree(object):
|
||||
for c in self._children[node]:
|
||||
self._reparent_nodes(c, node)
|
||||
|
||||
|
||||
# vi: sw=4:et:ai
|
||||
|
@ -1,37 +1,13 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2006-2017 Antony N. Pavlov <antony@niisi.msk.ru>
|
||||
# Arjan Molenaar <gaphor@gmail.com>
|
||||
# Artur Wroblewski <wrobell@pld-linux.org>
|
||||
# Dan Yeaw <dan@yeaw.me>
|
||||
#
|
||||
# This file is part of Gaphas.
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Library General Public License as published by the Free
|
||||
# Software Foundation; either version 2 of the License, or (at your option) any
|
||||
# later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Library General Public License
|
||||
# along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
Helper functions and classes for Cairo (drawing engine used by the canvas).
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
__version__ = "$Revision$"
|
||||
# $HeadURL$
|
||||
|
||||
from math import pi
|
||||
import cairo
|
||||
|
||||
__version__ = "$Revision$"
|
||||
# $HeadURL$
|
||||
|
||||
|
||||
def text_extents(cr, text, font=None, multiline=False, padding=1):
|
||||
"""
|
||||
@ -51,7 +27,7 @@ def text_extents(cr, text, font=None, multiline=False, padding=1):
|
||||
height += h + padding
|
||||
else:
|
||||
x_bear, y_bear, width, height, x_adv, y_adv = cr.text_extents(text)
|
||||
# width, height = width + x_bearing, height + y_bearing
|
||||
#width, height = width + x_bearing, height + y_bearing
|
||||
|
||||
if font:
|
||||
cr.restore()
|
||||
@ -77,13 +53,13 @@ def text_align(cr, x, y, text, align_x=0, align_y=0, padding_x=0, padding_y=0):
|
||||
|
||||
x_bear, y_bear, w, h, x_adv, y_adv = cr.text_extents(text)
|
||||
if align_x == 0:
|
||||
x += 0.5 - (w / 2 + x_bear)
|
||||
x = 0.5 - (w / 2 + x_bear) + x
|
||||
elif align_x < 0:
|
||||
x = - (w + x_bear) + x - padding_x
|
||||
else:
|
||||
x += padding_x
|
||||
x = x + padding_x
|
||||
if align_y == 0:
|
||||
y += 0.5 - (h / 2 + y_bear)
|
||||
y = 0.5 - (h / 2 + y_bear) + y
|
||||
elif align_y < 0:
|
||||
y = - (h + y_bear) + y - padding_y
|
||||
else:
|
||||
@ -101,9 +77,8 @@ def text_multiline(cr, x, y, text, padding=1):
|
||||
text - text to draw
|
||||
padding - additional padding between lines.
|
||||
"""
|
||||
if not text:
|
||||
return
|
||||
# cr.move_to(x, y)
|
||||
if not text: return
|
||||
#cr.move_to(x, y)
|
||||
for line in text.split('\n'):
|
||||
x_bear, y_bear, w, h, x_adv, y_adv = cr.text_extents(text)
|
||||
y += h
|
||||
@ -132,13 +107,14 @@ def text_set_font(cr, font):
|
||||
"""
|
||||
font = font.split()
|
||||
cr.select_font_face(font[0],
|
||||
'italic' in font and cairo.FONT_SLANT_ITALIC or cairo.FONT_SLANT_NORMAL,
|
||||
'bold' in font and cairo.FONT_WEIGHT_BOLD or cairo.FONT_WEIGHT_NORMAL
|
||||
)
|
||||
'italic' in font and cairo.FONT_SLANT_ITALIC \
|
||||
or cairo.FONT_SLANT_NORMAL,
|
||||
'bold' in font and cairo.FONT_WEIGHT_BOLD \
|
||||
or cairo.FONT_WEIGHT_NORMAL)
|
||||
cr.set_font_size(float(font[-1]))
|
||||
|
||||
|
||||
def path_ellipse(cr, x, y, width, height, angle=0):
|
||||
def path_ellipse (cr, x, y, width, height, angle=0):
|
||||
"""
|
||||
Draw an ellipse.
|
||||
x - center x
|
||||
@ -148,11 +124,12 @@ def path_ellipse(cr, x, y, width, height, angle=0):
|
||||
angle - angle in radians to rotate, clockwise
|
||||
"""
|
||||
cr.save()
|
||||
cr.translate(x, y)
|
||||
cr.rotate(angle)
|
||||
cr.scale(width / 2.0, height / 2.0)
|
||||
cr.translate (x, y)
|
||||
cr.rotate (angle)
|
||||
cr.scale (width / 2.0, height / 2.0)
|
||||
cr.move_to(1.0, 0.0)
|
||||
cr.arc(0.0, 0.0, 1.0, 0.0, 2.0 * pi)
|
||||
cr.arc (0.0, 0.0, 1.0, 0.0, 2.0 * pi)
|
||||
cr.restore()
|
||||
|
||||
|
||||
# vim:sw=4:et
|
||||
|
314
gaphas/view.py
314
gaphas/view.py
@ -1,57 +1,32 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2006-2017 Adrian Boguszewski <adrbogus1@student.pg.gda.pl>
|
||||
# Arjan Molenaar <gaphor@gmail.com>
|
||||
# Artur Wroblewski <wrobell@pld-linux.org>
|
||||
# Dan Yeaw <dan@yeaw.me>
|
||||
#
|
||||
# This file is part of Gaphas.
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Library General Public License as published by the Free
|
||||
# Software Foundation; either version 2 of the License, or (at your option) any
|
||||
# later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Library General Public License
|
||||
# along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
This module contains everything to display a Canvas on a screen.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
__version__ = "$Revision$"
|
||||
# $HeadURL$
|
||||
|
||||
import gobject
|
||||
import gtk
|
||||
from cairo import Matrix
|
||||
from six.moves import map
|
||||
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import GObject, Gtk, Gdk
|
||||
|
||||
from gaphas.canvas import Context
|
||||
from gaphas.decorators import async, PRIORITY_HIGH_IDLE
|
||||
from gaphas.decorators import nonrecursive
|
||||
from gaphas.geometry import Rectangle, distance_point_point_fast
|
||||
from gaphas.painter import DefaultPainter, BoundingBoxPainter
|
||||
from gaphas.quadtree import Quadtree
|
||||
from gaphas.tool import DefaultTool
|
||||
from canvas import Context
|
||||
from geometry import Rectangle, distance_point_point_fast
|
||||
from quadtree import Quadtree
|
||||
from tool import DefaultTool
|
||||
from painter import DefaultPainter, BoundingBoxPainter
|
||||
from decorators import async, PRIORITY_HIGH_IDLE
|
||||
from decorators import nonrecursive
|
||||
|
||||
# Handy debug flag for drawing bounding boxes around the items.
|
||||
DEBUG_DRAW_BOUNDING_BOX = False
|
||||
DEBUG_DRAW_QUADTREE = False
|
||||
|
||||
# The default cursor (use in case of a cursor reset)
|
||||
DEFAULT_CURSOR = Gdk.CursorType.LEFT_PTR
|
||||
DEFAULT_CURSOR = gtk.gdk.LEFT_PTR
|
||||
|
||||
|
||||
class View(object):
|
||||
"""
|
||||
View class for gaphas.Canvas objects.
|
||||
View class for gaphas.Canvas objects.
|
||||
"""
|
||||
|
||||
def __init__(self, canvas=None):
|
||||
@ -60,12 +35,12 @@ class View(object):
|
||||
self._bounding_box_painter = BoundingBoxPainter(self)
|
||||
|
||||
# Handling selections.
|
||||
# TODO: Move this to a context?
|
||||
### TODO: Move this to a context?
|
||||
self._selected_items = set()
|
||||
self._focused_item = None
|
||||
self._hovered_item = None
|
||||
self._dropzone_item = None
|
||||
#/
|
||||
###/
|
||||
|
||||
self._qtree = Quadtree()
|
||||
self._bounds = Rectangle(0, 0, 0, 0)
|
||||
@ -74,9 +49,11 @@ class View(object):
|
||||
if canvas:
|
||||
self._set_canvas(canvas)
|
||||
|
||||
|
||||
matrix = property(lambda s: s._matrix,
|
||||
doc="Canvas to view transformation matrix")
|
||||
|
||||
|
||||
def _set_canvas(self, canvas):
|
||||
"""
|
||||
Use view.canvas = my_canvas to set the canvas to be rendered
|
||||
@ -93,18 +70,21 @@ class View(object):
|
||||
|
||||
canvas = property(lambda s: s._canvas, _set_canvas)
|
||||
|
||||
|
||||
def emit(self, *args, **kwargs):
|
||||
"""
|
||||
Placeholder method for signal emission functionality.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
def queue_draw_item(self, *items):
|
||||
"""
|
||||
Placeholder for item redraw queueing.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
def select_item(self, item):
|
||||
"""
|
||||
Select an item. This adds @item to the set of selected items.
|
||||
@ -114,6 +94,7 @@ class View(object):
|
||||
self._selected_items.add(item)
|
||||
self.emit('selection-changed', self._selected_items)
|
||||
|
||||
|
||||
def unselect_item(self, item):
|
||||
"""
|
||||
Unselect an item.
|
||||
@ -123,10 +104,12 @@ class View(object):
|
||||
self._selected_items.discard(item)
|
||||
self.emit('selection-changed', self._selected_items)
|
||||
|
||||
|
||||
def select_all(self):
|
||||
for item in self.canvas.get_all_items():
|
||||
self.select_item(item)
|
||||
|
||||
|
||||
def unselect_all(self):
|
||||
"""
|
||||
Clearing the selected_item also clears the focused_item.
|
||||
@ -136,10 +119,12 @@ class View(object):
|
||||
self.focused_item = None
|
||||
self.emit('selection-changed', self._selected_items)
|
||||
|
||||
|
||||
selected_items = property(lambda s: s._selected_items,
|
||||
select_item, unselect_all,
|
||||
"Items selected by the view")
|
||||
|
||||
|
||||
def _set_focused_item(self, item):
|
||||
"""
|
||||
Set the focused item, this item is also added to the selected_items
|
||||
@ -154,16 +139,19 @@ class View(object):
|
||||
self._focused_item = item
|
||||
self.emit('focus-changed', item)
|
||||
|
||||
|
||||
def _del_focused_item(self):
|
||||
"""
|
||||
Items that loose focus remain selected.
|
||||
"""
|
||||
self._set_focused_item(None)
|
||||
|
||||
|
||||
focused_item = property(lambda s: s._focused_item,
|
||||
_set_focused_item, _del_focused_item,
|
||||
"The item with focus (receives key events a.o.)")
|
||||
|
||||
|
||||
def _set_hovered_item(self, item):
|
||||
"""
|
||||
Set the hovered item.
|
||||
@ -173,16 +161,19 @@ class View(object):
|
||||
self._hovered_item = item
|
||||
self.emit('hover-changed', item)
|
||||
|
||||
|
||||
def _del_hovered_item(self):
|
||||
"""
|
||||
Unset the hovered item.
|
||||
"""
|
||||
self._set_hovered_item(None)
|
||||
|
||||
|
||||
hovered_item = property(lambda s: s._hovered_item,
|
||||
_set_hovered_item, _del_hovered_item,
|
||||
"The item directly under the mouse pointer")
|
||||
|
||||
|
||||
def _set_dropzone_item(self, item):
|
||||
"""
|
||||
Set dropzone item.
|
||||
@ -192,15 +183,18 @@ class View(object):
|
||||
self._dropzone_item = item
|
||||
self.emit('dropzone-changed', item)
|
||||
|
||||
|
||||
def _del_dropzone_item(self):
|
||||
"""
|
||||
Unset dropzone item.
|
||||
"""
|
||||
self._set_dropzone_item(None)
|
||||
|
||||
|
||||
dropzone_item = property(lambda s: s._dropzone_item,
|
||||
_set_dropzone_item, _del_dropzone_item,
|
||||
'The item which can group other items')
|
||||
_set_dropzone_item, _del_dropzone_item,
|
||||
'The item which can group other items')
|
||||
|
||||
|
||||
def _set_painter(self, painter):
|
||||
"""
|
||||
@ -210,8 +204,10 @@ class View(object):
|
||||
painter.set_view(self)
|
||||
self.emit('painter-changed')
|
||||
|
||||
|
||||
painter = property(lambda s: s._painter, _set_painter)
|
||||
|
||||
|
||||
def _set_bounding_box_painter(self, painter):
|
||||
"""
|
||||
Set the painter to use for bounding box calculations.
|
||||
@ -220,8 +216,10 @@ class View(object):
|
||||
painter.set_view(self)
|
||||
self.emit('painter-changed')
|
||||
|
||||
|
||||
bounding_box_painter = property(lambda s: s._bounding_box_painter, _set_bounding_box_painter)
|
||||
|
||||
|
||||
def get_item_at_point(self, pos, selected=True):
|
||||
"""
|
||||
Return the topmost item located at ``pos`` (x, y).
|
||||
@ -240,12 +238,12 @@ class View(object):
|
||||
return item
|
||||
return None
|
||||
|
||||
|
||||
def get_handle_at_point(self, pos, distance=6):
|
||||
"""
|
||||
Look for a handle at ``pos`` and return the
|
||||
tuple (item, handle).
|
||||
"""
|
||||
|
||||
def find(item):
|
||||
""" Find item's handle at pos """
|
||||
v2i = self.get_matrix_v2i(item)
|
||||
@ -282,6 +280,7 @@ class View(object):
|
||||
return item, h
|
||||
return None, None
|
||||
|
||||
|
||||
def get_port_at_point(self, vpos, distance=10, exclude=None):
|
||||
"""
|
||||
Find item with port closest to specified position.
|
||||
@ -336,6 +335,7 @@ class View(object):
|
||||
|
||||
return item, port, glue_pos
|
||||
|
||||
|
||||
def get_items_in_rectangle(self, rect, intersect=True, reverse=False):
|
||||
"""
|
||||
Return the items in the rectangle 'rect'.
|
||||
@ -347,13 +347,15 @@ class View(object):
|
||||
items = self._qtree.find_inside(rect)
|
||||
return self._canvas.sort(items, reverse=reverse)
|
||||
|
||||
|
||||
def select_in_rectangle(self, rect):
|
||||
"""
|
||||
Select all items who have their bounding box within the
|
||||
rectangle @rect.
|
||||
"""
|
||||
items = self._qtree.find_inside(rect)
|
||||
list(map(self.select_item, items))
|
||||
map(self.select_item, items)
|
||||
|
||||
|
||||
def zoom(self, factor):
|
||||
"""
|
||||
@ -363,9 +365,10 @@ class View(object):
|
||||
self._matrix.scale(factor, factor)
|
||||
|
||||
# Make sure everything's updated
|
||||
# map(self.update_matrix, self._canvas.get_all_items())
|
||||
#map(self.update_matrix, self._canvas.get_all_items())
|
||||
self.request_update((), self._canvas.get_all_items())
|
||||
|
||||
|
||||
def set_item_bounding_box(self, item, bounds):
|
||||
"""
|
||||
Update the bounding box of the item.
|
||||
@ -380,17 +383,20 @@ class View(object):
|
||||
ix1, iy1 = v2i(bounds.x1, bounds.y1)
|
||||
self._qtree.add(item=item, bounds=bounds, data=(ix0, iy0, ix1, iy1))
|
||||
|
||||
|
||||
def get_item_bounding_box(self, item):
|
||||
"""
|
||||
Get the bounding box for the item, in view coordinates.
|
||||
"""
|
||||
return self._qtree.get_bounds(item)
|
||||
|
||||
|
||||
bounding_box = property(lambda s: s._bounds)
|
||||
|
||||
|
||||
def update_bounding_box(self, cr, items=None):
|
||||
"""
|
||||
Update the bounding boxes of the canvas items for this view, in
|
||||
Update the bounding boxes of the canvas items for this view, in
|
||||
canvas coordinates.
|
||||
"""
|
||||
painter = self._bounding_box_painter
|
||||
@ -405,11 +411,13 @@ class View(object):
|
||||
# Update the view's bounding box with the rest of the items
|
||||
self._bounds = Rectangle(*self._qtree.soft_bounds)
|
||||
|
||||
|
||||
def paint(self, cr):
|
||||
self._painter.paint(Context(cairo=cr,
|
||||
items=self.canvas.get_all_items(),
|
||||
area=None))
|
||||
|
||||
|
||||
def get_matrix_i2v(self, item):
|
||||
"""
|
||||
Get Item to View matrix for ``item``.
|
||||
@ -418,6 +426,7 @@ class View(object):
|
||||
self.update_matrix(item)
|
||||
return item._matrix_i2v[self]
|
||||
|
||||
|
||||
def get_matrix_v2i(self, item):
|
||||
"""
|
||||
Get View to Item matrix for ``item``.
|
||||
@ -426,6 +435,7 @@ class View(object):
|
||||
self.update_matrix(item)
|
||||
return item._matrix_v2i[self]
|
||||
|
||||
|
||||
def update_matrix(self, item):
|
||||
"""
|
||||
Update item matrices related to view.
|
||||
@ -443,6 +453,7 @@ class View(object):
|
||||
v2i.invert()
|
||||
item._matrix_v2i[self] = v2i
|
||||
|
||||
|
||||
def _clear_matrices(self):
|
||||
"""
|
||||
Clear registered data in Item's _matrix{i2c|v2i} attributes.
|
||||
@ -455,7 +466,9 @@ class View(object):
|
||||
pass
|
||||
|
||||
|
||||
class GtkView(Gtk.DrawingArea, Gtk.Scrollable, View):
|
||||
|
||||
|
||||
class GtkView(gtk.DrawingArea, View):
|
||||
# NOTE: Inherit from GTK+ class first, otherwise BusErrors may occur!
|
||||
"""
|
||||
GTK+ widget for rendering a canvas.Canvas to a screen.
|
||||
@ -473,92 +486,58 @@ class GtkView(Gtk.DrawingArea, Gtk.Scrollable, View):
|
||||
|
||||
# Signals: emited after the change takes effect.
|
||||
__gsignals__ = {
|
||||
'dropzone-changed': (GObject.SignalFlags.RUN_LAST, None,
|
||||
(GObject.TYPE_PYOBJECT,)),
|
||||
'hover-changed': (GObject.SignalFlags.RUN_LAST, None,
|
||||
(GObject.TYPE_PYOBJECT,)),
|
||||
'focus-changed': (GObject.SignalFlags.RUN_LAST, None,
|
||||
(GObject.TYPE_PYOBJECT,)),
|
||||
'selection-changed': (GObject.SignalFlags.RUN_LAST, None,
|
||||
(GObject.TYPE_PYOBJECT,)),
|
||||
'tool-changed': (GObject.SignalFlags.RUN_LAST, None,
|
||||
()),
|
||||
'painter-changed': (GObject.SignalFlags.RUN_LAST, None,
|
||||
())
|
||||
'set-scroll-adjustments': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
|
||||
(gtk.Adjustment, gtk.Adjustment)),
|
||||
'dropzone-changed': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
|
||||
(gobject.TYPE_PYOBJECT,)),
|
||||
'hover-changed': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
|
||||
(gobject.TYPE_PYOBJECT,)),
|
||||
'focus-changed': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
|
||||
(gobject.TYPE_PYOBJECT,)),
|
||||
'selection-changed': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
|
||||
(gobject.TYPE_PYOBJECT,)),
|
||||
'tool-changed': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
|
||||
()),
|
||||
'painter-changed': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
|
||||
())
|
||||
}
|
||||
|
||||
__gproperties__ = {
|
||||
"hscroll-policy": (Gtk.ScrollablePolicy, "hscroll-policy",
|
||||
"hscroll-policy", Gtk.ScrollablePolicy.MINIMUM,
|
||||
GObject.PARAM_READWRITE),
|
||||
"hadjustment": (Gtk.Adjustment, "hadjustment", "hadjustment",
|
||||
GObject.PARAM_READWRITE),
|
||||
"vscroll-policy": (Gtk.ScrollablePolicy, "hscroll-policy",
|
||||
"hscroll-policy", Gtk.ScrollablePolicy.MINIMUM,
|
||||
GObject.PARAM_READWRITE),
|
||||
"vadjustment": (Gtk.Adjustment, "hadjustment", "hadjustment",
|
||||
GObject.PARAM_READWRITE),
|
||||
}
|
||||
|
||||
def __init__(self, canvas=None, hadjustment=None, vadjustment=None):
|
||||
GObject.GObject.__init__(self)
|
||||
gtk.DrawingArea.__init__(self)
|
||||
|
||||
self._dirty_items = set()
|
||||
self._dirty_matrix_items = set()
|
||||
|
||||
View.__init__(self, canvas)
|
||||
|
||||
self.set_can_focus(True)
|
||||
self.add_events(Gdk.EventMask.BUTTON_PRESS_MASK
|
||||
| Gdk.EventMask.BUTTON_RELEASE_MASK
|
||||
| Gdk.EventMask.POINTER_MOTION_MASK
|
||||
| Gdk.EventMask.KEY_PRESS_MASK
|
||||
| Gdk.EventMask.KEY_RELEASE_MASK
|
||||
| Gdk.EventMask.SCROLL_MASK)
|
||||
self.set_flags(gtk.CAN_FOCUS)
|
||||
self.add_events(gtk.gdk.BUTTON_PRESS_MASK
|
||||
| gtk.gdk.BUTTON_RELEASE_MASK
|
||||
| gtk.gdk.POINTER_MOTION_MASK
|
||||
| gtk.gdk.KEY_PRESS_MASK
|
||||
| gtk.gdk.KEY_RELEASE_MASK
|
||||
| gtk.gdk.SCROLL_MASK)
|
||||
|
||||
self._hscroll_policy = None
|
||||
self._vscroll_policy = None
|
||||
self._hadjustment = None
|
||||
self._vadjustment = None
|
||||
self._hadjustment_handler_id = None
|
||||
self._vadjustment_handler_id = None
|
||||
|
||||
self._set_scroll_adjustments(None, None)
|
||||
self.emit('set-scroll-adjustments', hadjustment, vadjustment)
|
||||
|
||||
self._set_tool(DefaultTool())
|
||||
|
||||
# Set background to white.
|
||||
self.modify_bg(Gtk.StateType.NORMAL, Gdk.color_parse('#FFF'))
|
||||
self.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse('#FFF'))
|
||||
|
||||
def do_set_property(self, pspec, value):
|
||||
if pspec.name == 'hscroll-policy':
|
||||
self._hscroll_policy = value
|
||||
elif pspec.name == 'vscroll-policy':
|
||||
self._vscroll_policy = value
|
||||
elif pspec.name == 'hadjustment':
|
||||
self._set_scroll_adjustments(value, self._vadjustment)
|
||||
elif pspec.name == 'vadjustment':
|
||||
self._set_scroll_adjustments(self._hadjustment, value)
|
||||
else:
|
||||
raise AttributeError('Unknown property %s' % pspec.name)
|
||||
|
||||
def do_get_property(self, pspec):
|
||||
if pspec.name == 'hscroll-policy':
|
||||
return self._hscroll_policy
|
||||
elif pspec.name == 'vscroll-policy':
|
||||
return self._vscroll_policy
|
||||
elif pspec.name == 'hadjustment':
|
||||
return self._hadjustment
|
||||
elif pspec.name == 'vadjustment':
|
||||
return self._vadjustment
|
||||
else:
|
||||
raise AttributeError('Unknown property %s' % pspec.name)
|
||||
|
||||
def emit(self, *args, **kwargs):
|
||||
"""
|
||||
Delegate signal emissions to the DrawingArea (=GTK+)
|
||||
"""
|
||||
Gtk.DrawingArea.emit(self, *args, **kwargs)
|
||||
gtk.DrawingArea.emit(self, *args, **kwargs)
|
||||
|
||||
|
||||
def _set_canvas(self, canvas):
|
||||
"""
|
||||
@ -580,6 +559,7 @@ class GtkView(Gtk.DrawingArea, Gtk.Scrollable, View):
|
||||
|
||||
canvas = property(lambda s: s._canvas, _set_canvas)
|
||||
|
||||
|
||||
def _set_tool(self, tool):
|
||||
"""
|
||||
Set the tool to use. Tools should implement tool.Tool.
|
||||
@ -588,13 +568,17 @@ class GtkView(Gtk.DrawingArea, Gtk.Scrollable, View):
|
||||
tool.set_view(self)
|
||||
self.emit('tool-changed')
|
||||
|
||||
|
||||
tool = property(lambda s: s._tool, _set_tool)
|
||||
|
||||
|
||||
hadjustment = property(lambda s: s._hadjustment)
|
||||
|
||||
|
||||
vadjustment = property(lambda s: s._vadjustment)
|
||||
|
||||
def _set_scroll_adjustments(self, hadjustment, vadjustment):
|
||||
|
||||
def do_set_scroll_adjustments(self, hadjustment, vadjustment):
|
||||
if self._hadjustment_handler_id:
|
||||
self._hadjustment.disconnect(self._hadjustment_handler_id)
|
||||
self._hadjustment_handler_id = None
|
||||
@ -602,17 +586,18 @@ class GtkView(Gtk.DrawingArea, Gtk.Scrollable, View):
|
||||
self._vadjustment.disconnect(self._vadjustment_handler_id)
|
||||
self._vadjustment_handler_id = None
|
||||
|
||||
self._hadjustment = hadjustment or Gtk.Adjustment()
|
||||
self._vadjustment = vadjustment or Gtk.Adjustment()
|
||||
self._hadjustment = hadjustment or gtk.Adjustment()
|
||||
self._vadjustment = vadjustment or gtk.Adjustment()
|
||||
|
||||
self._hadjustment_handler_id = \
|
||||
self._hadjustment.connect('value-changed',
|
||||
self.on_adjustment_changed)
|
||||
self._hadjustment.connect('value-changed',
|
||||
self.on_adjustment_changed)
|
||||
self._vadjustment_handler_id = \
|
||||
self._vadjustment.connect('value-changed',
|
||||
self.on_adjustment_changed)
|
||||
self._vadjustment.connect('value-changed',
|
||||
self.on_adjustment_changed)
|
||||
self.update_adjustments()
|
||||
|
||||
|
||||
def zoom(self, factor):
|
||||
"""
|
||||
Zoom in/out by factor ``factor``.
|
||||
@ -620,10 +605,11 @@ class GtkView(Gtk.DrawingArea, Gtk.Scrollable, View):
|
||||
super(GtkView, self).zoom(factor)
|
||||
self.queue_draw_refresh()
|
||||
|
||||
|
||||
@async(single=True)
|
||||
def update_adjustments(self, allocation=None):
|
||||
if not allocation:
|
||||
allocation = self.get_allocation()
|
||||
allocation = self.allocation
|
||||
|
||||
hadjustment = self._hadjustment
|
||||
vadjustment = self._vadjustment
|
||||
@ -641,28 +627,26 @@ class GtkView(Gtk.DrawingArea, Gtk.Scrollable, View):
|
||||
u = c + v
|
||||
|
||||
# set lower limits
|
||||
hadjustment.set_lower(u.x)
|
||||
vadjustment.set_lower(u.y)
|
||||
hadjustment.lower, vadjustment.lower = u.x, u.y
|
||||
|
||||
# set upper limits
|
||||
hadjustment.set_upper(u.x1)
|
||||
vadjustment.set_upper(u.y1)
|
||||
hadjustment.upper, vadjustment.upper = u.x1, u.y1
|
||||
|
||||
# set page size
|
||||
aw, ah = allocation.width, allocation.height
|
||||
hadjustment.set_page_size(aw)
|
||||
vadjustment.set_page_size(ah)
|
||||
hadjustment.page_size = aw
|
||||
vadjustment.page_size = ah
|
||||
|
||||
# set increments
|
||||
hadjustment.set_page_increment(aw)
|
||||
hadjustment.set_step_increment(aw / 10)
|
||||
vadjustment.set_page_increment(ah)
|
||||
vadjustment.set_step_increment(ah / 10)
|
||||
hadjustment.page_increment = aw
|
||||
hadjustment.step_increment = aw / 10
|
||||
vadjustment.page_increment = ah
|
||||
vadjustment.step_increment = ah / 10
|
||||
|
||||
# set position
|
||||
if v.x != hadjustment.get_value() or v.y != vadjustment.get_value():
|
||||
hadjustment.set_value(v.x)
|
||||
vadjustment.set_value(v.y)
|
||||
if v.x != hadjustment.value or v.y != vadjustment.value:
|
||||
hadjustment.value, vadjustment.value = v.x, v.y
|
||||
|
||||
|
||||
def queue_draw_item(self, *items):
|
||||
"""
|
||||
@ -673,7 +657,7 @@ class GtkView(Gtk.DrawingArea, Gtk.Scrollable, View):
|
||||
TODO: Should we also create a (sorted) list of items that need redrawal?
|
||||
"""
|
||||
get_bounds = self._qtree.get_bounds
|
||||
items = [_f for _f in items if _f]
|
||||
items = filter(None, items)
|
||||
try:
|
||||
# create a copy, otherwise we'll change the original rectangle
|
||||
bounds = Rectangle(*get_bounds(items[0]))
|
||||
@ -683,26 +667,29 @@ class GtkView(Gtk.DrawingArea, Gtk.Scrollable, View):
|
||||
except IndexError:
|
||||
pass
|
||||
except KeyError:
|
||||
pass # No bounds calculated yet? bummer.
|
||||
pass # No bounds calculated yet? bummer.
|
||||
|
||||
|
||||
def queue_draw_area(self, x, y, w, h):
|
||||
"""
|
||||
Wrap draw_area to convert all values to ints.
|
||||
"""
|
||||
try:
|
||||
super(GtkView, self).queue_draw_area(int(x), int(y), int(w + 1), int(h + 1))
|
||||
super(GtkView, self).queue_draw_area(int(x), int(y), int(w+1), int(h+1))
|
||||
except OverflowError:
|
||||
# Okay, now the zoom factor is very large or something
|
||||
a = self.get_allocation()
|
||||
a = self.allocation
|
||||
super(GtkView, self).queue_draw_area(0, 0, a.width, a.height)
|
||||
|
||||
|
||||
def queue_draw_refresh(self):
|
||||
"""
|
||||
Redraw the entire view.
|
||||
"""
|
||||
a = self.get_allocation()
|
||||
a = self.allocation
|
||||
super(GtkView, self).queue_draw_area(0, 0, a.width, a.height)
|
||||
|
||||
|
||||
def request_update(self, items, matrix_only_items=(), removed_items=()):
|
||||
"""
|
||||
Request update for items. Items will get a full update treatment, while
|
||||
@ -731,13 +718,13 @@ class GtkView(Gtk.DrawingArea, Gtk.Scrollable, View):
|
||||
|
||||
self.update()
|
||||
|
||||
|
||||
@async(single=True, priority=PRIORITY_HIGH_IDLE)
|
||||
def update(self):
|
||||
"""
|
||||
Update view status according to the items updated by the canvas.
|
||||
"""
|
||||
if not self.get_window():
|
||||
return
|
||||
if not self.window: return
|
||||
|
||||
dirty_items = self._dirty_items
|
||||
dirty_matrix_items = self._dirty_matrix_items
|
||||
@ -773,12 +760,13 @@ class GtkView(Gtk.DrawingArea, Gtk.Scrollable, View):
|
||||
self._dirty_items.clear()
|
||||
self._dirty_matrix_items.clear()
|
||||
|
||||
|
||||
@async(single=False)
|
||||
def update_bounding_box(self, items):
|
||||
"""
|
||||
Update bounding box is not necessary.
|
||||
"""
|
||||
cr = self.get_window().cairo_create()
|
||||
cr = self.window.cairo_create()
|
||||
|
||||
cr.save()
|
||||
cr.rectangle(0, 0, 0, 0)
|
||||
@ -790,19 +778,19 @@ class GtkView(Gtk.DrawingArea, Gtk.Scrollable, View):
|
||||
self.queue_draw_item(*items)
|
||||
self.update_adjustments()
|
||||
|
||||
|
||||
@nonrecursive
|
||||
def do_configure_event(self, event):
|
||||
def do_size_allocate(self, allocation):
|
||||
"""
|
||||
Widget size has changed.
|
||||
Allocate the widget size ``(x, y, width, height)``.
|
||||
"""
|
||||
self.update_adjustments(event)
|
||||
self._qtree.resize((0, 0, event.width, event.height))
|
||||
gtk.DrawingArea.do_size_allocate(self, allocation)
|
||||
self.update_adjustments(allocation)
|
||||
self._qtree.resize((0, 0, allocation.width, allocation.height))
|
||||
|
||||
|
||||
def do_realize(self):
|
||||
"""
|
||||
The ::realize signal is emitted when widget is associated with a ``GdkWindow``.
|
||||
"""
|
||||
Gtk.DrawingArea.do_realize(self)
|
||||
gtk.DrawingArea.do_realize(self)
|
||||
|
||||
# Ensure updates are propagated
|
||||
self._canvas.register_view(self)
|
||||
@ -811,9 +799,6 @@ class GtkView(Gtk.DrawingArea, Gtk.Scrollable, View):
|
||||
self.request_update(self._canvas.get_all_items())
|
||||
|
||||
def do_unrealize(self):
|
||||
"""
|
||||
The ::unrealize signal is emitted when the ``GdkWindow`` associated with widget is destroyed.
|
||||
"""
|
||||
if self.canvas:
|
||||
# Although Item._matrix_{i2v|v2i} keys are automatically removed
|
||||
# (weak refs), better do it explicitly to be sure.
|
||||
@ -825,17 +810,23 @@ class GtkView(Gtk.DrawingArea, Gtk.Scrollable, View):
|
||||
|
||||
self._canvas.unregister_view(self)
|
||||
|
||||
Gtk.DrawingArea.do_unrealize(self)
|
||||
gtk.DrawingArea.do_unrealize(self)
|
||||
|
||||
def do_draw(self, cr):
|
||||
def do_expose_event(self, event):
|
||||
"""
|
||||
Render canvas to the screen.
|
||||
"""
|
||||
if not self._canvas:
|
||||
return
|
||||
|
||||
_, allocation = Gdk.cairo_get_clip_rectangle(cr)
|
||||
x, y, w, h = allocation.x, allocation.y, allocation.width, allocation.height
|
||||
area = event.area
|
||||
x, y, w, h = area.x, area.y, area.width, area.height
|
||||
cr = self.window.cairo_create()
|
||||
|
||||
# Draw no more than nessesary.
|
||||
cr.rectangle(x, y, w, h)
|
||||
cr.clip()
|
||||
|
||||
area = Rectangle(x, y, width=w, height=h)
|
||||
self._painter.paint(Context(cairo=cr,
|
||||
items=self.get_items_in_rectangle(area),
|
||||
@ -858,13 +849,13 @@ class GtkView(Gtk.DrawingArea, Gtk.Scrollable, View):
|
||||
cr.stroke()
|
||||
for b in bucket._buckets:
|
||||
draw_qtree_bucket(b)
|
||||
|
||||
cr.set_source_rgb(0, 0, .8)
|
||||
cr.set_line_width(1.0)
|
||||
draw_qtree_bucket(self._qtree._bucket)
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def do_event(self, event):
|
||||
"""
|
||||
Handle GDK events. Events are delegated to a `tool.Tool`.
|
||||
@ -873,14 +864,13 @@ class GtkView(Gtk.DrawingArea, Gtk.Scrollable, View):
|
||||
return self._tool.handle(event) and True or False
|
||||
return False
|
||||
|
||||
|
||||
def on_adjustment_changed(self, adj):
|
||||
"""
|
||||
Change the transformation matrix of the view to reflect the
|
||||
value of the x/y adjustment (scrollbar).
|
||||
"""
|
||||
value = adj.get_value()
|
||||
if value == 0.0:
|
||||
return
|
||||
if adj.value == 0.0: return
|
||||
|
||||
# Can not use self._matrix.translate( - adj.value , 0) here, since
|
||||
# the translate method effectively does a m * self._matrix, which
|
||||
@ -888,9 +878,9 @@ class GtkView(Gtk.DrawingArea, Gtk.Scrollable, View):
|
||||
|
||||
m = Matrix()
|
||||
if adj is self._hadjustment:
|
||||
m.translate(- value, 0)
|
||||
m.translate( - adj.value, 0)
|
||||
elif adj is self._vadjustment:
|
||||
m.translate(0, - value)
|
||||
m.translate(0, - adj.value)
|
||||
self._matrix *= m
|
||||
|
||||
# Force recalculation of the bounding boxes:
|
||||
@ -898,4 +888,14 @@ class GtkView(Gtk.DrawingArea, Gtk.Scrollable, View):
|
||||
|
||||
self.queue_draw_refresh()
|
||||
|
||||
|
||||
# Set a signal to set adjustments. This way a ScrolledWindow can set its own
|
||||
# Adjustment objects on the View. Otherwise a warning is shown:
|
||||
#
|
||||
# GtkWarning: gtk_scrolled_window_add(): cannot add non scrollable widget
|
||||
# use gtk_scrolled_window_add_with_viewport() instead
|
||||
|
||||
GtkView.set_set_scroll_adjustments_signal("set-scroll-adjustments")
|
||||
|
||||
|
||||
# vim: sw=4:et:ai
|
||||
|
@ -6,22 +6,17 @@ this file is licensed under the Python Software Foundation License, version 2.
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from _weakref import ref
|
||||
|
||||
__all__ = ['WeakSet']
|
||||
|
||||
|
||||
class WeakSet:
|
||||
def __init__(self, data=None):
|
||||
self.data = set()
|
||||
|
||||
def _remove(item, selfref=ref(self)):
|
||||
self = selfref()
|
||||
if self is not None:
|
||||
self.data.discard(item)
|
||||
|
||||
self._remove = _remove
|
||||
if data is not None:
|
||||
self.update(data)
|
||||
@ -37,15 +32,15 @@ class WeakSet:
|
||||
|
||||
def __contains__(self, item):
|
||||
"""
|
||||
>>> class C(object): pass
|
||||
>>> a = C()
|
||||
>>> b = C()
|
||||
>>> ws = WeakSet((a, b))
|
||||
>>> a in ws
|
||||
True
|
||||
>>> a = C()
|
||||
>>> a in ws
|
||||
False
|
||||
>>> class C(object): pass
|
||||
>>> a = C()
|
||||
>>> b = C()
|
||||
>>> ws = WeakSet((a, b))
|
||||
>>> a in ws
|
||||
True
|
||||
>>> a = C()
|
||||
>>> a in ws
|
||||
False
|
||||
"""
|
||||
return ref(item) in self.data
|
||||
|
||||
@ -106,7 +101,6 @@ class WeakSet:
|
||||
else:
|
||||
for element in other:
|
||||
self.add(element)
|
||||
|
||||
def __ior__(self, other):
|
||||
self.update(other)
|
||||
return self
|
||||
@ -122,7 +116,6 @@ class WeakSet:
|
||||
|
||||
def difference(self, other):
|
||||
return self._apply(other, self.data.difference)
|
||||
|
||||
__sub__ = difference
|
||||
|
||||
def difference_update(self, other):
|
||||
@ -140,19 +133,16 @@ class WeakSet:
|
||||
|
||||
def intersection(self, other):
|
||||
return self._apply(other, self.data.intersection)
|
||||
|
||||
__and__ = intersection
|
||||
|
||||
def intersection_update(self, other):
|
||||
self.data.intersection_update(ref(item) for item in other)
|
||||
|
||||
def __iand__(self, other):
|
||||
self.data.intersection_update(ref(item) for item in other)
|
||||
return self
|
||||
|
||||
def issubset(self, other):
|
||||
return self.data.issubset(ref(item) for item in other)
|
||||
|
||||
__lt__ = issubset
|
||||
|
||||
def __le__(self, other):
|
||||
@ -160,7 +150,6 @@ class WeakSet:
|
||||
|
||||
def issuperset(self, other):
|
||||
return self.data.issuperset(ref(item) for item in other)
|
||||
|
||||
__gt__ = issuperset
|
||||
|
||||
def __ge__(self, other):
|
||||
|
@ -3,7 +3,11 @@
|
||||
#tag_svn_revision = 1
|
||||
|
||||
[nosetests]
|
||||
with-doctest=1
|
||||
doctest-extension=.txt
|
||||
tests=gaphas,doc
|
||||
verbosity=2
|
||||
detailed-errors=1
|
||||
with-coverage=1
|
||||
cover-package=gaphas
|
||||
|
||||
|
75
setup.py
75
setup.py
@ -1,23 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2006-2017 Arjan Molenaar <gaphor@gmail.com>
|
||||
# Dan Yeaw <dan@yeaw.me>
|
||||
#
|
||||
# This file is part of Gaphas.
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Library General Public License as published by the Free
|
||||
# Software Foundation; either version 2 of the License, or (at your option) any
|
||||
# later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Library General Public License
|
||||
# along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
"""\
|
||||
Gaphas is a MVC canvas that uses Cairo_ for rendering. One of the nicer things
|
||||
of this widget is that the user (model) is not bothered with bounding box
|
||||
calculations: this is all done through Cairo.
|
||||
@ -34,22 +15,21 @@ Some more features:
|
||||
- User interaction is handled by Tools. Tools can be chained.
|
||||
- Versatile undo/redo system
|
||||
|
||||
GTK+ and PyGObject3_ are required.
|
||||
GTK+ and PyGTK_ are required.
|
||||
|
||||
.. _Cairo: http://cairographics.org/
|
||||
.. _PyGObject3: http://live.gnome.org/PyGObject
|
||||
.. _PyGTK: http://www.pygtk.org/
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from setuptools import setup, find_packages
|
||||
VERSION = '0.7.2'
|
||||
|
||||
from ez_setup import use_setuptools
|
||||
|
||||
VERSION = '0.8.0'
|
||||
|
||||
use_setuptools()
|
||||
|
||||
from setuptools import setup, find_packages
|
||||
from distutils.cmd import Command
|
||||
|
||||
setup(
|
||||
name='gaphas',
|
||||
version=VERSION,
|
||||
@ -57,47 +37,48 @@ setup(
|
||||
long_description=__doc__,
|
||||
|
||||
classifiers=[
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'Environment :: X11 Applications :: GTK',
|
||||
'Intended Audience :: Developers',
|
||||
'License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)',
|
||||
'Programming Language :: Python',
|
||||
'Topic :: Software Development :: Libraries :: Python Modules',
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'Environment :: X11 Applications :: GTK',
|
||||
'Intended Audience :: Developers',
|
||||
'License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)',
|
||||
'Programming Language :: Python',
|
||||
'Topic :: Software Development :: Libraries :: Python Modules',
|
||||
],
|
||||
|
||||
keywords='',
|
||||
|
||||
author="Arjan J. Molenaar",
|
||||
author_email='gaphor@gmail.com',
|
||||
author_email='arjanmol@users.sourceforge.net',
|
||||
|
||||
url='http://github.com/amolenaar/gaphas',
|
||||
url='http://gaphor.sourceforge.net',
|
||||
|
||||
# download_url='http://cheeseshop.python.org/',
|
||||
#download_url='http://cheeseshop.python.org/',
|
||||
|
||||
license='GNU Library General Public License (LGPL, see COPYING)',
|
||||
|
||||
packages=find_packages(exclude=['ez_setup']),
|
||||
|
||||
setup_requires=[
|
||||
'nose >= 0.10.4',
|
||||
'setuptools-git >= 0.3.4'
|
||||
setup_requires = [
|
||||
'nose >= 0.10.4',
|
||||
'setuptools-git >= 0.3.4'
|
||||
],
|
||||
|
||||
install_requires=[
|
||||
'decorator >= 3.0.0',
|
||||
'simplegeneric >= 0.6',
|
||||
'six >= 1.0'
|
||||
# 'pygobject >= 3.10.0'
|
||||
'decorator >= 3.0.0',
|
||||
'simplegeneric >= 0.6',
|
||||
# 'PyGTK >= 2.8.0',
|
||||
# 'cairo >= 1.8.2'
|
||||
],
|
||||
|
||||
zip_safe=False,
|
||||
|
||||
package_data={
|
||||
# -*- package_data: -*-
|
||||
# -*- package_data: -*-
|
||||
},
|
||||
|
||||
entry_points={
|
||||
entry_points = {
|
||||
},
|
||||
|
||||
test_suite='nose.collector',
|
||||
)
|
||||
test_suite = 'nose.collector',
|
||||
)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user