Frage

I'm working on a PyQt application that displays an interactive network using d3 in a QWebView. When the user clicks on a node in this network, I'd like to send the node's id from the Javascript application to the PyQt application. I'm trying to get this to work with the pyqtSlot decorator, but I'm running into a problem.

I can successfully call the change_page method in Python from Javascript when I first load the HTML into the QWebView and expose ConnectionContainer. When I try to call the method within the click event handler though, "container", the name of the Python object in Javascript, doesn't seem to exist anymore and causes an error.

How do I store this Python object so that the mouse event handler can see it?

Python code:

from PyQt4.Qt import (QStandardItem, QStandardItemModel, Qt, QFont,
    QWebView, pyqtSlot, QObject)
from calibre.ebooks.oeb.display.webview import load_html

class TOCView (QWebView):

def __init__(self, *args):
    QWebView.__init__(self, *args)

    self.container = ConnectionContainer()
    self.page().mainFrame().addToJavaScriptWindowObject("container", self.container)
    self.load_network()

def set_manager(self, manager):
    self.container.manager = manager

def load_network(self):
    path = 'C:/Users/Emily/Documents/Spring 2014/MART510 eBook Research/book_renderer.html'
    load_html(path, self, codec=getattr(path, 'encoding', 'utf-8'), mime_type=getattr(path,
    'mime_type', 'text/html'))

class ConnectionContainer(QObject):

def __init__(self):
    QObject.__init__(self)
    self.manager = None

def set_manager(self, manager):
    self.manager = manager

@pyqtSlot(float)
def change_page(self, page):
    print ("changed page")
    if self.manager is not None:
        self.manager.goto_page(page)

HTML/Javascript code:

  <!DOCTYPE html> 
  <meta charset="utf-8">
  <body> 
 <style> 
.link {  
stroke: #666;
opacity: 0.6;
stroke-width: 1.5px; 
} 
.node circle { 
stroke: #fff; 
opacity: 0.6;
stroke-width: 1.5px; 
} 
text { 
font: 7px serif; 
opacity: 0.6;
pointer-events: none; 
} 
</style> 

<script src=d3.v3.min.js></script>

<script> 
d3.json("eBookGraph.json", dataLoaded)

function dataLoaded(data) {
        var links = data.links
        var nodes = data.nodes

    // Compute the distinct nodes from the links.
    links.forEach(function(link) {
        link.source = nodes[link.source] || 
        (nodes[link.source] = {name: link.source});
        link.target = nodes[link.target] || 
        (nodes[link.target] = {name: link.target});
        link.value = +link.value;
    });

    var width = 500
    height = 500;

    var force = d3.layout.force() 
        .nodes(d3.values(nodes)) 
        .links(links) 
        .size([width, height]) 
        .linkDistance(50) 
        .charge(-200) 
        .on("tick", tick) 
        .start(); 

    var svg = d3.select("body")
        .append("svg")
          .attr({
            "width": "100%",
            "height": "100%"
          })
          .attr("viewBox", "0 0 " + width + " " + height )
          .attr("preserveAspectRatio", "xMidYMid meet")
          .attr("pointer-events", "all")
        .call(d3.behavior.zoom().on("zoom", redraw));

    var vis = svg
        .append('svg:g');

    function redraw() {
      vis.attr("transform",
          "translate(" + d3.event.translate + ")"
          + " scale(" + d3.event.scale + ")");
    }

    var link = svg.selectAll(".link") 
        .data(force.links()) 
        .enter().append("line") 
        .attr("class", "link")
        .style("opacity", 0.4); 

    var node = svg.selectAll(".node") 
        .data(force.nodes()) 
        .enter().append("g") 
        .attr("class", "node") 
        .on("mouseover", mouseover) 
        .on("mouseout", mouseout) 
        .on("click", click)
        .on("dblclick", dblclick)
        .call(force.drag); 

    node.append("circle") 
        .attr("r", 8)
        .style("fill", "#3182bd");

    node.append("text") 
        .attr("x", 12) 
        .attr("dy", ".35em") 
        .attr("page", function(d) { return d.id; })
        .style("fill", "#000000")
        .text(function(d) { return d.title; });

    function tick() { 
        link 
        .attr("x1", function(d) { return d.source.x; }) 
        .attr("y1", function(d) { return d.source.y; }) 
        .attr("x2", function(d) { return d.target.x; }) 
        .attr("y2", function(d) { return d.target.y; }); 

        node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; }); 
    }

    // "change page" printed to console here
    container.change_page(1.0)

    function mouseover() { 
    } 

    function mouseout() { 
    } 

    function click() {
        pageNum = d3.select(this).select("text").attr("page")
        alert(pageNum)

        // error here
        container.change_page(pageNum)
        alert("4")
    }

    function dblclick() {
    }
}



</script>
 </body>
War es hilfreich?

Lösung

It turned out I needed to expose the container object after the HTML finished loading.

class TOCView (QWebView):

    def __init__(self, *args):
        QWebView.__init__(self, *args)

        self.manager = None
        self.loadFinished.connect(self.load_finished)
        self.load_network()

    def load_finished(self):
        self.page().mainFrame().addToJavaScriptWindowObject("container", self)

    def set_manager(self, manager):
        self.manager = manager

    def load_network(self):
        path = 'C:/Users/Emily/Documents/Spring 2014/MART510 eBook Research/book_renderer.html'
        load_html(path, self, codec=getattr(path, 'encoding', 'utf-8'), mime_type=getattr(path,
        'mime_type', 'text/html'))

    @pyqtSlot(float)
    def change_page(self, page):
        print ("changed page: " + str(page))
        if self.manager is not None:
            self.manager.goto_page(page)
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top