Esempio n. 1
0
    def data2scene(self, data2scene):
        self._data2scene = data2scene
        self.scene2data, isInvertible = data2scene.inverted()
        assert isInvertible

        for patchNr in range(self._patchAccessor.patchCount):
            # the patch accessor uses the data coordinate system.
            # because the patch is drawn on the screen, its holds coordinates
            # corresponding to Qt's QGraphicsScene's system, which need to be
            # converted to scene coordinates

            # the image rectangle includes an overlap margin
            imageRectF = data2scene.mapRect(
                self._patchAccessor.patchRectF(patchNr, self.overlap))

            # the patch rectangle has per default no overlap
            patchRectF = data2scene.mapRect(
                self._patchAccessor.patchRectF(patchNr, 0))

            # add a little overlap when the overlap_draw setting is
            # activated
            if self._overlap_draw != 0:
                patchRectF = QRectF(
                    patchRectF.x() - self._overlap_draw,
                    patchRectF.y() - self._overlap_draw,
                    patchRectF.width() + 2 * self._overlap_draw,
                    patchRectF.height() + 2 * self._overlap_draw)

            patchRect = QRect(round(patchRectF.x()), round(patchRectF.y()),
                              round(patchRectF.width()),
                              round(patchRectF.height()))

            # the image rectangles of neighboring patches can overlap
            # slightly, to account for inaccuracies in sub-pixel
            # rendering of many ImagePatch objects
            imageRect = QRect(round(imageRectF.x()), round(imageRectF.y()),
                              round(imageRectF.width()),
                              round(imageRectF.height()))

            self.imageRectFs[patchNr] = imageRectF
            self.dataRectFs[patchNr] = imageRectF
            self.tileRectFs[patchNr] = patchRectF
            self.imageRects[patchNr] = imageRect
            self.tileRects[patchNr] = patchRect
Esempio n. 2
0
    def data2scene(self, data2scene):
        self._data2scene = data2scene
        self.scene2data, isInvertible = data2scene.inverted()
        assert isInvertible

        for patchNr in range(self._patchAccessor.patchCount):
            # the patch accessor uses the data coordinate system.
            # because the patch is drawn on the screen, its holds coordinates
            # corresponding to Qt's QGraphicsScene's system, which need to be
            # converted to scene coordinates

            # the image rectangle includes an overlap margin
            imageRectF = data2scene.mapRect(self._patchAccessor.patchRectF(patchNr, self.overlap))

            # the patch rectangle has per default no overlap
            patchRectF = data2scene.mapRect(self._patchAccessor.patchRectF(patchNr, 0))

            # add a little overlap when the overlap_draw setting is
            # activated
            if self._overlap_draw != 0:
                patchRectF = QRectF(patchRectF.x() - self._overlap_draw,
                                    patchRectF.y() - self._overlap_draw,
                                    patchRectF.width() + 2 * self._overlap_draw,
                                    patchRectF.height() + 2 * self._overlap_draw)

            patchRect = QRect(round(patchRectF.x()),
                              round(patchRectF.y()),
                              round(patchRectF.width()),
                              round(patchRectF.height()))

            # the image rectangles of neighboring patches can overlap
            # slightly, to account for inaccuracies in sub-pixel
            # rendering of many ImagePatch objects
            imageRect = QRect(round(imageRectF.x()),
                              round(imageRectF.y()),
                              round(imageRectF.width()),
                              round(imageRectF.height()))

            self.imageRectFs[patchNr] = imageRectF
            self.dataRectFs[ patchNr] = imageRectF
            self.tileRectFs[ patchNr] = patchRectF
            self.imageRects[ patchNr] = imageRect
            self.tileRects[  patchNr] = patchRect
    def relayout(self):
        """Approximate Fruchterman-Reingold spring layout"""
        nodes = list(self.nodes.values())
        pos = np.array([(np.cos(i / len(nodes) * 2 * np.pi + np.pi / 4),
                         np.sin(i / len(nodes) * 2 * np.pi + np.pi / 4))
                        for i in range(1, 1 + len(nodes))])
        K = 1 / np.sqrt(pos.shape[0])
        GRAVITY, ITERATIONS = 10, 20
        TEMPERATURES = np.linspace(.3, .01, ITERATIONS)
        for temp in chain([.8, .5], TEMPERATURES):
            # Repulsive forces
            delta = pos[:, np.newaxis, :] - pos
            delta /= np.abs(delta).sum(
                2)[:, :, np.newaxis]**2  # NOTE: This warning was expected
            delta = np.nan_to_num(delta)  # Reverse the effect of zero-division
            disp = -delta.sum(0) * K * K
            # Attractive forces
            for edge in self.edges:
                n1, n2 = nodes.index(edge.source), nodes.index(edge.dest)
                delta = pos[n1] - pos[n2]
                magnitude = np.abs(delta).sum()
                disp[n1] -= delta * magnitude / K
                disp[n2] += delta * magnitude / K
            # Gravity; tend toward center
            magnitude = np.sqrt(np.sum(np.abs(pos)**2, 1))
            disp -= (pos.T * K * GRAVITY * magnitude).T
            # Limit max displacement and reposition
            magnitude = np.sqrt(np.sum(np.abs(disp)**2, 1))
            pos += (disp.T / magnitude).T * np.clip(np.abs(disp), 0, temp)

        for node, position in zip(nodes, 500 * pos):
            node.setPos(*position)
        for edge in self.edges:
            edge.adjust()

        MARGIN, rect = 10, self.scene().itemsBoundingRect()
        rect = QRectF(rect.x() - MARGIN,
                      rect.y() - MARGIN,
                      rect.width() + 2 * MARGIN,
                      rect.height() + 2 * MARGIN)
        self.scene().setSceneRect(rect)
        self.scene().invalidate()
