checking in experimental new version of rxgraph based on Jon Bohren's smach_viewer
This commit is contained in:
parent
12a55635cd
commit
a8e345d03b
|
@ -0,0 +1,30 @@
|
||||||
|
cmake_minimum_required(VERSION 2.4.6)
|
||||||
|
include($ENV{ROS_ROOT}/core/rosbuild/rosbuild.cmake)
|
||||||
|
|
||||||
|
# Set the build type. Options are:
|
||||||
|
# Coverage : w/ debug symbols, w/o optimization, w/ code-coverage
|
||||||
|
# Debug : w/ debug symbols, w/o optimization
|
||||||
|
# Release : w/o debug symbols, w/ optimization
|
||||||
|
# RelWithDebInfo : w/ debug symbols, w/ optimization
|
||||||
|
# MinSizeRel : w/o debug symbols, w/ optimization, stripped binaries
|
||||||
|
#set(ROS_BUILD_TYPE RelWithDebInfo)
|
||||||
|
|
||||||
|
rosbuild_init()
|
||||||
|
|
||||||
|
#set the default path for built executables to the "bin" directory
|
||||||
|
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
|
||||||
|
#set the default path for built libraries to the "lib" directory
|
||||||
|
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
|
||||||
|
|
||||||
|
#uncomment if you have defined messages
|
||||||
|
#rosbuild_genmsg()
|
||||||
|
#uncomment if you have defined services
|
||||||
|
#rosbuild_gensrv()
|
||||||
|
|
||||||
|
#common commands for building c++ executables and libraries
|
||||||
|
#rosbuild_add_library(${PROJECT_NAME} src/example.cpp)
|
||||||
|
#target_link_libraries(${PROJECT_NAME} another_library)
|
||||||
|
#rosbuild_add_boost_directories()
|
||||||
|
#rosbuild_link_boost(${PROJECT_NAME} thread)
|
||||||
|
#rosbuild_add_executable(example examples/example.cpp)
|
||||||
|
#target_link_libraries(example ${PROJECT_NAME})
|
|
@ -0,0 +1 @@
|
||||||
|
include $(shell rospack find mk)/cmake.mk
|
|
@ -0,0 +1,18 @@
|
||||||
|
<package>
|
||||||
|
<description brief="rxgraph">
|
||||||
|
|
||||||
|
rxgraph
|
||||||
|
|
||||||
|
</description>
|
||||||
|
<author>Ken Conley</author>
|
||||||
|
<license>BSD</license>
|
||||||
|
<review status="unreviewed" notes=""/>
|
||||||
|
<url>http://ros.org/wiki/rxgraph</url>
|
||||||
|
|
||||||
|
<depend package="rosgraph"/>
|
||||||
|
<depend package="xdot"/>
|
||||||
|
<depend package="roslib"/>
|
||||||
|
|
||||||
|
</package>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
# Software License Agreement (BSD License)
|
||||||
|
#
|
||||||
|
# Copyright (c) 2010, Willow Garage, Inc.
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions
|
||||||
|
# are met:
|
||||||
|
#
|
||||||
|
# * Redistributions of source code must retain the above copyright
|
||||||
|
# notice, this list of conditions and the following disclaimer.
|
||||||
|
# * Redistributions in binary form must reproduce the above
|
||||||
|
# copyright notice, this list of conditions and the following
|
||||||
|
# disclaimer in the documentation and/or other materials provided
|
||||||
|
# with the distribution.
|
||||||
|
# * Neither the name of Willow Garage, Inc. nor the names of its
|
||||||
|
# contributors may be used to endorse or promote products derived
|
||||||
|
# from this software without specific prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||||
|
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||||
|
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||||
|
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||||
|
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
# POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
#
|
||||||
|
# Revision $Id: rxgraph.py 8782 2010-03-22 21:44:43Z kwc $
|
|
@ -0,0 +1,175 @@
|
||||||
|
# Software License Agreement (BSD License)
|
||||||
|
#
|
||||||
|
# Copyright (c) 2008, Willow Garage, Inc.
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions
|
||||||
|
# are met:
|
||||||
|
#
|
||||||
|
# * Redistributions of source code must retain the above copyright
|
||||||
|
# notice, this list of conditions and the following disclaimer.
|
||||||
|
# * Redistributions in binary form must reproduce the above
|
||||||
|
# copyright notice, this list of conditions and the following
|
||||||
|
# disclaimer in the documentation and/or other materials provided
|
||||||
|
# with the distribution.
|
||||||
|
# * Neither the name of Willow Garage, Inc. nor the names of its
|
||||||
|
# contributors may be used to endorse or promote products derived
|
||||||
|
# from this software without specific prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||||
|
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||||
|
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||||
|
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||||
|
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
# POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
#
|
||||||
|
# Revision $Id: rxgraph.py 8782 2010-03-22 21:44:43Z kwc $
|
||||||
|
|
||||||
|
import roslib.names
|
||||||
|
import rosgraph.graph
|
||||||
|
|
||||||
|
ORIENTATIONS = ['LR', 'TB', 'RL', 'BT']
|
||||||
|
|
||||||
|
INIT_DOTCODE = """
|
||||||
|
digraph G { initializing [label="initializing..."]; }
|
||||||
|
"""
|
||||||
|
|
||||||
|
# node/node connectivity
|
||||||
|
NODE_NODE_GRAPH = "node_node"
|
||||||
|
# node/topic connections where an actual network connection exists
|
||||||
|
NODE_TOPIC_GRAPH = "node_topic"
|
||||||
|
# all node/topic connections, even if no actual network connection
|
||||||
|
NODE_TOPIC_ALL_GRAPH = "node_topic_all"
|
||||||
|
|
||||||
|
def safe_dotcode_name(name):
|
||||||
|
"""
|
||||||
|
encode the name for dotcode symbol-safe syntax
|
||||||
|
"""
|
||||||
|
# not terribly efficient or sophisticated
|
||||||
|
ret = name.replace('/', '_')
|
||||||
|
ret = ret.replace(' ', '_')
|
||||||
|
ret = ret.replace('-', '_')
|
||||||
|
return ret.replace('.', '_')
|
||||||
|
|
||||||
|
def _edge_to_dot(e):
|
||||||
|
if e.label:
|
||||||
|
return ' %s->%s [label="%s"]'%(safe_dotcode_name(e.start), safe_dotcode_name(e.end), e.label)
|
||||||
|
else:
|
||||||
|
return ' %s->%s'%(safe_dotcode_name(e.start), safe_dotcode_name(e.end))
|
||||||
|
|
||||||
|
def _generate_node_dotcode(node, g, quiet):
|
||||||
|
if node in g.bad_nodes:
|
||||||
|
bn = g.bad_nodes[node]
|
||||||
|
if bn.type == rosgraph.graph.BadNode.DEAD:
|
||||||
|
return ' %s [color="red", shape="doublecircle", label="%s", URL="node:%s"];'%(
|
||||||
|
safe_dotcode_name(node), node, node)
|
||||||
|
else:
|
||||||
|
return ' %s [color="orange", shape="doublecircle", label="%s", URL="node:%s"];'%(
|
||||||
|
safe_dotcode_name(node), node, node, node)
|
||||||
|
else:
|
||||||
|
return ' %s [label="%s", URL="node:%s"];'%(safe_dotcode_name(node), node, node)
|
||||||
|
|
||||||
|
QUIET_NAMES = ['/diag_agg', '/runtime_logger', '/pr2_dashboard', '/rviz', '/rosout', '/cpu_monitor', '/monitor','/hd_monitor', '/rxloggerlevel', '/clock']
|
||||||
|
def _quiet_filter(name):
|
||||||
|
# ignore viewers
|
||||||
|
for n in QUIET_NAMES:
|
||||||
|
if n in name:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _quiet_filter_edge(edge):
|
||||||
|
for quiet_label in ['/time', '/clock', '/rosout']:
|
||||||
|
if quiet_label == edge.label:
|
||||||
|
return False
|
||||||
|
return _quiet_filter(edge.start) and _quiet_filter(edge.end)
|
||||||
|
|
||||||
|
def generate_namespaces(g, graph_mode, quiet=False):
|
||||||
|
"""
|
||||||
|
Determine the namespaces of the nodes being displayed
|
||||||
|
"""
|
||||||
|
namespaces = []
|
||||||
|
if graph_mode == NODE_NODE_GRAPH:
|
||||||
|
nodes = g.nn_nodes
|
||||||
|
if quiet:
|
||||||
|
nodes = [n for n in nodes if not n in QUIET_NAMES]
|
||||||
|
namespaces = list(set([roslib.names.namespace(n) for n in nodes]))
|
||||||
|
|
||||||
|
elif graph_mode == NODE_TOPIC_GRAPH or \
|
||||||
|
graph_mode == NODE_TOPIC_ALL_GRAPH:
|
||||||
|
nn_nodes = g.nn_nodes
|
||||||
|
nt_nodes = g.nt_nodes
|
||||||
|
if quiet:
|
||||||
|
nn_nodes = [n for n in nn_nodes if not n in QUIET_NAMES]
|
||||||
|
nt_nodes = [n for n in nt_nodes if not n in QUIET_NAMES]
|
||||||
|
if nn_nodes or nt_nodes:
|
||||||
|
namespaces = [roslib.names.namespace(n) for n in nn_nodes]
|
||||||
|
namespaces.extend([roslib.names.namespace(n) for n in nt_nodes])
|
||||||
|
|
||||||
|
return list(set(namespaces))
|
||||||
|
|
||||||
|
def _filter_edges(edges, nodes):
|
||||||
|
return [e for e in edges if e.start in nodes or e.end in nodes]
|
||||||
|
|
||||||
|
def generate_dotcode(g, ns_filter, graph_mode, orientation, quiet=False):
|
||||||
|
"""
|
||||||
|
@param g: Graph instance
|
||||||
|
@param ns_filter: namespace filter (must be canonicalized with trailing '/')
|
||||||
|
@type ns_filter: string
|
||||||
|
@param graph_mode str: NODE_NODE_GRAPH | NODE_TOPIC_GRAPH | NODE_TOPIC_ALL_GRAPH
|
||||||
|
@type graph_mode: str
|
||||||
|
@param orientation: rankdir value (see ORIENTATIONS dict)
|
||||||
|
@return: dotcode generated from graph singleton
|
||||||
|
@rtype: str
|
||||||
|
"""
|
||||||
|
#print "generate_dotcode", graph_mode
|
||||||
|
if ns_filter:
|
||||||
|
name_filter = ns_filter[:-1]
|
||||||
|
|
||||||
|
# create the node definitions
|
||||||
|
if graph_mode == NODE_NODE_GRAPH:
|
||||||
|
nodes = g.nn_nodes
|
||||||
|
if quiet:
|
||||||
|
nodes = [n for n in nodes if not n in QUIET_NAMES]
|
||||||
|
if ns_filter and ns_filter != '/':
|
||||||
|
nodes = [n for n in nodes if n.startswith(ns_filter) or n == name_filter]
|
||||||
|
if nodes:
|
||||||
|
nodes_str = '\n'.join([_generate_node_dotcode(n, g, quiet) for n in nodes])
|
||||||
|
else:
|
||||||
|
nodes_str = ' empty;'
|
||||||
|
|
||||||
|
elif graph_mode == NODE_TOPIC_GRAPH or \
|
||||||
|
graph_mode == NODE_TOPIC_ALL_GRAPH:
|
||||||
|
nn_nodes = g.nn_nodes
|
||||||
|
nt_nodes = g.nt_nodes
|
||||||
|
if quiet:
|
||||||
|
nn_nodes = [n for n in nn_nodes if not n in QUIET_NAMES]
|
||||||
|
nt_nodes = [n for n in nt_nodes if not n in QUIET_NAMES]
|
||||||
|
if nn_nodes or nt_nodes:
|
||||||
|
nodes_str = '\n'.join([_generate_node_dotcode(n, g, quiet) for n in nn_nodes])
|
||||||
|
nodes_str += '\n'.join([' %s [shape=box,label="%s",URL="topic:%s"];'%(
|
||||||
|
safe_dotcode_name(n), rosgraph.graph.node_topic(n), rosgraph.graph.node_topic(n)) for n in nt_nodes])
|
||||||
|
else:
|
||||||
|
nodes_str = ' empty;'
|
||||||
|
nodes = list(nn_nodes) + list(nt_nodes)
|
||||||
|
|
||||||
|
# create the edge definitions
|
||||||
|
if graph_mode == NODE_NODE_GRAPH:
|
||||||
|
edges = g.nn_edges
|
||||||
|
elif graph_mode == NODE_TOPIC_GRAPH:
|
||||||
|
edges = g.nt_edges
|
||||||
|
else:
|
||||||
|
edges = g.nt_all_edges
|
||||||
|
if quiet:
|
||||||
|
edges = filter(_quiet_filter_edge, edges)
|
||||||
|
|
||||||
|
edges = _filter_edges(edges, nodes)
|
||||||
|
edges_str = '\n'.join([_edge_to_dot(e) for e in edges])
|
||||||
|
return "digraph G {\n rankdir=%(orientation)s;\n%(nodes_str)s\n%(edges_str)s}\n"%vars()
|
||||||
|
|
|
@ -0,0 +1,187 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# Software License Agreement (BSD License)
|
||||||
|
#
|
||||||
|
# Copyright (c) 2008, Willow Garage, Inc.
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions
|
||||||
|
# are met:
|
||||||
|
#
|
||||||
|
# * Redistributions of source code must retain the above copyright
|
||||||
|
# notice, this list of conditions and the following disclaimer.
|
||||||
|
# * Redistributions in binary form must reproduce the above
|
||||||
|
# copyright notice, this list of conditions and the following
|
||||||
|
# disclaimer in the documentation and/or other materials provided
|
||||||
|
# with the distribution.
|
||||||
|
# * Neither the name of Willow Garage, Inc. nor the names of its
|
||||||
|
# contributors may be used to endorse or promote products derived
|
||||||
|
# from this software without specific prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||||
|
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||||
|
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||||
|
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||||
|
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
# POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
#
|
||||||
|
# Revision $Id: rxgraph.py 8782 2010-03-22 21:44:43Z kwc $
|
||||||
|
|
||||||
|
from __future__ import with_statement
|
||||||
|
|
||||||
|
import roslib; roslib.load_manifest('rxgraph')
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import threading
|
||||||
|
import traceback
|
||||||
|
import logging
|
||||||
|
from optparse import OptionParser
|
||||||
|
|
||||||
|
import rosgraph.graph
|
||||||
|
import rxgraph.dotcode
|
||||||
|
from rxgraph.dotcode import generate_dotcode, generate_namespaces, NODE_NODE_GRAPH, NODE_TOPIC_GRAPH
|
||||||
|
from rxgraph.viewer import RxGraphViewerFrame
|
||||||
|
|
||||||
|
import roslib.roslogging
|
||||||
|
|
||||||
|
# have to import later than others due to xdot calling wxversion
|
||||||
|
import wx
|
||||||
|
|
||||||
|
class DotUpdate(threading.Thread):
|
||||||
|
"""Thread to control update of dot file"""
|
||||||
|
|
||||||
|
def __init__(self, graph, viewer, output_file=None):
|
||||||
|
threading.Thread.__init__(self, name="DotUpdate")
|
||||||
|
self.viewer = viewer
|
||||||
|
self.graph = graph
|
||||||
|
self.output_file = None
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
viewer = self.viewer
|
||||||
|
current_ns_filter = viewer.ns_filter
|
||||||
|
g = self.graph
|
||||||
|
output_file = self.output_file
|
||||||
|
last_graph_mode = NODE_NODE_GRAPH
|
||||||
|
quiet = False
|
||||||
|
|
||||||
|
orientation = rxgraph.dotcode.ORIENTATIONS[0]
|
||||||
|
|
||||||
|
g.set_master_stale(5.0)
|
||||||
|
g.set_node_stale(5.0)
|
||||||
|
|
||||||
|
GUPDATE_INTERVAL = 0.5
|
||||||
|
last_gupdate = time.time() - GUPDATE_INTERVAL
|
||||||
|
try:
|
||||||
|
while not is_shutdown():
|
||||||
|
|
||||||
|
# throttle calls to g.update(). we want fast refresh
|
||||||
|
# on changes to the viewer's ns_filter, less so on the
|
||||||
|
# graph polling.
|
||||||
|
now = time.time()
|
||||||
|
if now - last_gupdate >= GUPDATE_INTERVAL:
|
||||||
|
changed = g.update()
|
||||||
|
last_gupdate = now
|
||||||
|
|
||||||
|
graph_mode = NODE_TOPIC_GRAPH if viewer.topic_boxes else NODE_NODE_GRAPH
|
||||||
|
|
||||||
|
changed |= viewer.ns_filter != current_ns_filter
|
||||||
|
changed |= quiet != viewer.quiet
|
||||||
|
changed |= graph_mode != last_graph_mode
|
||||||
|
|
||||||
|
quiet = viewer.quiet
|
||||||
|
last_graph_mode = graph_mode
|
||||||
|
|
||||||
|
if changed:
|
||||||
|
current_ns_filter = viewer.ns_filter
|
||||||
|
dotcode = generate_dotcode(g, current_ns_filter, graph_mode, orientation, quiet)
|
||||||
|
|
||||||
|
#compute path-combo box
|
||||||
|
namespaces = generate_namespaces(g, graph_mode, quiet)
|
||||||
|
viewer.update_namespaces(namespaces)
|
||||||
|
|
||||||
|
viewer.set_dotcode(dotcode)
|
||||||
|
|
||||||
|
# store dotcode if requested
|
||||||
|
if output_file:
|
||||||
|
with file(output_file, 'w') as f:
|
||||||
|
f.write(dotcode)
|
||||||
|
|
||||||
|
#information still changing, shorter yield
|
||||||
|
time.sleep(0.1)
|
||||||
|
else:
|
||||||
|
# no new information, let bad nodes update
|
||||||
|
g.bad_update()
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
changed = False
|
||||||
|
|
||||||
|
except wx.PyDeadObjectError:
|
||||||
|
# shutdown
|
||||||
|
pass
|
||||||
|
except:
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
_is_shutdown = False
|
||||||
|
def is_shutdown():
|
||||||
|
return _is_shutdown
|
||||||
|
|
||||||
|
def set_shutdown(is_shutdown):
|
||||||
|
global _is_shutdown
|
||||||
|
_is_shutdown = is_shutdown
|
||||||
|
|
||||||
|
def rxgraph_main():
|
||||||
|
parser = OptionParser(usage="usage: rxgraph [options]")
|
||||||
|
parser.add_option("-o", "--dot",
|
||||||
|
dest="output_file", default=None,
|
||||||
|
help="ouput graph as graphviz dot file", metavar="DOTFILE")
|
||||||
|
parser.add_option("--nodens",
|
||||||
|
dest="node_ns", default=None,
|
||||||
|
help="only show nodes in specified namespace")
|
||||||
|
parser.add_option("--topicns",
|
||||||
|
dest="topic_ns", default=None,
|
||||||
|
help="only show topics in specified namespace")
|
||||||
|
|
||||||
|
options, args = parser.parse_args()
|
||||||
|
if args:
|
||||||
|
parser.error("invalid arguments")
|
||||||
|
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
try:
|
||||||
|
subprocess.check_call(['dot', '-V'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||||
|
except:
|
||||||
|
print >> sys.stderr, "Graphviz does not appear to be installed on your system. Please run:\n\n\trosdep install rosgraph\n\nto install the necessary dependencies on your system"
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
roslib.roslogging.configure_logging('rxgraph', logging.DEBUG, additional=['rospy', 'roslib', 'rosgraph'])
|
||||||
|
try:
|
||||||
|
# make gtk play nice with Python threads
|
||||||
|
#gtk.gdk.threads_init()
|
||||||
|
|
||||||
|
graph = rosgraph.graph.Graph(options.node_ns, options.topic_ns)
|
||||||
|
|
||||||
|
app = wx.App()
|
||||||
|
frame = RxGraphViewerFrame()
|
||||||
|
frame.set_dotcode(rxgraph.dotcode.INIT_DOTCODE)
|
||||||
|
|
||||||
|
DotUpdate(graph, frame, output_file=options.output_file).start()
|
||||||
|
|
||||||
|
frame.Show()
|
||||||
|
app.MainLoop()
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
set_shutdown(True)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
rxgraph_main()
|
|
@ -0,0 +1,191 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# Copyright (c) 2010, Willow Garage, Inc.
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions are met:
|
||||||
|
#
|
||||||
|
# * Redistributions of source code must retain the above copyright
|
||||||
|
# notice, this list of conditions and the following disclaimer.
|
||||||
|
# * Redistributions in binary form must reproduce the above copyright
|
||||||
|
# notice, this list of conditions and the following disclaimer in the
|
||||||
|
# documentation and/or other materials provided with the distribution.
|
||||||
|
# * Neither the name of the Willow Garage, Inc. nor the names of its
|
||||||
|
# contributors may be used to endorse or promote products derived from
|
||||||
|
# this software without specific prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
# POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
#
|
||||||
|
# Author: Jonathan Bohren
|
||||||
|
|
||||||
|
import roslib; roslib.load_manifest('rxgraph')
|
||||||
|
import rospy
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import xdot
|
||||||
|
import wx
|
||||||
|
|
||||||
|
DOTCODE= """digraph G {
|
||||||
|
rankdir=LR;
|
||||||
|
_included_talker [label="/included/talker"];
|
||||||
|
_included_wg2_talker [label="/included/wg2/talker"];
|
||||||
|
_included2_talker [label="/included2/talker"];
|
||||||
|
_included2_listener [label="/included2/listener"];
|
||||||
|
_included_wg2_listener [label="/included/wg2/listener"];
|
||||||
|
_rosout [label="/rosout"];
|
||||||
|
_included_listener [label="/included/listener"];
|
||||||
|
_wg_listener [label="/wg/listener"];
|
||||||
|
_wg_talker2 [label="/wg/talker2"];
|
||||||
|
_wg_talker1 [label="/wg/talker1"];
|
||||||
|
_included2_wg2_talker [label="/included2/wg2/talker"];
|
||||||
|
_included2_wg2_listener [label="/included2/wg2/listener"];
|
||||||
|
_wg_talker1->_rosout [label="/rosout"]
|
||||||
|
_included2_wg2_listener->_rosout [label="/rosout"]
|
||||||
|
_wg_talker2->_wg_listener [label="/wg/hello"]
|
||||||
|
_wg_talker2->_rosout [label="/rosout"]
|
||||||
|
_included2_wg2_talker->_included2_wg2_listener [label="/included2/wg2/chatter"]
|
||||||
|
_included2_listener->_rosout [label="/rosout"]
|
||||||
|
_included_wg2_listener->_rosout [label="/rosout"]
|
||||||
|
_wg_talker1->_wg_listener [label="/wg/hello"]
|
||||||
|
_included2_talker->_included2_listener [label="/included2/chatter"]
|
||||||
|
_wg_listener->_rosout [label="/rosout"]
|
||||||
|
_included_wg2_talker->_included_wg2_listener [label="/included/wg2/chatter"]
|
||||||
|
_included_talker->_included_listener [label="/included/chatter"]
|
||||||
|
_included_wg2_talker->_rosout [label="/rosout"]
|
||||||
|
_included2_talker->_rosout [label="/rosout"]
|
||||||
|
_included2_wg2_talker->_rosout [label="/rosout"]
|
||||||
|
_included_talker->_rosout [label="/rosout"]
|
||||||
|
_included_listener->_rosout [label="/rosout"]}
|
||||||
|
"""
|
||||||
|
|
||||||
|
class RxGraphViewerFrame(wx.Frame):
|
||||||
|
def __init__(self):
|
||||||
|
wx.Frame.__init__(self, None, -1, "rxgraph", size=(720,480))
|
||||||
|
|
||||||
|
# TODO: convert to read-only property
|
||||||
|
self.ns_filter = None
|
||||||
|
self.quiet = False
|
||||||
|
self.topic_boxes = False
|
||||||
|
|
||||||
|
self._needs_refresh = False
|
||||||
|
|
||||||
|
vbox = wx.BoxSizer(wx.VERTICAL)
|
||||||
|
|
||||||
|
graph_view = wx.Panel(self, -1)
|
||||||
|
gv_vbox = wx.BoxSizer(wx.VERTICAL)
|
||||||
|
graph_view.SetSizer(gv_vbox)
|
||||||
|
|
||||||
|
# Construct toolbar
|
||||||
|
toolbar = wx.ToolBar(graph_view, -1)
|
||||||
|
|
||||||
|
toolbar.AddControl(wx.StaticText(toolbar,-1,"Path: "))
|
||||||
|
|
||||||
|
self._ns_combo = wx.ComboBox(toolbar, -1, style=wx.CB_DROPDOWN)
|
||||||
|
self._ns_combo .Bind(wx.EVT_COMBOBOX, self.set_path)
|
||||||
|
self._ns_combo.Append('/')
|
||||||
|
self._ns_combo.SetValue('/')
|
||||||
|
self._namespaces = ['/']
|
||||||
|
|
||||||
|
# display options
|
||||||
|
quiet_check = wx.CheckBox(toolbar, -1, label="Quiet")
|
||||||
|
quiet_check.Bind(wx.EVT_CHECKBOX, self.set_quiet_check)
|
||||||
|
topic_check = wx.CheckBox(toolbar, -1, label="All topics")
|
||||||
|
topic_check.Bind(wx.EVT_CHECKBOX, self.set_topic_boxes)
|
||||||
|
|
||||||
|
toolbar.AddControl(self._ns_combo)
|
||||||
|
|
||||||
|
toolbar.AddControl(wx.StaticText(toolbar,-1," "))
|
||||||
|
toolbar.AddControl(quiet_check)
|
||||||
|
toolbar.AddControl(wx.StaticText(toolbar,-1," "))
|
||||||
|
toolbar.AddControl(topic_check)
|
||||||
|
|
||||||
|
toolbar.AddControl(wx.StaticText(toolbar,-1," "))
|
||||||
|
toolbar.AddLabelTool(wx.ID_HELP, 'Help',
|
||||||
|
wx.ArtProvider.GetBitmap(wx.ART_HELP,wx.ART_OTHER,(16,16)) )
|
||||||
|
toolbar.Realize()
|
||||||
|
|
||||||
|
self.Bind(wx.EVT_TOOL, self.ShowControlsDialog, id=wx.ID_HELP)
|
||||||
|
|
||||||
|
# Create dot graph widget
|
||||||
|
self._widget = xdot.wxxdot.WxDotWindow(graph_view, -1)
|
||||||
|
self._widget.set_dotcode(DOTCODE)
|
||||||
|
self._widget.zoom_to_fit()
|
||||||
|
|
||||||
|
vbox.Add(graph_view, 1, wx.EXPAND | wx.ALL)
|
||||||
|
|
||||||
|
gv_vbox.Add(toolbar, 0, wx.EXPAND)
|
||||||
|
gv_vbox.Add(self._widget, 1, wx.EXPAND)
|
||||||
|
|
||||||
|
self.SetSizer(vbox)
|
||||||
|
self.Center()
|
||||||
|
|
||||||
|
self.Bind(wx.EVT_IDLE,self.OnIdle)
|
||||||
|
|
||||||
|
# user callback for select
|
||||||
|
self._widget.register_select_callback(self.select_cb)
|
||||||
|
|
||||||
|
def OnIdle(self, event):
|
||||||
|
if self._needs_refresh:
|
||||||
|
self.Refresh()
|
||||||
|
self._needs_refresh = False
|
||||||
|
|
||||||
|
def select_cb(self, *args):
|
||||||
|
print args[0].item.url
|
||||||
|
|
||||||
|
def set_quiet_check(self, event):
|
||||||
|
self.quiet = event.Checked()
|
||||||
|
|
||||||
|
def set_topic_boxes(self, event):
|
||||||
|
self.topic_boxes = event.Checked()
|
||||||
|
|
||||||
|
def set_path(self, event):
|
||||||
|
self.ns_filter = self._ns_combo.GetValue()
|
||||||
|
self._needs_zoom = True
|
||||||
|
|
||||||
|
def update_namespaces(self, namespaces):
|
||||||
|
# unfortunately this routine will not alphanumerically sort
|
||||||
|
curr = self._namespaces
|
||||||
|
new_filters = [n for n in namespaces if n not in curr]
|
||||||
|
for f in new_filters:
|
||||||
|
self._ns_combo.Append(f)
|
||||||
|
curr.extend(new_filters)
|
||||||
|
|
||||||
|
def ShowControlsDialog(self,event):
|
||||||
|
dial = wx.MessageDialog(None,
|
||||||
|
"Pan: Arrow Keys\nZoom: PageUp / PageDown\nZoom To Fit: F\n",
|
||||||
|
'Keyboard Controls', wx.OK)
|
||||||
|
dial.ShowModal()
|
||||||
|
|
||||||
|
def set_dotcode(self, dotcode, zoom=True):
|
||||||
|
if self._widget.set_dotcode(dotcode, None):
|
||||||
|
self.SetTitle('rxgraph')
|
||||||
|
if zoom or self._needs_zoom:
|
||||||
|
self._widget.zoom_to_fit()
|
||||||
|
self._needs_zoom = False
|
||||||
|
self._needs_refresh = True
|
||||||
|
wx.PostEvent(self.GetEventHandler(), wx.IdleEvent())
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
app = wx.App()
|
||||||
|
|
||||||
|
frame = RxGraphViewerFrame()
|
||||||
|
frame.Show()
|
||||||
|
|
||||||
|
# for testing only
|
||||||
|
frame.set_dotcode(DOTCODE)
|
||||||
|
|
||||||
|
app.MainLoop()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
|
@ -0,0 +1,30 @@
|
||||||
|
cmake_minimum_required(VERSION 2.4.6)
|
||||||
|
include($ENV{ROS_ROOT}/core/rosbuild/rosbuild.cmake)
|
||||||
|
|
||||||
|
# Set the build type. Options are:
|
||||||
|
# Coverage : w/ debug symbols, w/o optimization, w/ code-coverage
|
||||||
|
# Debug : w/ debug symbols, w/o optimization
|
||||||
|
# Release : w/o debug symbols, w/ optimization
|
||||||
|
# RelWithDebInfo : w/ debug symbols, w/ optimization
|
||||||
|
# MinSizeRel : w/o debug symbols, w/ optimization, stripped binaries
|
||||||
|
#set(ROS_BUILD_TYPE RelWithDebInfo)
|
||||||
|
|
||||||
|
rosbuild_init()
|
||||||
|
|
||||||
|
#set the default path for built executables to the "bin" directory
|
||||||
|
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
|
||||||
|
#set the default path for built libraries to the "lib" directory
|
||||||
|
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
|
||||||
|
|
||||||
|
#uncomment if you have defined messages
|
||||||
|
#rosbuild_genmsg()
|
||||||
|
#uncomment if you have defined services
|
||||||
|
#rosbuild_gensrv()
|
||||||
|
|
||||||
|
#common commands for building c++ executables and libraries
|
||||||
|
#rosbuild_add_library(${PROJECT_NAME} src/example.cpp)
|
||||||
|
#target_link_libraries(${PROJECT_NAME} another_library)
|
||||||
|
#rosbuild_add_boost_directories()
|
||||||
|
#rosbuild_link_boost(${PROJECT_NAME} thread)
|
||||||
|
#rosbuild_add_executable(example examples/example.cpp)
|
||||||
|
#target_link_libraries(example ${PROJECT_NAME})
|
|
@ -0,0 +1 @@
|
||||||
|
include $(shell rospack find mk)/cmake.mk
|
|
@ -0,0 +1,74 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
# Copyright (c) 2010, Willow Garage, Inc.
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions are met:
|
||||||
|
#
|
||||||
|
# * Redistributions of source code must retain the above copyright
|
||||||
|
# notice, this list of conditions and the following disclaimer.
|
||||||
|
# * Redistributions in binary form must reproduce the above copyright
|
||||||
|
# notice, this list of conditions and the following disclaimer in the
|
||||||
|
# documentation and/or other materials provided with the distribution.
|
||||||
|
# * Neither the name of the Willow Garage, Inc. nor the names of its
|
||||||
|
# contributors may be used to endorse or promote products derived from
|
||||||
|
# this software without specific prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
# POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
#
|
||||||
|
# Author: Jonathan Bohren
|
||||||
|
|
||||||
|
import roslib; roslib.load_manifest('xdot')
|
||||||
|
import rospy
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import xdot
|
||||||
|
import wx
|
||||||
|
|
||||||
|
def main():
|
||||||
|
import optparse
|
||||||
|
|
||||||
|
parser = optparse.OptionParser(
|
||||||
|
usage='\n\t%prog [file]')
|
||||||
|
parser.add_option(
|
||||||
|
'-f', '--filter',
|
||||||
|
type='choice', choices=('dot', 'neato', 'twopi', 'circo', 'fdp'),
|
||||||
|
dest='filter', default='dot',
|
||||||
|
help='graphviz filter: dot, neato, twopi, circo, or fdp [default: %default]')
|
||||||
|
|
||||||
|
(options, args) = parser.parse_args(sys.argv[1:])
|
||||||
|
if len(args) > 1:
|
||||||
|
parser.error('incorrect number of arguments')
|
||||||
|
|
||||||
|
app = wx.App()
|
||||||
|
|
||||||
|
frame = xdot.wxxdot.WxDotFrame()
|
||||||
|
frame.set_filter(options.filter)
|
||||||
|
|
||||||
|
"""Sample mouse event."""
|
||||||
|
#def print_cb(item,event):
|
||||||
|
# print "MOUSE_EVENT: "+str(item)
|
||||||
|
#frame.widget.register_select_callback(print_cb)
|
||||||
|
|
||||||
|
frame.Show()
|
||||||
|
if len(args) >= 1:
|
||||||
|
if args[0] == '-':
|
||||||
|
frame.set_dotcode(sys.stdin.read())
|
||||||
|
else:
|
||||||
|
frame.open_file(args[0])
|
||||||
|
|
||||||
|
app.MainLoop()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
|
@ -0,0 +1,26 @@
|
||||||
|
/**
|
||||||
|
\mainpage
|
||||||
|
\htmlinclude manifest.html
|
||||||
|
|
||||||
|
\b xdot is ...
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Provide an overview of your package.
|
||||||
|
-->
|
||||||
|
|
||||||
|
|
||||||
|
\section codeapi Code API
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Provide links to specific auto-generated API documentation within your
|
||||||
|
package that is of particular interest to a reader. Doxygen will
|
||||||
|
document pretty much every part of your code, so do your best here to
|
||||||
|
point the reader to the actual API.
|
||||||
|
|
||||||
|
If your codebase is fairly large or has different sets of APIs, you
|
||||||
|
should use the doxygen 'group' tag to keep these APIs together. For
|
||||||
|
example, the roscpp documentation has 'libros' group.
|
||||||
|
-->
|
||||||
|
|
||||||
|
|
||||||
|
*/
|
|
@ -0,0 +1,20 @@
|
||||||
|
<package>
|
||||||
|
<description brief="xdot">
|
||||||
|
XDot is an interactive viewer for graphs written in Graphviz's dot
|
||||||
|
language.
|
||||||
|
This package adds front-end capabilities to XDot including WX Widget
|
||||||
|
support and a mechanism for receiving callbacks when nodes are clicked.
|
||||||
|
</description>
|
||||||
|
<author>Jose Fonseca, ROS package and WX frontend by Jonathan Bohren</author>
|
||||||
|
<license>LGPL/BSD</license>
|
||||||
|
<review status="unreviewed" notes=""/>
|
||||||
|
<url>http://code.google.com/p/jrfonseca/wiki/XDot</url>
|
||||||
|
|
||||||
|
<depend package="rospy"/>
|
||||||
|
|
||||||
|
<rosdep name="wxpython"/>
|
||||||
|
<rosdep name="graphviz"/>
|
||||||
|
|
||||||
|
</package>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
|
||||||
|
import wxxdot
|
|
@ -0,0 +1,575 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
#
|
||||||
|
# wxpython widgets for using Jose Fonseca's cairo graphviz visualizer
|
||||||
|
# Copyright (c) 2010, Willow Garage, Inc.
|
||||||
|
#
|
||||||
|
# Source modified from Jose Fonseca's XDot pgtk widgets. That code is
|
||||||
|
# Copyright 2008 Jose Fonseca
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU Lesser General Public License as published
|
||||||
|
# by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program 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 Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from xdot import *
|
||||||
|
|
||||||
|
__all__ = ['WxDotWindow', 'WxDotFrame']
|
||||||
|
|
||||||
|
# We need to get the wx version with built-in cairo support
|
||||||
|
import wxversion
|
||||||
|
wxversion.select("2.8")
|
||||||
|
import wx
|
||||||
|
import wx.lib.wxcairo as wxcairo
|
||||||
|
|
||||||
|
# This is a crazy hack to get this to work on 64-bit systems
|
||||||
|
if 'wxMac' in wx.PlatformInfo:
|
||||||
|
pass # Implement if necessary
|
||||||
|
elif 'wxMSW' in wx.PlatformInfo:
|
||||||
|
pass # Implement if necessary
|
||||||
|
elif 'wxGTK' in wx.PlatformInfo:
|
||||||
|
import ctypes
|
||||||
|
gdkLib = wx.lib.wxcairo._findGDKLib()
|
||||||
|
gdkLib.gdk_cairo_create.restype = ctypes.c_void_p
|
||||||
|
|
||||||
|
class WxDragAction(object):
|
||||||
|
def __init__(self, dot_widget):
|
||||||
|
self.dot_widget = dot_widget
|
||||||
|
|
||||||
|
def on_button_press(self, event):
|
||||||
|
x,y = event.GetPositionTuple()
|
||||||
|
self.startmousex = self.prevmousex = x
|
||||||
|
self.startmousey = self.prevmousey = y
|
||||||
|
self.start()
|
||||||
|
|
||||||
|
def on_motion_notify(self, event):
|
||||||
|
x,y = event.GetPositionTuple()
|
||||||
|
deltax = self.prevmousex - x
|
||||||
|
deltay = self.prevmousey - y
|
||||||
|
self.drag(deltax, deltay)
|
||||||
|
self.prevmousex = x
|
||||||
|
self.prevmousey = y
|
||||||
|
|
||||||
|
def on_button_release(self, event):
|
||||||
|
x,y = event.GetPositionTuple()
|
||||||
|
self.stopmousex = x
|
||||||
|
self.stopmousey = y
|
||||||
|
self.stop()
|
||||||
|
|
||||||
|
def draw(self, cr):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def drag(self, deltax, deltay):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def abort(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class WxNullAction(WxDragAction):
|
||||||
|
def on_motion_notify(self, event):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class WxPanAction(WxDragAction):
|
||||||
|
def start(self):
|
||||||
|
self.dot_widget.set_cursor(wx.CURSOR_SIZING)
|
||||||
|
|
||||||
|
def drag(self, deltax, deltay):
|
||||||
|
self.dot_widget.x += deltax / self.dot_widget.zoom_ratio
|
||||||
|
self.dot_widget.y += deltay / self.dot_widget.zoom_ratio
|
||||||
|
self.dot_widget.Refresh()
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self.dot_widget.set_cursor(wx.CURSOR_ARROW)
|
||||||
|
|
||||||
|
abort = stop
|
||||||
|
|
||||||
|
class WxZoomAction(WxDragAction):
|
||||||
|
def drag(self, deltax, deltay):
|
||||||
|
self.dot_widget.zoom_ratio *= 1.005 ** (deltax + deltay)
|
||||||
|
self.dot_widget.zoom_to_fit_on_resize = False
|
||||||
|
self.dot_widget.Refresh()
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self.dot_widget.Refresh()
|
||||||
|
|
||||||
|
class WxZoomAreaAction(WxDragAction):
|
||||||
|
def drag(self, deltax, deltay):
|
||||||
|
self.dot_widget.Refresh()
|
||||||
|
|
||||||
|
def draw(self, cr):
|
||||||
|
cr.save()
|
||||||
|
cr.set_source_rgba(.5, .5, 1.0, 0.25)
|
||||||
|
cr.rectangle(self.startmousex, self.startmousey,
|
||||||
|
self.prevmousex - self.startmousex,
|
||||||
|
self.prevmousey - self.startmousey)
|
||||||
|
cr.fill()
|
||||||
|
cr.set_source_rgba(.5, .5, 1.0, 1.0)
|
||||||
|
cr.set_line_width(1)
|
||||||
|
cr.rectangle(self.startmousex - .5, self.startmousey - .5,
|
||||||
|
self.prevmousex - self.startmousex + 1,
|
||||||
|
self.prevmousey - self.startmousey + 1)
|
||||||
|
cr.stroke()
|
||||||
|
cr.restore()
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
x1, y1 = self.dot_widget.window2graph(self.startmousex,
|
||||||
|
self.startmousey)
|
||||||
|
x2, y2 = self.dot_widget.window2graph(self.stopmousex,
|
||||||
|
self.stopmousey)
|
||||||
|
self.dot_widget.zoom_to_area(x1, y1, x2, y2)
|
||||||
|
|
||||||
|
def abort(self):
|
||||||
|
self.dot_widget.Refresh()
|
||||||
|
|
||||||
|
class WxDotWindow(wx.Panel):
|
||||||
|
"""wxpython Frame that draws dot graphs."""
|
||||||
|
filter = 'dot'
|
||||||
|
|
||||||
|
def __init__(self, parent, id):
|
||||||
|
"""constructor"""
|
||||||
|
wx.Panel.__init__(self, parent, id)
|
||||||
|
|
||||||
|
self.graph = Graph()
|
||||||
|
self.openfilename = None
|
||||||
|
|
||||||
|
self.x, self.y = 0.0, 0.0
|
||||||
|
self.zoom_ratio = 1.0
|
||||||
|
self.zoom_to_fit_on_resize = False
|
||||||
|
self.animation = NoAnimation(self)
|
||||||
|
self.drag_action = WxNullAction(self)
|
||||||
|
self.presstime = None
|
||||||
|
self.highlight = None
|
||||||
|
|
||||||
|
# Bind events
|
||||||
|
self.Bind(wx.EVT_PAINT, self.OnPaint)
|
||||||
|
self.Bind(wx.EVT_SIZE, self.OnResize)
|
||||||
|
|
||||||
|
self.Bind(wx.EVT_MOUSEWHEEL, self.OnScroll)
|
||||||
|
|
||||||
|
self.Bind(wx.EVT_MOUSE_EVENTS, self.OnMouse)
|
||||||
|
|
||||||
|
self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
|
||||||
|
|
||||||
|
# Callback register
|
||||||
|
self.select_cbs = []
|
||||||
|
self.dc = None
|
||||||
|
self.ctx = None
|
||||||
|
|
||||||
|
### User callbacks
|
||||||
|
def register_select_callback(self, cb):
|
||||||
|
self.select_cbs.append(cb)
|
||||||
|
|
||||||
|
### Event handlers
|
||||||
|
def OnResize(self, event):
|
||||||
|
self.Refresh()
|
||||||
|
|
||||||
|
def OnPaint(self, event):
|
||||||
|
"""Redraw the graph."""
|
||||||
|
dc = wx.PaintDC(self)
|
||||||
|
|
||||||
|
#print dc
|
||||||
|
ctx = wxcairo.ContextFromDC(dc)
|
||||||
|
ctx = pangocairo.CairoContext(ctx)
|
||||||
|
|
||||||
|
# Get widget size
|
||||||
|
width, height = self.GetSize()
|
||||||
|
#width,height = self.dc.GetSizeTuple()
|
||||||
|
|
||||||
|
ctx.rectangle(0,0,width,height)
|
||||||
|
ctx.clip()
|
||||||
|
|
||||||
|
ctx.set_source_rgba(1.0, 1.0, 1.0, 1.0)
|
||||||
|
ctx.paint()
|
||||||
|
|
||||||
|
ctx.save()
|
||||||
|
ctx.translate(0.5*width, 0.5*height)
|
||||||
|
|
||||||
|
ctx.scale(self.zoom_ratio, self.zoom_ratio)
|
||||||
|
ctx.translate(-self.x, -self.y)
|
||||||
|
self.graph.draw(ctx, highlight_items=self.highlight)
|
||||||
|
ctx.restore()
|
||||||
|
|
||||||
|
self.drag_action.draw(ctx)
|
||||||
|
|
||||||
|
def OnScroll(self, event):
|
||||||
|
"""Zoom the view."""
|
||||||
|
if event.GetWheelRotation() > 0:
|
||||||
|
self.zoom_image(self.zoom_ratio * self.ZOOM_INCREMENT,
|
||||||
|
pos=(event.GetX(), event.GetY()))
|
||||||
|
else:
|
||||||
|
self.zoom_image(self.zoom_ratio / self.ZOOM_INCREMENT,
|
||||||
|
pos=(event.GetX(), event.GetY()))
|
||||||
|
|
||||||
|
def OnKeyDown(self, event):
|
||||||
|
"""Process key down event."""
|
||||||
|
key = event.GetKeyCode()
|
||||||
|
if key == wx.WXK_LEFT:
|
||||||
|
self.x -= self.POS_INCREMENT/self.zoom_ratio
|
||||||
|
self.Refresh()
|
||||||
|
if key == wx.WXK_RIGHT:
|
||||||
|
self.x += self.POS_INCREMENT/self.zoom_ratio
|
||||||
|
self.Refresh()
|
||||||
|
if key == wx.WXK_UP:
|
||||||
|
self.y -= self.POS_INCREMENT/self.zoom_ratio
|
||||||
|
self.Refresh()
|
||||||
|
if key == wx.WXK_DOWN:
|
||||||
|
self.y += self.POS_INCREMENT/self.zoom_ratio
|
||||||
|
self.Refresh()
|
||||||
|
if key == wx.WXK_PAGEUP:
|
||||||
|
self.zoom_image(self.zoom_ratio * self.ZOOM_INCREMENT)
|
||||||
|
self.Refresh()
|
||||||
|
if key == wx.WXK_PAGEDOWN:
|
||||||
|
self.zoom_image(self.zoom_ratio / self.ZOOM_INCREMENT)
|
||||||
|
self.Refresh()
|
||||||
|
if key == wx.WXK_ESCAPE:
|
||||||
|
self.drag_action.abort()
|
||||||
|
self.drag_action = WxNullAction(self)
|
||||||
|
if key == ord('F'):
|
||||||
|
self.zoom_to_fit()
|
||||||
|
if key == ord('R'):
|
||||||
|
self.reload()
|
||||||
|
if key == ord('Q'):
|
||||||
|
self.reload()
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
event.Skip()
|
||||||
|
|
||||||
|
### Helper functions
|
||||||
|
def get_current_pos(self):
|
||||||
|
"""Get the current graph position."""
|
||||||
|
return self.x, self.y
|
||||||
|
|
||||||
|
def set_current_pos(self, x, y):
|
||||||
|
"""Set the current graph position."""
|
||||||
|
self.x = x
|
||||||
|
self.y = y
|
||||||
|
self.Refresh()
|
||||||
|
|
||||||
|
def set_highlight(self, items):
|
||||||
|
"""Set a number of items to be hilighted."""
|
||||||
|
if self.highlight != items:
|
||||||
|
self.highlight = items
|
||||||
|
self.Refresh()
|
||||||
|
|
||||||
|
### Cursor manipulation
|
||||||
|
def set_cursor(self, cursor_type):
|
||||||
|
self.cursor = wx.StockCursor(cursor_type)
|
||||||
|
self.SetCursor(self.cursor)
|
||||||
|
|
||||||
|
### Zooming methods
|
||||||
|
def zoom_image(self, zoom_ratio, center=False, pos=None):
|
||||||
|
"""Zoom the graph."""
|
||||||
|
if center:
|
||||||
|
self.x = self.graph.width/2
|
||||||
|
self.y = self.graph.height/2
|
||||||
|
elif pos is not None:
|
||||||
|
width, height = self.GetSize()
|
||||||
|
x, y = pos
|
||||||
|
x -= 0.5*width
|
||||||
|
y -= 0.5*height
|
||||||
|
self.x += x / self.zoom_ratio - x / zoom_ratio
|
||||||
|
self.y += y / self.zoom_ratio - y / zoom_ratio
|
||||||
|
self.zoom_ratio = zoom_ratio
|
||||||
|
self.zoom_to_fit_on_resize = False
|
||||||
|
self.Refresh()
|
||||||
|
|
||||||
|
def zoom_to_area(self, x1, y1, x2, y2):
|
||||||
|
"""Zoom to an area of the graph."""
|
||||||
|
width, height = self.GetSize()
|
||||||
|
area_width = abs(x1 - x2)
|
||||||
|
area_height = abs(y1 - y2)
|
||||||
|
self.zoom_ratio = min(
|
||||||
|
float(width)/float(area_width),
|
||||||
|
float(height)/float(area_height)
|
||||||
|
)
|
||||||
|
self.zoom_to_fit_on_resize = False
|
||||||
|
self.x = (x1 + x2) / 2
|
||||||
|
self.y = (y1 + y2) / 2
|
||||||
|
self.Refresh()
|
||||||
|
|
||||||
|
def zoom_to_fit(self):
|
||||||
|
"""Zoom to fit the size of the graph."""
|
||||||
|
width,height = self.GetSize()
|
||||||
|
x = self.ZOOM_TO_FIT_MARGIN
|
||||||
|
y = self.ZOOM_TO_FIT_MARGIN
|
||||||
|
width -= 2 * self.ZOOM_TO_FIT_MARGIN
|
||||||
|
height -= 2 * self.ZOOM_TO_FIT_MARGIN
|
||||||
|
|
||||||
|
if float(self.graph.width) > 0 and float(self.graph.height) > 0 and width > 0 and height > 0:
|
||||||
|
zoom_ratio = min(
|
||||||
|
float(width)/float(self.graph.width),
|
||||||
|
float(height)/float(self.graph.height)
|
||||||
|
)
|
||||||
|
self.zoom_image(zoom_ratio, center=True)
|
||||||
|
self.zoom_to_fit_on_resize = True
|
||||||
|
|
||||||
|
ZOOM_INCREMENT = 1.25
|
||||||
|
ZOOM_TO_FIT_MARGIN = 12
|
||||||
|
|
||||||
|
def on_zoom_in(self, action):
|
||||||
|
self.zoom_image(self.zoom_ratio * self.ZOOM_INCREMENT)
|
||||||
|
|
||||||
|
def on_zoom_out(self, action):
|
||||||
|
self.zoom_image(self.zoom_ratio / self.ZOOM_INCREMENT)
|
||||||
|
|
||||||
|
def on_zoom_fit(self, action):
|
||||||
|
self.zoom_to_fit()
|
||||||
|
|
||||||
|
def on_zoom_100(self, action):
|
||||||
|
self.zoom_image(1.0)
|
||||||
|
|
||||||
|
POS_INCREMENT = 100
|
||||||
|
|
||||||
|
def get_drag_action(self, event):
|
||||||
|
"""Get a drag action for this click."""
|
||||||
|
# Grab the button
|
||||||
|
button = event.GetButton()
|
||||||
|
# Grab modifier keys
|
||||||
|
control_down = event.ControlDown()
|
||||||
|
alt_down = event.AltDown()
|
||||||
|
shift_down = event.ShiftDown()
|
||||||
|
|
||||||
|
drag = event.Dragging()
|
||||||
|
motion = event.Moving()
|
||||||
|
|
||||||
|
# Get the correct drag action for this click
|
||||||
|
if button in (wx.MOUSE_BTN_LEFT, wx.MOUSE_BTN_MIDDLE): # left or middle button
|
||||||
|
if control_down:
|
||||||
|
if shift_down:
|
||||||
|
return WxZoomAreaAction(self)
|
||||||
|
else:
|
||||||
|
return WxZoomAction(self)
|
||||||
|
else:
|
||||||
|
return WxPanAction(self)
|
||||||
|
|
||||||
|
return WxNullAction(self)
|
||||||
|
|
||||||
|
def OnMouse(self, event):
|
||||||
|
x,y = event.GetPositionTuple()
|
||||||
|
|
||||||
|
item = None
|
||||||
|
|
||||||
|
# Get the item
|
||||||
|
if not event.Dragging():
|
||||||
|
item = self.get_url(x, y)
|
||||||
|
if item is None:
|
||||||
|
item = self.get_jump(x, y)
|
||||||
|
|
||||||
|
if item is not None:
|
||||||
|
self.set_cursor(wx.CURSOR_HAND)
|
||||||
|
self.set_highlight(item.highlight)
|
||||||
|
|
||||||
|
for cb in self.select_cbs:
|
||||||
|
cb(item,event)
|
||||||
|
else:
|
||||||
|
self.set_cursor(wx.CURSOR_ARROW)
|
||||||
|
self.set_highlight(None)
|
||||||
|
|
||||||
|
if item is None:
|
||||||
|
if event.ButtonDown():
|
||||||
|
self.animation.stop()
|
||||||
|
self.drag_action.abort()
|
||||||
|
|
||||||
|
# Get the drag action
|
||||||
|
self.drag_action = self.get_drag_action(event)
|
||||||
|
self.drag_action.on_button_press(event)
|
||||||
|
|
||||||
|
self.pressx = x
|
||||||
|
self.pressy = y
|
||||||
|
|
||||||
|
if event.Dragging() or event.Moving():
|
||||||
|
self.drag_action.on_motion_notify(event)
|
||||||
|
|
||||||
|
if event.ButtonUp():
|
||||||
|
self.drag_action.on_button_release(event)
|
||||||
|
self.drag_action = WxNullAction(self)
|
||||||
|
|
||||||
|
event.Skip()
|
||||||
|
|
||||||
|
|
||||||
|
def on_area_size_allocate(self, area, allocation):
|
||||||
|
if self.zoom_to_fit_on_resize:
|
||||||
|
self.zoom_to_fit()
|
||||||
|
|
||||||
|
def animate_to(self, x, y):
|
||||||
|
self.animation = ZoomToAnimation(self, x, y)
|
||||||
|
self.animation.start()
|
||||||
|
|
||||||
|
def window2graph(self, x, y):
|
||||||
|
"Get the x,y coordinates in the graph from the x,y coordinates in the window."""
|
||||||
|
width, height = self.GetSize()
|
||||||
|
x -= 0.5*width
|
||||||
|
y -= 0.5*height
|
||||||
|
x /= self.zoom_ratio
|
||||||
|
y /= self.zoom_ratio
|
||||||
|
x += self.x
|
||||||
|
y += self.y
|
||||||
|
return x, y
|
||||||
|
|
||||||
|
def get_url(self, x, y):
|
||||||
|
x, y = self.window2graph(x, y)
|
||||||
|
return self.graph.get_url(x, y)
|
||||||
|
|
||||||
|
def get_jump(self, x, y):
|
||||||
|
x, y = self.window2graph(x, y)
|
||||||
|
return self.graph.get_jump(x, y)
|
||||||
|
|
||||||
|
def set_filter(self, filter):
|
||||||
|
self.filter = filter
|
||||||
|
|
||||||
|
def set_dotcode(self, dotcode, filename='<stdin>'):
|
||||||
|
if isinstance(dotcode, unicode):
|
||||||
|
dotcode = dotcode.encode('utf8')
|
||||||
|
p = subprocess.Popen(
|
||||||
|
[self.filter, '-Txdot'],
|
||||||
|
stdin=subprocess.PIPE,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
shell=False,
|
||||||
|
universal_newlines=True
|
||||||
|
)
|
||||||
|
xdotcode, error = p.communicate(dotcode)
|
||||||
|
if p.returncode != 0:
|
||||||
|
print "ERROR PARSING DOT CODE", error
|
||||||
|
dialog = gtk.MessageDialog(type=gtk.MESSAGE_ERROR,
|
||||||
|
message_format=error,
|
||||||
|
buttons=gtk.BUTTONS_OK)
|
||||||
|
dialog.set_title('Dot Viewer')
|
||||||
|
dialog.run()
|
||||||
|
dialog.destroy()
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
self.set_xdotcode(xdotcode)
|
||||||
|
except ParseError, ex:
|
||||||
|
print "ERROR PARSING XDOT CODE"
|
||||||
|
dialog = gtk.MessageDialog(type=gtk.MESSAGE_ERROR,
|
||||||
|
message_format=str(ex),
|
||||||
|
buttons=gtk.BUTTONS_OK)
|
||||||
|
dialog.set_title('Dot Viewer')
|
||||||
|
dialog.run()
|
||||||
|
dialog.destroy()
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
self.openfilename = filename
|
||||||
|
return True
|
||||||
|
|
||||||
|
def set_xdotcode(self, xdotcode):
|
||||||
|
"""Set xdot code."""
|
||||||
|
#print xdotcode
|
||||||
|
parser = XDotParser(xdotcode)
|
||||||
|
self.graph = parser.parse()
|
||||||
|
self.highlight = None
|
||||||
|
#self.zoom_image(self.zoom_ratio, center=True)
|
||||||
|
|
||||||
|
def reload(self):
|
||||||
|
if self.openfilename is not None:
|
||||||
|
try:
|
||||||
|
fp = file(self.openfilename, 'rt')
|
||||||
|
self.set_dotcode(fp.read(), self.openfilename)
|
||||||
|
fp.close()
|
||||||
|
except IOError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class WxDotFrame(wx.Frame):
|
||||||
|
def __init__(self):
|
||||||
|
wx.Frame.__init__(self, None, -1, "Dot Viewer", size=(512,512))
|
||||||
|
|
||||||
|
vbox = wx.BoxSizer(wx.VERTICAL)
|
||||||
|
|
||||||
|
# Construct toolbar
|
||||||
|
toolbar = wx.ToolBar(self, -1)
|
||||||
|
toolbar.AddLabelTool(wx.ID_OPEN, 'Open File',
|
||||||
|
wx.ArtProvider.GetBitmap(wx.ART_FOLDER_OPEN,wx.ART_OTHER,(16,16)))
|
||||||
|
toolbar.AddLabelTool(wx.ID_HELP, 'Help',
|
||||||
|
wx.ArtProvider.GetBitmap(wx.ART_HELP,wx.ART_OTHER,(16,16)) )
|
||||||
|
toolbar.Realize()
|
||||||
|
|
||||||
|
self.Bind(wx.EVT_TOOL, self.DoOpenFile, id=wx.ID_OPEN)
|
||||||
|
self.Bind(wx.EVT_TOOL, self.ShowControlsDialog, id=wx.ID_HELP)
|
||||||
|
|
||||||
|
# Create dot widge
|
||||||
|
self.widget = WxDotWindow(self, -1)
|
||||||
|
|
||||||
|
# Add elements to sizer
|
||||||
|
vbox.Add(toolbar, 0, wx.EXPAND)
|
||||||
|
vbox.Add(self.widget, 100, wx.EXPAND | wx.ALL)
|
||||||
|
|
||||||
|
self.SetSizer(vbox)
|
||||||
|
self.Center()
|
||||||
|
|
||||||
|
def ShowControlsDialog(self,event):
|
||||||
|
dial = wx.MessageDialog(None,
|
||||||
|
"\
|
||||||
|
Pan: Arrow Keys\n\
|
||||||
|
Zoom: PageUp / PageDown\n\
|
||||||
|
Zoom To Fit: F\n\
|
||||||
|
Refresh: R",
|
||||||
|
'Keyboard Controls', wx.OK)
|
||||||
|
dial.ShowModal()
|
||||||
|
|
||||||
|
def DoOpenFile(self,event):
|
||||||
|
wcd = 'All files (*)|*|GraphViz Dot Files(*.dot)|*.dot|'
|
||||||
|
dir = os.getcwd()
|
||||||
|
open_dlg = wx.FileDialog(self, message='Choose a file', defaultDir=dir, defaultFile='',
|
||||||
|
wildcard=wcd, style=wx.OPEN|wx.CHANGE_DIR)
|
||||||
|
if open_dlg.ShowModal() == wx.ID_OK:
|
||||||
|
path = open_dlg.GetPath()
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.open_file(path)
|
||||||
|
|
||||||
|
except IOError, error:
|
||||||
|
dlg = wx.MessageDialog(self, 'Error opening file\n' + str(error))
|
||||||
|
dlg.ShowModal()
|
||||||
|
|
||||||
|
except UnicodeDecodeError, error:
|
||||||
|
dlg = wx.MessageDialog(self, 'Error opening file\n' + str(error))
|
||||||
|
dlg.ShowModal()
|
||||||
|
|
||||||
|
open_dlg.Destroy()
|
||||||
|
|
||||||
|
def OnExit(self, event):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def set_dotcode(self, dotcode, filename='<stdin>'):
|
||||||
|
if self.widget.set_dotcode(dotcode, filename):
|
||||||
|
self.SetTitle(os.path.basename(filename) + ' - Dot Viewer')
|
||||||
|
self.widget.zoom_to_fit()
|
||||||
|
|
||||||
|
def set_xdotcode(self, xdotcode, filename='<stdin>'):
|
||||||
|
if self.widget.set_xdotcode(xdotcode):
|
||||||
|
self.SetTitle(os.path.basename(filename) + ' - Dot Viewer')
|
||||||
|
self.widget.zoom_to_fit()
|
||||||
|
|
||||||
|
def open_file(self, filename):
|
||||||
|
try:
|
||||||
|
fp = file(filename, 'rt')
|
||||||
|
self.set_dotcode(fp.read(), filename)
|
||||||
|
fp.close()
|
||||||
|
except IOError, ex:
|
||||||
|
"""
|
||||||
|
dlg = gtk.MessageDialog(type=gtk.MESSAGE_ERROR,
|
||||||
|
message_format=str(ex),
|
||||||
|
buttons=gtk.BUTTONS_OK)
|
||||||
|
dlg.set_title('Dot Viewer')
|
||||||
|
dlg.run()
|
||||||
|
dlg.destroy()
|
||||||
|
"""
|
||||||
|
|
||||||
|
def set_filter(self, filter):
|
||||||
|
self.widget.set_filter(filter)
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue