示例#1
0
class VideoContainer:
    """A Container handling a master QWidget that encloses a video QWidget, and if required other widgets as well (buttons, alerts, etc.)

    Does not handle x-screen change (this should be done by the parent widget)
    """

    class Signals(QtCore.QObject):
        close = QtCore.Signal(object)
        drop  = QtCore.Signal(object)

    # Right-click pop-up menu
    # Adding installed cameras here could be done as follows: create this
    # class and the callbacks dynamically

    class RightClickMenu(QuickMenu):
        title = "popup"
        elements = [
            QuickMenuSection(title="Choose Action"),
            QuickMenuElement(
                title="Maximize / Normalize Size",
                method_name="maximize"),
            QuickMenuElement(title="Remove Camera")
        ]

    class ContainerWidget(QtWidgets.QWidget):
        """Main level widget: this contains the VideoWidget and additionally, alert widgets, button for changing x screen, etc.
        """
        # TODO: connect close signal

        def __init__(self, parent):
            super().__init__(parent)
            self.setSizePolicy(
                QtWidgets.QSizePolicy.Expanding,
                QtWidgets.QSizePolicy.Expanding)

    class VideoWidget(QtWidgets.QFrame):
        """The video appears here.  Must handle drag'n'drop of camera info.  Or choosing camera from a pop-up menu.
        """

        def __init__(self, parent=None, mouse_gesture_handler=lambda e: None):
            super().__init__(parent)
            # self.setStyleSheet("background-color: dark-blue")
            # self.setStyleSheet("background-color: rgba(0,0,0,0)");
            # self.setStyleSheet("border: 1px solid gray; border-radius: 10px; margin: 0px; padding: 0px; background: white;")
            self.setStyleSheet(style.video_widget)
            self.setAutoFillBackground(True)
            self.setAcceptDrops(True)
            
            self.signals = VideoContainer.Signals()
            
            self.device = None
            
            """
            pal = QtGui.QPalette()
            pal.setColor(QtGui.QPalette.Background, QtCore.Qt.black);
            self.setPalette(pal);
            """
            self.setSizePolicy(
                QtWidgets.QSizePolicy.Expanding,
                QtWidgets.QSizePolicy.Expanding)
            self.mouse_click_ctx = MouseClickContext(mouse_gesture_handler)

        # TODO: how we communicate, say, a double-click, to the higher-level widget
        # VideoContainer .. give it a callback that hides/shows all other
        # widgets .. (stream decoding should be paused as well if needed)

        def setDevice(self, device):
            self.device = device

        def dragEnterEvent(self, e):
            print("VideoWidget: dragEnterEvent")
            e.accept()

        def dropEvent(self, e):
            print("VideoWidget: dropEvent")
            # print("VideoWidget: drop event to i,j=",self.row_index,self.column_index)
            formlist = e.mimeData().formats()
            if ("application/octet-stream" in formlist):
                device = pickle.loads(
                    e.mimeData().data("application/octet-stream").data())
                print("VideoWidget: got: ", device) # DataModel.RTSPCameraDevice
                self.signals.drop.emit(device)
                e.accept()
            else:
                e.ignore()

        def mousePressEvent(self, e):
            print("VideoWidget: mousePress")
            self.mouse_click_ctx.atPressEvent(e)
            super().mousePressEvent(e)
            
        def mouseMoveEvent(self, e):
            if not (e.buttons() & QtCore.Qt.LeftButton):
                return
            
            leni = ( e.pos() - self.mouse_click_ctx.info.pos ).manhattanLength()
            
            if (leni < QtWidgets.QApplication.startDragDistance()):
                return

            drag = QtGui.QDrag(self)
            mimeData = QtCore.QMimeData()

            mimeData.setData("application/octet-stream", 
                             pickle.dumps(self.device)
                             # pickle.dumps(None)
                             )
            drag.setMimeData(mimeData)

            dropAction = drag.exec_(QtCore.Qt.CopyAction | QtCore.Qt.MoveAction)
            
            
        def mouseReleaseEvent(self, e):
            self.mouse_click_ctx.atReleaseEvent(e)
            super().mouseReleaseEvent(e)

    parameter_defs = {
        "parent_container"  : None,                 # RootVideoContainer or child class
        "filterchain_group" : FilterChainGroup,     # Filterchain manager class
        "n_xscreen"         : (int, 0),             # x-screen index
        "verbose"           : (bool, False)
    }

    def __init__(self, **kwargs):
        # auxiliary string for debugging output
        self.pre = self.__class__.__name__ + " : "
        # check for input parameters, attach them to this instance as
        # attributes
        parameterInitCheck(VideoContainer.parameter_defs, kwargs, self)
        self.signals = self.Signals()
        
        # reset callback functions
        self.set_cb_focus()
        self.set_cb_unfocus()

        # container state variables
        self.double_click_focus = False  # has this container been double-click focused

        # right-click menu
        self.right_click_menu = self.RightClickMenu()
        self.right_click_menu.maximize.triggered.connect(lambda x: self.handle_left_double_click(
            None))  # use lambda as we must connect to a function
        self.right_click_menu.remove_camera.triggered.connect(self.clearDevice)

        # no stream yet
        self.device = None
        self.filterchain = None
        self.viewport = ViewPort() # viewport instance is used by ManagedFilterChain(s)


    def report(self, *args):
        if (self.verbose):
            print(self.pre, *args)

    def __del__(self):
        # self.close()
        pass

    # callback setters

    def set_cb_focus(self, cb=lambda x: None):
        self.cb_focus = cb

    def set_cb_unfocus(self, cb=lambda x: None):
        self.cb_unfocus = cb

    def makeWidget(self, parent=None):
        self.main_widget = self.ContainerWidget(parent)
        # self.signals.close.connect(self.close_slot) # not closed by clicking
        # the close symbol
        # this layout includes VideoWidget, buttons, alerts, etc.
        self.main_layout = QtWidgets.QVBoxLayout(self.main_widget)
        self.video = self.VideoWidget(
            parent=self.main_widget,
            mouse_gesture_handler=self.mouseGestureHandler)

        self.main_layout.addWidget(self.video)
        self.video.setSizePolicy(
            QtWidgets.QSizePolicy.Expanding,
            QtWidgets.QSizePolicy.Expanding)
        
        self.video.signals.drop.connect(self.setDevice)


    def hide(self):
        """Hide the widget.  Stream is not required while hidden.
        """
        self.main_widget.hide()

    def show(self):
        """Show the widget.  Stream is required again (if it was on)
        """
        self.main_widget.show()

    def close(self):
        """Called by the RootContainer when it's being closed
        """
        self.clearDevice()
        self.main_widget.close()

    def setDevice(self, device): 
        """Sets the video stream
        
        :param device:      A rather generic device class.  In this case DataModel.RTSPCameraDevice.
        """
        print(self.pre, "setDevice :", device)
        
        if (not device and not self.device): # None can be passed as an argument when the device has not been set yet
            return
            
        if (self.device):
            if self.device == device:
                print(self.pre, "setDevice : same device")
                return
            
        if self.filterchain: # there's video already
            self.clearDevice()
        
        self.device = device
        self.video.setDevice(self.device) # inform the video widget so it can start drags
        
        # ManagedFilterChain.addViewPort accepts ViewPort instance
        self.filterchain = self.filterchain_group.get(_id = self.device._id)
        
        if self.filterchain:
            self.viewport.setXScreenNum(self.n_xscreen)
            self.viewport.setWindowId  (int(self.video.winId()))
            self.filterchain.addViewPort(self.viewport)


    def clearDevice(self):
        """Remove the current stream
        """
        print(self.pre, "clearDevice")
        if not self.device:
            return
        
        self.filterchain.delViewPort(self.viewport)
        
        self.filterchain = None
        self.device = None
        
        self.video.update()
        

    def getDevice(self): # e.g. DataModel.RTSPCameraDevice
        return self.device
    

    def mouseGestureHandler(self, info):
        """This is the callback for MouseClickContext.  Passed to VideoWidget as a parameter
        """
        print(self.pre, ": mouseGestureHandler: ")
        # *** single click events ***
        if (info.fsingle):
            print(self.pre, ": mouseGestureHandler: single click")
            if (info.button == QtCore.Qt.LeftButton):
                print(self.pre, ": mouseGestureHandler: Left button clicked")
            elif (info.button == QtCore.Qt.RightButton):
                print(self.pre, ": mouseGestureHandler: Right button clicked")
                self.handle_right_single_click(info)
        # *** double click events ***
        elif (info.fdouble):
            if (info.button == QtCore.Qt.LeftButton):
                print(
                    self.pre,
                    ": mouseGestureHandler: Left button double-clicked")
                self.handle_left_double_click(info)
            elif (info.button == QtCore.Qt.RightButton):
                print(
                    self.pre,
                    ": mouseGestureHandler: Right button double-clicked")

    # mouse gesture handlers

    def handle_left_double_click(self, info):
        """Whatever we want to do, when the VideoWidget has been double-clicked with the left button
        """
        if (self.double_click_focus == False):  # turn focus on
            print(self.pre, "handle_left_double_click: focus on")
            self.cb_focus()
        else:  # turn focus off
            print(self.pre, "handle_left_double_click: focus off")
            self.cb_unfocus()
        self.double_click_focus = not(
            self.double_click_focus)  # boolean switch

    def handle_right_single_click(self, info):
        # get the QMenu object from the QuickMenu helper class and show it
        self.right_click_menu.menu.popup(QtGui.QCursor.pos())