Esempio n. 4
0
    def relayout(self):
        """Approximate Fruchterman-Reingold spring layout"""
        nodes = list(self.nodes.values())
        pos = np.array(
            [
                (np.cos(i / len(nodes) * 2 * np.pi + np.pi / 4), np.sin(i / len(nodes) * 2 * np.pi + np.pi / 4))
                for i in range(1, 1 + len(nodes))
            ]
        )
        K = 1 / np.sqrt(pos.shape[0])
        GRAVITY, ITERATIONS = 10, 20
        TEMPERATURES = np.linspace(0.3, 0.01, ITERATIONS)
        for temp in chain([0.8, 0.5], TEMPERATURES):
            # Repulsive forces
            delta = pos[:, np.newaxis, :] - pos
            delta /= np.abs(delta).sum(2)[:, :, np.newaxis] ** 2  # NOTE: This warning was expected
            delta = np.nan_to_num(delta)  # Reverse the effect of zero-division
            disp = -delta.sum(0) * K * K
            # Attractive forces
            for edge in self.edges:
                n1, n2 = nodes.index(edge.source), nodes.index(edge.dest)
                delta = pos[n1] - pos[n2]
                magnitude = np.abs(delta).sum()
                disp[n1] -= delta * magnitude / K
                disp[n2] += delta * magnitude / K
            # Gravity; tend toward center
            magnitude = np.sqrt(np.sum(np.abs(pos) ** 2, 1))
            disp -= (pos.T * K * GRAVITY * magnitude).T
            # Limit max displacement and reposition
            magnitude = np.sqrt(np.sum(np.abs(disp) ** 2, 1))
            pos += (disp.T / magnitude).T * np.clip(np.abs(disp), 0, temp)

        for node, position in zip(nodes, 500 * pos):
            node.setPos(*position)
        for edge in self.edges:
            edge.adjust()

        MARGIN, rect = 10, self.scene().itemsBoundingRect()
        rect = QRectF(rect.x() - MARGIN, rect.y() - MARGIN, rect.width() + 2 * MARGIN, rect.height() + 2 * MARGIN)
        self.scene().setSceneRect(rect)
        self.scene().invalidate()
Esempio n. 5
0
    def overlay_for(pt1, pt2, frequency):
        # Construct the line-geometry, we'll use this to construct the ellipsoid
        line = QLineF(pt1, pt2)

        # Determine the radius for the ellipsoid
        radius = fresnel_radius(line.length(), frequency)

        # Draw the ellipsoid
        zone = QPainterPath()
        zone.addEllipse(QPointF(0., 0.), line.length() / 2, radius)

        # Rotate the ellipsoid - same angle as the line
        transform = QTransform()
        transform.rotate(-line.angle())
        zone = transform.map(zone)

        # Center the zone over the line
        lc = QRectF(pt1, pt2).center()
        zc = zone.boundingRect().center()
        zone.translate(lc.x() - zc.x(), lc.y() - zc.y())

        return line, zone
