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())
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()
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