示例#2
0
class VideoContainer:
    """
    This class is not a QWidget itself.  It's used to organize / encapsulate QWidgets and QSignals.
    
    This is a Container handling a master QWidget that encloses a video QWidget, and if required, other widgets as well (buttons, alerts, etc.)

    Does not handle X-screen hop / change (this should be done by the parent widget)
    
    Internal QWidgets:
    
    ::    
         
        ContainerWidget
             |
             +-- VideoWidget (handles drag'n'drop)
                    - Drag'n'drop receives objects of the type device.RTSPCameraDevice (has member _id to define uniquely a stream)
                      into VideoContainer.setDevice (i.e. into the main container object method)
                

    Important parameter members:
    
    - self.filterchain_group : an instance of FilterChainGroup: filterchains can be instantiated / get'ted by a unique _id
    - self.viewport : an instance of ViewPort: several ViewPort instances can be added to a filterchain; they represent endpoints of the video on the screen (OpenGL windows)

    Important QWidget members:
    
    - self.main_widget: instance of self.ContainerWidget

    QWidgets are instantiated like this:
    
    ::
    
        makeWidget()
            # this is called by the parent container object in its placeChildren method (see for example root.RootVideoContainer)
            self.main_widget = self.ContainerWidget
            self.video = self.VideoWidget(parent = self.main_widget)

    """
    class Signals(QtCore.QObject):
        close = QtCore.Signal(object)
        drop = QtCore.Signal(object)
        left_double_click = QtCore.Signal()
        right_double_click = QtCore.Signal()

    # Right-click pop-up menu
    # Adding installed cameras here could be done as follows: create this
    # class and the callbacks dynamically

    class RightClickMenu(QuickMenu):
        title = "popup"
        elements = [
            QuickMenuSection(title="Choose Action"),
            QuickMenuElement(title="Maximize / Normalize Size",
                             method_name="maximize"),
            QuickMenuElement(title="Remove Camera")
        ]

    class ContainerWidget(QtWidgets.QWidget):
        """Main level widget: this contains the VideoWidget and additionally, alert widgets, button for changing x screen, etc.
        """

        # TODO: connect close signal

        def __init__(self, parent):
            super().__init__(parent)
            self.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
                               QtWidgets.QSizePolicy.Expanding)

    class VideoWidget(QtWidgets.QFrame):
        """The video appears here.  Must handle drag'n'drop of camera info.  Or choosing camera from a pop-up menu.
        """
        def __init__(self, parent=None, mouse_gesture_handler=lambda e: None):
            super().__init__(parent)
            # qtwidget must have focus to receive keyboard events
            # however, i don't see code in the other widgets that set focus... why is it needed here?
            self.setFocusPolicy(QtCore.Qt.FocusPolicy.StrongFocus)
            # self.setStyleSheet("background-color: dark-blue")
            # self.setStyleSheet("background-color: rgba(0,0,0,0)");
            # self.setStyleSheet("border: 1px solid gray; border-radius: 10px; margin: 0px; padding: 0px; background: white;")
            self.setStyleSheet(style.video_widget)
            self.setAutoFillBackground(True)
            self.setAcceptDrops(True)

            self.signals = VideoContainer.Signals()

            self.device = None
            """
            pal = QtGui.QPalette()
            pal.setColor(QtGui.QPalette.Background, QtCore.Qt.black);
            self.setPalette(pal);
            """
            self.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
                               QtWidgets.QSizePolicy.Expanding)
            self.mouse_click_ctx = MouseClickContext(mouse_gesture_handler)

        # TODO: how we communicate, say, a double-click, to the higher-level widget
        # VideoContainer .. give it a callback that hides/shows all other
        # widgets .. (stream decoding should be paused as well if needed)

        def setDevice(self, device):
            self.device = device

        def dragEnterEvent(self, e):
            print("VideoWidget: dragEnterEvent")
            e.accept()

        def dropEvent(self, e):
            print("VideoWidget: dropEvent")
            # print("VideoWidget: drop event to i,j=",self.row_index,self.column_index)
            formlist = e.mimeData().formats()
            if ("application/octet-stream" in formlist):
                device = pickle.loads(
                    e.mimeData().data("application/octet-stream").data())
                print("VideoWidget: got: ", device)  # device.RTSPCameraDevice
                self.signals.drop.emit(device)
                e.accept()
            else:
                e.ignore()

        # if ptz is enabled, this will stop ptz movement
        def keyReleaseEvent(self, e):
            ## TODO: ONVIF: stuff moved into valkka.live.onvif.thread
            if (e.key() == QtCore.Qt.Key_Left):
                # print("VideoWidget: left arrow released")
                ##self.ptz_service.ws_client.Stop(ProfileToken=token)
                ## ONVIF: TODO: do like this:
                """
                if self.onvif_thread:
                    self.onvif_thread.signals.command.emit(["stop", {}])
                """
                pass
            if (e.key() == QtCore.Qt.Key_Right):
                # print("VideoWidget: right arrow released")
                pass
            if (e.key() == QtCore.Qt.Key_Down):
                # print("VideoWidget: down arrow released")
                pass
            if (e.key() == QtCore.Qt.Key_Up):
                # print("VideoWidget: up arrow released")
                pass

        # if ptz is enabled, this will start ptz movement
        def keyPressEvent(self, e):
            # this is gross, but hacked in here to get things working
            # would like to factor this better later
            ## TODO: ONVIF: stuff moved into valkka.live.onvif.thread

            if (e.key() == QtCore.Qt.Key_Left):
                #print("VideoWidget: left arrow pressed")
                pass
            if (e.key() == QtCore.Qt.Key_Right):
                # print("VideoWidget: right arrow pressed")
                pass
            if (e.key() == QtCore.Qt.Key_Down):
                #print("VideoWidget: down arrow pressed")
                pass
            if (e.key() == QtCore.Qt.Key_Up):
                # print("VideoWidget: up arrow pressed")
                pass

        def mousePressEvent(self, e):
            print("VideoWidget: mousePress")
            self.mouse_click_ctx.atPressEvent(e)
            super().mousePressEvent(e)

        def mouseMoveEvent(self, e):
            if not (e.buttons() & QtCore.Qt.LeftButton):
                return

            leni = (e.pos() - self.mouse_click_ctx.info.pos).manhattanLength()

            if (leni < QtWidgets.QApplication.startDragDistance()):
                return

            drag = QtGui.QDrag(self)
            mimeData = QtCore.QMimeData()

            mimeData.setData("application/octet-stream",
                             pickle.dumps(self.device)
                             # pickle.dumps(None)
                             )
            drag.setMimeData(mimeData)

            dropAction = drag.exec_(QtCore.Qt.CopyAction
                                    | QtCore.Qt.MoveAction)

        def mouseReleaseEvent(self, e):
            self.mouse_click_ctx.atReleaseEvent(e)
            super().mouseReleaseEvent(e)

    parameter_defs = {
        "parent_container": None,  # RootVideoContainer or child class
        "filterchain_group":
        None,  # Instance of FilterChainGroup.  Filterchain manager class.  # None for debugging
        "n_xscreen": (int, 0),  # x-screen index
        "verbose": (bool, False),
        "device_id": (int, -1)  # optional: the unique id of this video stream
    }

    def __init__(self, **kwargs):
        # auxiliary string for debugging output
        self.pre = self.__class__.__name__ + " : "
        # check for input parameters, attach them to this instance as
        # attributes
        print(self.pre, kwargs)
        parameterInitCheck(VideoContainer.parameter_defs, kwargs, self)
        self.signals = self.Signals()

        # reset callback functions
        self.set_cb_focus()
        self.set_cb_unfocus()

        # container state variables
        self.double_click_focus = False  # has this container been double-click focused

        # right-click menu
        self.right_click_menu = self.RightClickMenu()
        self.right_click_menu.maximize.triggered.connect(
            lambda x: self.handle_left_double_click(None)
        )  # use lambda as we must connect to a function
        self.right_click_menu.remove_camera.triggered.connect(self.clearDevice)

        # no stream yet
        self.device = None
        self.onvif_thread = None  # TODO: ONVIF
        self.filterchain = None
        self.viewport = ViewPort(
        )  # viewport instance is used by ManagedFilterChain(s)

    def serialize(self):
        """Return a dict of parameters that the parent object needs to de-serialize & instantiate this object
        """
        return {"device_id": self.getDeviceId()}

    def report(self, *args):
        if (self.verbose):
            print(self.pre, *args)

    def __del__(self):
        # self.close()
        pass

    # callback setters

    def set_cb_focus(self, cb=lambda x: None):
        self.cb_focus = cb

    def set_cb_unfocus(self, cb=lambda x: None):
        self.cb_unfocus = cb

    def makeWidget(self, parent=None):
        self.main_widget = self.ContainerWidget(parent)
        # self.signals.close.connect(self.close_slot) # not closed by clicking
        # the close symbol
        # this layout includes VideoWidget, buttons, alerts, etc.
        self.main_layout = QtWidgets.QVBoxLayout(self.main_widget)
        self.video = self.VideoWidget(
            parent=self.main_widget,
            mouse_gesture_handler=self.mouseGestureHandler)

        self.main_layout.addWidget(self.video)
        self.video.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
                                 QtWidgets.QSizePolicy.Expanding)

        self.video.signals.drop.connect(self.setDevice)

        # this VideoContainer was initialized with a device id, so we stream the video now
        if self.device_id > -1:
            self.setDeviceById(self.device_id)

    def hide(self):
        """Hide the widget.  Stream is not required while hidden.
        """
        self.main_widget.hide()

    def show(self):
        """Show the widget.  Stream is required again (if it was on)
        """
        self.main_widget.show()

    def close(self):
        """Called by the RootContainer when it's being closed
        """
        self.clearDevice()
        self.main_widget.close()

    def setDevice(self, device):
        """Sets the video stream
        
        :param device:      A rather generic device class.  In this case device.RTSPCameraDevice.
        """
        print(self.pre, "setDevice :", device)

        if (
                not device and not self.device
        ):  # None can be passed as an argument when the device has not been set yet
            return

        if (self.device):
            if self.device == device:
                print(self.pre, "setDevice : same device")
                return

        if self.filterchain:  # there's video already
            self.clearDevice()

        self.device = device
        self.video.setDevice(
            self.device)  # inform the video widget so it can start drags

        # ManagedFilterChain.addViewPort accepts ViewPort instance
        self.filterchain = self.filterchain_group.get(_id=self.device._id)
        if self.filterchain:
            self.viewport.setXScreenNum(self.n_xscreen)
            self.viewport.setWindowId(int(self.video.winId()))
            self.filterchain.addViewPort(self.viewport)
            # self.onvif_thread = self.onvif_group.get(_id = self.device._id) # TODO: ONVIF
            ## self.onvif_group should be passed to this class as an argument
            ## for the object class look into valkka.live.onvif.group

    def setDeviceById(self, _id):
        """Set the video to this VideoContainer by stream id only
        """
        print(self.pre, "setDeviceById:", _id)
        try:
            device = singleton.devices_by_id[_id]
        except KeyError:
            print(self.pre, "setDeviceById: no device with id", _id)
            return
        else:
            self.setDevice(device)

    def clearDevice(self):
        """Remove the current stream
        """
        print(self.pre, "clearDevice")
        if not self.device:
            return

        self.filterchain.delViewPort(self.viewport)

        self.filterchain = None
        self.device = None

        self.video.update()

    def getDevice(self):  # e.g. device.RTSPCameraDevice
        return self.device

    def getDeviceId(self):
        if self.device is None:
            return -1
        else:
            return self.device._id  # e.g. device.RTSPCameraDevice

    def mouseGestureHandler(self, info):
        """This is the callback for MouseClickContext.  Passed to VideoWidget as a parameter
        """
        print(self.pre, ": mouseGestureHandler: ")
        # *** single click events ***
        if (info.fsingle):
            print(self.pre, ": mouseGestureHandler: single click")
            if (info.button == QtCore.Qt.LeftButton):
                print(self.pre, ": mouseGestureHandler: Left button clicked")
            elif (info.button == QtCore.Qt.RightButton):
                print(self.pre, ": mouseGestureHandler: Right button clicked")
                self.handle_right_single_click(info)
        # *** double click events ***
        elif (info.fdouble):
            if (info.button == QtCore.Qt.LeftButton):
                print(self.pre,
                      ": mouseGestureHandler: Left button double-clicked")
                self.handle_left_double_click(info)
            elif (info.button == QtCore.Qt.RightButton):
                print(self.pre,
                      ": mouseGestureHandler: Right button double-clicked")
                self.handle_right_double_click(info)

    # mouse gesture handlers

    def handle_left_double_click(self, info):
        """Whatever we want to do, when the VideoWidget has been double-clicked with the left button
        """
        if (self.double_click_focus == False):  # turn focus on
            print(self.pre, "handle_left_double_click: focus on")
            self.cb_focus()
        else:  # turn focus off
            print(self.pre, "handle_left_double_click: focus off")
            self.cb_unfocus()
        self.double_click_focus = not (self.double_click_focus
                                       )  # boolean switch
        self.signals.left_double_click.emit()

    def handle_right_single_click(self, info):
        # get the QMenu object from the QuickMenu helper class and show it
        self.right_click_menu.menu.popup(QtGui.QCursor.pos())

    def handle_right_double_click(self, info):
        self.signals.right_double_click.emit()