Esempio n. 6
0
class XYGraphicsScene(QGraphicsScene):
    def __init__(self, parent):
        QGraphicsScene.__init__(self, parent)

        self.cc_x = 1
        self.cc_y = 2
        self.m_channels = []
        self.m_mouseLock = False
        self.m_smooth = False
        self.m_smooth_x = 0
        self.m_smooth_y = 0

        self.setBackgroundBrush(Qt.black)

        cursorPen   = QPen(QColor(255, 255, 255), 2)
        cursorBrush = QColor(255, 255, 255, 50)
        self.m_cursor = self.addEllipse(QRectF(-10, -10, 20, 20), cursorPen, cursorBrush)

        linePen = QPen(QColor(200, 200, 200, 100), 1, Qt.DashLine)
        self.m_lineH = self.addLine(-9999, 0, 9999, 0, linePen)
        self.m_lineV = self.addLine(0, -9999, 0, 9999, linePen)

        self.p_size = QRectF(-100, -100, 100, 100)

    def setControlX(self, x):
        self.cc_x = x

    def setControlY(self, y):
        self.cc_y = y

    def setChannels(self, channels):
        self.m_channels = channels

    def setPosX(self, x, forward=True):
        if not self.m_mouseLock:
            pos_x = x * (self.p_size.x() + self.p_size.width())
            self.m_cursor.setPos(pos_x, self.m_cursor.y())
            self.m_lineV.setX(pos_x)

            if forward:
                self.sendMIDI(pos_x / (self.p_size.x() + self.p_size.width()), None)
            else:
                self.m_smooth_x = pos_x

    def setPosY(self, y, forward=True):
        if not self.m_mouseLock:
            pos_y = y * (self.p_size.y() + self.p_size.height())
            self.m_cursor.setPos(self.m_cursor.x(), pos_y)
            self.m_lineH.setY(pos_y)

            if forward:
                self.sendMIDI(None, pos_y / (self.p_size.y() + self.p_size.height()))
            else:
                self.m_smooth_y = pos_y

    def setSmooth(self, smooth):
        self.m_smooth = smooth

    def setSmoothValues(self, x, y):
        self.m_smooth_x = x * (self.p_size.x() + self.p_size.width())
        self.m_smooth_y = y * (self.p_size.y() + self.p_size.height())

    def handleCC(self, param, value):
        sendUpdate = False
        xp = yp = 0.0

        if param == self.cc_x:
            sendUpdate = True
            xp = (float(value) / 63) - 1.0
            yp = self.m_cursor.y() / (self.p_size.y() + self.p_size.height())

            if xp < -1.0:
                xp = -1.0
            elif xp > 1.0:
                xp = 1.0

            self.setPosX(xp, False)

        if param == self.cc_y:
            sendUpdate = True
            xp = self.m_cursor.x() / (self.p_size.x() + self.p_size.width())
            yp = (float(value) / 63) - 1.0

            if yp < -1.0:
                yp = -1.0
            elif yp > 1.0:
                yp = 1.0

            self.setPosY(yp, False)

        if sendUpdate:
            self.emit(SIGNAL("cursorMoved(double, double)"), xp, yp)

    def handleMousePos(self, pos):
        if not self.p_size.contains(pos):
            if pos.x() < self.p_size.x():
                pos.setX(self.p_size.x())
            elif pos.x() > self.p_size.x() + self.p_size.width():
                pos.setX(self.p_size.x() + self.p_size.width())

            if pos.y() < self.p_size.y():
                pos.setY(self.p_size.y())
            elif pos.y() > self.p_size.y() + self.p_size.height():
                pos.setY(self.p_size.y() + self.p_size.height())

        self.m_smooth_x = pos.x()
        self.m_smooth_y = pos.y()

        if not self.m_smooth:
            self.m_cursor.setPos(pos)
            self.m_lineH.setY(pos.y())
            self.m_lineV.setX(pos.x())

            xp = pos.x() / (self.p_size.x() + self.p_size.width())
            yp = pos.y() / (self.p_size.y() + self.p_size.height())

            self.sendMIDI(xp, yp)
            self.emit(SIGNAL("cursorMoved(double, double)"), xp, yp)

    def sendMIDI(self, xp=None, yp=None):
        global jack_midi_out_data
        rate = float(0xff) / 4

        if xp != None:
            value = int((xp * rate) + rate)
            for channel in self.m_channels:
                jack_midi_out_data.put_nowait((0xB0 + channel - 1, self.cc_x, value))

        if yp != None:
            value = int((yp * rate) + rate)
            for channel in self.m_channels:
                jack_midi_out_data.put_nowait((0xB0 + channel - 1, self.cc_y, value))

    def updateSize(self, size):
        self.p_size.setRect(-(size.width() / 2), -(size.height() / 2), size.width(), size.height())

    def updateSmooth(self):
        if self.m_smooth:
            if self.m_cursor.x() != self.m_smooth_x or self.m_cursor.y() != self.m_smooth_y:
                if abs(self.m_cursor.x() - self.m_smooth_x) <= 0.001:
                    self.m_smooth_x = self.m_cursor.x()
                    return
                elif abs(self.m_cursor.y() - self.m_smooth_y) <= 0.001:
                    self.m_smooth_y = self.m_cursor.y()
                    return

                new_x = (self.m_smooth_x + self.m_cursor.x() * 3) / 4
                new_y = (self.m_smooth_y + self.m_cursor.y() * 3) / 4
                pos = QPointF(new_x, new_y)

                self.m_cursor.setPos(pos)
                self.m_lineH.setY(pos.y())
                self.m_lineV.setX(pos.x())

                xp = pos.x() / (self.p_size.x() + self.p_size.width())
                yp = pos.y() / (self.p_size.y() + self.p_size.height())

                self.sendMIDI(xp, yp)
                self.emit(SIGNAL("cursorMoved(double, double)"), xp, yp)

    def keyPressEvent(self, event):
        event.accept()

    def wheelEvent(self, event):
        event.accept()

    def mousePressEvent(self, event):
        self.m_mouseLock = True
        self.handleMousePos(event.scenePos())
        QGraphicsScene.mousePressEvent(self, event)

    def mouseMoveEvent(self, event):
        self.handleMousePos(event.scenePos())
        QGraphicsScene.mouseMoveEvent(self, event)

    def mouseReleaseEvent(self, event):
        self.m_mouseLock = False
        QGraphicsScene.mouseReleaseEvent(self, event)
Esempio n. 7
0
    def draw_text(self, txt, txtlen, window):
        if ((txtlen>0) and not ((txt == self.cursor_char) and (self._cursor_visible == False))): # If there IS something to print
            if (self.pbuffer_painter[window.id] == None):
                self.brush.setColor(self.ztoq_color[self.cur_bg])
                self.pbuffer_painter[window.id] = QPainter(self.pbuffer[window.id])
                self.pbuffer_painter[window.id].setPen(self.ztoq_color[self.cur_fg])
                self.pbuffer_painter[window.id].setBackground(self.brush)

            painter = self.pbuffer_painter[window.id]

            # @type window ZWindow
            if (window.cursor == None):
                if (window.id == 0): # Main window
                    window.set_cursor_position(1, self.height)
                    window.set_cursor_real_position(2, self.height*(self.linesize-1))
                else:
                    window.set_cursor_position(1, 1)
                    window.set_cursor_real_position(2, self.linesize-1)

            if (txt=='\n'):
                if (window.cursor[1]==self.height):
                    if (window.scrolling):
                        self.scroll(painter)
                    window.set_cursor_position(1, window.cursor[1])
                    window.set_cursor_real_position(2, window.cursor_real_pos[1])
                else:
                    window.set_cursor_position(1, window.cursor[1]+1)
                    window.set_cursor_real_position(2, window.cursor_real_pos[1]+self.linesize)
            else:
                rect = QRectF(window.cursor_real_pos[0], window.cursor_real_pos[1], self.pbuffer[window.id].width()-window.cursor_real_pos[0], self.linesize)

                painter.setFont(self.font())
                #painter.setRenderHint(QPainter.TextAntialiasing)
                if (self._input_buffer_printing == False):
                    painter.setBackgroundMode(Qt.OpaqueMode)
                else:
                    painter.setBackgroundMode(Qt.TransparentMode)
                bounding_rect = painter.boundingRect(rect,txt)
                if (rect.contains(bounding_rect)):
                    #print rect.x(), rect.y(), rect.width(),rect.height(), txt, bounding_rect
                    painter.drawText(bounding_rect, txt)
                    if txt != self.cursor_char:
                        window.set_cursor_position(window.cursor[0]+txtlen, window.cursor[1])
                        window.set_cursor_real_position(rect.x()+bounding_rect.width(), rect.y())
                else: # There is not enough space
                    #print "Not enough space to print:", txt
                    self.scroll(painter)
                    window.set_cursor_position(1, self.height)
                    window.set_cursor_real_position(2, self.height*(self.linesize-1))
                    rect.setX(2)
                    rect.setY(window.cursor_real_pos[1])
                    rect.setWidth(self.pbuffer[window.id].width()-window.cursor_real_pos[0])
                    rect.setHeight(self.linesize)
                    bounding_rect = painter.boundingRect(rect,txt)
                    painter.drawText(bounding_rect, txt)
                    if txt != self.cursor_char:
                        window.set_cursor_position(window.cursor[0]+txtlen, window.cursor[1])
                        window.set_cursor_real_position(rect.x()+bounding_rect.width(), rect.y())