示例#3
0
class VideoContainer:
  """
  
  - Create and manage a ViewPort instance (encapsulates x window id and x screen number)
  
  - A method that is given the stream parameters .. this would be used by the drag'n'drop in the future.  setStream(stream)
  - Change x screen method
  
  - setStream(pars)
    - pars: info about the stream, most importantly, the slot number
    - VideoContainer adds its own information and ..
    - .. calls ResourceManager(pars,view_port)
      => ResourceManager has the ManagedFilterchain instances .. chooses the correct one .. and calls addViewPort(view_port)


  ResourceManager (not using this..)
    -ManagedFilterChain(s) instances

  VideoContainer
    - ViewPort instance

  drag'n'drop => pars => VideoContainer => ViewPort
  => ResourceManager => ManagedFilterchain(s)

  References:
    VideoContainer -> ViewPort
    ManagedFilterchain -> ViewPort

  - GPUHandler finds X screens and instantiates OpenGLThreads
  - OpenGLThreads are passed to ManagedFilterchain(s) when they're instantiated
  - Create a minimalistic GUI:
    * Create a new window => Creates a new VideoContainer on x-screen 0
    * Exit => closeEvent => closes all VideoContainers
    
  - VideoContainer initialized with a list of QScreen instances
  - VideoContainer's switch x-screen button => call makeWidget(QScreen) .. updates self.win_id .. updates self.view_port.x_screen_num (call view_port.setXScreenNum())
    => calls ResourceManager(pars,view_port)
    
    
  - Take stuff from the gui tools module .. create a separate gui tools here
    
  """
  def __init__(self,gpu_handler,filterchains):
    self.pre="VideoContainer: "
    self.gpu_handler  =gpu_handler
    self.filterchains =filterchains
    self.closed       =False
    
    self.n            =0 # x-screen number
    self.openglthread =self.gpu_handler.openglthreads[self.n]
    self.index        =-1 # no video set

    qapp    =QtCore.QCoreApplication.instance()
    desktop =qapp.desktop()

    self.viewport =ViewPort(window_id=0, x_screen_num=self.n)

    self.makeWidget(self.gpu_handler.true_screens[self.n]) # create widget into a certain xscreen


  def setStream(self,pars):
    """
    :param pars:  Generic parameters. Used to identify the filterchain
    :return:      None
    """
    assert(pars.__class__==dict)
    assert("index" in pars)
    index=pars["index"]
    assert(index.__class__==int)

    try:
      fc=self.filterchains[index]
    except IndexError:
      print(self.pre,"setStream: no such filterchain",index)
      return

    assert(issubclass(fc.__class__,ManagedFilterchain))

    self.index =index
    self.viewport.setXScreenNum(self.n)
    self.viewport.setWindowId  (self.win_id)

    fc.addViewPort(self.viewport)


  def remStream(self):
    index=self.index
    print(self.pre,"remStream: index",index)
    if (index<0):
      return

    try:
      fc = self.filterchains[index]
    except IndexError:
      print(self.pre, "setStream: no such filterchain", index)
      return

    assert (issubclass(fc.__class__, ManagedFilterchain))

    fc.delViewPort(self.viewport)


  def makeWidget(self,qscreen):
    """ Widget needs to be re-created when jumping from one x-screen to another
    
    :param qscreen:   QScreen
    """
    
    class MyWidget(QtWidgets.QWidget):
      
      class MySignals(QtCore.QObject):
        close =QtCore.Signal(object)
        
        
      def __init__(self,parent=None):
        super().__init__(parent)
        self.signals =self.MySignals()
      
      
      def closeEvent(self, e):
        self.signals.close.emit(e)
        e.accept()
        
    
    self.main_widget=MyWidget()
    self.main_widget.signals.close.connect(self.close_slot)
    
    self.main_widget.show()
    
    self.main_widget.windowHandle().setScreen(qscreen)
    self.lay        =QtWidgets.QVBoxLayout(self.main_widget)

    self.video      =TestWidget0(self.main_widget)
    self.lay.addWidget(self.video)
    self.video.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)

    if (len(self.gpu_handler.true_screens)>1):
      self.button     =QtWidgets.QPushButton("Change Screen",self.main_widget)
      self.lay.addWidget(self.button)
      self.button.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum)
      self.button.clicked.connect(self.cycle_slot)

    self.win_id = int(self.video.winId())

    # create drop-down menu for all cameras?
    self.dropdown =QtWidgets.QComboBox(self.main_widget)
    self.dropdown.addItem("<Choose A Camera>", -1)
    for index, fc in enumerate(self.filterchains):
      assert(fc.__class__==ManagedFilterchain) # filterchains should have a copy of the parameter set (=rtsp address, date when added, etc.) that invoked them
      self.dropdown.addItem(fc.address,index) # QComboBox.addItem(str,qvariant) .. now qvariant is just a python object! :)

    self.lay.addWidget(self.dropdown)

    self.dropdown.currentIndexChanged.connect(self.dropdown_changed_slot)

    self.main_widget.show()

    # this will call dropdown_changed_slot and activate the video if needed
    # index = filterchain index
    # argument of self.dropdown.setCurrentIndex = index of the QComboBox dropdown item
    if self.index>=0:
      self.dropdown.setCurrentIndex(self.index+1) # TODO: here self.index maps from parameters => filterchain .. parameters => menu item index mapping required as well

    
  def cycle_slot(self):
    """Cycle from one X-Screen to another
    """
    # viewport has x screen number and x window id
    self.n +=1
    if (self.n>=len(self.gpu_handler.true_screens)):
      self.n=0
    print("cycle_slot: going to screen:",self.n)

    # """
    if (self.index>-1): # video has been chosen..
      fc = self.filterchains[self.index]
      fc.delViewPort(self.viewport)
    # """

    # WORKS WITH LATEST PYQT5 5.11.2
    self.makeWidget(self.gpu_handler.true_screens[self.n])

    # """
    if (self.index>-1):
      fc.addViewPort(self.viewport)
    # """


  def dropdown_changed_slot(self,i):
    print(self.pre,"dropdown_changed_slot: combobox selection now",i)
    index=self.dropdown.itemData(i)
    print(self.pre,"dropdown_changed_slot: index",index)
    if (index<0):
      self.remStream()
    else:
      self.setStream({"index":index})


  def close_slot(self):
    self.close()


  def mouseDoubleClickEvent(self,e):
    print("double click!")
        
  
  def getVideoWidget(self):
    return self.video

  
  def getWidget(self):
    return self.main_widget


  def close(self):
    if (self.closed):
      return
    self.remStream()
    self.openglthread =None
    self.gpu_handler  =None
    self.filterchains =[]
    self.main_widget.close()
    self.main_widget.deleteLater()
    self.closed=True