Esempio n. 8
0
    def layout(self,
               scene,
               nodes,
               center=None,
               padX=None,
               padY=None,
               direction=None,
               animationGroup=None):
        """
        Lays out the nodes for this scene based on a block layering algorithm.
        
        :param      scene          | <XNodeScene>
                    nodes          | [<XNode>, ..]
                    center         | <QPointF> || None
                    padX           | <int> || None
                    padY           | <int> || None
                    direction      | <Qt.Direction>
                    animationGroup | <QAnimationGroup> || None
        
        :return     {<XNode>: <QRectF>, ..} | new rects per affected node
        """
        nodes = filter(lambda x: x is not None and x.isVisible(), nodes)

        # make sure we have at least 1 node, otherwise, it is already laid out
        if not nodes or len(nodes) == 1:
            return {}

        # calculate the default padding based on the scene
        if padX == None:
            if direction == Qt.Vertical:
                padX = 2 * scene.cellWidth()
            else:
                padX = 4 * scene.cellWidth()

        if padY == None:
            if direction == Qt.Vertical:
                padY = 4 * scene.cellHeight()
            else:
                padY = 2 * scene.cellWidth()

        # step 1: create a mapping of the connections
        connection_map = self.connectionMap(scene, nodes)

        # step 2: organize the nodes into layers based on their connection chain
        layers = self.generateLayers(scene, nodes, connection_map)
        layers = list(reversed(layers))

        # step 3: calculate the total dimensions for the layout
        bounds = QRectF()

        # step 3.1: compare the nodes together that have common connections
        layer_widths = []
        layer_heights = []
        node_heights = {}
        node_widths = {}

        for layer_index, layer in enumerate(layers):
            layer_w = 0
            layer_h = 0

            layer_node_w = []
            layer_node_h = []

            self.organizeLayer(layer, connection_map)

            for node in layer:
                rect = node.rect()

                layer_node_w.append(rect.width())
                layer_node_h.append(rect.height())

                if direction == Qt.Vertical:
                    layer_w += rect.width()
                    layer_h = max(rect.height(), layer_h)
                else:
                    layer_w = max(rect.width(), layer_w)
                    layer_h += rect.height()

            # update the bounding area
            if direction == Qt.Vertical:
                layer_w += padX * 1 - len(layer)
                bounds.setWidth(max(layer_w, bounds.width()))
                bounds.setHeight(bounds.height() + layer_h)
            else:
                layer_h += padY * 1 - len(layer)
                bounds.setWidth(bounds.width() + layer_w)
                bounds.setHeight(max(layer_h, bounds.height()))

            node_widths[layer_index] = layer_node_w
            node_heights[layer_index] = layer_node_h

            layer_widths.append(layer_w)
            layer_heights.append(layer_h)

        if not center:
            center = scene.sceneRect().center()

        w = bounds.width()
        h = bounds.height()
        bounds.setX(center.x() - bounds.width() / 2.0)
        bounds.setY(center.y() - bounds.height() / 2.0)
        bounds.setWidth(w)
        bounds.setHeight(h)

        # step 4: assign positions for each node by layer
        processed_nodes = {}
        layer_grps = [(i, layer) for i, layer in enumerate(layers)]
        layer_grps.sort(key=lambda x: len(x[1]))

        for layer_index, layer in reversed(layer_grps):
            layer_width = layer_widths[layer_index]
            layer_height = layer_heights[layer_index]

            # determine the starting point for this layer
            if direction == Qt.Vertical:
                offset = layer_index * padY + sum(layer_heights[:layer_index])
                point = QPointF(bounds.x(), offset + bounds.y())
            else:
                offset = layer_index * padX + sum(layer_widths[:layer_index])
                point = QPointF(offset + bounds.x(), bounds.y())

            # assign node positions based on existing connections
            for node_index, node in enumerate(layer):
                max_, min_ = (None, None)
                inputs, outputs = connection_map[node]
                for connected_node in inputs + outputs:
                    if not connected_node in processed_nodes:
                        continue

                    npos = processed_nodes[connected_node]
                    nrect = connected_node.rect()
                    rect = QRectF(npos.x(), npos.y(), nrect.width(),
                                  nrect.height())

                    if direction == Qt.Vertical:
                        if min_ is None:
                            min_ = rect.left()
                        min_ = min(rect.left(), min_)
                        max_ = max(rect.right(), max_)
                    else:
                        if min_ is None:
                            min_ = rect.top()
                        min_ = min(rect.top(), min_)
                        max_ = max(rect.bottom(), max_)

                if direction == Qt.Vertical:
                    off_x = 0
                    off_y = (layer_height - node.rect().height()) / 2.0
                    start_x = (bounds.width() - layer_width)
                    start_y = 0
                else:
                    off_x = (layer_width - node.rect().width()) / 2.0
                    off_y = 0
                    start_x = 0
                    start_y = (bounds.height() - layer_height)

                # align against existing nodes
                if not None in (min_, max):
                    if direction == Qt.Vertical:
                        off_x = (max_ - min_) / 2.0 - node.rect().width() / 2.0
                        point_x = min_ + off_x
                        point_y = point.y() + off_y
                    else:
                        off_y = (max_ -
                                 min_) / 2.0 - node.rect().height() / 2.0
                        point_x = point.x() + off_x
                        point_y = min_ + off_y

                # otherwise, align based on its position in the layer
                else:
                    if direction == Qt.Vertical:
                        off_x = sum(node_widths[layer_index][:node_index])
                        off_x += node_index * padX
                        off_x += start_x

                        point_x = point.x() + off_x
                        point_y = point.y() + off_y
                    else:
                        off_y = sum(node_heights[layer_index][:node_index])
                        off_y += node_index * padY
                        off_y += start_y

                        point_x = point.x() + off_x
                        point_y = point.y() + off_y

                if not animationGroup:
                    node.setPos(point_x, point_y)
                else:
                    anim = XNodeAnimation(node, 'setPos')
                    anim.setStartValue(node.pos())
                    anim.setEndValue(QPointF(point_x, point_y))
                    animationGroup.addAnimation(anim)

                processed_nodes[node] = QPointF(point_x, point_y)

                if self._testing:
                    QApplication.processEvents()
                    time.sleep(1)

        return processed_nodes
Esempio n. 9
0
class SlideshowFrame(object):
    def __init__(self, win, rect, filename, metadata):
        self.win = win
        self.rect = rect
        self.filename = filename
        self.metadata = metadata

        if self.metadata:
            self.line1 = self.metadata['Name']
            self.line2 = "%s Mission" % (self.metadata['Mission'])
            self.line3 = "%s" % (self.metadata['Time'])
        else:
            self.line1 = ""
            self.line2 = ""
            self.line3 = ""

        self.image = QGraphicsPixmapItem()
        self.win.scene.addItem(self.image)
        self.image.setTransformationMode(Qt.SmoothTransformation)

        self.use_two_lines = True

        self.fontsize1 = 32
        self.fontsize2 = 26
        self.fontsize3 = 24

        self.font1 = QFont('Times New Roman', self.fontsize1)
        self.font2 = QFont('Times New Roman', self.fontsize2)
        self.font3 = QFont('Times New Roman', self.fontsize3)

        self.title1 = self.win.scene.addText(self.line1, self.font1)
        self.title1.setDefaultTextColor(Qt.white)
        self.title1.setVisible(False)

        self.title2 = self.win.scene.addText(self.line2, self.font2)
        self.title2.setDefaultTextColor(Qt.white)
        self.title2.setVisible(False)

        self.title3 = self.win.scene.addText(self.line3, self.font3)
        self.title3.setDefaultTextColor(Qt.white)
        self.title3.setVisible(False)

        self.reservedHeight = 128
        self.padding = 20

        self.hide()

    def move(self, x, y):
        self.rect = QRectF(x, y, self.rect.width(), self.rect.height())

    def __rotate(self, metadata, origImgSize):
        # Qt only handles orientation properly from v5.5
        try:
            # try directly to get the tag, because sometimes get_tags() returns
            # tags that don't actually are in the file
            rot = metadata['Exif.Image.Orientation']
        except KeyError:
            # guess :-/
            rot = '1'

        # see http://www.daveperrett.com/images/articles/2012-07-28-exif-orientation-handling-is-a-ghetto/EXIF_Orientations.jpg
        # we have to 'undo' the rotations, so the numbers are negative
        if rot == '1':
            rotate = 0
            imgSize = origImgSize
        if rot == '8':
            rotate = -90
            imgSize = QSize(origImgSize.height(), origImgSize.width())
        if rot == '3':
            rotate = -180
            imgSize = origImgSize
        if rot == '6':
            rotate = -270
            imgSize = QSize(origImgSize.height(), origImgSize.width())

        # undo the last rotation and apply the new one
        self.image.setRotation(rotate)

        return imgSize

    def __zoomFit(self, imgSize):

        reservedHeight = self.reservedHeight + self.padding * 2

        hZoom = self.rect.width() / imgSize.width()
        vZoom = (self.rect.height() - reservedHeight) / imgSize.height()
        scale = min(hZoom, vZoom)

        self.image.setScale(scale)

        width = imgSize.width() * scale
        height = imgSize.height() * scale
        self.image.setPos(
            (self.rect.width() - width) / 2 + self.rect.x(),
            (self.rect.height() - reservedHeight - height) / 2 + self.rect.y())

    def layoutText(self):
        reservedHeight = self.reservedHeight + self.padding
        vertical_spacing = (self.reservedHeight -
                            self.title1.boundingRect().height() -
                            self.title2.boundingRect().height() -
                            self.title3.boundingRect().height()) / 3

        x = (self.rect.width() - self.title1.boundingRect().width()) / 2
        y = self.rect.height() - reservedHeight + vertical_spacing
        self.title1.setPos(x + self.rect.x(), y + self.rect.y())

        x = (self.rect.width() - self.title2.boundingRect().width()) / 2
        y = self.rect.height(
        ) - reservedHeight + vertical_spacing * 2 + self.title1.boundingRect(
        ).height()
        self.title2.setPos(x + self.rect.x(), y + self.rect.y())

        x = (self.rect.width() - self.title3.boundingRect().width()) / 2
        y = self.rect.height(
        ) - reservedHeight + vertical_spacing * 3 + self.title1.boundingRect(
        ).height() + self.title2.boundingRect().height()
        self.title3.setPos(x + self.rect.x(), y + self.rect.y())

    def show(self):
        img = QPixmap(self.filename)

        try:
            metadata = GExiv2.Metadata(self.filename)
        except GLib.Error as e:
            print(repr(e))
            return

        self.image.setPixmap(img)
        self.image.setScale(1.0)
        self.image.setRotation(0)

        imgSize = self.__rotate(metadata, img.size())
        self.__zoomFit(imgSize)
        self.layoutText()

        self.title1.setVisible(True)
        self.title2.setVisible(True)
        self.title3.setVisible(True)
        self.image.setVisible(True)

    def hide(self):
        self.title1.setVisible(False)
        self.title2.setVisible(False)
        self.title3.setVisible(False)
        self.image.setVisible(False)
        self.image.setPixmap(QPixmap())

    def showImage(self, file, md):
        self.metadata = md

        try:
            metadata = GExiv2.Metadata(file)
        except GLib.Error as e:
            print(repr(e))
            return

        img = QPixmap(file)

        self.image.setPixmap(img)
        self.image.setScale(1.0)
        self.image.setRotation(0)

        imgSize = self.__rotate(metadata, img.size())

        self.__zoomFit(imgSize)
        self.layoutMetadata()